fix: normalize all API responses, add error handling and reset confirmation

- Extract normalizeConfig() for consistent response normalization
- Add onError handlers to mutations with useNotify
- Add window.confirm before destructive reset action
- Add resetConfirm translation key to all locales
This commit is contained in:
Fringg
2026-02-13 00:41:00 +03:00
parent 1a0a5ff453
commit 150a1b2dba
6 changed files with 33 additions and 12 deletions

View File

@@ -33,6 +33,7 @@ export const BUTTON_SECTIONS = [
export type ButtonSection = (typeof BUTTON_SECTIONS)[number]; export type ButtonSection = (typeof BUTTON_SECTIONS)[number];
// Bot-side locales (includes 'ua' for Ukrainian, mapped from ISO 'uk' internally).
export const BOT_LOCALES = ['ru', 'en', 'ua', 'zh', 'fa'] as const; export const BOT_LOCALES = ['ru', 'en', 'ua', 'zh', 'fa'] as const;
export type BotLocale = (typeof BOT_LOCALES)[number]; export type BotLocale = (typeof BOT_LOCALES)[number];
@@ -54,19 +55,23 @@ export const DEFAULT_BUTTON_STYLES: ButtonStylesConfig = {
admin: { ...DEFAULT_SECTION, style: 'danger' }, admin: { ...DEFAULT_SECTION, style: 'danger' },
}; };
function normalizeConfig(data: ButtonStylesConfig): ButtonStylesConfig {
const result = {} as ButtonStylesConfig;
for (const section of BUTTON_SECTIONS) {
result[section] = {
...DEFAULT_SECTION,
...data[section],
labels: { ...(data[section]?.labels || {}) },
};
}
return result;
}
export const buttonStylesApi = { export const buttonStylesApi = {
getStyles: async (): Promise<ButtonStylesConfig> => { getStyles: async (): Promise<ButtonStylesConfig> => {
try { try {
const response = await apiClient.get<ButtonStylesConfig>('/cabinet/admin/button-styles'); const response = await apiClient.get<ButtonStylesConfig>('/cabinet/admin/button-styles');
const data = response.data; return normalizeConfig(response.data);
for (const section of BUTTON_SECTIONS) {
data[section] = {
...DEFAULT_SECTION,
...data[section],
labels: { ...(data[section]?.labels || {}) },
};
}
return data;
} catch { } catch {
return DEFAULT_BUTTON_STYLES; return DEFAULT_BUTTON_STYLES;
} }
@@ -77,11 +82,11 @@ export const buttonStylesApi = {
'/cabinet/admin/button-styles', '/cabinet/admin/button-styles',
update, update,
); );
return response.data; return normalizeConfig(response.data);
}, },
resetStyles: async (): Promise<ButtonStylesConfig> => { resetStyles: async (): Promise<ButtonStylesConfig> => {
const response = await apiClient.post<ButtonStylesConfig>('/cabinet/admin/button-styles/reset'); const response = await apiClient.post<ButtonStylesConfig>('/cabinet/admin/button-styles/reset');
return response.data; return normalizeConfig(response.data);
}, },
}; };

View File

