Local modifications to ClusterLabs/Anvil by Alteeve
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

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;