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;
icon: string | null;
replaces_tab: ReplacesTab | null;
display_mode?: InfoPageDisplayMode;
display_mode: InfoPageDisplayMode;
}
export interface InfoPageUpdateRequest {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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<InfoPageType>(initialPageType);
const [replacesTab, setReplacesTab] = useState<ReplacesTab | null>(null);
const [displayMode, setDisplayMode] = useState<InfoPageDisplayMode>('both');
const [saveError, setSaveError] = useState<string | null>(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() {
<span className="text-sm text-dark-300">{t('admin.infoPages.fields.isActive')}</span>
</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 */}
<div>
<label id="ip-pagetype-label" className="label">