Merge pull request 'NCFRONT-72' (#4) from NCFRONT-72 into main

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2026-02-17 19:33:05 +08:00
7 changed files with 219 additions and 167 deletions
+50 -118
View File
@@ -142,6 +142,7 @@ export type FormState = {
inn?: string | undefined; inn?: string | undefined;
} | undefined, } | undefined,
mailConfirm?: boolean, mailConfirm?: boolean,
userId?: number,
error: Record<string, string> | null error: Record<string, string> | null
}; };
@@ -256,7 +257,7 @@ export async function registration(
if (response.ok) { if (response.ok) {
let parsed = await response.json() as { let parsed = await response.json() as {
message_desc: string, message_desc: string,
message_body: { token: string }, message_body: { token: string, userId: number },
message_code: number message_code: number
}; };
@@ -271,7 +272,24 @@ export async function registration(
...(referralCode !== '' && { referralLink: referralCode }), ...(referralCode !== '' && { referralLink: referralCode }),
...(accountType === 'b2b' && { companyName: companyName, inn: inn }) ...(accountType === 'b2b' && { companyName: companyName, inn: inn })
}); });
await createSession(parsed.message_body.token, email as string); /* await createSession(parsed.message_body.token, email as string); */
return {
previousState: {
fullName,
email,
password,
confirm_password,
phone,
agree,
...(referralCode !== '' && { referralLink: referralCode }),
...(accountType === 'b2b' && { companyName: companyName, inn: inn })
},
mailConfirm: true,
userId: parsed.message_body.userId,
error: null
};
} else { } else {
console.log(parsed); console.log(parsed);
throw parsed; throw parsed;
@@ -280,19 +298,6 @@ export async function registration(
throw (`${response.status}`); throw (`${response.status}`);
} }
/* return {
previousState: {
fullName,
email,
password,
confirm_password,
phone,
companyName,
agree
},
mailConfirm: true,
error: null
}; */
} catch (error: unknown) { } catch (error: unknown) {
const errors: Record<string, string> = {}; const errors: Record<string, string> = {};
if (error) { if (error) {
@@ -341,6 +346,7 @@ export async function registration(
agree, agree,
inn inn
}, },
mailConfirm: false,
error: errors error: errors
}; };
} }
@@ -397,34 +403,28 @@ export async function tokenLifeExtension() {
} }
export async function confirmEmail( export async function confirmEmail(
state: any | undefined, state: { error: string } | undefined,
formData: any, formData: FormData
) { ) {
console.log('confirmEmail'); console.log('confirmEmail');
const emailConfirm = formData.get('emailConfirm') as string || ''; const emailConfirm = formData.get('emailConfirm') as string || '';
console.log(emailConfirm); console.log(emailConfirm);
} const userId = formData.get('userId') as string || '';
export async function resetPassword(
state: any | undefined,
formData: FormData,
) {
const email = formData.get('email');
console.log('resetPassword');
try { try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, { const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Accept': 'application/json', "Accept": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
version: 1, version: 1,
msg_id: 20010, msg_id: 20009,
message_body: { message_body: {
email: email, resend: 0,
action: "resetPassword" user_id: userId,
verify_token: emailConfirm
} }
}), }),
}); });
@@ -432,122 +432,54 @@ export async function resetPassword(
if (response.ok) { if (response.ok) {
const parsed = await response.json(); const parsed = await response.json();
console.log(parsed); console.log(parsed);
if (parsed.message_code === 0) {
} else { if (parsed.message_code === 0) {
throw 'error'; await createSession(parsed.message_body.token, parsed.message_body.email as string);
} }
} }
} catch (error) { } catch (error) {
return { return {
error: error error: 'error'
}; }
} }
console.log('redirect')
redirect(`/reset-password?email=${email}`); redirect('/pages/dashboard');
} }
export type FormStateConfirmPassword = { export async function resendConfirmEmail(userId: number) {
previousState: { console.log('resendConfirmEmail');
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);
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 { try {
const response = await fetch(`${API_BASE_URL}/api/v1/data`, { const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Accept': 'application/json', "Accept": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
version: 1, version: 1,
msg_id: 20010, msg_id: 20009,
message_body: { message_body: {
email: email, resend: 1,
password: password, user_id: userId,
verifyToken: verifyToken,
action: "confirmVerification"
} }
}), }),
}); });
if (response.ok) { if (response.ok) {
const parsed = await response.json(); const parsed = await response.json();
console.log(parsed); console.log(parsed);
if (parsed.message_code === 0) { if (parsed.message_code === 0) {
await createSession(parsed.message_body.authToken, email as string); return {
} else { fine: true
throw parsed.message_body; }
} }
} else {
throw (`${response.status}`);
} }
} catch (error: unknown) { } catch (error) {
const typedError = error as any; return {
const errors: Record<string, string> = {}; fine: false
if (error) {
return {
previousState: {
email,
password,
confirm_password,
verifyToken
},
error: errors
};
} }
throw error;
} }
redirect('/pages/dashboard');
} }
+13 -4
View File
@@ -172,13 +172,22 @@
margin-bottom: 18px; margin-bottom: 18px;
margin: 20px 0 0 0; margin: 20px 0 0 0;
&:disabled {
opacity: 0.5;
}
&:hover:not(:disabled) { &:hover:not(:disabled) {
transform: translateY(-2px); transform: translateY(-2px);
} }
&:disabled {
opacity: 0.5;
background: linear-gradient(135deg, #414291, #4b3975);
&:hover {
transform: none;
}
}
&.refresh {
width: fit-content;
}
} }
.form-group-confrim-window { .form-group-confrim-window {
@@ -0,0 +1,127 @@
import styles from '@/app/styles/module/login.module.scss';
import { useTranslations } from 'next-intl';
import { ChangeEvent, MouseEvent, useActionState, useState, useEffect } from 'react';
import { resendConfirmEmail, confirmEmail } from '@/app/actions/auth';
import { IconRefresh } from '@/app/ui/icons/icons';
export default function ConfirmMailModalWindow({ userId, email }: { userId: number | undefined, email: string | undefined }) {
const [stateEmailConfrim, formActionEmailConfrim, isPendingEmailConfrim] = useActionState(
confirmEmail,
undefined,
);
const [confirmCodeValue, setConfirmCodeValue] = useState('');
const [isInputDisabled, setIsInputDisabled] = useState(true);
const [timer, setTimer] = useState(30);
const t = useTranslations('Login-register-form');
async function reSendEmailConfirmCode(e: MouseEvent) {
e.preventDefault();
if (userId) {
await resendConfirmEmail(userId);
}
}
function onChangeHandler(e: ChangeEvent<HTMLInputElement>) {
const newValue = e.target.value.replace(/([^0-9])/g, '');
if (newValue.length <= 6) {
setConfirmCodeValue(newValue);
}
}
useEffect(() => {
let interval: NodeJS.Timeout;
if (timer > 0 && isInputDisabled) {
interval = setInterval(() => {
setTimer(prev => {
if (prev <= 1) {
setIsInputDisabled(false);
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => {
if (interval) clearInterval(interval);
};
}, [timer, isInputDisabled]);
/* const resetTimer = () => {
setIsInputDisabled(true);
setTimer(30);
}; */
return (
<div
className="modal-window"
onClick={(e) => e.stopPropagation()}
>
<div className={`${styles['form-group']} ${styles['form-group-confrim-window']}`}>
<form action={(e) => {
formActionEmailConfrim(e);
}}>
<div className={`${styles['form-group']}`}>
<h4>
{t('enter-confirmation-code')}
<br />
</h4>
<p
className="mb-6"
>
{email}
</p>
<input
type="text"
id="emailConfirm"
name="emailConfirm"
className={`${styles['form-input']}`}
placeholder={t('confirmation-code')}
value={confirmCodeValue}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
onChangeHandler(e)
}}
/>
<input
type="hiden"
id="userId"
name="userId"
style={{ display: 'none' }}
defaultValue={userId ? userId : ''}
/>
{stateEmailConfrim?.error && (
<p
className="text-sm text-red-500"
>
{t('entered-code-is-incorrect')}
</p>
)}
</div>
<div>
{stateEmailConfrim?.error}
</div>
<div className="flex gap-2">
<button
type="submit"
className={`${styles['btn']}`}
disabled={confirmCodeValue.length !== 6}
>
{t('confirm')}
</button>
<button
className={`${styles['btn'] + ' ' + styles['refresh']} flex`}
disabled={isInputDisabled}
onClick={(e) => {
reSendEmailConfirmCode(e);
}}
title={t('resend-code')}
>
<IconRefresh />
{timer !== 0 ? timer : ''}
</button>
</div>
</form>
</div>
</div>
)
}
+14 -43
View File
@@ -1,8 +1,10 @@
'use client' 'use client'
import styles from '@/app/styles/module/login.module.scss'; import styles from '@/app/styles/module/login.module.scss';
import { ChangeEvent, useActionState, useEffect, useMemo, useRef, useState } from 'react'; import { ChangeEvent, useActionState, useEffect, useRef, useState } from 'react';
import { registration, FormState, confirmEmail } 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';
@@ -10,9 +12,11 @@ import { IconEye } from '@/app/ui/icons/icons';
import { getSignupFormSchema } from '@/app/actions/definitions'; import { getSignupFormSchema } from '@/app/actions/definitions';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import * as z from 'zod' import * as z from 'zod'
import ConfirmMailModalWindow from '@/app/ui/forms/confirm-mail-modal-window';
import { fetchINN } from '@/app/actions/action'; import { fetchINN } from '@/app/actions/action';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
export default function RegisterForm() { export default function RegisterForm() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const referralCode = searchParams.get('ref'); const referralCode = searchParams.get('ref');
@@ -22,11 +26,6 @@ export default function RegisterForm() {
undefined, undefined,
); );
const [stateEmailConfrim, formActionEmailConfrim, isPendingEmailConfrim] = useActionState(
confirmEmail,
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);
@@ -245,6 +244,13 @@ export default function RegisterForm() {
return ( return (
<> <>
<button
onClick={() => {
setShowMailConfirmWindow(!showMailConfirmWindow)
}}
>
show modal
</button>
{showMailConfirmWindow && ( {showMailConfirmWindow && (
<div <div
className="modal-wrapper" className="modal-wrapper"
@@ -252,42 +258,7 @@ export default function RegisterForm() {
setShowMailConfirmWindow(false); setShowMailConfirmWindow(false);
}} }}
> >
<div <ConfirmMailModalWindow userId={state?.userId} email={formState?.previousState?.email} />
className="modal-window"
onClick={(e) => e.stopPropagation()}
>
<div className={`${styles['form-group']} ${styles['form-group-confrim-window']}`}>
<form action={(e) => {
formActionEmailConfrim(e);
}}>
<div className={`${styles['form-group']}`}>
<h4
className="mb-6"
>
{t('enter-confirmation-code')}
</h4>
<label
className={`${styles['form-label']}`}
>
{t('confirmation-code')}
</label>
<input
type="text"
id="emailConfirm"
name="emailConfirm"
className={`${styles['form-input']}`}
placeholder="mail-code"
defaultValue=""
onChange={(e) => {
}}
/>
</div>
<button type="submit" className={`${styles['btn']}`}>
Проверить
</button>
</form>
</div>
</div>
</div> </div>
)} )}
<form action={(e) => { <form action={(e) => {
+7
View File
@@ -162,6 +162,13 @@ export function IconBurgerMenu() {
) )
} }
export function IconRefresh() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V4h2v7h-7V9h4.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.925 0 3.475-1.1T17.65 14h2.1q-.7 2.65-2.85 4.325T12 20" /></svg>
)
}
export function IconSearch() { export function IconSearch() {
return ( return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="icon"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5A6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5S14 7.01 14 9.5S11.99 14 9.5 14" /></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="icon"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5A6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5S14 7.01 14 9.5S11.99 14 9.5 14" /></svg>
+4 -1
View File
@@ -296,7 +296,10 @@
"register-error-phone-not-allowed-symbols": "Phone contains invalid characters.", "register-error-phone-not-allowed-symbols": "Phone contains invalid characters.",
"register-error-password-not-allowed-symbols": "Password contains invalid characters. Can be used @.!#$%&'*+/=?^_{|}~-", "register-error-password-not-allowed-symbols": "Password contains invalid characters. Can be used @.!#$%&'*+/=?^_{|}~-",
"confirmation-code": "Confirmation code", "confirmation-code": "Confirmation code",
"enter-confirmation-code": "Enter the confirmation code", "enter-confirmation-code": "Enter the confirmation code sent to your email:",
"confirm": "Confirm",
"entered-code-is-incorrect": "The code you entered is incorrect.",
"resend-code": "Resend code",
"personal": "Personal", "personal": "Personal",
"unknown-error": "Unknown error", "unknown-error": "Unknown error",
"change-password": "Change Password", "change-password": "Change Password",
+4 -1
View File
@@ -296,7 +296,10 @@
"register-error-phone-not-allowed-symbols": "Телефон содержит недопустимые символы.", "register-error-phone-not-allowed-symbols": "Телефон содержит недопустимые символы.",
"register-error-password-not-allowed-symbols": "Пароль содержит недопустимые символы. Можно использовать @.!#$%&'*+/=?^_{|}~-", "register-error-password-not-allowed-symbols": "Пароль содержит недопустимые символы. Можно использовать @.!#$%&'*+/=?^_{|}~-",
"confirmation-code": "Код подтверждения", "confirmation-code": "Код подтверждения",
"enter-confirmation-code": "Введите код подтверждения", "enter-confirmation-code": "Введите код подтверждения высланный на почту:",
"confirm": "Подтвердить",
"entered-code-is-incorrect": "Введенный код не верен.",
"resend-code": "Выслать код заново",
"personal": "Личный", "personal": "Личный",
"unknown-error": "Неизвестная ошибка", "unknown-error": "Неизвестная ошибка",
"change-password": "Изменить пароль", "change-password": "Изменить пароль",