@@ -10,6 +10,7 @@ import {
BOT_LOCALES, BOT_LOCALES,
} from '../../api/buttonStyles'; } from '../../api/buttonStyles';
import { Toggle } from './Toggle'; import { Toggle } from './Toggle';
import { useNotify } from '../../platform/hooks/useNotify';
type StyleValue = 'primary' | 'success' | 'danger' | 'default'; type StyleValue = 'primary' | 'success' | 'danger' | 'default';
@@ -40,6 +41,7 @@ function stylesEqual(a: ButtonStylesConfig, b: ButtonStylesConfig): boolean {
export function ButtonsTab() { export function ButtonsTab() {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const notify = useNotify();
const { data: serverStyles } = useQuery({ const { data: serverStyles } = useQuery({
queryKey: ['button-styles'], queryKey: ['button-styles'],
@@ -73,6 +75,9 @@ export function ButtonsTab() {
setDraftStyles(data); setDraftStyles(data);
queryClient.setQueryData(['button-styles'], data); queryClient.setQueryData(['button-styles'], data);
}, },
onError: () => {
notify.error(t('common.error'));
},
}); });
const resetMutation = useMutation({ const resetMutation = useMutation({
@@ -82,6 +87,9 @@ export function ButtonsTab() {
setDraftStyles(data); setDraftStyles(data);
queryClient.setQueryData(['button-styles'], data); queryClient.setQueryData(['button-styles'], data);
}, },
onError: () => {
notify.error(t('common.error'));
},
}); });
const updateSection = useCallback( const updateSection = useCallback(
@@ -333,7 +341,11 @@ export function ButtonsTab() {
{/* Reset */} {/* Reset */}
<div className="flex justify-end"> <div className="flex justify-end">
<button <button
onClick={() => resetMutation.mutate()} onClick={() => {
if (window.confirm(t('admin.buttons.resetConfirm'))) {
resetMutation.mutate();
}
}}
disabled={resetMutation.isPending} disabled={resetMutation.isPending}
className="rounded-xl bg-dark-700 px-4 py-2 text-sm text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-50" className="rounded-xl bg-dark-700 px-4 py-2 text-sm text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-50"
> >

View File

@@ -1292,6 +1292,7 @@
"emojiId": "Custom Emoji ID", "emojiId": "Custom Emoji ID",
"emojiPlaceholder": "Custom emoji ID (optional)", "emojiPlaceholder": "Custom emoji ID (optional)",
"resetAll": "Reset all styles", "resetAll": "Reset all styles",
"resetConfirm": "Reset all button styles to defaults?",
"hidden": "Hidden", "hidden": "Hidden",
"customLabels": "Button labels", "customLabels": "Button labels",
"labelPlaceholder": "Custom button text", "labelPlaceholder": "Custom button text",

View File

@@ -1001,6 +1001,7 @@
"emojiId": "شناسه ایموجی سفارشی", "emojiId": "شناسه ایموجی سفارشی",
"emojiPlaceholder": "شناسه ایموجی سفارشی (اختیاری)", "emojiPlaceholder": "شناسه ایموجی سفارشی (اختیاری)",
"resetAll": "بازنشانی همه سبک‌ها", "resetAll": "بازنشانی همه سبک‌ها",
"resetConfirm": "همه سبک‌های دکمه به حالت پیش‌فرض بازنشانی شوند؟",
"hidden": "پنهان", "hidden": "پنهان",
"customLabels": "نام‌های دکمه", "customLabels": "نام‌های دکمه",
"labelPlaceholder": "متن سفارشی دکمه", "labelPlaceholder": "متن سفارشی دکمه",

View File

@@ -1807,6 +1807,7 @@
"emojiId": "Custom Emoji ID", "emojiId": "Custom Emoji ID",
"emojiPlaceholder": "ID кастомного эмодзи (необязательно)", "emojiPlaceholder": "ID кастомного эмодзи (необязательно)",
"resetAll": "Сбросить все стили", "resetAll": "Сбросить все стили",
"resetConfirm": "Сбросить все стили кнопок к значениям по умолчанию?",
"hidden": "Скрыта", "hidden": "Скрыта",
"customLabels": "Названия кнопки", "customLabels": "Названия кнопки",
"labelPlaceholder": "Свой текст кнопки", "labelPlaceholder": "Свой текст кнопки",

View File

@@ -1039,6 +1039,7 @@
"emojiId": "自定义表情 ID", "emojiId": "自定义表情 ID",
"emojiPlaceholder": "自定义表情 ID可选", "emojiPlaceholder": "自定义表情 ID可选",
"resetAll": "重置所有样式", "resetAll": "重置所有样式",
"resetConfirm": "将所有按钮样式重置为默认值?",
"hidden": "已隐藏", "hidden": "已隐藏",
"customLabels": "按钮名称", "customLabels": "按钮名称",
"labelPlaceholder": "自定义按钮文本", "labelPlaceholder": "自定义按钮文本",