fix(design-preview): убрать блоки «Состояния раздела»

Блоки были переносом из канваса и не несли пользы на страницах макета.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-17 22:12:02 +07:00
1 parent 59d68a3a45
commit 7f9d69d27d
8 files changed
+7 -113

No files matched your search

-45
View File
@@ -301,51 +301,6 @@ export function CopyList({
)
}
export type StateItem = {
title: string
when: string
text: string
tone?: 'wn' | 'er'
cta: string
}
/** Пустые и аварийные состояния раздела (как «Состояния раздела» канваса). */
export function StatesFrame({
title,
description,
items,
}: {
title: string
description: string
items: StateItem[]
}) {
return (
<Frame spacing="sm" className="w-full">
<FrameHeader>
<FrameTitle>{title}</FrameTitle>
<FrameDescription>{description}</FrameDescription>
</FrameHeader>
<FramePanel className="@container p-0">
<ul className="grid @2xl:grid-cols-2">
{items.map((item) => (
<li key={item.title} className="flex flex-col items-start gap-2 border-b px-4 py-3">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-medium">{item.title}</span>
<StatusBadge tone={item.tone ?? 'mu'}>{item.tone === 'er' ? 'Ошибка' : item.tone === 'wn' ? 'Внимание' : 'Пусто'}</StatusBadge>
</div>
<p className="text-muted-foreground text-xs">Когда: {item.when}</p>
<p className="text-sm text-pretty">{item.text}</p>
<Button type="button" size="sm" variant="outline" onClick={() => toast(item.cta)}>
{item.cta}
</Button>
</li>
))}
</ul>
</FramePanel>
</Frame>
)
}
/** Компактный список «ключ — значение» для боковых окон. */
export function KvList({ items }: { items: Array<[string, string]> }) {
return (
@@ -9,7 +9,7 @@ import { Field, FieldDescription, FieldLabel } from '@authportal/ui/components/f
import { Input } from '@authportal/ui/components/input'
import { Switch } from '@authportal/ui/components/switch'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { PageHead, PreviewSheet, StatesFrame, StatusBadge } from '../bits'
import { PageHead, PreviewSheet, StatusBadge } from '../bits'
import { MOCK_SWITCHER } from '../mock'
import { usePreviewStore } from '../preview-store'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
@@ -174,15 +174,6 @@ export function AppLinksPage() {
)}
onRowClick={setEditing}
/>
<StatesFrame
title="Состояния раздела"
description="Ошибки адреса, пустое меню и сбой сохранения"
items={[
{ title: 'Некорректный URL', when: 'адрес не https или не входит в allowlist return_to', text: 'Укажите https-адрес из доверенного домена (.shnt.top).', tone: 'wn', cta: 'Исправить' },
{ title: 'Меню пусто', when: 'все приложения скрыты', text: 'В переключателе не останется ни одного приложения — оставьте хотя бы одно.', tone: 'wn', cta: 'Показать приложения' },
{ title: 'Не удалось сохранить', when: 'PUT /api/v1/admin/app-switcher вернул ошибку', text: 'Изменения не записаны. Повторите сохранение.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="menu">
<AppSwitcherAdminEditor
@@ -12,7 +12,7 @@ import { KpiStatGrid } from '@/components/reui-kit/kpi-stat-grid'
import { QuickActionGrid } from '@/components/reui-kit/quick-action-grid'
import { Button } from '@authportal/ui/components/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { PageHead, StatesFrame } from '../bits'
import { PageHead } from '../bits'
const ICONS: Record<AppId, typeof CloudIcon> = {
cfdm: CloudIcon,
@@ -72,15 +72,6 @@ export function AppsPage() {
}
})}
/>
<StatesFrame
title="Состояния раздела"
description="Пустой список, сбой SSO, недоступный каталог"
items={[
{ title: 'Нет доступных приложений', when: 'администратор ещё не выдал права', text: 'Обратитесь к администратору, чтобы получить доступ.', cta: 'Написать администратору' },
{ title: 'SSO не удался', when: 'не удалось выпустить токен для приложения', text: 'Открываем приложение по базовому адресу — войдите в нём отдельно. Портал записал событие в журнал.', tone: 'wn', cta: 'Повторить SSO' },
{ title: 'Каталог недоступен', when: 'GET /api/v1/catalog вернул ошибку', text: 'Не удалось загрузить список приложений. Повторите запрос.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="no">
<QuickActionGrid
@@ -7,7 +7,7 @@ import { AuditRetentionPanel } from '@/components/reui-kit/audit-retention-panel
import { KpiStatGrid } from '@/components/reui-kit/kpi-stat-grid'
import { Button } from '@authportal/ui/components/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { Segmented, CodeBlock, KvList, PageHead, PreviewSheet, StatesFrame, StatusBadge, TypedConfirm } from '../bits'
import { Segmented, CodeBlock, KvList, PageHead, PreviewSheet, StatusBadge, TypedConfirm } from '../bits'
import { MOCK_AUDIT, APP_SHORT, type Tone } from '../mock'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
@@ -106,14 +106,6 @@ export function AuditPage() {
<TabsContent value="all" className="flex min-w-0 flex-col gap-4">
<AuditLogTimeline entries={MOCK_AUDIT} totalCount={MOCK_AUDIT.length} />
{grid('Журнал изменений', 'Поиск по событию, пользователю и IP · фильтры: источник, важность, объект', MOCK_AUDIT)}
<StatesFrame
title="Состояния журнала"
description="Пустые и аварийные состояния"
items={[
{ title: 'Нет событий', when: 'за выбранный период записей нет', text: 'За выбранный период в журнале нет записей.', cta: 'Сменить период' },
{ title: 'Журнал недоступен', when: 'запрос журнала завершился ошибкой', text: 'Не удалось загрузить журнал. Повторите запрос.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="auth">{grid('События аутентификации', 'Входы, выходы и SSO · подробности — в разделе «Входы»', auth)}</TabsContent>
<TabsContent value="users">{grid('Изменения пользователей', 'Создание, правки, права и passkeys', users)}</TabsContent>
@@ -5,7 +5,7 @@ import { toast } from 'sonner'
import { KpiStatGrid } from '@/components/reui-kit/kpi-stat-grid'
import { Button } from '@authportal/ui/components/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { Segmented, CodeBlock, KvList, PageHead, PreviewSheet, SignalAlert, StatesFrame, StatusBadge } from '../bits'
import { Segmented, CodeBlock, KvList, PageHead, PreviewSheet, SignalAlert, StatusBadge } from '../bits'
import { MOCK_LOGINS, MOCK_SESSIONS, type LoginRow, type SessionRow } from '../mock'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
@@ -114,14 +114,6 @@ export function LoginsPage() {
]}
onRowClick={setEvent}
/>
<StatesFrame
title="Состояния журнала"
description="Пустые и аварийные состояния"
items={[
{ title: 'Нет событий', when: 'за выбранный период входов не было', text: 'За выбранный период входов не было.', cta: 'Сменить период' },
{ title: 'Журнал недоступен', when: 'запрос журнала входов завершился ошибкой', text: 'Не удалось загрузить журнал. Повторите запрос.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="sess" className="flex min-w-0 flex-col gap-4">
<PreviewSectionGrid
@@ -17,7 +17,7 @@ import { Input } from '@authportal/ui/components/input'
import { Switch } from '@authportal/ui/components/switch'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { Textarea } from '@authportal/ui/components/textarea'
import { CopyList, KvFrame, PageHead, PreviewSheet, SignalAlert, StatesFrame, StatusBadge, TypedConfirm } from '../bits'
import { CopyList, KvFrame, PageHead, PreviewSheet, SignalAlert, StatusBadge, TypedConfirm } from '../bits'
import {
MOCK_CLIENT_SECRET,
MOCK_OIDC_CLIENTS,
@@ -140,15 +140,6 @@ export function OidcPage() {
]}
onRowClick={setEditing}
/>
<StatesFrame
title="Состояния раздела"
description="Пустой список, ошибки redirect URI и метаданных"
items={[
{ title: 'Нет OIDC-клиентов', when: 'ни одно внешнее приложение не зарегистрировано', text: 'Создайте клиента для Technitium DNS или другого OIDC-приложения.', cta: 'Создать клиента' },
{ title: 'Ошибка redirect URI', when: 'redirect URI не https или содержит wildcard', text: 'Укажите точный https-адрес колбэка приложения.', tone: 'wn', cta: 'Исправить' },
{ title: 'Метаданные недоступны', when: 'GET /api/v1/admin/oidc/meta вернул ошибку', text: 'Не удалось загрузить параметры issuer. Повторите запрос.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="meta" className="flex min-w-0 flex-col gap-4">
<CopyList title="Параметры для подключения приложения" description="Скопируйте в настройки OIDC внешнего приложения" items={OIDC_ENDPOINTS} />
@@ -8,7 +8,7 @@ import { Field, FieldDescription, FieldGroup, FieldLabel } from '@authportal/ui/
import { Input } from '@authportal/ui/components/input'
import { Switch } from '@authportal/ui/components/switch'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { KvFrame, KvList, PageHead, PreviewSheet, SignalAlert, StatesFrame, StatusBadge, TypedConfirm } from '../bits'
import { KvFrame, KvList, PageHead, PreviewSheet, SignalAlert, StatusBadge, TypedConfirm } from '../bits'
import { MOCK_PASSKEYS, type PasskeyRow } from '../mock'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
@@ -80,15 +80,6 @@ export function SecurityPage() {
setRenameValue(r.name)
}}
/>
<StatesFrame
title="Состояния раздела"
description="Пустой список и сбои WebAuthn"
items={[
{ title: 'Ключи не зарегистрированы', when: 'у пользователя нет ни одного passkey', text: 'Добавьте passkey, чтобы входить без пароля.', cta: 'Добавить passkey' },
{ title: 'Браузер не поддерживает passkeys', when: 'WebAuthn недоступен (HTTP или старый браузер)', text: 'Откройте портал по HTTPS в современном браузере. Вход по паролю остаётся доступным.', tone: 'wn', cta: 'Понятно' },
{ title: 'Регистрация отменена', when: 'системный диалог закрыт или истёк таймаут', text: 'Passkey не добавлен. Повторите попытку.', tone: 'er', cta: 'Повторить' },
]}
/>
</TabsContent>
<TabsContent value="pass">
<Frame spacing="sm" className="w-full">
@@ -8,7 +8,7 @@ import { KpiStatGrid } from '@/components/reui-kit/kpi-stat-grid'
import { Button } from '@authportal/ui/components/button'
import { AccessDialog } from '../access-dialog'
import { diffAccess, type AccessValue } from '../access-model'
import { CodeBlock, KvList, PageHead, PreviewSheet, SignalAlert, StatesFrame, TypedConfirm } from '../bits'
import { CodeBlock, KvList, PageHead, PreviewSheet, SignalAlert, TypedConfirm } from '../bits'
import { MOCK_USERS, USER_JOURNAL } from '../mock'
import { usePreviewStore } from '../preview-store'
@@ -143,15 +143,6 @@ export function UsersPage() {
onBulkSetRole={onBulkSetRole}
onBulkDeactivate={onBulkDeactivate}
/>
<StatesFrame
title="Состояния раздела"
description="Пустые и аварийные состояния каталога"
items={[
{ title: 'Нет пользователей', when: 'каталог пуст — первый вход ещё не выполнен', text: 'Создайте первого пользователя и выдайте ему доступ к приложениям.', cta: 'Создать пользователя' },
{ title: 'Нельзя отключить себя', when: 'попытка отключить собственную запись или последнего администратора', text: 'В портале должен остаться хотя бы один активный администратор.', tone: 'wn', cta: 'Понятно' },
{ title: 'Каталог недоступен', when: 'GET /api/v1/admin/users завершился ошибкой', text: 'Ошибка загрузки. Повторите запрос.', tone: 'er', cta: 'Повторить' },
]}
/>
<CreateUserSheet open={createOpen} onOpenChange={setCreateOpen} pending={false} error={null} onSubmit={onCreate} />