You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.6 KiB
266 lines
8.6 KiB
import { Box, BoxProps, SxProps, Theme } from '@mui/material'; |
|
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; |
|
|
|
import INPUT_TYPES from '../lib/consts/INPUT_TYPES'; |
|
|
|
import ContainedButton from './ContainedButton'; |
|
import FlexBox from './FlexBox'; |
|
import Grid from './Grid'; |
|
import InputWithRef, { InputForwardedRefContent } from './InputWithRef'; |
|
import MessageGroup, { MessageGroupForwardedRefContent } from './MessageGroup'; |
|
import OutlinedInputWithLabel from './OutlinedInputWithLabel'; |
|
import Spinner from './Spinner'; |
|
import { buildPeacefulStringTestBatch } from '../lib/test_input'; |
|
import useFormUtils from '../hooks/useFormUtils'; |
|
import useProtectedState from '../hooks/useProtectedState'; |
|
|
|
const INPUT_ROOT_SX: SxProps<Theme> = { width: '100%' }; |
|
|
|
const INPUT_ID_PREFIX_GATE = 'gate-input'; |
|
|
|
const INPUT_ID_GATE_ID = `${INPUT_ID_PREFIX_GATE}-credential-id`; |
|
const INPUT_ID_GATE_PASSPHRASE = `${INPUT_ID_PREFIX_GATE}-credential-passphrase`; |
|
|
|
const MSG_ID_GATE_ACCESS = 'access'; |
|
|
|
const GateForm = forwardRef<GateFormForwardedRefContent, GateFormProps>( |
|
( |
|
{ |
|
formContainer: isFormContainer = true, |
|
gridProps: { |
|
columns: gridColumns = { xs: 1, sm: 2 }, |
|
layout, |
|
spacing: gridSpacing = '1em', |
|
...restGridProps |
|
} = {}, |
|
identifierId = INPUT_ID_GATE_ID, |
|
identifierInputTestBatchBuilder: |
|
buildIdentifierInputTestBatch = buildPeacefulStringTestBatch, |
|
identifierLabel, |
|
identifierOutlinedInputWithLabelProps: { |
|
formControlProps: identifierFormControlProps = {}, |
|
inputProps: identifierInputProps, |
|
...restIdentifierOutlinedInputWithLabelProps |
|
} = {}, |
|
onIdentifierBlurAppend, |
|
onSubmit, |
|
onSubmitAppend, |
|
passphraseId = INPUT_ID_GATE_PASSPHRASE, |
|
passphraseLabel, |
|
passphraseOutlinedInputWithLabelProps: { |
|
formControlProps: passphraseFormControlProps = {}, |
|
inputProps: passphraseInputProps, |
|
...restPassphraseOutlinedInputWithLabelProps |
|
} = {}, |
|
submitLabel, |
|
// Props that depend on others. |
|
allowSubmit: isAllowSubmit = isFormContainer, |
|
}, |
|
ref, |
|
) => { |
|
const { sx: identifierSx, ...restIdentifierFormControlProps } = |
|
identifierFormControlProps; |
|
const { sx: passphraseSx, ...restPassphraseFormControlProps } = |
|
passphraseFormControlProps; |
|
|
|
const inputIdentifierRef = useRef<InputForwardedRefContent<'string'>>({}); |
|
const inputPassphraseRef = useRef<InputForwardedRefContent<'string'>>({}); |
|
const messageGroupRef = useRef<MessageGroupForwardedRefContent>({}); |
|
|
|
const [isSubmitting, setIsSubmitting] = useProtectedState<boolean>(false); |
|
|
|
const formUtils = useFormUtils( |
|
[INPUT_ID_GATE_ID, INPUT_ID_GATE_PASSPHRASE], |
|
messageGroupRef, |
|
); |
|
const { |
|
buildFinishInputTestBatchFunction, |
|
buildInputFirstRenderFunction, |
|
buildInputUnmountFunction, |
|
isFormInvalid, |
|
setMessage, |
|
} = formUtils; |
|
|
|
const submitHandler: DivFormEventHandler = useMemo( |
|
() => |
|
onSubmit ?? |
|
((...args) => { |
|
const { 0: event } = args; |
|
|
|
event.preventDefault(); |
|
|
|
setMessage(MSG_ID_GATE_ACCESS); |
|
setIsSubmitting(true); |
|
|
|
const { target } = event; |
|
const { elements } = target as HTMLFormElement; |
|
|
|
const { value: identifierValue } = elements.namedItem( |
|
INPUT_ID_GATE_ID, |
|
) as HTMLInputElement; |
|
const { value: passphraseValue } = elements.namedItem( |
|
INPUT_ID_GATE_PASSPHRASE, |
|
) as HTMLInputElement; |
|
|
|
onSubmitAppend?.call( |
|
null, |
|
identifierValue, |
|
passphraseValue, |
|
(message?) => { |
|
setMessage(MSG_ID_GATE_ACCESS, message); |
|
}, |
|
setIsSubmitting, |
|
...args, |
|
); |
|
}), |
|
[onSubmit, onSubmitAppend, setIsSubmitting, setMessage], |
|
); |
|
|
|
const submitElement = useMemo( |
|
() => |
|
isSubmitting ? ( |
|
<Spinner mt={0} /> |
|
) : ( |
|
<FlexBox row sx={{ justifyContent: 'flex-end' }}> |
|
<ContainedButton disabled={isFormInvalid} type="submit"> |
|
{submitLabel} |
|
</ContainedButton> |
|
</FlexBox> |
|
), |
|
[isFormInvalid, isSubmitting, submitLabel], |
|
); |
|
|
|
const submitAreaGridLayout = useMemo(() => { |
|
const result: GridLayout = {}; |
|
|
|
if (isAllowSubmit) { |
|
result['gate-cell-message-group'] = { |
|
children: <MessageGroup count={1} ref={messageGroupRef} />, |
|
sm: 2, |
|
}; |
|
result['gate-cell-submit'] = { children: submitElement, sm: 2 }; |
|
} |
|
|
|
return result; |
|
}, [isAllowSubmit, submitElement]); |
|
|
|
const containerProps = useMemo(() => { |
|
const result: BoxProps = {}; |
|
|
|
if (isFormContainer) { |
|
result.component = 'form'; |
|
result.onSubmit = submitHandler; |
|
} |
|
|
|
return result; |
|
}, [isFormContainer, submitHandler]); |
|
|
|
useImperativeHandle(ref, () => ({ |
|
get: () => ({ |
|
identifier: inputIdentifierRef.current.getValue?.call(null) ?? '', |
|
passphrase: inputPassphraseRef.current.getValue?.call(null) ?? '', |
|
}), |
|
messageGroup: { |
|
...messageGroupRef.current, |
|
}, |
|
setIsSubmitting: (value) => { |
|
setIsSubmitting(value); |
|
}, |
|
})); |
|
|
|
return ( |
|
<Box {...containerProps}> |
|
<Grid |
|
columns={gridColumns} |
|
layout={{ |
|
'gate-input-cell-credential-id': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ |
|
...restIdentifierFormControlProps, |
|
sx: { ...INPUT_ROOT_SX, ...identifierSx }, |
|
}} |
|
id={identifierId} |
|
inputProps={identifierInputProps} |
|
label={identifierLabel} |
|
{...restIdentifierOutlinedInputWithLabelProps} |
|
/> |
|
} |
|
inputTestBatch={buildIdentifierInputTestBatch( |
|
identifierLabel, |
|
() => { |
|
setMessage(identifierId); |
|
}, |
|
{ |
|
onFinishBatch: |
|
buildFinishInputTestBatchFunction(identifierId), |
|
}, |
|
(message) => { |
|
setMessage(identifierId, { children: message }); |
|
}, |
|
)} |
|
onBlurAppend={(...args) => { |
|
onIdentifierBlurAppend?.call(null, ...args); |
|
}} |
|
onFirstRender={buildInputFirstRenderFunction(identifierId)} |
|
onUnmount={buildInputUnmountFunction(identifierId)} |
|
ref={inputIdentifierRef} |
|
required |
|
/> |
|
), |
|
}, |
|
'gate-input-cell-credential-passphrase': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ |
|
...restPassphraseFormControlProps, |
|
sx: { ...INPUT_ROOT_SX, ...passphraseSx }, |
|
}} |
|
id={passphraseId} |
|
inputProps={passphraseInputProps} |
|
label={passphraseLabel} |
|
type={INPUT_TYPES.password} |
|
{...restPassphraseOutlinedInputWithLabelProps} |
|
/> |
|
} |
|
inputTestBatch={buildPeacefulStringTestBatch( |
|
passphraseLabel, |
|
() => { |
|
setMessage(passphraseId); |
|
}, |
|
{ |
|
onFinishBatch: |
|
buildFinishInputTestBatchFunction(passphraseId), |
|
}, |
|
(message) => { |
|
setMessage(passphraseId, { |
|
children: message, |
|
}); |
|
}, |
|
)} |
|
onFirstRender={buildInputFirstRenderFunction(passphraseId)} |
|
onUnmount={buildInputUnmountFunction(passphraseId)} |
|
ref={inputPassphraseRef} |
|
required |
|
/> |
|
), |
|
}, |
|
...submitAreaGridLayout, |
|
}} |
|
spacing={gridSpacing} |
|
{...restGridProps} |
|
/> |
|
</Box> |
|
); |
|
}, |
|
); |
|
|
|
GateForm.displayName = 'GateForm'; |
|
|
|
export { INPUT_ID_GATE_ID, INPUT_ID_GATE_PASSPHRASE }; |
|
|
|
export default GateForm;
|
|
|