0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00
logto/packages/experience/src/pages/Register/IdentifierRegisterForm/index.tsx

170 lines
5.3 KiB
TypeScript

import { AgreeToTermsPolicy, ExtraParamsKey, type SignInIdentifier } from '@logto/schemas';
import classNames from 'classnames';
import { useCallback, useContext, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useSearchParams } from 'react-router-dom';
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
import LockIcon from '@/assets/icons/lock.svg?react';
import Button from '@/components/Button';
import ErrorMessage from '@/components/ErrorMessage';
import { SmartInputField } from '@/components/InputFields';
import type { IdentifierInputValue } from '@/components/InputFields/SmartInputField';
import TermsAndPrivacyCheckbox from '@/containers/TermsAndPrivacyCheckbox';
import useSingleSignOnWatch from '@/hooks/use-single-sign-on-watch';
import useTerms from '@/hooks/use-terms';
import { getGeneralIdentifierErrorMessage, validateIdentifierField } from '@/utils/form';
import styles from './index.module.scss';
import useOnSubmit from './use-on-submit';
type Props = {
readonly className?: string;
// eslint-disable-next-line react/boolean-prop-naming
readonly autoFocus?: boolean;
readonly signUpMethods: SignInIdentifier[];
};
type FormState = {
id: IdentifierInputValue;
};
const IdentifierRegisterForm = ({ className, autoFocus, signUpMethods }: Props) => {
const { t } = useTranslation();
const { termsValidation, agreeToTermsPolicy } = useTerms();
const { errorMessage, clearErrorMessage, onSubmit } = useOnSubmit();
const { identifierInputValue, setIdentifierInputValue } = useContext(UserInteractionContext);
const [searchParams] = useSearchParams();
const {
watch,
handleSubmit,
formState: { errors, isValid, isSubmitting },
control,
} = useForm<FormState>({
reValidateMode: 'onBlur',
});
// Watch identifier field and check single sign on method availability
const { showSingleSignOnForm, navigateToSingleSignOn } = useSingleSignOnWatch(watch('id'));
useEffect(() => {
if (!isValid) {
clearErrorMessage();
}
}, [clearErrorMessage, isValid]);
const onSubmitHandler = useCallback(
async (event?: React.FormEvent<HTMLFormElement>) => {
clearErrorMessage();
void handleSubmit(async ({ id: { type, value } }) => {
if (!type) {
return;
}
setIdentifierInputValue({ type, value });
if (showSingleSignOnForm) {
await navigateToSingleSignOn();
return;
}
if (!(await termsValidation())) {
return;
}
await onSubmit(type, value);
})(event);
},
[
clearErrorMessage,
handleSubmit,
navigateToSingleSignOn,
onSubmit,
setIdentifierInputValue,
showSingleSignOnForm,
termsValidation,
]
);
return (
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
<Controller
control={control}
name="id"
rules={{
validate: ({ type, value }) => {
if (!type || !value) {
return getGeneralIdentifierErrorMessage(signUpMethods, 'required');
}
const errorMessage = validateIdentifierField(type, value);
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;
},
}}
render={({ field }) => (
<SmartInputField
autoComplete="off"
autoFocus={autoFocus}
className={styles.inputField}
{...field}
defaultValue={
identifierInputValue?.value ?? searchParams.get(ExtraParamsKey.LoginHint) ?? undefined
}
defaultType={identifierInputValue?.type}
isDanger={!!errors.id || !!errorMessage}
errorMessage={errors.id?.message}
enabledTypes={signUpMethods}
/>
)}
/>
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
{showSingleSignOnForm && (
<div className={styles.message}>{t('description.single_sign_on_enabled')}</div>
)}
{/**
* Have to use css to hide the terms element.
* Remove element from dom will trigger a form re-render.
* Form rerender will trigger autofill.
* If the autofill value is SSO enabled, it will always show SSO form.
*/}
<TermsAndPrivacyCheckbox
className={classNames(
styles.terms,
/**
* Hide the terms checkbox when the policy is set to `Automatic`.
* In registration, the terms checkbox is always shown for `Manual` and `ManualRegistrationOnly` policies.
*/
agreeToTermsPolicy === AgreeToTermsPolicy.Automatic && styles.hidden
)}
/>
<Button
name="submit"
title={showSingleSignOnForm ? 'action.single_sign_on' : 'action.create_account'}
icon={showSingleSignOnForm ? <LockIcon /> : undefined}
htmlType="submit"
isLoading={isSubmitting}
/>
<input hidden type="submit" />
</form>
);
};
export default IdentifierRegisterForm;