feat: info pages — tab replacement, custom pages on /info, responsive fixes

- Add replaces_tab field to admin editor with conflict detection
- Show custom InfoPages as extra tabs on /info page
- Replace built-in tab content when replacement is configured
- Rich DOMPurify sanitizer for TipTap content (images, video, iframe)
- Fix query race: built-in queries wait for tab-replacements to load
- Horizontal scroll for tabs on mobile, 44px touch targets
- Responsive prose: scrollable tables, h-auto on img/video, light theme borders
- Loyalty tier names truncation, progress bar labels stack on mobile
This commit is contained in:
Fringg
2026-04-24 15:01:11 +03:00
parent c16593aeee
commit 656946952a
9 changed files with 481 additions and 29 deletions

View File

@@ -2,6 +2,8 @@ import apiClient from './client';
export type InfoPageType = 'page' | 'faq';
export type ReplacesTab = 'faq' | 'rules' | 'privacy' | 'offer';
export interface InfoPage {
id: number;
slug: string;
@@ -11,6 +13,7 @@ export interface InfoPage {
is_active: boolean;
sort_order: number;
icon: string | null;
replaces_tab: ReplacesTab | null;
created_at: string;
updated_at: string | null;
}
@@ -23,6 +26,7 @@ export interface InfoPageListItem {
is_active: boolean;
sort_order: number;
icon: string | null;
replaces_tab: ReplacesTab | null;
updated_at: string | null;
}
@@ -34,6 +38,7 @@ export interface InfoPageCreateRequest {
is_active: boolean;
sort_order: number;
icon: string | null;
replaces_tab: ReplacesTab | null;
}
export interface InfoPageUpdateRequest {
@@ -44,8 +49,11 @@ export interface InfoPageUpdateRequest {
is_active?: boolean;
sort_order?: number;
icon?: string | null;
replaces_tab?: ReplacesTab | null;
}
export type TabReplacements = Record<ReplacesTab, string | null>;
/** Single FAQ Q&A item stored in content JSONB. */
export interface FaqItem {
q: string;
@@ -58,6 +66,11 @@ export interface InfoPageReorderRequest {
export const infoPagesApi = {
// Public endpoints
getTabReplacements: async (): Promise<TabReplacements> => {
const response = await apiClient.get<TabReplacements>('/cabinet/info-pages/tab-replacements');
return response.data;
},
getPages: async (pageType?: InfoPageType): Promise<InfoPageListItem[]> => {
const params = pageType ? { page_type: pageType } : undefined;
const response = await apiClient.get<InfoPageListItem[]>('/cabinet/info-pages', { params });