add change password modal window
This commit is contained in:
+112
-35
@@ -9,19 +9,14 @@ export async function authorization(
|
||||
previousState: {
|
||||
email: string;
|
||||
}
|
||||
error: Record<string, string>
|
||||
error: Record<string, string>;
|
||||
requirePasswordChange?: boolean;
|
||||
userData?: { email: string; id: number };
|
||||
} | undefined,
|
||||
formData: FormData,
|
||||
) {
|
||||
const email = formData.get('email') as string || '';
|
||||
const password = formData.get('password');
|
||||
|
||||
/* для теста */
|
||||
if (email === 'test' && password === 'test') {
|
||||
/* await createSession('1111', 'test', 0); */
|
||||
/* redirect('/pages/'); */
|
||||
}
|
||||
/* для теста */
|
||||
const password = formData.get('password') as string || '';
|
||||
|
||||
const validatedFields = loginFormSchema.safeParse({
|
||||
email,
|
||||
@@ -67,9 +62,27 @@ export async function authorization(
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
let parsed = await response.json();
|
||||
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);
|
||||
} else {
|
||||
throw (`${parsed.message_code}`);
|
||||
@@ -95,16 +108,80 @@ export async function authorization(
|
||||
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 {
|
||||
previousState: {
|
||||
email,
|
||||
password
|
||||
},
|
||||
error: errors
|
||||
success: false,
|
||||
error: 'request-ended-with-an-error'
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
} catch (error) {
|
||||
console.error('Password update error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'password-update-failed'
|
||||
};
|
||||
}
|
||||
|
||||
redirect('/pages/');
|
||||
@@ -114,25 +191,25 @@ export async function logout() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 100,
|
||||
token: token,
|
||||
message_body: {
|
||||
action: "logout"
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
let parsed = await response.json();
|
||||
}
|
||||
/* const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 100,
|
||||
token: token,
|
||||
message_body: {
|
||||
action: "logout"
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
let parsed = await response.json();
|
||||
} */
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
|
||||
@@ -121,7 +121,7 @@ export async function getSessionData(type: 'token' | 'email' | 'employeId'): Pro
|
||||
|
||||
|
||||
export async function deleteSession() {
|
||||
console.log('delete session')
|
||||
|
||||
try {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.delete('session_nc_ap');
|
||||
|
||||
@@ -4,6 +4,7 @@ import { API_BASE_URL } from '@/app/actions/definitions';
|
||||
import { getSessionData } from '@/app/actions/session';
|
||||
|
||||
export async function fetchTariffStatistic() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -17,7 +18,8 @@ export async function fetchTariffStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -52,7 +55,8 @@ export async function fetchUserFilesTopStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -87,7 +92,8 @@ export async function fetchUserGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -122,7 +129,8 @@ export async function fetchSubscribeGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -157,7 +166,8 @@ export async function fetchProtectedContentGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -192,7 +203,8 @@ export async function fetchViolationGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -227,7 +240,8 @@ export async function fetchTokensGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'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() {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -262,7 +277,8 @@ export async function fetchIncomeGeneralStatistic() {
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ export async function fetchStuffList(page?: number, size?: number, sortBy?: stri
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 100,
|
||||
token: token,
|
||||
message_body: {
|
||||
action: 'get_all',
|
||||
page: page,
|
||||
@@ -24,7 +23,8 @@ export async function fetchStuffList(page?: number, size?: number, sortBy?: stri
|
||||
}),
|
||||
headers: {
|
||||
'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: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -124,7 +125,8 @@ export async function removeStuff(id: number) {
|
||||
}),
|
||||
headers: {
|
||||
'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: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -204,7 +207,8 @@ export async function fetchEmployeInfo() {
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,7 +244,8 @@ export async function fetchStuffTemplates() {
|
||||
}),
|
||||
headers: {
|
||||
'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: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -316,7 +322,8 @@ export async function removeStuffTemplates(id: number,) {
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
|
||||
version: 1,
|
||||
msg_id: 30014,
|
||||
message_body: {
|
||||
/* token: token, */
|
||||
action: 'getAllWithPagination',
|
||||
page: page,
|
||||
size: size,
|
||||
@@ -24,7 +23,8 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
|
||||
}),
|
||||
headers: {
|
||||
'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) {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/info`, {
|
||||
@@ -60,7 +61,8 @@ export async function fetchUsesInfo(userInfo: number) {
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
@use './variable.scss' as v;
|
||||
@use './edit-permissions-modal.scss';
|
||||
@use './animation.scss';
|
||||
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
|
||||
@@ -294,4 +294,45 @@
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
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
@@ -2,10 +2,11 @@
|
||||
|
||||
import styles from '@/app/styles/module/login.module.scss'
|
||||
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 { IconEye } from '@/app/ui/icons/icons';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import ModalWindow from '@/app/components/modalWindow';
|
||||
|
||||
export default function LoginForm() {
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
@@ -15,8 +16,23 @@ export default function LoginForm() {
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -27,92 +43,238 @@ export default function LoginForm() {
|
||||
queryClient.clear();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<form
|
||||
className={`${styles['form-wrapper']}`}
|
||||
action={formAction}
|
||||
>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']}`}>
|
||||
{t('email-adress')}
|
||||
</label>
|
||||
<input
|
||||
type="readOnly"
|
||||
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.toLocaleLowerCase();
|
||||
}}
|
||||
/>
|
||||
{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={() => {
|
||||
showPassowrd();
|
||||
}}
|
||||
type="button"
|
||||
className={`show-password-button ${showPassword ? 'show' : ''}`}
|
||||
>
|
||||
<IconEye />
|
||||
</button>
|
||||
</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>
|
||||
)}
|
||||
{/* <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}
|
||||
const handlePasswordChangeRequired = (email: string, id: number, oldPassword: string, token: string) => {
|
||||
setPendingUserData({ email, id, oldPassword, token });
|
||||
setShowChangePasswordModal(true);
|
||||
};
|
||||
|
||||
const handlePasswordUpdate = async () => {
|
||||
if (!pendingUserData) return;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
setPasswordUpdateError(t('passwords-do-not-match'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
setPasswordUpdateError(t('password-too-short'));
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingPasswordUpdate(true);
|
||||
setPasswordUpdateError(null);
|
||||
|
||||
try {
|
||||
const result = await updatePasswordAction(
|
||||
pendingUserData.id,
|
||||
pendingUserData.oldPassword,
|
||||
newPassword,
|
||||
pendingUserData.email,
|
||||
pendingUserData.token
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
setShowChangePasswordModal(false);
|
||||
setIsRedirecting(true);
|
||||
setTimeout(() => {
|
||||
window.location.href = '/pages/';
|
||||
}, 500);
|
||||
} else {
|
||||
setPasswordUpdateError(result.error || t('password-update-failed'));
|
||||
}
|
||||
} catch (error) {
|
||||
setPasswordUpdateError(t('password-update-failed'));
|
||||
} finally {
|
||||
setPendingPasswordUpdate(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isRedirecting) {
|
||||
return (
|
||||
<div
|
||||
className="loading-animation"
|
||||
>
|
||||
{t("sign-in")}
|
||||
{isPending && (
|
||||
<div className="loading-animation">
|
||||
<div className="global-spinner"></div>
|
||||
<div
|
||||
className="global-spinner large"
|
||||
>
|
||||
</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>
|
||||
{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>
|
||||
</form>
|
||||
{state?.requirePasswordChange && state?.userData && (
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -228,7 +228,6 @@
|
||||
"already-have-an-account": "Already have an account",
|
||||
"company": "Company",
|
||||
"company-placeholder": "Your company",
|
||||
"confirm-password": "Confirm password",
|
||||
"copyright-protection": "Copyright protection",
|
||||
"create-an-account": "Create an account",
|
||||
"email-adress": "Email adress",
|
||||
@@ -291,7 +290,19 @@
|
||||
"confirmation-code": "Confirmation code",
|
||||
"enter-confirmation-code": "Enter the confirmation code",
|
||||
"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": {
|
||||
"edit-permissions": "Edit Permissions",
|
||||
|
||||
@@ -228,7 +228,6 @@
|
||||
"already-have-an-account": "Уже есть аккаунт",
|
||||
"company": "Компания",
|
||||
"company-placeholder": "ООО «Ваша компания»",
|
||||
"confirm-password": "Подтвердите пароль",
|
||||
"copyright-protection": "Защита авторских прав",
|
||||
"create-an-account": "Создать аккаунт",
|
||||
"email-adress": "Email адрес",
|
||||
@@ -291,7 +290,19 @@
|
||||
"confirmation-code": "Код подтверждения",
|
||||
"enter-confirmation-code": "Введите код подтверждения",
|
||||
"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": {
|
||||
"edit-permissions": "Редактирование прав доступа",
|
||||
|
||||
Reference in New Issue
Block a user