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');
|
const { data } = await apiClient.get('/v1/admin/me');
|
||||||
return data;
|
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 { Card, Descriptions, Spin, Button, Form, Input, Select, Tag, message, Space, Avatar } from 'antd';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
import { useUpdateAdmin } from '../../hooks/useAdmins';
|
import { useUpdateProfile } from '../../hooks/useProfile';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const ProfilePage: React.FC = () => {
|
const ProfilePage: React.FC = () => {
|
||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
const updateAdmin = useUpdateAdmin();
|
const updateProfile = useUpdateProfile();
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
// Проверка, является ли текущий пользователь superadmin
|
// Редактирование своего профиля доступно всем ролям (PUT /v1/admin/me)
|
||||||
const isSuperadmin = user?.role === 'superadmin';
|
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 = () => {
|
const startEditing = () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
|
const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
|
||||||
@@ -30,32 +55,6 @@ const ProfilePage: React.FC = () => {
|
|||||||
setEditing(true);
|
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) =>
|
const isBadValue = (val: any) =>
|
||||||
val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
|
val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
|
||||||
|
|
||||||
@@ -126,11 +125,9 @@ const ProfilePage: React.FC = () => {
|
|||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="Последний вход">{formatDate(user.last_login)}</Descriptions.Item>
|
<Descriptions.Item label="Последний вход">{formatDate(user.last_login)}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
{isSuperadmin && (
|
|
||||||
<Button type="primary" style={{ marginTop: 16 }} onClick={startEditing}>
|
<Button type="primary" style={{ marginTop: 16 }} onClick={startEditing}>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Form form={form} layout="vertical" onFinish={handleSave}>
|
<Form form={form} layout="vertical" onFinish={handleSave}>
|
||||||
@@ -191,7 +188,7 @@ const ProfilePage: React.FC = () => {
|
|||||||
<Input.TextArea rows={4} placeholder='{"key": "value"}' />
|
<Input.TextArea rows={4} placeholder='{"key": "value"}' />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" htmlType="submit" loading={updateAdmin.isPending}>
|
<Button type="primary" htmlType="submit" loading={updateProfile.isPending}>
|
||||||
Сохранить
|
Сохранить
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setEditing(false)}>Отмена</Button>
|
<Button onClick={() => setEditing(false)}>Отмена</Button>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface AuthState {
|
|||||||
login: (email: string, password: string) => Promise<void>;
|
login: (email: string, password: string) => Promise<void>;
|
||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
checkAuth: () => Promise<void>;
|
checkAuth: () => Promise<void>;
|
||||||
|
setUser: (user: Admin) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthState>((set) => ({
|
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