add sync form validate

This commit is contained in:
smanylov
2025-12-22 13:02:39 +07:00
parent 02ebf8aa07
commit e26a8c4742
4 changed files with 111 additions and 30 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "no-copy-frontend", "name": "no-copy-frontend",
"version": "0.6.0", "version": "0.7.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 2999", "dev": "next dev -p 2999",
+1 -3
View File
@@ -133,7 +133,7 @@ export async function authorization(
redirect('/pages/dashboard'); redirect('/pages/dashboard');
} }
type FormState = { export type FormState = {
previousState: { previousState: {
fullName: string; fullName: string;
email: string; email: string;
@@ -225,8 +225,6 @@ export async function registration(
await createSession(parsed.message_body.token, email); await createSession(parsed.message_body.token, email);
} else { } else {
console.log('error'); console.log('error');
/* console.log(parsed.message_body); */
/* throw (`${parsed.message_code.fieldErrors}`); */
throw parsed; throw parsed;
} }
} else { } else {
+103 -24
View File
@@ -1,22 +1,30 @@
'use client' 'use client'
import styles from '@/app/styles/login.module.scss'; import styles from '@/app/styles/login.module.scss';
import { useActionState, useState } from 'react'; import { ChangeEvent, useActionState, useEffect, useRef, useState } from 'react';
import { registration } from '@/app/actions/auth'; import { registration, FormState } from '@/app/actions/auth';
import PhoneInput from '@/app/ui/inputs/phone-input'; import PhoneInput from '@/app/ui/inputs/phone-input';
import Link from 'next/link'; import Link from 'next/link';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { IconEye } from '@/app/ui/icons/icons'; import { IconEye } from '@/app/ui/icons/icons';
import { SignupFormSchema } from '@/app/actions/definitions';
import * as z from 'zod'
export default function RegisterForm() { export default function RegisterForm() {
const [state, formAction, isPending] = useActionState( const [state, formAction, isPending] = useActionState(
registration, registration,
undefined, undefined,
); );
const t = useTranslations('Login-register-form');
const t = useTranslations('Login-register-form');
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [formState, setFormState] = useState<FormState | undefined>(undefined);
const passwordRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setFormState(state);
}, [state])
function handleMouseDown(target: 'password' | 'confirm-password') { function handleMouseDown(target: 'password' | 'confirm-password') {
if (target === 'password') { if (target === 'password') {
@@ -43,6 +51,71 @@ export default function RegisterForm() {
} }
} }
function validateField(
fieldName: string,
value: string,
) {
switch (fieldName) {
case 'confirm_password':
const baseSchema = SignupFormSchema.shape.confirm_password;
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 'password':
case 'agree':
const schema = SignupFormSchema.shape[fieldName];
if (fieldName === 'phone') {
return schema.safeParse(value.replace(/[-\(\)\s]/g, ''));
}
return schema.safeParse(value);
default:
throw new Error(`Unknown field: ${fieldName}`);
}
}
function onChangeHandler(e: ChangeEvent<HTMLInputElement>): void {
const fieldName = e.target.name;
const validatedField = 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
};
});
}
}
return ( return (
<form action={(e) => { <form action={(e) => {
formAction(e); formAction(e);
@@ -59,12 +132,13 @@ export default function RegisterForm() {
name="fullName" name="fullName"
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder={t('name-placeholder')} placeholder={t('name-placeholder')}
defaultValue={state?.previousState?.fullName ? state?.previousState?.fullName : ''} defaultValue={formState?.previousState?.fullName ? formState?.previousState?.fullName : ''}
onChange={onChangeHandler}
/> />
{state?.error?.fullName && ( {formState?.error?.fullName && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{ {
state?.error?.fullName.split('&').map((e) => { formState?.error?.fullName.split('&').map((e) => {
return t(e) + " " return t(e) + " "
}) })
} }
@@ -82,12 +156,13 @@ export default function RegisterForm() {
name="email" name="email"
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder="ivan@example.com" placeholder="ivan@example.com"
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''} defaultValue={formState?.previousState?.email ? formState?.previousState?.email : ''}
onChange={onChangeHandler}
/> />
{state?.error?.email && ( {formState?.error?.email && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{ {
state?.error?.email.split('&').map((e) => { formState?.error?.email.split('&').map((e) => {
return t(e) + " " return t(e) + " "
}) })
} }
@@ -98,10 +173,10 @@ export default function RegisterForm() {
<label className={`${styles['form-label']} ${styles['required']}`}> <label className={`${styles['form-label']} ${styles['required']}`}>
{t('phone')} {t('phone')}
</label> </label>
<PhoneInput phoneState={state?.previousState?.phone} /> <PhoneInput phoneState={formState?.previousState?.phone} validateHandler={onChangeHandler} />
{state?.error?.phone && ( {formState?.error?.phone && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{t(state?.error?.phone)} {t(formState?.error?.phone)}
</p> </p>
)} )}
</div> </div>
@@ -118,7 +193,7 @@ export default function RegisterForm() {
name="companyName" name="companyName"
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder={t('company-placeholder')} placeholder={t('company-placeholder')}
defaultValue={state?.previousState?.companyName ? state?.previousState?.companyName : ''} defaultValue={formState?.previousState?.companyName ? formState?.previousState?.companyName : ''}
/> />
</div> </div>
<div className={`${styles['form-row']}`}> <div className={`${styles['form-row']}`}>
@@ -130,11 +205,13 @@ export default function RegisterForm() {
className={`${styles['password-wrapper']}`} className={`${styles['password-wrapper']}`}
> >
<input <input
ref={passwordRef}
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
id="password" name="password" id="password" name="password"
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder={t('password-placeholder')} placeholder={t('password-placeholder')}
defaultValue={state?.previousState?.password ? state?.previousState?.password : ''} defaultValue={formState?.previousState?.password ? formState?.previousState?.password : ''}
onChange={onChangeHandler}
/> />
<button <button
onMouseDown={() => { onMouseDown={() => {
@@ -152,10 +229,10 @@ export default function RegisterForm() {
<IconEye /> <IconEye />
</button> </button>
</div> </div>
{state?.error?.password && ( {formState?.error?.password && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{ {
state?.error?.password.split('&').map((e) => { formState?.error?.password.split('&').map((e) => {
return t(e) return t(e)
}) })
} }
@@ -173,7 +250,8 @@ export default function RegisterForm() {
name="confirm_password" name="confirm_password"
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder={t('repeat-password')} placeholder={t('repeat-password')}
defaultValue={state?.previousState?.confirm_password ? state?.previousState?.confirm_password : ''} defaultValue={formState?.previousState?.confirm_password ? formState?.previousState?.confirm_password : ''}
onChange={onChangeHandler}
/> />
<button <button
onMouseDown={() => { onMouseDown={() => {
@@ -191,9 +269,9 @@ export default function RegisterForm() {
<IconEye /> <IconEye />
</button> </button>
</div> </div>
{state?.error?.confirm_password && ( {formState?.error?.confirm_password && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{t(state?.error?.confirm_password)} {t(formState?.error?.confirm_password)}
</p> </p>
)} )}
</div> </div>
@@ -203,7 +281,8 @@ export default function RegisterForm() {
type="checkbox" type="checkbox"
id="agree" id="agree"
name="agree" name="agree"
defaultChecked={state?.previousState?.agree ? true : false} defaultChecked={formState?.previousState?.agree ? true : false}
onChange={onChangeHandler}
/> />
<label htmlFor="agree"> <label htmlFor="agree">
{t('i-agree-to')} <Link {t('i-agree-to')} <Link
@@ -219,14 +298,14 @@ export default function RegisterForm() {
</Link> </Link>
</label> </label>
</div> </div>
{state?.error?.agree && ( {formState?.error?.agree && (
<p className="text-sm text-red-500"> <p className="text-sm text-red-500">
{t(state?.error?.agree)} {t(formState?.error?.agree)}
</p> </p>
)} )}
{state?.error?.server && ( {formState?.error?.server && (
<p className="text-sm text-red-500 mt-4"> <p className="text-sm text-red-500 mt-4">
{t(state?.error?.server)} {t(formState?.error?.server)}
</p> </p>
)} )}
<button type="submit" className={`${styles['btn']}`}> <button type="submit" className={`${styles['btn']}`}>
+6 -2
View File
@@ -5,9 +5,10 @@ import styles from '@/app/styles/login.module.scss';
interface PhoneInputProps { interface PhoneInputProps {
phoneState: string | undefined; phoneState: string | undefined;
validateHandler: (e: ChangeEvent<HTMLInputElement>) => void;
} }
export default function PhoneInput({ phoneState }: PhoneInputProps) { export default function PhoneInput({ phoneState, validateHandler }: PhoneInputProps) {
const [phone, setPhone] = useState(phoneState ? phoneState : ''); const [phone, setPhone] = useState(phoneState ? phoneState : '');
const formatPhoneNumber = (value: string, isBackspace: boolean): string => { const formatPhoneNumber = (value: string, isBackspace: boolean): string => {
@@ -73,7 +74,10 @@ export default function PhoneInput({ phoneState }: PhoneInputProps) {
className={`${styles['form-input']}`} className={`${styles['form-input']}`}
placeholder="+7 (999) 123-45-67" placeholder="+7 (999) 123-45-67"
value={phone} value={phone}
onChange={handleChange} onChange={(e) => {
handleChange(e);
validateHandler(e);
}}
/> />
) )
} }