add reset password
This commit is contained in:
@@ -2,7 +2,6 @@ import { useTranslations } from 'next-intl'
|
||||
import LogoIcon from '@/app/ui/logo-icon';
|
||||
import styles from '@/app/styles/module/login.module.scss';
|
||||
import Link from 'next/link';
|
||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
||||
import ForgotPasswordForm from '@/app/ui/forms/forgot-password-form';
|
||||
|
||||
export default function Page() {
|
||||
@@ -10,9 +9,6 @@ export default function Page() {
|
||||
|
||||
return (
|
||||
<div className={`${styles['login-container-wrapper']}`}>
|
||||
<div className={`${styles['login-container-language']}`}>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<div className={`${styles['login-container']}`}>
|
||||
<div className={`${styles['logo']}`}>
|
||||
<LogoIcon />
|
||||
|
||||
@@ -2,7 +2,6 @@ import LogoIcon from '@/app/ui/logo-icon';
|
||||
import styles from '@/app/styles/module/login.module.scss'
|
||||
import Link from 'next/link';
|
||||
import RegisterForm from '@/app/ui/forms/register-form';
|
||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
||||
import {useTranslations} from 'next-intl';
|
||||
|
||||
export default function Page() {
|
||||
@@ -10,9 +9,6 @@ export default function Page() {
|
||||
|
||||
return (
|
||||
<div className={`${styles['login-container-wrapper']}`}>
|
||||
<div className={`${styles['login-container-language']}`}>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<div className={`${styles['login-container']}`}>
|
||||
<div className={`${styles['logo']}`}>
|
||||
<LogoIcon />
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useTranslations } from 'next-intl'
|
||||
import LogoIcon from '@/app/ui/logo-icon';
|
||||
import styles from '@/app/styles/module/login.module.scss';
|
||||
import Link from 'next/link';
|
||||
import ResetPasswordForm from '@/app/ui/forms/reset-password-form';
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
return (
|
||||
<div className={`${styles['login-container-wrapper']}`}>
|
||||
<div className={`${styles['login-container']}`}>
|
||||
<div className={`${styles['logo']}`}>
|
||||
<LogoIcon />
|
||||
<h1>NO COPY</h1>
|
||||
<p>
|
||||
{t('recover-password')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ResetPasswordForm />
|
||||
|
||||
<div className={`${styles['register-link']}`}>
|
||||
<p>{t('already-have-an-account')}? <Link href="login">{t('login')}</Link></p>
|
||||
</div>
|
||||
<div className={`${styles['register-link']}`}>
|
||||
<p>{t('no-account')}?
|
||||
<Link href="register"> {t('register')}</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -341,4 +341,152 @@ export async function confirmEmail(
|
||||
console.log('confirmEmail');
|
||||
const emailConfirm = formData.get('emailConfirm') as string || '';
|
||||
console.log(emailConfirm);
|
||||
}
|
||||
|
||||
export async function resetPassword(
|
||||
state: any | undefined,
|
||||
formData: any,
|
||||
) {
|
||||
const email = formData.get('email');
|
||||
console.log('resetPassword');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 20010,
|
||||
message_body: {
|
||||
email: email,
|
||||
action: "resetPassword"
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
|
||||
const parsed = await response.json();
|
||||
console.log(parsed);
|
||||
if (parsed.message_code === 0) {
|
||||
|
||||
} else {
|
||||
throw parsed.message_body;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
redirect(`/reset-password?email=${email}`);
|
||||
}
|
||||
|
||||
export type FormStateConfirmPassword = {
|
||||
previousState: {
|
||||
email: string;
|
||||
password: string;
|
||||
confirm_password: string;
|
||||
verifyToken: string;
|
||||
},
|
||||
error: Record<string, string> | null
|
||||
};
|
||||
|
||||
export async function confirmPassword(
|
||||
state: FormStateConfirmPassword | undefined,
|
||||
formData: any,
|
||||
): Promise<FormStateConfirmPassword | undefined> {
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
const confirm_password = formData.get('confirm_password');
|
||||
const verifyToken = formData.get('verify-token');
|
||||
console.log('confirmPassword');
|
||||
|
||||
const SignupFormSchema = await getSignupFormSchema('resetPassword');
|
||||
|
||||
const dataToValidate = {
|
||||
password,
|
||||
confirm_password
|
||||
};
|
||||
|
||||
const validatedFields = SignupFormSchema.safeParse(dataToValidate);
|
||||
|
||||
console.log(validatedFields);
|
||||
|
||||
if (!validatedFields.success) {
|
||||
const errors: Record<string, string> = {}
|
||||
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||
message: string,
|
||||
path: string
|
||||
}) => {
|
||||
if (errors[obj.path[0]]) {
|
||||
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
||||
} else {
|
||||
errors[obj.path[0]] = obj.message;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
previousState: {
|
||||
email,
|
||||
password,
|
||||
confirm_password,
|
||||
verifyToken
|
||||
},
|
||||
error: errors
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 20010,
|
||||
message_body: {
|
||||
email: email,
|
||||
password: password,
|
||||
verifyToken: verifyToken,
|
||||
action: "confirmVerification"
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
|
||||
const parsed = await response.json();
|
||||
console.log(parsed);
|
||||
if (parsed.message_code === 0) {
|
||||
await createSession(parsed.message_body.authToken, email as string);
|
||||
} else {
|
||||
throw parsed.message_body;
|
||||
}
|
||||
} else {
|
||||
throw (`${response.status}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const typedError = error as any;
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
previousState: {
|
||||
email,
|
||||
password,
|
||||
confirm_password,
|
||||
verifyToken
|
||||
},
|
||||
error: errors
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
redirect('/pages/dashboard');
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as z from 'zod'
|
||||
import { createValidationSchema } from '@/app/lib/validation-config-parser';
|
||||
import { createValidationSchema, createValidationSchemaForPssword } from '@/app/lib/validation-config-parser';
|
||||
import validationConfig from '@/app/lib/validation-config.json';
|
||||
|
||||
export type FormState =
|
||||
@@ -38,7 +38,7 @@ const defaultConfig = validationConfig as ValidationConfig;
|
||||
let cachedSignupSchema: any = null;
|
||||
let schemaPromise: Promise<any> | null = null;
|
||||
|
||||
async function loadConfigFromBackend(accountType: 'b2b' | 'b2c'): Promise<any> {
|
||||
async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise<any> {
|
||||
try {
|
||||
const response = await fetch('/api/validation-config');
|
||||
if (!response.ok) throw new Error('Failed to load config');
|
||||
@@ -50,15 +50,19 @@ async function loadConfigFromBackend(accountType: 'b2b' | 'b2c'): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSignupFormSchema(accountType: 'b2b' | 'b2c') {
|
||||
export async function getSignupFormSchema(schemaType?: 'b2b' | 'b2c' | 'resetPassword') {
|
||||
if (cachedSignupSchema) {
|
||||
return cachedSignupSchema;
|
||||
}
|
||||
|
||||
if (!schemaPromise) {
|
||||
schemaPromise = (async () => {
|
||||
const config = await loadConfigFromBackend(accountType);
|
||||
cachedSignupSchema = createValidationSchema(config, accountType);
|
||||
const config = await loadConfigFromBackend();
|
||||
if (schemaType === 'resetPassword') {
|
||||
cachedSignupSchema = createValidationSchemaForPssword(config);
|
||||
} else {
|
||||
cachedSignupSchema = createValidationSchema(config, schemaType);
|
||||
}
|
||||
|
||||
return cachedSignupSchema;
|
||||
})();
|
||||
|
||||
@@ -163,79 +163,63 @@ export function createValidationSchema(config: ValidationConfig, accountType?: '
|
||||
schema.password = passwordSchema;
|
||||
}
|
||||
|
||||
// companyName (опциональное поле), надо будет удалить потом
|
||||
/* if (config.companyName) {
|
||||
let companySchema = z.string();
|
||||
|
||||
// Если поле опциональное, добавляем .optional().nullable()
|
||||
if (config.companyName.optional?.value) {
|
||||
companySchema = companySchema
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val === null || val === undefined ? '' : val))
|
||||
.refine(
|
||||
(value) => {
|
||||
// Для пустого значения пропускаем проверки
|
||||
if (!value || value.trim().length === 0) return true;
|
||||
|
||||
// Проверка минимальной длины
|
||||
const minLength = config.companyName?.minSize?.value || 2;
|
||||
return value.trim().length >= minLength;
|
||||
},
|
||||
{
|
||||
message: config.companyName.minSize?.message || 'register-error-company-name-min'
|
||||
}
|
||||
) as unknown as z.ZodString;
|
||||
} else {
|
||||
// Если поле обязательное
|
||||
if (config.companyName.minSize) {
|
||||
companySchema = companySchema.min(config.companyName.minSize.value, {
|
||||
error: config.companyName.minSize.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Максимальная длина (применяется всегда)
|
||||
if (config.companyName.maxSize) {
|
||||
companySchema = companySchema.refine(
|
||||
(value) => {
|
||||
if (config.companyName?.optional?.value && (!value || value.trim().length === 0)) {
|
||||
return true;
|
||||
}
|
||||
const maxLength = config.companyName?.maxSize?.value || 200;
|
||||
return (value || '').trim().length <= maxLength;
|
||||
},
|
||||
{
|
||||
message: config.companyName.maxSize.message
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Допустимые символы
|
||||
if (config.companyName.allowedSymbols?.value) {
|
||||
companySchema = companySchema.refine(
|
||||
(value) => {
|
||||
if (config.companyName?.optional?.value && (!value || value.trim().length === 0)) {
|
||||
return true;
|
||||
}
|
||||
const regex = new RegExp(config.companyName.allowedSymbols!.value);
|
||||
return regex.test(value || '');
|
||||
},
|
||||
{
|
||||
message: config.companyName.allowedSymbols.message
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
companySchema = companySchema.transform((val) => val?.trim() || '') as unknown as z.ZodString;
|
||||
|
||||
schema.companyName = companySchema;
|
||||
} */
|
||||
|
||||
// Дополнительные поля для формы регистрации
|
||||
schema.confirm_password = z.string();
|
||||
schema.agree = z.string().min(2, { error: 'register-error-agree' });
|
||||
|
||||
return z
|
||||
.object(schema)
|
||||
.refine((data) => data.password === data.confirm_password, {
|
||||
message: 'repeat-password',
|
||||
path: ['confirm_password'],
|
||||
});
|
||||
}
|
||||
|
||||
export function createValidationSchemaForPssword(config: ValidationConfig) {
|
||||
const schema: Record<string, any> = {};
|
||||
|
||||
// password
|
||||
if (config.password) {
|
||||
let passwordSchema = z
|
||||
.string()
|
||||
.min(config.password.minSize?.value || 8, {
|
||||
error: config.password.minSize?.message || 'register-error-password-symbols'
|
||||
})
|
||||
.max(config.password.maxSize?.value || 124, {
|
||||
error: config.password.maxSize?.message || 'register-error-max-password'
|
||||
})
|
||||
.trim();
|
||||
|
||||
// Проверка допустимых символов
|
||||
if (config.password.allowedSymbols?.value) {
|
||||
passwordSchema = passwordSchema.regex(
|
||||
new RegExp(config.password.allowedSymbols.value),
|
||||
{ message: config.password.allowedSymbols.message }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверка наличия цифры
|
||||
if (config.password.requiredDigit?.value) {
|
||||
passwordSchema = passwordSchema.regex(
|
||||
/[0-9]/,
|
||||
{ message: config.password.requiredDigit.message }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверка наличия буквы
|
||||
if (config.password.requiredLetter?.value) {
|
||||
passwordSchema = passwordSchema.regex(
|
||||
/[a-zA-Zа-яёА-ЯЁ]/,
|
||||
{ message: config.password.requiredLetter.message }
|
||||
);
|
||||
}
|
||||
|
||||
schema.password = passwordSchema;
|
||||
}
|
||||
|
||||
// Дополнительные поля для формы регистрации
|
||||
schema.confirm_password = z.string();
|
||||
|
||||
return z
|
||||
.object(schema)
|
||||
.refine((data) => data.password === data.confirm_password, {
|
||||
|
||||
@@ -4,12 +4,11 @@ import styles from '@/app/styles/module/login.module.scss'
|
||||
import { useActionState } from 'react';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import {resetPassword} from '@/app/actions/auth';
|
||||
|
||||
export default function ForgotPasswordForm() {
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
() => {
|
||||
console.log("test");
|
||||
},
|
||||
resetPassword,
|
||||
undefined,
|
||||
);
|
||||
const t = useTranslations('Login-register-form');
|
||||
@@ -27,9 +26,9 @@ export default function ForgotPasswordForm() {
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder={t('enter-email')}
|
||||
/>
|
||||
{/* {state?.error?.email && (
|
||||
{/* {state === 'error' && (
|
||||
<p className={`${styles['form-error']}`}>
|
||||
{t(state?.error?.email)}
|
||||
Восстановление пароля не работает
|
||||
</p>
|
||||
)} */}
|
||||
</div>
|
||||
@@ -38,15 +37,8 @@ export default function ForgotPasswordForm() {
|
||||
{t(state?.error.server)}
|
||||
</p>
|
||||
)} */}
|
||||
<div className={`${styles['form-options']}`}>
|
||||
<div className={`${styles['form-checkbox']}`}>
|
||||
<small>
|
||||
функционал не реализован
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" className={`${styles['btn']}`}>
|
||||
{t('sign-in')}
|
||||
Восстановить пароль
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
'use client'
|
||||
|
||||
import styles from '@/app/styles/module/login.module.scss'
|
||||
import { useActionState, useEffect, useRef, useState, ChangeEvent } from 'react';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { confirmPassword, FormStateConfirmPassword } from '@/app/actions/auth';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { IconEye } from '@/app/ui/icons/icons';
|
||||
import { SignupFormSchema } from '@/app/actions/definitions';
|
||||
import * as z from 'zod';
|
||||
|
||||
export default function ResetPasswordForm() {
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
confirmPassword,
|
||||
undefined,
|
||||
);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [formState, setFormState] = useState<FormStateConfirmPassword | undefined>(undefined);
|
||||
const t = useTranslations('Login-register-form');
|
||||
const searchParams = useSearchParams();
|
||||
const email = searchParams.get('email');
|
||||
console.log(email);
|
||||
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
setFormState(state);
|
||||
}
|
||||
}, [state])
|
||||
|
||||
function showPassowrd(target: 'password' | 'confirm-password') {
|
||||
if (target === 'password') {
|
||||
setShowPassword(!showPassword);
|
||||
} else {
|
||||
setShowConfirmPassword(!showConfirmPassword);
|
||||
}
|
||||
}
|
||||
|
||||
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 'password':
|
||||
const schema = SignupFormSchema.shape[fieldName];
|
||||
return schema.safeParse(value);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown field: ${fieldName}`);
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeHandler(e: ChangeEvent<HTMLInputElement>): void {
|
||||
const fieldName = e.target.name;
|
||||
if (fieldName !== 'phone') {
|
||||
e.target.value = e.target.value.replace(/([^a-zA-Zа-яёА-ЯЁ0-9@.!#$%&"'*+/=?^_{|}~\-\s])/g, '');
|
||||
}
|
||||
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 (
|
||||
<form action={formAction}>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<input
|
||||
type="hidden"
|
||||
id="email"
|
||||
name="email"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder={t('enter-email')}
|
||||
defaultValue={email ? email : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>
|
||||
Код восстановления
|
||||
</label>
|
||||
<input
|
||||
type="readOnly"
|
||||
id="verify-token"
|
||||
name="verify-token"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder={'Код восстановления'}
|
||||
defaultValue={formState?.previousState?.verifyToken ? formState?.previousState?.verifyToken : ''}
|
||||
/>
|
||||
</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>
|
||||
{/* {state?.error.server && (
|
||||
<p className={`${styles['form-error']}`}>
|
||||
{t(state?.error.server)}
|
||||
</p>
|
||||
)} */}
|
||||
<button type="submit" className={`${styles['btn']}`}>
|
||||
Изменить пароль
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user