Compare commits
2
Commits
76009e4abb
...
c470af4709
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c470af4709 | ||
|
|
3770e12d20 |
+1
-1
@@ -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",
|
||||||
|
|||||||
+112
-35
@@ -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 {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 './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;
|
||||||
|
|||||||
@@ -294,4 +294,45 @@
|
|||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
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
@@ -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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "Редактирование прав доступа",
|
||||||
|
|||||||
Reference in New Issue
Block a user