mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
170 lines
5.3 KiB
TypeScript
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;
|