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.
321 lines
9.5 KiB
321 lines
9.5 KiB
import { SxProps, Theme } from '@mui/material'; |
|
import { |
|
forwardRef, |
|
useCallback, |
|
useImperativeHandle, |
|
useMemo, |
|
useRef, |
|
useState, |
|
} 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, |
|
createTestInputFunction, |
|
} from '../lib/test_input'; |
|
|
|
const INPUT_ROOT_SX: SxProps<Theme> = { width: '100%' }; |
|
const IT_IDS = { |
|
identifier: 'identifier', |
|
passphrase: 'passphrase', |
|
}; |
|
const MESSAGE_KEY: GateFormMessageKey = { |
|
accessError: 'accessError', |
|
identifierInputError: 'identifierInputError', |
|
passphraseInputError: 'passphraseInputError', |
|
}; |
|
|
|
const GateForm = forwardRef<GateFormForwardedRefContent, GateFormProps>( |
|
( |
|
{ |
|
allowSubmit: isAllowSubmit = true, |
|
gridProps: { |
|
columns: gridColumns = { xs: 1, sm: 2 }, |
|
layout, |
|
spacing: gridSpacing = '1em', |
|
...restGridProps |
|
} = {}, |
|
identifierLabel, |
|
identifierOutlinedInputWithLabelProps: { |
|
formControlProps: identifierFormControlProps = {}, |
|
inputProps: identifierInputProps, |
|
...restIdentifierOutlinedInputWithLabelProps |
|
} = {}, |
|
identifierInputTestBatchBuilder: overwriteIdentifierInputTestBatch, |
|
onIdentifierBlurAppend, |
|
onSubmit, |
|
onSubmitAppend, |
|
passphraseLabel, |
|
passphraseOutlinedInputWithLabelProps: { |
|
formControlProps: passphraseFormControlProps = {}, |
|
inputProps: passphraseInputProps, |
|
...restPassphraseOutlinedInputWithLabelProps |
|
} = {}, |
|
submitLabel, |
|
}, |
|
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 [isInputIdentifierValid, setIsInputIdentifierValid] = |
|
useState<boolean>(false); |
|
const [isInputPassphraseValid, setIsInputPassphraseValid] = |
|
useState<boolean>(false); |
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); |
|
|
|
const setAccessErrorMessage: GateFormMessageSetter = useCallback( |
|
(message?) => { |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
MESSAGE_KEY.accessError, |
|
message, |
|
); |
|
}, |
|
[], |
|
); |
|
const setIdentifierInputErrorMessage: GateFormMessageSetter = useCallback( |
|
(message?) => { |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
MESSAGE_KEY.identifierInputError, |
|
message, |
|
); |
|
}, |
|
[], |
|
); |
|
const setPassphraseInputErrorMessage: GateFormMessageSetter = useCallback( |
|
(message?) => { |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
MESSAGE_KEY.passphraseInputError, |
|
message, |
|
); |
|
}, |
|
[], |
|
); |
|
|
|
const messagesGroupSxDisplay = useMemo( |
|
() => (isAllowSubmit ? undefined : 'none'), |
|
[isAllowSubmit], |
|
); |
|
const identifierInputTestBatch = useMemo( |
|
() => |
|
overwriteIdentifierInputTestBatch?.call( |
|
null, |
|
setIdentifierInputErrorMessage, |
|
inputIdentifierRef.current, |
|
) ?? |
|
buildPeacefulStringTestBatch( |
|
identifierLabel, |
|
() => { |
|
setIdentifierInputErrorMessage(); |
|
}, |
|
{ getValue: inputIdentifierRef.current.getValue }, |
|
(message) => { |
|
setIdentifierInputErrorMessage({ |
|
children: message, |
|
type: 'warning', |
|
}); |
|
}, |
|
), |
|
[ |
|
identifierLabel, |
|
overwriteIdentifierInputTestBatch, |
|
setIdentifierInputErrorMessage, |
|
], |
|
); |
|
const inputTests: InputTestBatches = useMemo( |
|
() => ({ |
|
[IT_IDS.identifier]: identifierInputTestBatch, |
|
[IT_IDS.passphrase]: buildPeacefulStringTestBatch( |
|
passphraseLabel, |
|
() => { |
|
setPassphraseInputErrorMessage(); |
|
}, |
|
{ getValue: inputPassphraseRef.current.getValue }, |
|
(message) => { |
|
setPassphraseInputErrorMessage({ |
|
children: message, |
|
type: 'warning', |
|
}); |
|
}, |
|
), |
|
}), |
|
[ |
|
identifierInputTestBatch, |
|
passphraseLabel, |
|
setPassphraseInputErrorMessage, |
|
], |
|
); |
|
const submitHandler: ContainedButtonProps['onClick'] = useMemo( |
|
() => |
|
onSubmit ?? |
|
((...args) => { |
|
setAccessErrorMessage(); |
|
setIsSubmitting(true); |
|
onSubmitAppend?.call( |
|
null, |
|
inputIdentifierRef.current, |
|
inputPassphraseRef.current, |
|
setAccessErrorMessage, |
|
setIsSubmitting, |
|
messageGroupRef.current, |
|
...args, |
|
); |
|
}), |
|
[onSubmit, onSubmitAppend, setAccessErrorMessage], |
|
); |
|
const submitElement = useMemo( |
|
() => |
|
isSubmitting ? ( |
|
<Spinner mt={0} /> |
|
) : ( |
|
<FlexBox row sx={{ justifyContent: 'flex-end' }}> |
|
<ContainedButton |
|
disabled={!isInputIdentifierValid || !isInputPassphraseValid} |
|
onClick={submitHandler} |
|
> |
|
{submitLabel} |
|
</ContainedButton> |
|
</FlexBox> |
|
), |
|
[ |
|
isInputIdentifierValid, |
|
isInputPassphraseValid, |
|
isSubmitting, |
|
submitHandler, |
|
submitLabel, |
|
], |
|
); |
|
const submitGrid = useMemo( |
|
() => |
|
isAllowSubmit |
|
? { |
|
children: submitElement, |
|
sm: 2, |
|
} |
|
: undefined, |
|
[isAllowSubmit, submitElement], |
|
); |
|
|
|
const testInput = useMemo( |
|
() => createTestInputFunction(inputTests), |
|
[inputTests], |
|
); |
|
|
|
useImperativeHandle(ref, () => ({ |
|
get: () => ({ |
|
identifier: inputIdentifierRef.current.getValue?.call(null) ?? '', |
|
passphrase: inputPassphraseRef.current.getValue?.call(null) ?? '', |
|
}), |
|
messageGroup: { |
|
...messageGroupRef.current, |
|
}, |
|
setIsSubmitting: (value) => { |
|
setIsSubmitting(value); |
|
}, |
|
})); |
|
|
|
return ( |
|
<Grid |
|
columns={gridColumns} |
|
layout={{ |
|
'credential-identifier': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ |
|
...restIdentifierFormControlProps, |
|
sx: { ...INPUT_ROOT_SX, ...identifierSx }, |
|
}} |
|
id="credential-identifier-input" |
|
inputProps={{ |
|
onBlur: (event) => { |
|
const { |
|
target: { value }, |
|
} = event; |
|
|
|
const valid = testInput({ |
|
inputs: { [IT_IDS.identifier]: { value } }, |
|
}); |
|
setIsInputIdentifierValid(valid); |
|
|
|
onIdentifierBlurAppend?.call(null, event); |
|
}, |
|
onFocus: () => { |
|
setIdentifierInputErrorMessage(); |
|
}, |
|
...identifierInputProps, |
|
}} |
|
label={identifierLabel} |
|
{...restIdentifierOutlinedInputWithLabelProps} |
|
/> |
|
} |
|
ref={inputIdentifierRef} |
|
/> |
|
), |
|
}, |
|
'credential-passphrase': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ |
|
...restPassphraseFormControlProps, |
|
sx: { ...INPUT_ROOT_SX, ...passphraseSx }, |
|
}} |
|
id="credential-passphrase-input" |
|
inputProps={{ |
|
onBlur: ({ target: { value } }) => { |
|
const valid = testInput({ |
|
inputs: { [IT_IDS.passphrase]: { value } }, |
|
}); |
|
setIsInputPassphraseValid(valid); |
|
}, |
|
onFocus: () => { |
|
setPassphraseInputErrorMessage(); |
|
}, |
|
type: INPUT_TYPES.password, |
|
...passphraseInputProps, |
|
}} |
|
label={passphraseLabel} |
|
{...restPassphraseOutlinedInputWithLabelProps} |
|
/> |
|
} |
|
ref={inputPassphraseRef} |
|
/> |
|
), |
|
}, |
|
'credential-message-group': { |
|
children: <MessageGroup count={1} ref={messageGroupRef} />, |
|
sm: 2, |
|
sx: { display: messagesGroupSxDisplay }, |
|
}, |
|
'credential-submit': submitGrid, |
|
}} |
|
spacing={gridSpacing} |
|
{...restGridProps} |
|
/> |
|
); |
|
}, |
|
); |
|
|
|
GateForm.displayName = 'GateForm'; |
|
|
|
export default GateForm;
|
|
|