fix(profile): редактирование профиля через PUT /v1/admin/me
Self-edit доступен всем ролям без PUT /admins/:id. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,4 +16,8 @@ export const authApi = {
|
||||
const { data } = await apiClient.get('/v1/admin/me');
|
||||
return data;
|
||||
},
|
||||
updateMe: async (payload: Partial<Admin>): Promise<Admin> => {
|
||||
const { data } = await apiClient.put('/v1/admin/me', payload);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
@@ -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<Admin>) => authApi.updateMe(data),
|
||||
onSuccess: (user) => {
|
||||
setUser(user);
|
||||
message.success('Профиль обновлён');
|
||||
},
|
||||
onError: () => message.error('Ошибка обновления профиля'),
|
||||
});
|
||||
};
|
||||
@@ -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 = () => {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Последний вход">{formatDate(user.last_login)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{isSuperadmin && (
|
||||
<Button type="primary" style={{ marginTop: 16 }} onClick={startEditing}>
|
||||
Редактировать
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" style={{ marginTop: 16 }} onClick={startEditing}>
|
||||
Редактировать
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Form form={form} layout="vertical" onFinish={handleSave}>
|
||||
@@ -191,7 +188,7 @@ const ProfilePage: React.FC = () => {
|
||||
<Input.TextArea rows={4} placeholder='{"key": "value"}' />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" loading={updateAdmin.isPending}>
|
||||
<Button type="primary" htmlType="submit" loading={updateProfile.isPending}>
|
||||
Сохранить
|
||||
</Button>
|
||||
<Button onClick={() => setEditing(false)}>Отмена</Button>
|
||||
|
||||
@@ -13,6 +13,7 @@ interface AuthState {
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
checkAuth: () => Promise<void>;
|
||||
setUser: (user: Admin) => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
@@ -69,4 +70,6 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
}));
|
||||
Reference in New Issue
Block a user