From 25784dbe181a515a3c340f086fda347f77b86031 Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:37:16 +1000 Subject: [PATCH 01/22] feat(balance): per-method quick amounts on top-up page --- src/pages/TopUpAmount.tsx | 6 +++++- src/types/index.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/TopUpAmount.tsx b/src/pages/TopUpAmount.tsx index 8d27106..1eefbbc 100644 --- a/src/pages/TopUpAmount.tsx +++ b/src/pages/TopUpAmount.tsx @@ -375,7 +375,11 @@ export default function TopUpAmount() { } }; - const quickAmounts = [100, 300, 500, 1000].filter((a) => a >= minRubles && a <= maxRubles); + const quickAmounts = ( + method.quick_amounts && method.quick_amounts.length > 0 + ? method.quick_amounts.map((kopeks) => kopeks / 100) + : [100, 300, 500, 1000] + ).filter((a) => a >= minRubles && a <= maxRubles); const currencyDecimals = targetCurrency === 'IRR' || targetCurrency === 'RUB' ? 0 : 2; const getQuickValue = (rub: number) => targetCurrency === 'IRR' diff --git a/src/types/index.ts b/src/types/index.ts index f2d8245..20cbbb8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -448,6 +448,7 @@ export interface PaymentMethod { max_amount_kopeks: number; is_available: boolean; options?: PaymentMethodOption[] | null; + quick_amounts?: number[]; // Если true — после получения payment_url кабинет сразу делает // window.location.href вместо показа панели с кнопкой "Открыть". open_url_direct?: boolean; From bb8b823b3778ac31538cb980c7323f7ca33d212a Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:39:22 +1000 Subject: [PATCH 02/22] feat(admin): quick amounts editor in payment method settings --- src/locales/en.json | 6 +++ src/locales/ru.json | 6 +++ src/pages/AdminPaymentMethodEdit.tsx | 81 ++++++++++++++++++++++++++++ src/types/index.ts | 2 + 4 files changed, 95 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index 63eeeb0..314fa52 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1607,6 +1607,12 @@ "subOptions": "Payment options", "minAmount": "Min amount (kopeks)", "maxAmount": "Max amount (kopeks)", + "quickAmounts": "Quick amounts (₽)", + "quickAmountsHint": "Quick top-up amount buttons. Empty — defaults are used: {{defaults}} ₽", + "quickAmountsPlaceholder": "Amount in rubles", + "quickAmountsAdd": "Add", + "quickAmountsInvalid": "Enter a positive amount in rubles", + "quickAmountsLimit": "No more than 10 amounts", "conditions": "Display conditions", "userTypeFilter": "User type", "userTypeAll": "All", diff --git a/src/locales/ru.json b/src/locales/ru.json index caec2c7..00a12f3 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1629,6 +1629,12 @@ "subOptions": "Варианты оплаты", "minAmount": "Мин. сумма (коп.)", "maxAmount": "Макс. сумма (коп.)", + "quickAmounts": "Быстрые суммы (₽)", + "quickAmountsHint": "Кнопки быстрого выбора суммы пополнения. Пусто — используются значения по умолчанию: {{defaults}} ₽", + "quickAmountsPlaceholder": "Сумма в рублях", + "quickAmountsAdd": "Добавить", + "quickAmountsInvalid": "Введите положительную сумму в рублях", + "quickAmountsLimit": "Не более 10 сумм", "conditions": "Условия отображения", "userTypeFilter": "Тип пользователя", "userTypeAll": "Все", diff --git a/src/pages/AdminPaymentMethodEdit.tsx b/src/pages/AdminPaymentMethodEdit.tsx index f5fa4b2..3e778fb 100644 --- a/src/pages/AdminPaymentMethodEdit.tsx +++ b/src/pages/AdminPaymentMethodEdit.tsx @@ -41,6 +41,9 @@ export default function AdminPaymentMethodEdit() { const [promoGroupFilterMode, setPromoGroupFilterMode] = useState<'all' | 'selected'>('all'); const [selectedPromoGroupIds, setSelectedPromoGroupIds] = useState([]); const [openUrlDirect, setOpenUrlDirect] = useState(false); + const [quickAmounts, setQuickAmounts] = useState([]); + const [quickAmountInput, setQuickAmountInput] = useState(''); + const [quickAmountsError, setQuickAmountsError] = useState(null); // Initialize state when config loads useEffect(() => { @@ -56,6 +59,7 @@ export default function AdminPaymentMethodEdit() { setSelectedPromoGroupIds(config.allowed_promo_group_ids); // ?? false — защита от stale-config (backend ещё не пришёл с миграцией) setOpenUrlDirect(config.open_url_direct ?? false); + setQuickAmounts((config.quick_amounts ?? []).map((kopeks) => kopeks / 100)); } }, [config]); @@ -104,6 +108,14 @@ export default function AdminPaymentMethodEdit() { data.reset_max_amount = true; } + if (quickAmounts.length > 0) { + data.quick_amounts = [...quickAmounts] + .sort((a, b) => a - b) + .map((rubles) => Math.round(rubles * 100)); + } else { + data.reset_quick_amounts = true; + } + updateMethodMutation.mutate(data); }; @@ -113,6 +125,29 @@ export default function AdminPaymentMethodEdit() { ); }; + const addQuickAmount = () => { + setQuickAmountsError(null); + const value = parseFloat(quickAmountInput.replace(',', '.')); + if (isNaN(value) || value <= 0) { + setQuickAmountsError(t('admin.paymentMethods.quickAmountsInvalid')); + return; + } + if (quickAmounts.includes(value)) { + setQuickAmountInput(''); + return; + } + if (quickAmounts.length >= 10) { + setQuickAmountsError(t('admin.paymentMethods.quickAmountsLimit')); + return; + } + setQuickAmounts((prev) => [...prev, value].sort((a, b) => a - b)); + setQuickAmountInput(''); + }; + + const removeQuickAmount = (value: number) => { + setQuickAmounts((prev) => prev.filter((amount) => amount !== value)); + }; + if (isLoading) { return (
@@ -305,6 +340,52 @@ export default function AdminPaymentMethodEdit() {
+
+ + {quickAmounts.length > 0 && ( +
+ {quickAmounts.map((value) => ( + + ))} +
+ )} +
+ setQuickAmountInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + addQuickAmount(); + } + }} + placeholder={t('admin.paymentMethods.quickAmountsPlaceholder')} + className="input flex-1" + /> + +
+ {quickAmountsError &&

{quickAmountsError}

} +

