feat: add display mode selector to info page editor

This commit is contained in:
Boris Kovalskii
2026-06-11 09:03:01 +10:00
parent e979aa8863
commit 04cbbb5e51
4 changed files with 45 additions and 4 deletions

View File

@@ -43,7 +43,7 @@ export interface InfoPageCreateRequest {
sort_order: number; sort_order: number;
icon: string | null; icon: string | null;
replaces_tab: ReplacesTab | null; replaces_tab: ReplacesTab | null;
display_mode?: InfoPageDisplayMode; display_mode: InfoPageDisplayMode;
} }
export interface InfoPageUpdateRequest { export interface InfoPageUpdateRequest {

View File

@@ -4083,7 +4083,8 @@
"sortOrder": "Sort Order", "sortOrder": "Sort Order",
"icon": "Icon (emoji)", "icon": "Icon (emoji)",
"pageType": "Page Type", "pageType": "Page Type",
"replacesTab": "Replaces Tab" "replacesTab": "Replaces Tab",
"displayMode": "Visibility"
}, },
"replacesTabNone": "None", "replacesTabNone": "None",
"replacesTabOptions": { "replacesTabOptions": {
@@ -4092,6 +4093,11 @@
"privacy": "Privacy", "privacy": "Privacy",
"offer": "Offer" "offer": "Offer"
}, },
"displayModes": {
"bot": "Bot only",
"web": "Web only",
"both": "Bot and web"
},
"replacesTabConflict": "already assigned", "replacesTabConflict": "already assigned",
"replacesTabWarning": "Another page already replaces this tab. It will be unassigned on save.", "replacesTabWarning": "Another page already replaces this tab. It will be unassigned on save.",
"filter": { "filter": {

View File

@@ -4628,7 +4628,8 @@
"sortOrder": "Порядок сортировки", "sortOrder": "Порядок сортировки",
"icon": "Иконка (эмодзи)", "icon": "Иконка (эмодзи)",
"pageType": "Тип страницы", "pageType": "Тип страницы",
"replacesTab": "Заменяет таб" "replacesTab": "Заменяет таб",
"displayMode": "Отображение"
}, },
"replacesTabNone": "Не заменяет", "replacesTabNone": "Не заменяет",
"replacesTabOptions": { "replacesTabOptions": {
@@ -4637,6 +4638,11 @@
"privacy": "Конфиденциальность", "privacy": "Конфиденциальность",
"offer": "Оферта" "offer": "Оферта"
}, },
"displayModes": {
"bot": "Только бот",
"web": "Только веб",
"both": "Бот и веб"
},
"replacesTabConflict": "уже занят", "replacesTabConflict": "уже занят",
"replacesTabWarning": "Другая страница уже заменяет этот таб. При сохранении она будет снята.", "replacesTabWarning": "Другая страница уже заменяет этот таб. При сохранении она будет снята.",
"filter": { "filter": {

View File

@@ -37,7 +37,7 @@ import {
TrashSmallIcon, TrashSmallIcon,
PlusSmallIcon, PlusSmallIcon,
} from '@/components/icons'; } 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; const AVAILABLE_LOCALES = ['ru', 'en', 'zh', 'fa'] as const;
type LocaleCode = (typeof AVAILABLE_LOCALES)[number]; type LocaleCode = (typeof AVAILABLE_LOCALES)[number];
@@ -643,6 +643,7 @@ export default function AdminInfoPageEditor() {
const [sortOrder, setSortOrder] = useState(0); const [sortOrder, setSortOrder] = useState(0);
const [pageType, setPageType] = useState<InfoPageType>(initialPageType); const [pageType, setPageType] = useState<InfoPageType>(initialPageType);
const [replacesTab, setReplacesTab] = useState<ReplacesTab | null>(null); const [replacesTab, setReplacesTab] = useState<ReplacesTab | null>(null);
const [displayMode, setDisplayMode] = useState<InfoPageDisplayMode>('both');
const [saveError, setSaveError] = useState<string | null>(null); const [saveError, setSaveError] = useState<string | null>(null);
// FAQ Q&A state per locale // FAQ Q&A state per locale
@@ -857,6 +858,7 @@ export default function AdminInfoPageEditor() {
setSortOrder(pageData.sort_order); setSortOrder(pageData.sort_order);
setPageType(pageData.page_type ?? 'page'); setPageType(pageData.page_type ?? 'page');
setReplacesTab(pageData.replaces_tab ?? null); setReplacesTab(pageData.replaces_tab ?? null);
setDisplayMode(pageData.display_mode ?? 'both');
setTitles(pageData.title); setTitles(pageData.title);
if (pageData.page_type === 'faq') { if (pageData.page_type === 'faq') {
@@ -927,6 +929,7 @@ export default function AdminInfoPageEditor() {
sort_order: number; sort_order: number;
icon: string | null; icon: string | null;
replaces_tab: ReplacesTab | null; replaces_tab: ReplacesTab | null;
display_mode: InfoPageDisplayMode;
}) => { }) => {
if (isEdit && pageId != null) { if (isEdit && pageId != null) {
return infoPagesApi.updatePage(pageId, data); return infoPagesApi.updatePage(pageId, data);
@@ -976,6 +979,7 @@ export default function AdminInfoPageEditor() {
sort_order: sortOrder, sort_order: sortOrder,
icon: icon.trim() || null, icon: icon.trim() || null,
replaces_tab: replacesTab, replaces_tab: replacesTab,
display_mode: displayMode,
}; };
haptic.buttonPress(); haptic.buttonPress();
@@ -1089,6 +1093,31 @@ export default function AdminInfoPageEditor() {
<span className="text-sm text-dark-300">{t('admin.infoPages.fields.isActive')}</span> <span className="text-sm text-dark-300">{t('admin.infoPages.fields.isActive')}</span>
</div> </div>
<div>
<label id="ip-displaymode-label" className="label">
{t('admin.infoPages.fields.displayMode')}
</label>
<div className="flex gap-1" role="radiogroup" aria-labelledby="ip-displaymode-label">
{(['bot', 'web', 'both'] as const).map((mode) => (
<button
key={mode}
type="button"
role="radio"
aria-checked={displayMode === mode}
onClick={() => setDisplayMode(mode)}
className={cn(
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
displayMode === mode
? 'bg-accent-500 text-white'
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
)}
>
{t(`admin.infoPages.displayModes.${mode}`)}
</button>
))}
</div>
</div>
{/* Page type selector */} {/* Page type selector */}
<div> <div>
<label id="ip-pagetype-label" className="label"> <label id="ip-pagetype-label" className="label">