From 0d6a85a4f91ec07b1f87207e1502e30fbf3aeb2c Mon Sep 17 00:00:00 2001 From: Boris Kovalskii <36034823+JustYay@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:15:59 +1000 Subject: [PATCH] feat: add system pages admin section --- src/App.tsx | 11 + src/locales/en.json | 39 ++- src/locales/ru.json | 39 ++- src/pages/AdminInfoPages.tsx | 11 + src/pages/AdminLegalPages.tsx | 569 ++++++++++++++++++++++++++++++++++ src/pages/AdminPanel.tsx | 6 + 6 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 src/pages/AdminLegalPages.tsx diff --git a/src/App.tsx b/src/App.tsx index 46b78bc..abbd610 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -155,6 +155,7 @@ const AdminNewsCreate = lazyWithRetry(() => import('./pages/AdminNewsCreate')); const InfoPageView = lazyWithRetry(() => import('./pages/InfoPageView')); const AdminInfoPages = lazyWithRetry(() => import('./pages/AdminInfoPages')); const AdminInfoPageEditor = lazyWithRetry(() => import('./pages/AdminInfoPageEditor')); +const AdminLegalPages = lazyWithRetry(() => import('./pages/AdminLegalPages')); function ProtectedRoute({ children, @@ -1345,6 +1346,16 @@ function App() { } /> + + + + + + } + />
+ + ))} +
+ {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')} +
+
+ +