Files
no-copy-frontend/src/app/ui/forms/register-form.tsx
T

600 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import styles from '@/app/styles/module/login.module.scss';
import { ChangeEvent, useActionState, useEffect, useRef, useState } from 'react';
import { registration, FormState } from '@/app/actions/auth';
import PhoneInput from '@/app/ui/inputs/phone-input';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { IconEye } from '@/app/ui/icons/icons';
import { getSignupFormSchema } from '@/app/actions/definitions';
import { useSearchParams } from 'next/navigation';
import * as z from 'zod'
import ConfirmMailModalWindow from '@/app/ui/forms/confirm-mail-modal-window';
import { fetchINN } from '@/app/actions/action';
import { useDebouncedCallback } from 'use-debounce';
export default function RegisterForm() {
const searchParams = useSearchParams();
const referralCode = searchParams.get('ref');
const [state, formAction, isPending] = useActionState(
registration,
undefined,
);
const t = useTranslations('Login-register-form');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [formState, setFormState] = useState<FormState | undefined>(undefined);
const [showMailConfirmWindow, setShowMailConfirmWindow] = useState(false);
const passwordRef = useRef<HTMLInputElement>(null);
const [accountType, setAccountType] = useState<'b2c' | 'b2b'>('b2c');
useEffect(() => {
setFormState(state);
if (state?.mailConfirm) {
setShowMailConfirmWindow(true);
}
}, [state]);
const debouncedFetchINN = useDebouncedCallback(
async (value) => {
const response = await fetchINN(value);
if (response.companyName) {
setFormState({
...formState,
previousState: {
...formState?.previousState,
companyName: response.companyName
},
error: {
...formState?.error,
companyName: ''
}
});
} else {
setFormState({
...formState,
previousState: {
companyName: response.companyName
},
error: {
...formState?.error,
companyName: '',
inn: 'inn-invalid'
}
});
}
},
500
);
function showPassowrd(target: 'password' | 'confirm-password') {
if (target === 'password') {
setShowPassword(!showPassword);
} else {
setShowConfirmPassword(!showConfirmPassword);
}
}
type SignupFormSchema = Awaited<ReturnType<typeof getSignupFormSchema>>;
const [signupFormSchema, setSignupFormSchema] = useState<SignupFormSchema | null>(null);;
useEffect(() => {
let isMounted = true;
const loadSchema = async () => {
const result = await getSignupFormSchema(accountType);
if (isMounted) {
setSignupFormSchema(result);
}
};
loadSchema();
return () => {
isMounted = false;
};
}, [accountType]);
async function validateField(
fieldName: string,
value: string,
) {
switch (fieldName) {
case 'confirm_password':
const baseSchema = signupFormSchema?.shape.confirm_password;
if (!baseSchema) {
return { success: true, data: value };
}
const baseResult = baseSchema.safeParse(value);
if (!baseResult.success) {
return baseResult;
}
const currentPassword = passwordRef.current?.value || '';
if (value !== currentPassword) {
return {
success: false,
error: new z.ZodError([
{
code: z.ZodIssueCode.custom,
message: 'repeat-password',
path: ['confirm_password']
}
])
};
}
return { success: true, data: value };
case 'email':
case 'fullName':
case 'phone':
case 'referralCode':
case 'password':
case 'companyName':
case 'agree':
case 'inn':
const schema = signupFormSchema?.shape[fieldName];
if (!schema) {
return { success: true, data: value };
}
if (fieldName === 'phone') {
return schema.safeParse(value.replace(/[-\(\)\s]/g, ''));
}
if (fieldName === 'companyName' && accountType === 'b2c') {
return { success: true, data: value };
}
if (fieldName === 'referralCode') {
return { success: true, data: value };
}
return schema.safeParse(value);
default:
throw new Error(`Unknown field: ${fieldName}`);
}
}
async function onChangeHandler(e: ChangeEvent<HTMLInputElement>): Promise<void> {
const fieldName = e.target.name;
if (fieldName !== 'phone') {
e.target.value = e.target.value.replace(/([^a-zA-Zа-яёА-ЯЁ0-9@.!#$%&"'*+/=?^_{|}~\-\s])/g, '');
}
if (fieldName === 'inn') {
const currentValue = e.target.value;
const cleanedValue = currentValue.replace(/([^0-9])/g, '');
if (cleanedValue.length > 10) {
e.target.value = cleanedValue.slice(0, 10);
debouncedFetchINN(e.target.value);
} else {
e.target.value = cleanedValue;
}
if (cleanedValue.length === 10) {
debouncedFetchINN(cleanedValue);
} else {
if (formState) {
setFormState({
...formState,
previousState: {
...formState?.previousState,
companyName: ''
},
error: {
...formState?.error,
companyName: '',
inn: ''
}
});
}
}
}
const validatedField = await validateField(fieldName, e.target.value);
if (validatedField.success) {
setFormState(prevState => {
if (!prevState?.error || !prevState?.error[fieldName]) {
return prevState;
}
const { [fieldName]: _, ...newError } = prevState.error;
return {
...prevState,
error: newError
};
});
}
}
function switchRegistrationTypeHandler(e: React.MouseEvent<HTMLButtonElement>, type: 'b2c' | 'b2b') {
e.preventDefault();
setAccountType(type);
if (formState?.previousState) {
setFormState({
...formState,
previousState: {
...formState.previousState,
companyName: '',
inn: ''
},
error: {
...formState.error,
companyName: '',
inn: ''
}
});
}
}
return (
<>
<button
onClick={() => {
setShowMailConfirmWindow(!showMailConfirmWindow)
}}
>
show modal
</button>
{showMailConfirmWindow && (
<div
className="modal-wrapper"
onClick={() => {
setShowMailConfirmWindow(false);
}}
>
<ConfirmMailModalWindow userId={state?.userId} email={formState?.previousState?.email} />
</div>
)}
<form action={(e) => {
formAction(e);
}}>
<div className={`${styles['form-switcher']}`}>
<button
className={`${styles['form-switcher-button']} ${accountType === 'b2c' ? styles['active'] : ''}`}
onClick={(e) => {
switchRegistrationTypeHandler(e, 'b2c');
}}
>
{t('personal')}
</button>
<button
className={`${styles['form-switcher-button']} ${accountType === 'b2b' ? styles['active'] : ''}`}
onClick={(e) => {
switchRegistrationTypeHandler(e, 'b2b');
}}
>
{t('company')}
</button>
<input
type="hidden"
id="accountType"
name="accountType"
className={`${styles['form-input-hidden']}`}
value={accountType}
/>
</div>
<div className={`${styles['form-group']}`}>
<label
className={`${styles['form-label']} ${styles['required']}`}
>
{t('full-name')}
</label>
<input
type="text"
id="fullName"
name="fullName"
className={`${styles['form-input']}`}
placeholder={t('name-placeholder')}
defaultValue={formState?.previousState?.fullName ? formState?.previousState?.fullName : ''}
onChange={onChangeHandler}
/>
{formState?.error?.fullName && (
<p className="text-sm text-red-500">
{
formState?.error?.fullName.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
{accountType === 'b2b' ? (
<>
<div className={`${styles['form-group']}`}>
<label
className={`${styles['form-label']} ${styles['required']}`}
>
{t('INN')}
</label>
<input
type="text"
id="inn"
name="inn"
className={`${styles['form-input']}`}
placeholder={t('inn-placeholder')}
defaultValue={formState?.previousState?.inn ? formState?.previousState?.inn : ''}
onChange={onChangeHandler}
/>
{formState?.error?.inn && (
<p className="text-sm text-red-500">
{
formState?.error?.inn.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
<div className={`${styles['form-group']}`}>
<label
className={`${styles['form-label']}`}
>
{t('company')}
</label>
<input
type="text"
id="companyName"
name="companyName"
className={`${styles['form-input']}`}
placeholder={t('field-filled-automatically')}
value={formState?.previousState?.companyName ?? ''}
readOnly={true}
/* defaultValue={formState?.previousState?.companyName ? formState?.previousState?.companyName : ''} */
onChange={onChangeHandler}
/>
{formState?.error?.companyName && (
<p className="text-sm text-red-500">
{
formState?.error?.companyName.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
</>
) : (
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{t('referal-code')}
</label>
<input
type="text"
id="referralCode"
name="referralCode"
className={`${styles['form-input']}`}
placeholder={t('referal-code')}
defaultValue={referralCode ? referralCode : ''}
onChange={(e) => {
e.target.value = e.target.value.toLocaleLowerCase();
onChangeHandler(e)
}}
/>
{formState?.error?.referralCode && (
<p className="text-sm text-red-500">
{
formState?.error?.referralCode.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
)
}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']} ${styles['required']}`}>
{t('email-adress')}
</label>
<input
type="text"
id="email"
name="email"
className={`${styles['form-input']}`}
placeholder="ivan@example.com"
defaultValue={formState?.previousState?.email ? formState?.previousState?.email : ''}
onChange={(e) => {
e.target.value = e.target.value.toLocaleLowerCase();
onChangeHandler(e)
}}
/>
{formState?.error?.email && (
<p className="text-sm text-red-500">
{
formState?.error?.email.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']} ${styles['required']}`}>
{t('phone')}
</label>
<PhoneInput phoneState={formState?.previousState?.phone} validateHandler={onChangeHandler} />
{formState?.error?.phone && (
<p className="text-sm text-red-500">
{
formState?.error?.phone.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']} ${styles['required']}`}>
{t('password')}
</label>
<div
className={`${styles['password-wrapper']}`}
>
<input
ref={passwordRef}
type={showPassword ? "text" : "password"}
id="password"
name="password"
className={`${styles['form-input']} ${styles['password']}`}
placeholder={t('password-placeholder')}
defaultValue={formState?.previousState?.password ? formState?.previousState?.password : ''}
onChange={onChangeHandler}
/>
<button
onClick={() => {
showPassowrd('password')
}}
type="button"
className={`show-password-button ${showPassword ? 'show' : ''}`}
>
<IconEye />
</button>
</div>
{formState?.error?.password && (
<p className="text-sm text-red-500">
{
formState?.error?.password.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']} ${styles['required']}`}>
{t('confirm-password')}
</label>
<div className={`${styles['password-wrapper']}`}>
<input
type={showConfirmPassword ? "text" : "password"}
id="confirm_password"
name="confirm_password"
className={`${styles['form-input']} ${styles['password']}`}
placeholder={t('repeat-password')}
defaultValue={formState?.previousState?.confirm_password ? formState?.previousState?.confirm_password : ''}
onChange={onChangeHandler}
/>
<button
onClick={() => {
showPassowrd('confirm-password');
}}
type="button"
className={`show-password-button ${showConfirmPassword ? 'show' : ''}`}
>
<IconEye />
</button>
</div>
{formState?.error?.confirm_password && (
<p className="text-sm text-red-500">
{t(formState?.error?.confirm_password)}
</p>
)}
</div>
<div className={`${styles['form-checkbox']}`}>
<input
type="checkbox"
id="agree"
name="agree"
defaultChecked={formState?.previousState?.agree ? true : false}
onChange={onChangeHandler}
/>
<label htmlFor="agree">
{t('i-agree-to')} <Link
href="/terms-of-use"
target="_blank"
>
{t('terms-of-use')}
</Link> {t('and')} <Link
href="/privacy-policy"
target="_blank"
>
{t('privacy-policy')}
</Link>
</label>
</div>
{formState?.error?.agree && (
<p className="text-sm text-red-500">
{t(formState?.error?.agree)}
</p>
)}
{formState?.error?.server && (
<p className="text-sm text-red-500 mt-4">
{t(formState?.error?.server)}
</p>
)}
<button
type="submit"
className={`${styles['btn']}`}
disabled={isPending}
>
{isPending ? (
<div className="loading-animation">
<div className="global-spinner"></div>
</div>
) : (
t("create-an-account")
)}
</button>
</form >
</>
)
}