diff --git a/src/app/[locale]/reset-password/page.tsx b/src/app/[locale]/reset-password/page.tsx
new file mode 100644
index 0000000..eac8849
--- /dev/null
+++ b/src/app/[locale]/reset-password/page.tsx
@@ -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 (
+
+
+
+
+
NO COPY
+
+ {t('recover-password')}
+
+
+
+
+
+
+
{t('already-have-an-account')}? {t('login')}
+
+
+
{t('no-account')}?
+ {t('register')}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts
index 5f7b474..39cfbcd 100644
--- a/src/app/actions/auth.ts
+++ b/src/app/actions/auth.ts
@@ -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
| null
+};
+
+export async function confirmPassword(
+ state: FormStateConfirmPassword | undefined,
+ formData: any,
+): Promise {
+ 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 = {}
+ 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 = {};
+
+ if (error) {
+ return {
+ previousState: {
+ email,
+ password,
+ confirm_password,
+ verifyToken
+ },
+ error: errors
+ };
+ }
+ throw error;
+ }
+
+ redirect('/pages/dashboard');
}
\ No newline at end of file
diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts
index 6488098..51e9662 100644
--- a/src/app/actions/definitions.ts
+++ b/src/app/actions/definitions.ts
@@ -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 | null = null;
-async function loadConfigFromBackend(accountType: 'b2b' | 'b2c'): Promise {
+async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise {
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 {
}
}
-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;
})();
diff --git a/src/app/lib/validation-config-parser.ts b/src/app/lib/validation-config-parser.ts
index e6bcf97..d3a3825 100644
--- a/src/app/lib/validation-config-parser.ts
+++ b/src/app/lib/validation-config-parser.ts
@@ -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 = {};
+
+ // 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, {
diff --git a/src/app/ui/forms/forgot-password-form.tsx b/src/app/ui/forms/forgot-password-form.tsx
index 6c71e9b..93f48c5 100644
--- a/src/app/ui/forms/forgot-password-form.tsx
+++ b/src/app/ui/forms/forgot-password-form.tsx
@@ -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' && (
- {t(state?.error?.email)}
+ Восстановление пароля не работает
)} */}
@@ -38,15 +37,8 @@ export default function ForgotPasswordForm() {
{t(state?.error.server)}
)} */}
-
-
-
- функционал не реализован
-
-
-
)
diff --git a/src/app/ui/forms/reset-password-form.tsx b/src/app/ui/forms/reset-password-form.tsx
new file mode 100644
index 0000000..3550d9c
--- /dev/null
+++ b/src/app/ui/forms/reset-password-form.tsx
@@ -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
(null);
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [formState, setFormState] = useState(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): 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 (
+
+ )
+}
\ No newline at end of file