+ {t('admin.paymentMethods.quickAmountsHint', { + defaults: config.default_quick_amounts.map((kopeks) => kopeks / 100).join(', '), + })} +

+
+ {/* Display conditions */}

diff --git a/src/types/index.ts b/src/types/index.ts index 20cbbb8..f4fd660 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -688,6 +688,8 @@ export interface PaymentMethodConfig { default_display_name: string; sub_options: Record | null; available_sub_options: PaymentMethodSubOptionInfo[] | null; + quick_amounts: number[] | null; + default_quick_amounts: number[]; min_amount_kopeks: number | null; max_amount_kopeks: number | null; default_min_amount_kopeks: number; From 92198854b2efbc213d7bb4a951f85f249d9c9bb2 Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:52:04 +1000 Subject: [PATCH 03/22] fix(balance): align quick amounts edge cases with bot behavior --- src/locales/en.json | 1 + src/locales/ru.json | 1 + src/pages/AdminPaymentMethodEdit.tsx | 4 +++- src/pages/TopUpAmount.tsx | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 314fa52..456a868 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1613,6 +1613,7 @@ "quickAmountsAdd": "Add", "quickAmountsInvalid": "Enter a positive amount in rubles", "quickAmountsLimit": "No more than 10 amounts", + "quickAmountsRemove": "Remove amount {{value}} ₽", "conditions": "Display conditions", "userTypeFilter": "User type", "userTypeAll": "All", diff --git a/src/locales/ru.json b/src/locales/ru.json index 00a12f3..9a86c5e 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1635,6 +1635,7 @@ "quickAmountsAdd": "Добавить", "quickAmountsInvalid": "Введите положительную сумму в рублях", "quickAmountsLimit": "Не более 10 сумм", + "quickAmountsRemove": "Удалить сумму {{value}} ₽", "conditions": "Условия отображения", "userTypeFilter": "Тип пользователя", "userTypeAll": "Все", diff --git a/src/pages/AdminPaymentMethodEdit.tsx b/src/pages/AdminPaymentMethodEdit.tsx index 3e778fb..f71c33c 100644 --- a/src/pages/AdminPaymentMethodEdit.tsx +++ b/src/pages/AdminPaymentMethodEdit.tsx @@ -127,7 +127,8 @@ export default function AdminPaymentMethodEdit() { const addQuickAmount = () => { setQuickAmountsError(null); - const value = parseFloat(quickAmountInput.replace(',', '.')); + const parsed = parseFloat(quickAmountInput.replace(',', '.')); + const value = Math.round(parsed); if (isNaN(value) || value <= 0) { setQuickAmountsError(t('admin.paymentMethods.quickAmountsInvalid')); return; @@ -351,6 +352,7 @@ export default function AdminPaymentMethodEdit() { key={value} type="button" onClick={() => removeQuickAmount(value)} + aria-label={t('admin.paymentMethods.quickAmountsRemove', { value })} className="flex items-center gap-1.5 rounded-xl border border-accent-500/30 bg-accent-500/10 px-3 py-1.5 text-sm font-medium text-accent-300 transition-colors hover:border-error-500/40 hover:bg-error-500/10 hover:text-error-400" > {value} ₽ diff --git a/src/pages/TopUpAmount.tsx b/src/pages/TopUpAmount.tsx index 1eefbbc..ebaecae 100644 --- a/src/pages/TopUpAmount.tsx +++ b/src/pages/TopUpAmount.tsx @@ -376,7 +376,7 @@ export default function TopUpAmount() { }; const quickAmounts = ( - method.quick_amounts && method.quick_amounts.length > 0 + method.quick_amounts != null ? method.quick_amounts.map((kopeks) => kopeks / 100) : [100, 300, 500, 1000] ).filter((a) => a >= minRubles && a <= maxRubles); From e979aa8863e32cd141722dbc1690fac12bfedeae Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:01:06 +1000 Subject: [PATCH 04/22] feat: add admin legal pages api client and display mode types --- src/api/adminLegalPages.ts | 152 +++++++++++++++++++++++++++++++++++++ src/api/info.ts | 12 +++ src/api/infoPages.ts | 6 ++ 3 files changed, 170 insertions(+) create mode 100644 src/api/adminLegalPages.ts diff --git a/src/api/adminLegalPages.ts b/src/api/adminLegalPages.ts new file mode 100644 index 0000000..855862a --- /dev/null +++ b/src/api/adminLegalPages.ts @@ -0,0 +1,152 @@ +import apiClient from './client'; + +export type LegalDisplayMode = 'bot' | 'web' | 'both'; + +export interface LegalDocumentItem { + language: string; + content: string; + is_enabled: boolean; + updated_at: string | null; +} + +export interface LegalDocumentResponse { + display_mode: LegalDisplayMode; + display_mode_env_locked: boolean; + items: LegalDocumentItem[]; +} + +export interface LegalDocumentUpdateRequest { + display_mode?: LegalDisplayMode; + items?: Array<{ language: string; content: string; is_enabled: boolean }>; +} + +export interface RulesItem { + language: string; + content: string; + updated_at: string | null; +} + +export interface RulesResponse { + display_mode: LegalDisplayMode; + display_mode_env_locked: boolean; + items: RulesItem[]; +} + +export interface RulesUpdateRequest { + display_mode?: LegalDisplayMode; + items?: Array<{ language: string; content: string }>; +} + +export interface FaqSettingItem { + language: string; + is_enabled: boolean; +} + +export interface FaqPageItem { + id: number; + language: string; + title: string; + content: string; + display_order: number; + is_active: boolean; + updated_at: string | null; +} + +export interface FaqResponse { + display_mode: LegalDisplayMode; + display_mode_env_locked: boolean; + settings: FaqSettingItem[]; + pages: FaqPageItem[]; +} + +export interface FaqUpdateRequest { + display_mode?: LegalDisplayMode; + settings?: FaqSettingItem[]; +} + +export interface FaqPageCreateRequest { + language: string; + title: string; + content: string; + display_order?: number; + is_active?: boolean; +} + +export interface FaqPageUpdateRequest { + title?: string; + content?: string; + display_order?: number; + is_active?: boolean; +} + +export const adminLegalPagesApi = { + getPrivacyPolicy: async (): Promise => { + const response = await apiClient.get( + '/cabinet/admin/legal-pages/privacy-policy', + ); + return response.data; + }, + + updatePrivacyPolicy: async (data: LegalDocumentUpdateRequest): Promise => { + const response = await apiClient.put( + '/cabinet/admin/legal-pages/privacy-policy', + data, + ); + return response.data; + }, + + getPublicOffer: async (): Promise => { + const response = await apiClient.get( + '/cabinet/admin/legal-pages/public-offer', + ); + return response.data; + }, + + updatePublicOffer: async (data: LegalDocumentUpdateRequest): Promise => { + const response = await apiClient.put( + '/cabinet/admin/legal-pages/public-offer', + data, + ); + return response.data; + }, + + getRules: async (): Promise => { + const response = await apiClient.get('/cabinet/admin/legal-pages/rules'); + return response.data; + }, + + updateRules: async (data: RulesUpdateRequest): Promise => { + const response = await apiClient.put('/cabinet/admin/legal-pages/rules', data); + return response.data; + }, + + getFaq: async (): Promise => { + const response = await apiClient.get('/cabinet/admin/legal-pages/faq'); + return response.data; + }, + + updateFaq: async (data: FaqUpdateRequest): Promise => { + const response = await apiClient.put('/cabinet/admin/legal-pages/faq', data); + return response.data; + }, + + createFaqPage: async (data: FaqPageCreateRequest): Promise => { + const response = await apiClient.post( + '/cabinet/admin/legal-pages/faq/pages', + data, + ); + return response.data; + }, + + updateFaqPage: async (id: number, data: FaqPageUpdateRequest): Promise => { + const response = await apiClient.put( + `/cabinet/admin/legal-pages/faq/pages/${id}`, + data, + ); + return response.data; + }, + + deleteFaqPage: async (id: number): Promise => { + await apiClient.delete(`/cabinet/admin/legal-pages/faq/pages/${id}`); + }, +}; diff --git a/src/api/info.ts b/src/api/info.ts index 7ccf50d..76894d1 100644 --- a/src/api/info.ts +++ b/src/api/info.ts @@ -37,6 +37,13 @@ export interface LanguageInfo { flag: string; } +export interface InfoVisibility { + faq: boolean; + rules: boolean; + privacy: boolean; + offer: boolean; +} + export const infoApi = { // Get FAQ pages list getFaqPages: async (): Promise => { @@ -99,4 +106,9 @@ export const infoApi = { const response = await apiClient.get('/cabinet/info/support-config'); return response.data; }, + + getVisibility: async (): Promise => { + const response = await apiClient.get('/cabinet/info/visibility'); + return response.data; + }, }; diff --git a/src/api/infoPages.ts b/src/api/infoPages.ts index 39a39a4..b482510 100644 --- a/src/api/infoPages.ts +++ b/src/api/infoPages.ts @@ -4,6 +4,8 @@ export type InfoPageType = 'page' | 'faq'; export type ReplacesTab = 'faq' | 'rules' | 'privacy' | 'offer'; +export type InfoPageDisplayMode = 'bot' | 'web' | 'both'; + export interface InfoPage { id: number; slug: string; @@ -14,6 +16,7 @@ export interface InfoPage { sort_order: number; icon: string | null; replaces_tab: ReplacesTab | null; + display_mode: InfoPageDisplayMode; created_at: string; updated_at: string | null; } @@ -27,6 +30,7 @@ export interface InfoPageListItem { sort_order: number; icon: string | null; replaces_tab: ReplacesTab | null; + display_mode: InfoPageDisplayMode; updated_at: string | null; } @@ -39,6 +43,7 @@ export interface InfoPageCreateRequest { sort_order: number; icon: string | null; replaces_tab: ReplacesTab | null; + display_mode?: InfoPageDisplayMode; } export interface InfoPageUpdateRequest { @@ -50,6 +55,7 @@ export interface InfoPageUpdateRequest { sort_order?: number; icon?: string | null; replaces_tab?: ReplacesTab | null; + display_mode?: InfoPageDisplayMode; } export type TabReplacements = Record; From 04cbbb5e5182ee3996d62356917c7786132ea8ac Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:03:01 +1000 Subject: [PATCH 05/22] feat: add display mode selector to info page editor --- src/api/infoPages.ts | 2 +- src/locales/en.json | 8 +++++++- src/locales/ru.json | 8 +++++++- src/pages/AdminInfoPageEditor.tsx | 31 ++++++++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/api/infoPages.ts b/src/api/infoPages.ts index b482510..899a438 100644 --- a/src/api/infoPages.ts +++ b/src/api/infoPages.ts @@ -43,7 +43,7 @@ export interface InfoPageCreateRequest { sort_order: number; icon: string | null; replaces_tab: ReplacesTab | null; - display_mode?: InfoPageDisplayMode; + display_mode: InfoPageDisplayMode; } export interface InfoPageUpdateRequest { diff --git a/src/locales/en.json b/src/locales/en.json index 456a868..e02bb0c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -4083,7 +4083,8 @@ "sortOrder": "Sort Order", "icon": "Icon (emoji)", "pageType": "Page Type", - "replacesTab": "Replaces Tab" + "replacesTab": "Replaces Tab", + "displayMode": "Visibility" }, "replacesTabNone": "None", "replacesTabOptions": { @@ -4092,6 +4093,11 @@ "privacy": "Privacy", "offer": "Offer" }, + "displayModes": { + "bot": "Bot only", + "web": "Web only", + "both": "Bot and web" + }, "replacesTabConflict": "already assigned", "replacesTabWarning": "Another page already replaces this tab. It will be unassigned on save.", "filter": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 9a86c5e..71b4f35 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -4628,7 +4628,8 @@ "sortOrder": "Порядок сортировки", "icon": "Иконка (эмодзи)", "pageType": "Тип страницы", - "replacesTab": "Заменяет таб" + "replacesTab": "Заменяет таб", + "displayMode": "Отображение" }, "replacesTabNone": "Не заменяет", "replacesTabOptions": { @@ -4637,6 +4638,11 @@ "privacy": "Конфиденциальность", "offer": "Оферта" }, + "displayModes": { + "bot": "Только бот", + "web": "Только веб", + "both": "Бот и веб" + }, "replacesTabConflict": "уже занят", "replacesTabWarning": "Другая страница уже заменяет этот таб. При сохранении она будет снята.", "filter": { diff --git a/src/pages/AdminInfoPageEditor.tsx b/src/pages/AdminInfoPageEditor.tsx index 4fcc4f6..d69b979 100644 --- a/src/pages/AdminInfoPageEditor.tsx +++ b/src/pages/AdminInfoPageEditor.tsx @@ -37,7 +37,7 @@ import { TrashSmallIcon, PlusSmallIcon, } from '@/components/icons'; -import type { InfoPageType, FaqItem, ReplacesTab } from '../api/infoPages'; +import type { InfoPageType, FaqItem, ReplacesTab, InfoPageDisplayMode } from '../api/infoPages'; const AVAILABLE_LOCALES = ['ru', 'en', 'zh', 'fa'] as const; type LocaleCode = (typeof AVAILABLE_LOCALES)[number]; @@ -643,6 +643,7 @@ export default function AdminInfoPageEditor() { const [sortOrder, setSortOrder] = useState(0); const [pageType, setPageType] = useState(initialPageType); const [replacesTab, setReplacesTab] = useState(null); + const [displayMode, setDisplayMode] = useState('both'); const [saveError, setSaveError] = useState(null); // FAQ Q&A state per locale @@ -857,6 +858,7 @@ export default function AdminInfoPageEditor() { setSortOrder(pageData.sort_order); setPageType(pageData.page_type ?? 'page'); setReplacesTab(pageData.replaces_tab ?? null); + setDisplayMode(pageData.display_mode ?? 'both'); setTitles(pageData.title); if (pageData.page_type === 'faq') { @@ -927,6 +929,7 @@ export default function AdminInfoPageEditor() { sort_order: number; icon: string | null; replaces_tab: ReplacesTab | null; + display_mode: InfoPageDisplayMode; }) => { if (isEdit && pageId != null) { return infoPagesApi.updatePage(pageId, data); @@ -976,6 +979,7 @@ export default function AdminInfoPageEditor() { sort_order: sortOrder, icon: icon.trim() || null, replaces_tab: replacesTab, + display_mode: displayMode, }; haptic.buttonPress(); @@ -1089,6 +1093,31 @@ export default function AdminInfoPageEditor() { {t('admin.infoPages.fields.isActive')}

+
+ +
+ {(['bot', 'web', 'both'] as const).map((mode) => ( + + ))} +
+
+ {/* Page type selector */}
+ + ))} +
+ {disabled && ( +

{t('admin.legalPages.displayModeLocked')}

+ )} + + ); +} + +function LanguageTabs({ + languages, + active, + onChange, +}: { + languages: string[]; + active: string; + onChange: (lang: string) => void; +}) { + const { t } = useTranslation(); + return ( +
+ +
+ {languages.map((lang) => ( + + ))} +
+
+ ); +} + +function DocumentEditor({ kind }: { kind: 'privacy-policy' | 'public-offer' }) { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const haptic = useHapticFeedback(); + const [displayMode, setDisplayMode] = useState('both'); + const [contents, setContents] = useState>({}); + const [enabled, setEnabled] = useState>({}); + const [activeLang, setActiveLang] = useState('ru'); + const [populated, setPopulated] = useState(false); + const [saveError, setSaveError] = useState(null); + + const { data, isLoading } = useQuery({ + queryKey: ['admin', 'legal-pages', kind], + queryFn: () => + kind === 'privacy-policy' + ? adminLegalPagesApi.getPrivacyPolicy() + : adminLegalPagesApi.getPublicOffer(), + staleTime: 0, + }); + + useEffect(() => { + if (!data || populated) return; + setDisplayMode(data.display_mode); + const nextContents: Record = {}; + const nextEnabled: Record = {}; + for (const item of data.items) { + nextContents[item.language] = item.content; + nextEnabled[item.language] = item.is_enabled; + } + setContents(nextContents); + setEnabled(nextEnabled); + if (data.items.length > 0 && !data.items.some((item) => item.language === 'ru')) { + setActiveLang(data.items[0].language); + } + setPopulated(true); + }, [data, populated]); + + const saveMutation = useMutation({ + mutationFn: () => { + const payload = { + ...(data?.display_mode_env_locked ? {} : { display_mode: displayMode }), + items: Object.keys(contents).map((language) => ({ + language, + content: contents[language] ?? '', + is_enabled: enabled[language] ?? false, + })), + }; + return kind === 'privacy-policy' + ? adminLegalPagesApi.updatePrivacyPolicy(payload) + : adminLegalPagesApi.updatePublicOffer(payload); + }, + onSuccess: () => { + haptic.success(); + setSaveError(null); + queryClient.invalidateQueries({ queryKey: ['admin', 'legal-pages', kind] }); + }, + onError: (err) => { + haptic.error(); + setSaveError(extractErrorDetail(err) ?? t('admin.legalPages.saveError')); + }, + }); + + if (isLoading || !data) { + return
; + } + + const languages = data.items.map((item) => item.language); + + return ( +
+ + +
+ setEnabled((prev) => ({ ...prev, [activeLang]: !prev[activeLang] }))} + aria-label={t('admin.legalPages.enabled')} + /> + {t('admin.legalPages.enabled')} +
+
+ +