diff --git a/src/app/api/user/user.api.ts b/src/app/api/user/user.api.ts index c2c9b91..3dc3d7a 100644 --- a/src/app/api/user/user.api.ts +++ b/src/app/api/user/user.api.ts @@ -1,4 +1,5 @@ import Agent from '@api/agent.api'; +import ChangePasswordDto from '@models/user/user.model'; import { API_ROUTES } from '@/app/common/constants/api-routes.constants'; import { @@ -36,9 +37,13 @@ const UserApi = { API_ROUTES.ADMIN_AUTHORIZATION.REFRESH_TOKEN, token, ), + adminLogout: () => Agent.post( API_ROUTES.ADMIN_AUTHORIZATION.LOGOUT, {}, ), + + changePassword: (data: ChangePasswordDto) => + Agent.post(API_ROUTES.ADMIN_AUTHORIZATION.CHANGE_PASSWORD, data), }; -export default UserApi; +export default UserApi; \ No newline at end of file diff --git a/src/app/common/constants/api-routes.constants.ts b/src/app/common/constants/api-routes.constants.ts index fe9aa00..7b87244 100644 --- a/src/app/common/constants/api-routes.constants.ts +++ b/src/app/common/constants/api-routes.constants.ts @@ -223,6 +223,7 @@ export const API_ROUTES = { GOOGLE_LOGIN: "auth/google-login", REFRESH_TOKEN: "auth/refresh-token", LOGOUT: "auth/logout", + CHANGE_PASSWORD: "auth/change-password", }, COMMENTS: { GET_BY_STREETCODE_ID: "comment/getByStreetcodeId", diff --git a/src/app/common/constants/frontend-routes.constants.ts b/src/app/common/constants/frontend-routes.constants.ts index a979636..09c9f8b 100644 --- a/src/app/common/constants/frontend-routes.constants.ts +++ b/src/app/common/constants/frontend-routes.constants.ts @@ -15,6 +15,7 @@ const FRONTEND_ROUTES = { EDITOR: '/admin-panel/editor', CALENDAR: '/admin-panel/calendar', VACANCIES: '/admin-panel/vacancies', + SETTINGS: '/admin-panel/settings', }, ADMIN_RELATIVE: { PARTNERS: 'partners', @@ -26,6 +27,7 @@ const FRONTEND_ROUTES = { CALENDAR: 'calendar', NEWS: 'news', VACANCIES: 'vacancies', + SETTINGS: 'settings', }, OTHER_PAGES: { CATALOG: '/catalog', diff --git a/src/app/router/Routes.tsx b/src/app/router/Routes.tsx index 963ef57..a109a63 100644 --- a/src/app/router/Routes.tsx +++ b/src/app/router/Routes.tsx @@ -20,6 +20,7 @@ import Partners from "@/features/AdminPage/PartnersPage/Partners.component"; import Streetcodes from "@/features/AdminPage/StreetcodesPage/Streetcodes.component"; import TeamPage from "@/features/AdminPage/TeamPage/TeamPage.component"; import VacanciesPage from "@/features/AdminPage/VacanciesPage/VacanciesPage.component"; +import SettingsPage from "@/features/AdminPage/SettingsPage/SettingsPage.component"; import StreetcodeCatalog from "@/features/StreetcodeCatalogPage/StreetcodeCatalog.component"; const router = createBrowserRouter( @@ -38,6 +39,7 @@ const router = createBrowserRouter( } /> } /> } /> + } /> } /> diff --git a/src/app/stores/user-login-store.ts b/src/app/stores/user-login-store.ts index cb46761..fd8386a 100644 --- a/src/app/stores/user-login-store.ts +++ b/src/app/stores/user-login-store.ts @@ -1,21 +1,16 @@ import { makeAutoObservable } from "mobx"; import UserApi from "@api/user/user.api"; - +import { ChangePasswordDto } from "@models/user/user.model"; import { RefreshTokenResponce, UserLoginResponce } from "@/models/user/user.model"; export default class UserLoginStore { private timeoutHandler: ReturnType | null = null; - private static readonly tokenStorageName = "token"; - private static readonly dateStorageName = "expireAt"; - private static readonly refreshTokenStorageName = "refreshToken"; - private static readonly userIdStorageName = "userId"; public userLoginResponce?: UserLoginResponce; - private callback?: () => void; public constructor() { @@ -38,10 +33,6 @@ export default class UserLoginStore { return localStorage.setItem(UserLoginStore.tokenStorageName, newToken); } - private static clearToken() { - localStorage.removeItem(UserLoginStore.tokenStorageName); - } - private static getRefreshToken() { return localStorage.getItem(UserLoginStore.refreshTokenStorageName); } @@ -50,10 +41,6 @@ export default class UserLoginStore { localStorage.setItem(UserLoginStore.refreshTokenStorageName, refreshToken); } - private static clearRefreshToken() { - localStorage.removeItem(UserLoginStore.refreshTokenStorageName); - } - public setCallback(func: () => void) { this.callback = func; } @@ -73,7 +60,6 @@ export default class UserLoginStore { if (this.timeoutHandler) { clearTimeout(this.timeoutHandler); } - UserLoginStore.clearUserData(); } @@ -122,4 +108,13 @@ export default class UserLoginStore { UserLoginStore.setRefreshToken(refreshToken.refreshToken); return refreshToken; }); -} + + public changePassword = async (data: ChangePasswordDto) => { + try { + await UserApi.changePassword(data); + return { success: true }; + } catch (error) { + return { success: false, error }; + } + }; +} \ No newline at end of file diff --git a/src/features/AdminPage/AdminBar.component.tsx b/src/features/AdminPage/AdminBar.component.tsx index efcf0c6..972b4fa 100644 --- a/src/features/AdminPage/AdminBar.component.tsx +++ b/src/features/AdminPage/AdminBar.component.tsx @@ -49,6 +49,10 @@ const adminNavItems = [ title: 'Словник', to: FRONTEND_ROUTES.ADMIN.DICTIONARY, }, + { + title: 'Налаштування', + to: FRONTEND_ROUTES.ADMIN.SETTINGS, + }, ]; const AdminBar = () => { diff --git a/src/features/AdminPage/SettingsPage/SettingsPage.component.tsx b/src/features/AdminPage/SettingsPage/SettingsPage.component.tsx new file mode 100644 index 0000000..9ad32a5 --- /dev/null +++ b/src/features/AdminPage/SettingsPage/SettingsPage.component.tsx @@ -0,0 +1,149 @@ +import { useState } from 'react'; +import { Tabs, Form, Input, Button, message } from 'antd'; +import useMobx from '@stores/root-store'; +import { ChangePasswordDto } from '@models/user/user.model'; +import './SettingsPage.styles.scss'; + +const SettingsPage = () => { + const { userLoginStore } = useMobx(); + const [passwordForm] = Form.useForm(); + const [profileForm] = Form.useForm(); + + const [loading, setLoading] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const loginResponse = userLoginStore.userLoginResponce; + + const userData = loginResponse?.user; + + const initialValues = { + username: userData?.name ? `${userData.name} ${userData.surname}` : 'Адміністратор', + email: userData?.email || 'admin@streetcode.com', + }; + + const handlePasswordChange = async (data: ChangePasswordDto) => { + setLoading(true); + + try { + const result = await userLoginStore.changePassword(data); + + if (!result.success) { + message.error('Помилка зміни пароля: перевірте поточний пароль'); + return; + } + + message.success('Пароль успішно змінено'); + passwordForm.resetFields(); + } catch (error) { + console.error('Password change error:', error); + message.error('Сталася помилка'); + } finally { + setLoading(false); + } + }; + + const onFinishProfile = (values: any) => { + console.log('Profile update:', values); + message.success('Дані профілю оновлено'); + setIsEditing(false); + }; + + const items = [ + { + key: '1', + label: 'Профіль', + children: ( +
+ + + + + + + + {!isEditing ? ( + + ) : ( +
+ + +
+ )} +
+ ), + }, + { + key: '2', + label: 'Безпека', + children: ( +
+ + + + + + + + + ({ + validator(_, value) { + if (!value || getFieldValue('newPassword') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('Паролі не співпадають!')); + }, + }), + ]} + > + + + + +
+ ), + }, + ]; + + return ( +
+
+

Налаштування

+
+ +
+ ); +}; + +export default SettingsPage; \ No newline at end of file diff --git a/src/features/AdminPage/SettingsPage/SettingsPage.styles.scss b/src/features/AdminPage/SettingsPage/SettingsPage.styles.scss new file mode 100644 index 0000000..d58807d --- /dev/null +++ b/src/features/AdminPage/SettingsPage/SettingsPage.styles.scss @@ -0,0 +1,30 @@ +@use "@sass/variables/_variables.colors.scss" as c; + +.settings-page { + width: 100%; + padding: 24px; + background: #fff; + border-radius: 8px; + + .settings-page-header { + margin-bottom: 24px; + border-bottom: 1px solid #f0f0f0; + padding-bottom: 16px; + } + + .settings-form { + max-width: 400px; + margin-top: 20px; + } + + .settings-tabs { + .ant-tabs-tab-active { + .ant-tabs-tab-btn { + color: c.$dark-red-color; + } + } + .ant-tabs-ink-bar { + background: c.$dark-red-color; + } + } +} \ No newline at end of file diff --git a/src/models/user/user.model.ts b/src/models/user/user.model.ts index 2d3f8fe..1f46b11 100644 --- a/src/models/user/user.model.ts +++ b/src/models/user/user.model.ts @@ -35,3 +35,9 @@ export enum UserRole { Administrator, Moderator, } + +export interface ChangePasswordDto { + currentPassword: string; + newPassword: string; + confirmNewPassword: string; +}