From bc20deac2337f03213655377d997dacd8b5b1c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Mon, 13 Jul 2026 17:54:23 +0300 Subject: [PATCH] =?UTF-8?q?fix(profile):=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=20PUT=20/v1/admin/me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-edit доступен всем ролям без PUT /admins/:id. Co-authored-by: Cursor --- src/api/authApi.ts | 4 ++ src/hooks/useProfile.ts | 18 ++++++++ src/pages/profile/ProfilePage.tsx | 71 +++++++++++++++---------------- src/store/authStore.ts | 3 ++ 4 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 src/hooks/useProfile.ts diff --git a/src/api/authApi.ts b/src/api/authApi.ts index 6a54830..164ba25 100644 --- a/src/api/authApi.ts +++ b/src/api/authApi.ts @@ -16,4 +16,8 @@ export const authApi = { const { data } = await apiClient.get('/v1/admin/me'); return data; }, + updateMe: async (payload: Partial): Promise => { + const { data } = await apiClient.put('/v1/admin/me', payload); + return data; + }, }; \ No newline at end of file diff --git a/src/hooks/useProfile.ts b/src/hooks/useProfile.ts new file mode 100644 index 0000000..70f946c --- /dev/null +++ b/src/hooks/useProfile.ts @@ -0,0 +1,18 @@ +import { useMutation } from '@tanstack/react-query'; +import { message } from 'antd'; +import { authApi } from '../api/authApi'; +import { useAuthStore } from '../store/authStore'; +import { Admin } from '../types/api'; + +export const useUpdateProfile = () => { + const setUser = useAuthStore((s) => s.setUser); + + return useMutation({ + mutationFn: (data: Partial) => authApi.updateMe(data), + onSuccess: (user) => { + setUser(user); + message.success('Профиль обновлён'); + }, + onError: () => message.error('Ошибка обновления профиля'), + }); +}; diff --git a/src/pages/profile/ProfilePage.tsx b/src/pages/profile/ProfilePage.tsx index 8b6ed9a..9030bf0 100644 --- a/src/pages/profile/ProfilePage.tsx +++ b/src/pages/profile/ProfilePage.tsx @@ -2,19 +2,44 @@ import React, { useState } from 'react'; import { Card, Descriptions, Spin, Button, Form, Input, Select, Tag, message, Space, Avatar } from 'antd'; import { UserOutlined } from '@ant-design/icons'; import { useAuthStore } from '../../store/authStore'; -import { useUpdateAdmin } from '../../hooks/useAdmins'; +import { useUpdateProfile } from '../../hooks/useProfile'; import dayjs from 'dayjs'; const ProfilePage: React.FC = () => { const { user } = useAuthStore(); - const updateAdmin = useUpdateAdmin(); + const updateProfile = useUpdateProfile(); const [editing, setEditing] = useState(false); const [form] = Form.useForm(); - // Проверка, является ли текущий пользователь superadmin - const isSuperadmin = user?.role === 'superadmin'; + // Редактирование своего профиля доступно всем ролям (PUT /v1/admin/me) + const handleSave = () => { + if (!user) return; + form.validateFields().then((values) => { + const allowed = ['nickname', 'timezone', 'language', 'phone', 'avatar_url', 'preferences'] as const; + const payload = Object.fromEntries( + Object.entries(values).filter(([key, v]) => + (allowed as readonly string[]).includes(key) && v !== '' && v !== undefined && v !== null + ) + ); + if (payload.preferences) { + try { + payload.preferences = JSON.parse(payload.preferences as string); + } catch { + message.error('Поле «Настройки» должно быть валидным JSON'); + return; + } + } + updateProfile.mutate( + payload, + { + onSuccess: () => { + setEditing(false); + }, + } + ); + }); + }; - // Заполняем форму при входе в режим редактирования const startEditing = () => { if (!user) return; const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val); @@ -30,32 +55,6 @@ const ProfilePage: React.FC = () => { setEditing(true); }; - const handleSave = () => { - if (!user || !isSuperadmin) return; - form.validateFields().then((values) => { - const payload = Object.fromEntries( - Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null) - ); - if (payload.preferences) { - try { - payload.preferences = JSON.parse(payload.preferences as string); - } catch { - message.error('Поле «Настройки» должно быть валидным JSON'); - return; - } - } - updateAdmin.mutate( - { id: user.id, data: payload }, - { - onSuccess: () => { - message.success('Профиль обновлён'); - setEditing(false); - }, - } - ); - }); - }; - const isBadValue = (val: any) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined; @@ -126,11 +125,9 @@ const ProfilePage: React.FC = () => { {formatDate(user.last_login)} - {isSuperadmin && ( - - )} + ) : (
@@ -191,7 +188,7 @@ const ProfilePage: React.FC = () => { - diff --git a/src/store/authStore.ts b/src/store/authStore.ts index a2be7b1..ac0bfc6 100644 --- a/src/store/authStore.ts +++ b/src/store/authStore.ts @@ -13,6 +13,7 @@ interface AuthState { login: (email: string, password: string) => Promise; logout: () => Promise; checkAuth: () => Promise; + setUser: (user: Admin) => void; } export const useAuthStore = create((set) => ({ @@ -69,4 +70,6 @@ export const useAuthStore = create((set) => ({ }); } }, + + setUser: (user) => set({ user }), })); \ No newline at end of file