Compare commits

...
2 Commits
Author SHA1 Message Date
smanylov c470af4709 increment version 2026-05-22 15:00:16 +07:00
smanylov 3770e12d20 add change password modal window 2026-05-22 14:59:59 +07:00
12 changed files with 545 additions and 147 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "no-copy-admin-panel-frontend", "name": "no-copy-admin-panel-frontend",
"version": "0.11.0", "version": "0.12.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 2996", "dev": "next dev -p 2996",
+111 -34
View File
@@ -9,19 +9,14 @@ export async function authorization(
previousState: { previousState: {
email: string; email: string;
} }
error: Record<string, string> error: Record<string, string>;
requirePasswordChange?: boolean;
userData?: { email: string; id: number };
} | undefined, } | undefined,
formData: FormData, formData: FormData,
) { ) {
const email = formData.get('email') as string || ''; const email = formData.get('email') as string || '';
const password = formData.get('password'); const password = formData.get('password') as string || '';
/* для теста */
if (email === 'test' && password === 'test') {
/* await createSession('1111', 'test', 0); */
/* redirect('/pages/'); */
}
/* для теста */
const validatedFields = loginFormSchema.safeParse({ const validatedFields = loginFormSchema.safeParse({
email, email,
@@ -67,9 +62,27 @@ export async function authorization(
'Accept': 'application/json' 'Accept': 'application/json'
} }
}); });
if (response.ok) { if (response.ok) {
let parsed = await response.json(); let parsed = await response.json();
if (parsed.message_code === 0) { if (parsed.message_code === 0) {
if (!parsed.message_body.admin.active_password) {
const errors: Record<string, string> = {}
return {
previousState: {
email
},
error: errors,
requirePasswordChange: true,
userData: {
email: parsed.message_body.admin.email,
id: parsed.message_body.admin.id,
password: password,
token: parsed.message_body.token
}
};
}
await createSession(parsed.message_body.token, email, parsed.message_body.admin.id); await createSession(parsed.message_body.token, email, parsed.message_body.admin.id);
} else { } else {
throw (`${parsed.message_code}`); throw (`${parsed.message_code}`);
@@ -95,16 +108,80 @@ export async function authorization(
break; break;
} }
if (error) { return {
previousState: {
email,
password
},
error: errors
};
}
redirect('/pages/');
}
export async function updatePasswordAction(
id: number,
oldPassword: string | null,
newPassword: string,
email: string,
token: string
) {
try {
const requestBody: any = {
version: 1,
msg_id: 100,
message_body: {
action: "update",
admin_id: id,
new_password: newPassword
}
};
if (oldPassword) {
requestBody.message_body.old_password = oldPassword;
}
const response = await fetch(`${API_BASE_URL}/api/admin`, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message_code === 0) {
console.log(parsed);
if (parsed.message_desc === 'Success') {
console.log('Success');
await createSession(token, email, id);
return { success: true };
} else {
return { success: false };
}
} else {
return {
success: false,
error: `password-update-failed-code-${parsed.message_code}`
};
}
} else {
return { return {
previousState: { success: false,
email, error: 'request-ended-with-an-error'
password
},
error: errors
}; };
} }
throw error; } catch (error) {
console.error('Password update error:', error);
return {
success: false,
error: 'password-update-failed'
};
} }
redirect('/pages/'); redirect('/pages/');
@@ -114,25 +191,25 @@ export async function logout() {
const token = await getSessionData('token'); const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin`, { /* const response = await fetch(`${API_BASE_URL}/api/admin`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
version: 1, version: 1,
msg_id: 100, msg_id: 100,
token: token, token: token,
message_body: { message_body: {
action: "logout" action: "logout"
} }
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
} }
}); });
if (response.ok) { if (response.ok) {
let parsed = await response.json(); let parsed = await response.json();
} } */
} catch (error) { } catch (error) {
throw error; throw error;
} finally { } finally {
+1 -1
View File
@@ -121,7 +121,7 @@ export async function getSessionData(type: 'token' | 'email' | 'employeId'): Pro
export async function deleteSession() { export async function deleteSession() {
console.log('delete session')
try { try {
const cookieStore = await cookies() const cookieStore = await cookies()
cookieStore.delete('session_nc_ap'); cookieStore.delete('session_nc_ap');
+24 -8
View File
@@ -4,6 +4,7 @@ import { API_BASE_URL } from '@/app/actions/definitions';
import { getSessionData } from '@/app/actions/session'; import { getSessionData } from '@/app/actions/session';
export async function fetchTariffStatistic() { export async function fetchTariffStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -17,7 +18,8 @@ export async function fetchTariffStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -39,6 +41,7 @@ export async function fetchTariffStatistic() {
} }
export async function fetchUserFilesTopStatistic() { export async function fetchUserFilesTopStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -52,7 +55,8 @@ export async function fetchUserFilesTopStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -74,6 +78,7 @@ export async function fetchUserFilesTopStatistic() {
} }
export async function fetchUserGeneralStatistic() { export async function fetchUserGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -87,7 +92,8 @@ export async function fetchUserGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -109,6 +115,7 @@ export async function fetchUserGeneralStatistic() {
} }
export async function fetchSubscribeGeneralStatistic() { export async function fetchSubscribeGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -122,7 +129,8 @@ export async function fetchSubscribeGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -144,6 +152,7 @@ export async function fetchSubscribeGeneralStatistic() {
} }
export async function fetchProtectedContentGeneralStatistic() { export async function fetchProtectedContentGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -157,7 +166,8 @@ export async function fetchProtectedContentGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -179,6 +189,7 @@ export async function fetchProtectedContentGeneralStatistic() {
} }
export async function fetchViolationGeneralStatistic() { export async function fetchViolationGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -192,7 +203,8 @@ export async function fetchViolationGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -214,6 +226,7 @@ export async function fetchViolationGeneralStatistic() {
} }
export async function fetchTokensGeneralStatistic() { export async function fetchTokensGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -227,7 +240,8 @@ export async function fetchTokensGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -249,6 +263,7 @@ export async function fetchTokensGeneralStatistic() {
} }
export async function fetchIncomeGeneralStatistic() { export async function fetchIncomeGeneralStatistic() {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -262,7 +277,8 @@ export async function fetchIncomeGeneralStatistic() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
+16 -9
View File
@@ -12,7 +12,6 @@ export async function fetchStuffList(page?: number, size?: number, sortBy?: stri
body: JSON.stringify({ body: JSON.stringify({
version: 1, version: 1,
msg_id: 100, msg_id: 100,
token: token,
message_body: { message_body: {
action: 'get_all', action: 'get_all',
page: page, page: page,
@@ -24,7 +23,8 @@ export async function fetchStuffList(page?: number, size?: number, sortBy?: stri
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -83,7 +83,8 @@ export async function createStuff(fullName: string, email: string, password: str
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -124,7 +125,8 @@ export async function removeStuff(id: number) {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -166,7 +168,8 @@ export async function updateStuffPermissions(id: number, permissions: Permission
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -204,7 +207,8 @@ export async function fetchEmployeInfo() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -240,7 +244,8 @@ export async function fetchStuffTemplates() {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -279,7 +284,8 @@ export async function createStuffTemplates(name: string, changePassword: boolean
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -316,7 +322,8 @@ export async function removeStuffTemplates(id: number,) {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
+5 -3
View File
@@ -13,7 +13,6 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
version: 1, version: 1,
msg_id: 30014, msg_id: 30014,
message_body: { message_body: {
/* token: token, */
action: 'getAllWithPagination', action: 'getAllWithPagination',
page: page, page: page,
size: size, size: size,
@@ -24,7 +23,8 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
@@ -46,6 +46,7 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
} }
export async function fetchUsesInfo(userInfo: number) { export async function fetchUsesInfo(userInfo: number) {
const token = await getSessionData('token');
try { try {
const response = await fetch(`${API_BASE_URL}/api/admin/info`, { const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
@@ -60,7 +61,8 @@ export async function fetchUsesInfo(userInfo: number) {
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json',
'Authorization': `Bearer ${token}`
} }
}); });
+70
View File
@@ -0,0 +1,70 @@
@use './variable.scss' as v;
.loading-animation {
display: block;
text-align: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.global-spinner {
display: block;
width: 24px;
height: 24px;
margin: 0 auto;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 4px solid #e5e7eb;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
&.large {
width: 80px;
height: 80px;
&::before {
border: 10px solid #e5e7eb;
border-top: 10px solid #667eea;
}
}
}
}
.loading-placeholder {
display: flex;
align-items: center;
justify-content: center;
/* min-width: 80px;
min-height: 80px; */
&.start {
justify-content: start;
}
.loading-animation {
display: block;
text-align: center;
position: relative;
left: auto;
top: auto;
transform: none;
}
.global-spinner {
display: inline-block;
width: 24px;
height: 24px;
position: relative;
}
}
+1
View File
@@ -1,5 +1,6 @@
@use './variable.scss' as v; @use './variable.scss' as v;
@use './edit-permissions-modal.scss'; @use './edit-permissions-modal.scss';
@use './animation.scss';
:root { :root {
--primary-color: #2563eb; --primary-color: #2563eb;
+41
View File
@@ -295,3 +295,44 @@
display: flex; display: flex;
gap: 10px; gap: 10px;
} }
.change-password-modal {
padding: 24px;
min-width: 400px;
h3 {
margin-bottom: 16px;
}
.modal-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 24px;
}
.btn-secondary {
padding: 8px 16px;
background: #6c757d;
color: white;
cursor: pointer;
margin: 20 0 0;
&:hover {
background: #5a6268;
}
}
}
.change-password-btn {
background: none;
border: none;
color: #007bff;
text-decoration: underline;
cursor: pointer;
margin-left: 8px;
&:hover {
color: #0056b3;
}
}
+248 -86
View File
@@ -2,10 +2,11 @@
import styles from '@/app/styles/module/login.module.scss' import styles from '@/app/styles/module/login.module.scss'
import { useActionState, useState, useRef, useEffect } from 'react'; import { useActionState, useState, useRef, useEffect } from 'react';
import { authorization } from '@/app/actions/auth'; import { authorization, updatePasswordAction } from '@/app/actions/auth';
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 { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import ModalWindow from '@/app/components/modalWindow';
export default function LoginForm() { export default function LoginForm() {
const [state, formAction, isPending] = useActionState( const [state, formAction, isPending] = useActionState(
@@ -15,8 +16,23 @@ export default function LoginForm() {
const t = useTranslations('Login-register-form'); const t = useTranslations('Login-register-form');
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
const [pendingPasswordUpdate, setPendingPasswordUpdate] = useState(false);
const [pendingUserData, setPendingUserData] = useState<{
email: string;
id: number;
oldPassword: string;
token: string;
} | null>(null);
const [passwordUpdateError, setPasswordUpdateError] = useState<string | null>(null);
function showPassowrd() { const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showNewPassword, setShowNewPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isRedirecting, setIsRedirecting] = useState(false);
function toggleShowPassword() {
setShowPassword(!showPassword); setShowPassword(!showPassword);
} }
@@ -27,92 +43,238 @@ export default function LoginForm() {
queryClient.clear(); queryClient.clear();
}, []) }, [])
return ( const handlePasswordChangeRequired = (email: string, id: number, oldPassword: string, token: string) => {
<form setPendingUserData({ email, id, oldPassword, token });
className={`${styles['form-wrapper']}`} setShowChangePasswordModal(true);
action={formAction} };
>
<div className={`${styles['form-group']}`}> const handlePasswordUpdate = async () => {
<label className={`${styles['form-label']}`}> if (!pendingUserData) return;
{t('email-adress')}
</label> if (newPassword !== confirmPassword) {
<input setPasswordUpdateError(t('passwords-do-not-match'));
type="readOnly" return;
id="email" }
name="email"
className={`${styles['form-input']}`} if (newPassword.length < 6) {
placeholder={t('enter-email')} setPasswordUpdateError(t('password-too-short'));
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''} return;
onChange={(e) => { }
e.target.value = e.target.value.toLocaleLowerCase();
}} setPendingPasswordUpdate(true);
/> setPasswordUpdateError(null);
{state?.error?.email && (
<p className={`${styles['form-error']}`}> try {
{t(state?.error?.email)} const result = await updatePasswordAction(
</p> pendingUserData.id,
)} pendingUserData.oldPassword,
</div> newPassword,
<div className={`${styles['form-group']}`}> pendingUserData.email,
<label className={`${styles['form-label']}`}> pendingUserData.token
{t('password')} );
</label>
<div className={`${styles['password-wrapper']}`}> if (result.success) {
<input setShowChangePasswordModal(false);
type={showPassword ? "text" : "password"} setIsRedirecting(true);
id="password" setTimeout(() => {
name="password" window.location.href = '/pages/';
className={`${styles['form-input']} ${styles['password']}`} }, 500);
placeholder={t('enter-password')} } else {
/> setPasswordUpdateError(result.error || t('password-update-failed'));
<button }
onClick={() => { } catch (error) {
showPassowrd(); setPasswordUpdateError(t('password-update-failed'));
}} } finally {
type="button" setPendingPasswordUpdate(false);
className={`show-password-button ${showPassword ? 'show' : ''}`} }
> };
<IconEye />
</button> if (isRedirecting) {
</div> return (
{state?.error?.password && ( <div
<p className={`${styles['form-error']}`}> className="loading-animation"
{t(state?.error?.password)}
</p>
)}
</div>
{state?.error.server && (
<p className={`${styles['form-error']}`}>
{t(state?.error.server)}
</p>
)}
{/* <div className={`${styles['form-options']}`}>
<div className={`${styles['form-checkbox']}`}>
<input type="checkbox" id="remember" name="remember" ref={checkBoxRememberMe} />
<label
onClick={() => {
if (checkBoxRememberMe.current) {
checkBoxRememberMe.current.click();
}
}}
className="select-none"
>
{t('remember-me')}
</label>
</div>
</div> */}
<button
type="submit"
className={`${styles['btn']}`}
disabled={isPending}
> >
{t("sign-in")} <div
{isPending && ( className="global-spinner large"
<div className="loading-animation"> >
<div className="global-spinner"></div> </div>
</div>
)
}
return (
<>
<form
className={`${styles['form-wrapper']}`}
action={formAction}
>
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{t('email-adress')}
</label>
<input
type="text"
id="email"
name="email"
className={`${styles['form-input']}`}
placeholder={t('enter-email')}
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''}
onChange={(e) => {
e.target.value = e.target.value.toLowerCase();
}}
/>
{state?.error?.email && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.email)}
</p>
)}
</div>
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{t('password')}
</label>
<div className={`${styles['password-wrapper']}`}>
<input
type={showPassword ? "text" : "password"}
id="password"
name="password"
className={`${styles['form-input']} ${styles['password']}`}
placeholder={t('enter-password')}
/>
<button
onClick={toggleShowPassword}
type="button"
className={`show-password-button ${showPassword ? 'show' : ''}`}
>
<IconEye />
</button>
</div> </div>
{state?.error?.password && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.password)}
</p>
)}
</div>
{state?.error?.server && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.server)}
</p>
)} )}
</button> {state?.requirePasswordChange && state?.userData && (
</form> <p className={`${styles['form-error']}`}>
{t('password-change-required')}
<button
type="button"
onClick={() => handlePasswordChangeRequired(state.userData.email, state.userData.id, state.userData!.password, state.userData!.token)}
className={styles['change-password-btn']}
>
{t('change-password')}
</button>
</p>
)}
<button
type="submit"
className={`${styles['btn']}`}
disabled={isPending}
>
{t("sign-in")}
{isPending && (
<div className="loading-animation">
<div className="global-spinner"></div>
</div>
)}
</button>
</form>
<ModalWindow
state={showChangePasswordModal}
callBack={() => {
setShowChangePasswordModal(false);
setPendingUserData(null);
setNewPassword('');
setConfirmPassword('');
setPasswordUpdateError(null);
}}
>
<div className={styles['change-password-modal']}>
<h3>{t('change-password')}</h3>
<p>{t('password-change-required-message')}</p>
<div className={styles['form-group']}>
<label className={styles['form-label']}>
{t('new-password')}
</label>
<div className={styles['password-wrapper']}>
<input
type={showNewPassword ? "text" : "password"}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className={styles['form-input']}
placeholder={t('enter-new-password')}
/>
<button
type="button"
onClick={() => setShowNewPassword(!showNewPassword)}
className="show-password-button"
>
<IconEye />
</button>
</div>
</div>
<div className={styles['form-group']}>
<label className={styles['form-label']}>
{t('confirm-password')}
</label>
<div className={styles['password-wrapper']}>
<input
type={showConfirmPassword ? "text" : "password"}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className={styles['form-input']}
placeholder={t('confirm-new-password')}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="show-password-button"
>
<IconEye />
</button>
</div>
</div>
{passwordUpdateError && (
<p className={styles['form-error']}>{passwordUpdateError}</p>
)}
<div className={styles['modal-buttons']}>
<button
type="button"
onClick={() => {
setShowChangePasswordModal(false);
setPendingUserData(null);
}}
className={`${styles['btn-secondary']} ${styles['btn']}`}
>
{t('cancel')}
</button>
<button
type="button"
onClick={handlePasswordUpdate}
disabled={pendingPasswordUpdate || !newPassword || !confirmPassword}
className={styles['btn']}
>
{t('update-password')}
{pendingPasswordUpdate && (
<div className="loading-animation">
<div className="global-spinner"></div>
</div>
)}
</button>
</div>
</div>
</ModalWindow>
</>
) )
} }
+13 -2
View File
@@ -228,7 +228,6 @@
"already-have-an-account": "Already have an account", "already-have-an-account": "Already have an account",
"company": "Company", "company": "Company",
"company-placeholder": "Your company", "company-placeholder": "Your company",
"confirm-password": "Confirm password",
"copyright-protection": "Copyright protection", "copyright-protection": "Copyright protection",
"create-an-account": "Create an account", "create-an-account": "Create an account",
"email-adress": "Email adress", "email-adress": "Email adress",
@@ -291,7 +290,19 @@
"confirmation-code": "Confirmation code", "confirmation-code": "Confirmation code",
"enter-confirmation-code": "Enter the confirmation code", "enter-confirmation-code": "Enter the confirmation code",
"full-name-too-short": "Full name must be at least 2 characters", "full-name-too-short": "Full name must be at least 2 characters",
"full-name-too-long": "Full name must not exceed 100 characters" "full-name-too-long": "Full name must not exceed 100 characters",
"passwords-do-not-match": "Passwords do not match",
"password-too-short": "Password must be at least 6 characters",
"password-change-required": "Password change required",
"change-password": "Change password",
"password-change-required-message": "You must set a new password to continue",
"new-password": "New password",
"confirm-password": "Confirm password",
"enter-new-password": "Enter new password",
"confirm-new-password": "Confirm new password",
"update-password": "Update password",
"password-update-failed": "Failed to update password. Please try again later",
"cancel": "Cancel"
}, },
"Permissions": { "Permissions": {
"edit-permissions": "Edit Permissions", "edit-permissions": "Edit Permissions",
+13 -2
View File
@@ -228,7 +228,6 @@
"already-have-an-account": "Уже есть аккаунт", "already-have-an-account": "Уже есть аккаунт",
"company": "Компания", "company": "Компания",
"company-placeholder": "ООО «Ваша компания»", "company-placeholder": "ООО «Ваша компания»",
"confirm-password": "Подтвердите пароль",
"copyright-protection": "Защита авторских прав", "copyright-protection": "Защита авторских прав",
"create-an-account": "Создать аккаунт", "create-an-account": "Создать аккаунт",
"email-adress": "Email адрес", "email-adress": "Email адрес",
@@ -291,7 +290,19 @@
"confirmation-code": "Код подтверждения", "confirmation-code": "Код подтверждения",
"enter-confirmation-code": "Введите код подтверждения", "enter-confirmation-code": "Введите код подтверждения",
"full-name-too-short": "Полное имя должно состоять как минимум из 2 символов", "full-name-too-short": "Полное имя должно состоять как минимум из 2 символов",
"full-name-too-long": "Полное имя не должно превышать 100 символов" "full-name-too-long": "Полное имя не должно превышать 100 символов",
"passwords-do-not-match": "Пароли не совпадают",
"password-too-short": "Пароль должен содержать не менее 6 символов",
"password-change-required": "Требуется смена пароля",
"change-password": "Сменить пароль",
"password-change-required-message": "Для продолжения работы необходимо установить новый пароль",
"new-password": "Новый пароль",
"confirm-password": "Подтверждение пароля",
"enter-new-password": "Введите новый пароль",
"confirm-new-password": "Подтвердите новый пароль",
"update-password": "Обновить пароль",
"password-update-failed": "Не удалось обновить пароль. Попробуйте позже",
"cancel": "Отмена"
}, },
"Permissions": { "Permissions": {
"edit-permissions": "Редактирование прав доступа", "edit-permissions": "Редактирование прав доступа",