diff --git a/src/App.tsx b/src/App.tsx index bfab85e..59da62b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -269,6 +269,7 @@ function App() { } /> } /> } /> + } /> => { + const response = await apiClient.get( + '/cabinet/admin/legal-pages/recurrent-payments', + ); + return response.data; + }, + + updateRecurrentPayments: async ( + data: LegalDocumentUpdateRequest, + ): Promise => { + const response = await apiClient.put( + '/cabinet/admin/legal-pages/recurrent-payments', + data, + ); + return response.data; + }, + getRules: async (): Promise => { const response = await apiClient.get('/cabinet/admin/legal-pages/rules'); return response.data; diff --git a/src/api/info.ts b/src/api/info.ts index 76894d1..d67162e 100644 --- a/src/api/info.ts +++ b/src/api/info.ts @@ -23,6 +23,11 @@ export interface PublicOfferResponse { updated_at: string | null; } +export interface RecurrentPaymentsResponse { + content: string; + updated_at: string | null; +} + export interface ServiceInfo { name: string; description: string | null; @@ -42,6 +47,7 @@ export interface InfoVisibility { rules: boolean; privacy: boolean; offer: boolean; + recurrent: boolean; } export const infoApi = { @@ -75,6 +81,14 @@ export const infoApi = { return response.data; }, + // Get recurring-payments document + getRecurrentPayments: async (): Promise => { + const response = await apiClient.get( + '/cabinet/info/recurrent-payments', + ); + return response.data; + }, + // Get service info getServiceInfo: async (): Promise => { const response = await apiClient.get('/cabinet/info/service'); diff --git a/src/components/LegalFooter.tsx b/src/components/LegalFooter.tsx index 4e67efd..c26c16c 100644 --- a/src/components/LegalFooter.tsx +++ b/src/components/LegalFooter.tsx @@ -7,12 +7,10 @@ interface LegalLink { fallback: string; } -// Recurring-payments has no dedicated legal document in the backend yet, so it is -// intentionally omitted rather than pointed at unrelated content. Re-add once a -// `recurrent-payments` document + public endpoint exist. const LINKS: LegalLink[] = [ { href: '/offer', labelKey: 'footer.offer', fallback: 'Публичная оферта' }, { href: '/privacy', labelKey: 'footer.privacy', fallback: 'Политика конфиденциальности' }, + { href: '/recurrent-payments', labelKey: 'footer.recurrent', fallback: 'Рекуррентные платежи' }, ]; interface LegalFooterProps { diff --git a/src/locales/en.json b/src/locales/en.json index 8b6028b..801f63c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -4181,6 +4181,7 @@ "tabs": { "privacy": "Privacy", "offer": "Offer", + "recurrent": "Recurring Payments", "rules": "Rules", "faq": "FAQ" }, diff --git a/src/locales/fa.json b/src/locales/fa.json index 9558add..64b6dee 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -4006,6 +4006,7 @@ "tabs": { "privacy": "حریم خصوصی", "offer": "پیشنهاد", + "recurrent": "پرداخت‌های تکراری", "rules": "قوانین", "faq": "FAQ" }, diff --git a/src/locales/ru.json b/src/locales/ru.json index 7464301..92b5149 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -4726,6 +4726,7 @@ "tabs": { "privacy": "Политика конф.", "offer": "Оферта", + "recurrent": "Рекуррентные платежи", "rules": "Правила", "faq": "FAQ" }, diff --git a/src/locales/zh.json b/src/locales/zh.json index 5fd3a47..08eb37c 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -4005,6 +4005,7 @@ "tabs": { "privacy": "隐私", "offer": "条款", + "recurrent": "定期付款", "rules": "规则", "faq": "FAQ" }, diff --git a/src/pages/AdminLegalPages.tsx b/src/pages/AdminLegalPages.tsx index 04a8ef4..d2c8508 100644 --- a/src/pages/AdminLegalPages.tsx +++ b/src/pages/AdminLegalPages.tsx @@ -14,10 +14,35 @@ import { useDestructiveConfirm } from '../platform/hooks/useNativeDialog'; import { cn } from '../lib/utils'; import { ChevronDownIcon, ChevronUpIcon, PlusIcon, TrashIcon } from '@/components/icons'; -type LegalTab = 'privacy' | 'offer' | 'rules' | 'faq'; +type LegalTab = 'privacy' | 'offer' | 'recurrent' | 'rules' | 'faq'; const DISPLAY_MODES: LegalDisplayMode[] = ['bot', 'web', 'both']; +type DocumentKind = 'privacy-policy' | 'public-offer' | 'recurrent-payments'; + +const DOCUMENT_API: Record< + DocumentKind, + { + get: () => ReturnType; + update: ( + data: Parameters[0], + ) => ReturnType; + } +> = { + 'privacy-policy': { + get: adminLegalPagesApi.getPrivacyPolicy, + update: adminLegalPagesApi.updatePrivacyPolicy, + }, + 'public-offer': { + get: adminLegalPagesApi.getPublicOffer, + update: adminLegalPagesApi.updatePublicOffer, + }, + 'recurrent-payments': { + get: adminLegalPagesApi.getRecurrentPayments, + update: adminLegalPagesApi.updateRecurrentPayments, + }, +}; + function extractErrorDetail(err: unknown): string | null { const error = err as { response?: { data?: { detail?: unknown } } }; const detail = error.response?.data?.detail; @@ -103,7 +128,7 @@ function DocumentEditor({ kind, onDirtyChange, }: { - kind: 'privacy-policy' | 'public-offer'; + kind: DocumentKind; onDirtyChange: (dirty: boolean) => void; }) { const { t } = useTranslation(); @@ -118,10 +143,7 @@ function DocumentEditor({ const { data, isLoading, isFetching } = useQuery({ queryKey: ['admin', 'legal-pages', kind], - queryFn: () => - kind === 'privacy-policy' - ? adminLegalPagesApi.getPrivacyPolicy() - : adminLegalPagesApi.getPublicOffer(), + queryFn: () => DOCUMENT_API[kind].get(), staleTime: 0, gcTime: 0, }); @@ -167,9 +189,7 @@ function DocumentEditor({ is_enabled: enabled[language] ?? false, })), }; - return kind === 'privacy-policy' - ? adminLegalPagesApi.updatePrivacyPolicy(payload) - : adminLegalPagesApi.updatePublicOffer(payload); + return DOCUMENT_API[kind].update(payload); }, onSuccess: () => { haptic.success(); @@ -661,7 +681,7 @@ export default function AdminLegalPages() {
- {(['privacy', 'offer', 'rules', 'faq'] as const).map((tab) => ( + {(['privacy', 'offer', 'recurrent', 'rules', 'faq'] as const).map((tab) => (
diff --git a/src/pages/PublicLegal.tsx b/src/pages/PublicLegal.tsx index 024b17b..20b8a46 100644 --- a/src/pages/PublicLegal.tsx +++ b/src/pages/PublicLegal.tsx @@ -5,7 +5,7 @@ import { infoApi } from '../api/info'; import { formatContent } from '../utils/legalContent'; import LanguageSwitcher from '../components/LanguageSwitcher'; -export type PublicLegalDoc = 'offer' | 'privacy'; +export type PublicLegalDoc = 'offer' | 'privacy' | 'recurrent'; interface PublicLegalProps { doc: PublicLegalDoc; @@ -32,6 +32,12 @@ const DOC_CONFIG: Record< titleFallback: 'Политика конфиденциальности', fetch: infoApi.getPrivacyPolicy, }, + recurrent: { + queryKey: 'recurrent-payments', + titleKey: 'footer.recurrent', + titleFallback: 'Рекуррентные платежи', + fetch: infoApi.getRecurrentPayments, + }, }; // Public, unauthenticated viewer for the legal documents linked from the login