refactor(web): вынести общие блоки макета в components/portal
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
b89e72ec1b
commit
6898d2255d
23 files changed
+92
-86
No files matched your search
@@ -33,7 +33,7 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@authportal/ui/components/sheet'
|
||||
import type { Tone } from './mock'
|
||||
import type { Tone } from './tone'
|
||||
import { TONE_BADGE } from './tone'
|
||||
|
||||
const TONE_GLYPH: Record<Tone, string> = { ok: '●', wn: '▲', er: '✕', mu: '○', in: '' }
|
||||
@@ -104,7 +104,7 @@ export function SignalAlert({
|
||||
}
|
||||
|
||||
/** Боковое окно (как CreateUserSheet): заголовок, прокручиваемое тело, подвал. */
|
||||
export function PreviewSheet({
|
||||
export function PortalSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { Badge } from '@/components/reui/badge'
|
||||
import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame'
|
||||
import { IconTile } from '@/components/reui/icon-tile'
|
||||
import { cn } from '@authportal/ui/lib/utils'
|
||||
import type { Tone } from './mock'
|
||||
import type { Tone } from './tone'
|
||||
import { StatusBadge } from './bits'
|
||||
|
||||
export type AppCardItem = {
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { useState, type ReactNode } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { cn } from '@authportal/ui/lib/utils'
|
||||
import { CodeBlock, PreviewSheet, SignalAlert, StatusBadge } from './bits'
|
||||
import { CodeBlock, PortalSheet, SignalAlert, StatusBadge } from './bits'
|
||||
|
||||
/**
|
||||
* Баннер доски: заголовок и пояснение, справа «Объяснить» — открывает панель
|
||||
@@ -38,7 +38,7 @@ export function ExplainBanner({
|
||||
Объяснить
|
||||
</Button>
|
||||
</div>
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title="Ассистент · разбор"
|
||||
@@ -77,7 +77,7 @@ export function ExplainBanner({
|
||||
<SignalAlert tone="wn" title="Ассистент ничего не меняет сам">
|
||||
Блокировка и сброс passkeys выполняются вручную с подтверждением и попадают в журнал «Изменения».
|
||||
</SignalAlert>
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+3
-3
@@ -32,8 +32,8 @@ import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
|
||||
import { cn } from '@authportal/ui/lib/utils'
|
||||
import { StatusBadge } from './bits'
|
||||
import type { Tone } from './mock'
|
||||
import type { RowMark } from './preview-store'
|
||||
import type { Tone } from './tone'
|
||||
import type { RowMark } from './tone'
|
||||
import { TONE_DOT } from './tone'
|
||||
|
||||
export type GridColumn<T> = {
|
||||
@@ -83,7 +83,7 @@ const ACTIONS_COLUMN_SIZE = 120
|
||||
* сохранённые виды линией со счётчиками, панель массовых действий, таблица со статусом и меню строки,
|
||||
* подвал «Показано N из M». Ширина таблицы не меньше суммы колонок — прокрутка внутри контейнера.
|
||||
*/
|
||||
export function PreviewSectionGrid<T extends object>({
|
||||
export function SectionGrid<T extends object>({
|
||||
title,
|
||||
description,
|
||||
rows,
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { Tone } from './mock'
|
||||
export type Tone = 'ok' | 'wn' | 'er' | 'mu' | 'in'
|
||||
|
||||
/** Пометка строки с накопленной правкой. */
|
||||
export type RowMark = 'new' | 'dirty' | 'deleted'
|
||||
|
||||
|
||||
type BadgeVariant =
|
||||
| 'success-light'
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '@authportal/ui/components/select'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
|
||||
import { CodeBlock, SignalAlert } from './bits'
|
||||
import { CodeBlock, SignalAlert } from '@/components/portal/bits'
|
||||
import { withLevel, levelOf, type AccessValue, type Level } from './access-model'
|
||||
import { APP_SHORT, USER_JOURNAL } from './mock'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StatusBadge } from './bits'
|
||||
import { StatusBadge } from '@/components/portal/bits'
|
||||
import type { Tone } from './mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from './section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
type MatrixRow = {
|
||||
id: string
|
||||
@@ -39,7 +39,7 @@ const COLUMNS: GridColumn<MatrixRow>[] = [
|
||||
/** Матрица доступа по доске: сколько пользователей имеет доступ к каждому приложению и с каким уровнем. */
|
||||
export function AccessMatrix() {
|
||||
return (
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Матрица доступа"
|
||||
description="Сколько пользователей имеет доступ к каждому приложению и с каким уровнем"
|
||||
rows={ROWS}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { AdminUser } from '@authportal/shared'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { AccessEditor } from './access-editor'
|
||||
import { diffAccess, type AccessValue } from './access-model'
|
||||
import { PreviewSheet } from './bits'
|
||||
import { PortalSheet } from '@/components/portal/bits'
|
||||
|
||||
/** Окно «Права доступа»: широкая правая панель, чтобы таблица оставалась в контексте. */
|
||||
export function AccessSheet({
|
||||
@@ -19,7 +19,7 @@ export function AccessSheet({
|
||||
const changes = useMemo(() => diffAccess({ apps: user.apps, permissions: user.permissions }, value).length, [user, value])
|
||||
|
||||
return (
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open
|
||||
onOpenChange={(o) => !o && onClose()}
|
||||
size="lg"
|
||||
@@ -47,6 +47,6 @@ export function AccessSheet({
|
||||
}
|
||||
>
|
||||
<AccessEditor user={user} value={value} onChange={setValue} />
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
)
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import { Field, FieldDescription, FieldError, FieldLabel } from '@authportal/ui/
|
||||
import { Input } from '@authportal/ui/components/input'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Textarea } from '@authportal/ui/components/textarea'
|
||||
import { PreviewSheet, SignalAlert } from './bits'
|
||||
import { PortalSheet, SignalAlert } from '@/components/portal/bits'
|
||||
|
||||
/** Окно «Создать пользователя» по доске: email, имя, временный пароль, администратор и заметка про журнал. */
|
||||
export function CreateUserPreviewSheet({
|
||||
export function CreateUserPortalSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
@@ -37,7 +37,7 @@ export function CreateUserPreviewSheet({
|
||||
}
|
||||
|
||||
return (
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={open}
|
||||
onOpenChange={(o) => {
|
||||
if (!o) reset()
|
||||
@@ -93,12 +93,12 @@ export function CreateUserPreviewSheet({
|
||||
<SignalAlert tone="wn" title="Журнал">
|
||||
Создание записывается в журнал «Изменения» с вашим именем и IP.
|
||||
</SignalAlert>
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
)
|
||||
}
|
||||
|
||||
/** Окно «Создать OIDC-клиент» по доске: название, redirect URI, включён. */
|
||||
export function CreateOidcPreviewSheet({
|
||||
export function CreateOidcPortalSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
@@ -128,7 +128,7 @@ export function CreateOidcPreviewSheet({
|
||||
}
|
||||
|
||||
return (
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={open}
|
||||
onOpenChange={(o) => {
|
||||
if (!o) reset()
|
||||
@@ -189,6 +189,6 @@ export function CreateOidcPreviewSheet({
|
||||
<FieldDescription>Выключенный клиент не может получить токены</FieldDescription>
|
||||
</div>
|
||||
</Field>
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
)
|
||||
}
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
type AuditLogEntry,
|
||||
} from '@authportal/shared'
|
||||
|
||||
export type Tone = 'ok' | 'wn' | 'er' | 'mu' | 'in'
|
||||
import type { Tone } from '@/components/portal/tone'
|
||||
export type { Tone }
|
||||
|
||||
export type NavItem = {
|
||||
id: string
|
||||
|
||||
@@ -15,13 +15,13 @@ import {
|
||||
} from '@authportal/ui/components/select'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { PageHead, PreviewSheet, StatusBadge, PageTab, PageTabsList, FormFrame, SettingRow } from '../bits'
|
||||
import { AppCards } from '../canvas-blocks'
|
||||
import { kpi } from '../kpi'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { PageHead, PortalSheet, StatusBadge, PageTab, PageTabsList, FormFrame, SettingRow } from '@/components/portal/bits'
|
||||
import { AppCards } from '@/components/portal/canvas-blocks'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import { MOCK_SWITCHER } from '../mock'
|
||||
import { usePreviewStore } from '../preview-store'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
type LinkRow = {
|
||||
id: AppId
|
||||
@@ -180,7 +180,7 @@ export function AppLinksPage() {
|
||||
<PageTab value="menu" label="Меню" />
|
||||
</PageTabsList>
|
||||
<TabsContent value="list" className="min-w-0">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Ссылки сервисов"
|
||||
description="Порядок строк = порядок в меню · переключатель управляет видимостью · изменения применяются после сохранения"
|
||||
rows={rows}
|
||||
@@ -247,7 +247,7 @@ export function AppLinksPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={editing !== null}
|
||||
onOpenChange={(o) => !o && setEditing(null)}
|
||||
title="Приложение"
|
||||
@@ -323,7 +323,7 @@ export function AppLinksPage() {
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import { Link } from '@tanstack/react-router'
|
||||
import { BadgeCheckIcon, CloudIcon, GlobeIcon, KeyRoundIcon, LayoutDashboardIcon, LayoutGridIcon, ServerIcon, ShieldIcon } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { APPS, type AppId } from '@authportal/shared'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { PageHead, PageTab, PageTabsList } from '../bits'
|
||||
import { AppCards, type AppCardItem } from '../canvas-blocks'
|
||||
import { kpi } from '../kpi'
|
||||
import { PageHead, PageTab, PageTabsList } from '@/components/portal/bits'
|
||||
import { AppCards, type AppCardItem } from '@/components/portal/canvas-blocks'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
|
||||
const ICONS: Record<AppId, typeof CloudIcon> = {
|
||||
cfdm: CloudIcon,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { HistoryIcon, NetworkIcon, OctagonAlertIcon, UserCogIcon } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { Input } from '@authportal/ui/components/input'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { CodeBlock, FormFrame, KvList, PageHead, PageTab, PageTabsList, PreviewSheet, SettingRow, StatusBadge, TypedConfirm } from '../bits'
|
||||
import { ActivityFeed } from '../canvas-blocks'
|
||||
import { kpi } from '../kpi'
|
||||
import { CodeBlock, FormFrame, KvList, PageHead, PageTab, PageTabsList, PortalSheet, SettingRow, StatusBadge, TypedConfirm } from '@/components/portal/bits'
|
||||
import { ActivityFeed } from '@/components/portal/canvas-blocks'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import { AUDIT_FEED, AUDIT_ROWS, type AuditRow } from '../mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
const mono = (v: string) => <span className="font-mono text-[12.5px] whitespace-nowrap">{v}</span>
|
||||
|
||||
@@ -74,7 +74,7 @@ export function AuditPage() {
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="table" className="min-w-0">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Журнал изменений"
|
||||
description="Поиск по событию, пользователю и IP · виды по важности"
|
||||
rows={AUDIT_ROWS}
|
||||
@@ -113,7 +113,7 @@ export function AuditPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={entry !== null}
|
||||
onOpenChange={(o) => !o && setEntry(null)}
|
||||
title="Событие журнала"
|
||||
@@ -137,7 +137,7 @@ export function AuditPage() {
|
||||
</CodeBlock>
|
||||
</>
|
||||
) : null}
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
|
||||
<TypedConfirm
|
||||
open={purgeOpen}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Button } from '@authportal/ui/components/button'
|
||||
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from '@authportal/ui/components/field'
|
||||
import { Input } from '@authportal/ui/components/input'
|
||||
import { Separator } from '@authportal/ui/components/separator'
|
||||
import { SignalAlert } from '../bits'
|
||||
import { SignalAlert } from '@/components/portal/bits'
|
||||
|
||||
const PERKS = [
|
||||
{ icon: FingerprintIcon, title: 'Passkey вместо пароля', text: 'Отпечаток, Face ID или ключ безопасности' },
|
||||
|
||||
@@ -2,14 +2,14 @@ import { CircleCheckIcon, GlobeIcon, MonitorSmartphoneIcon, ShieldAlertIcon } fr
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { toast } from 'sonner'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { ExplainBanner } from '../explain-banner'
|
||||
import { Segmented, CodeBlock, KvList, PageHead, PreviewSheet, StatusBadge, PageTab, PageTabsList } from '../bits'
|
||||
import { kpi } from '../kpi'
|
||||
import { ExplainBanner } from '@/components/portal/explain-banner'
|
||||
import { Segmented, CodeBlock, KvList, PageHead, PortalSheet, StatusBadge, PageTab, PageTabsList } from '@/components/portal/bits'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import { MOCK_LOGINS, MOCK_SESSIONS, type LoginRow, type SessionRow } from '../mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
const PERIODS = ['24 ч', '7 д', '30 д', '90 д']
|
||||
|
||||
@@ -76,7 +76,7 @@ export function LoginsPage() {
|
||||
<PageTab value="sess" label="Активные сессии" count={sessions.length} />
|
||||
</PageTabsList>
|
||||
<TabsContent value="hist" className="flex min-w-0 flex-col gap-4">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Журнал входов"
|
||||
description="Поиск по логину и IP · фильтры: результат, приложение, пользователь · период выбирается выше"
|
||||
rows={MOCK_LOGINS}
|
||||
@@ -94,7 +94,7 @@ export function LoginsPage() {
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="sess" className="flex min-w-0 flex-col gap-4">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Активные сессии"
|
||||
description="Токены, действующие сейчас · завершение сессии выкидывает пользователя из портала и приложений"
|
||||
rows={sessions}
|
||||
@@ -112,7 +112,7 @@ export function LoginsPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={event !== null}
|
||||
onOpenChange={(o) => !o && setEvent(null)}
|
||||
title="Событие входа"
|
||||
@@ -148,7 +148,7 @@ export function LoginsPage() {
|
||||
<CodeBlock label="Событие">{`action: ${event.tone === 'er' ? 'auth.login_failed' : 'auth.login'}\nsource_app: portal\nseverity: ${event.tone === 'er' ? 'warning' : 'info'}\nip: ${event.ip}`}</CodeBlock>
|
||||
</>
|
||||
) : null}
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { useMemo, useState } from 'react'
|
||||
import { CircleCheckIcon, CopyIcon, KeyRoundIcon, LockKeyholeIcon, TicketIcon } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import type { CreateOidcClientValues } from '@/components/reui-kit/create-oidc-client-sheet'
|
||||
import { CreateOidcPreviewSheet } from '../create-sheets'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { CreateOidcPortalSheet } from '../create-sheets'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -18,9 +18,9 @@ import { Input } from '@authportal/ui/components/input'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { Textarea } from '@authportal/ui/components/textarea'
|
||||
import { ExplainBanner } from '../explain-banner'
|
||||
import { CopyList, KvFrame, PageHead, PreviewSheet, StatusBadge, TypedConfirm, PageTab, PageTabsList } from '../bits'
|
||||
import { kpi } from '../kpi'
|
||||
import { ExplainBanner } from '@/components/portal/explain-banner'
|
||||
import { CopyList, KvFrame, PageHead, PortalSheet, StatusBadge, TypedConfirm, PageTab, PageTabsList } from '@/components/portal/bits'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import {
|
||||
MOCK_CLIENT_SECRET,
|
||||
MOCK_OIDC_CLIENTS,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
type OidcClientRow,
|
||||
type Tone
|
||||
} from '../mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
function tone(c: OidcClientRow): Tone {
|
||||
return !c.enabled ? 'mu' : c.stale ? 'wn' : 'ok'
|
||||
@@ -111,7 +111,7 @@ export function OidcPage() {
|
||||
<PageTab value="meta" label="Параметры issuer" />
|
||||
</PageTabsList>
|
||||
<TabsContent value="clients" className="flex min-w-0 flex-col gap-4">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Зарегистрированные клиенты"
|
||||
description="Каждый клиент — внешнее приложение (RP) · секрет показывается один раз при создании и обновлении"
|
||||
rows={clients}
|
||||
@@ -148,9 +148,9 @@ export function OidcPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<CreateOidcPreviewSheet open={createOpen} onOpenChange={setCreateOpen} onSubmit={onCreate} />
|
||||
<CreateOidcPortalSheet open={createOpen} onOpenChange={setCreateOpen} onSubmit={onCreate} />
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={editing !== null}
|
||||
onOpenChange={(o) => !o && setEditing(null)}
|
||||
title="OIDC-клиент"
|
||||
@@ -193,7 +193,7 @@ export function OidcPage() {
|
||||
</Field>
|
||||
</>
|
||||
) : null}
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
|
||||
<TypedConfirm
|
||||
open={rotating !== null}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ClockIcon, FingerprintIcon, KeyRoundIcon, LockIcon, MonitorSmartphoneIcon, TrashIcon } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { Field, FieldDescription, FieldLabel } from '@authportal/ui/components/field'
|
||||
import { Input } from '@authportal/ui/components/input'
|
||||
import { Switch } from '@authportal/ui/components/switch'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { ExplainBanner } from '../explain-banner'
|
||||
import { KvFrame, KvList, PageHead, PreviewSheet, SignalAlert, StatusBadge, TypedConfirm, PageTab, PageTabsList, FormFrame, SettingRow } from '../bits'
|
||||
import { kpi } from '../kpi'
|
||||
import { ExplainBanner } from '@/components/portal/explain-banner'
|
||||
import { KvFrame, KvList, PageHead, PortalSheet, SignalAlert, StatusBadge, TypedConfirm, PageTab, PageTabsList, FormFrame, SettingRow } from '@/components/portal/bits'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import { MOCK_PASSKEYS, type PasskeyRow } from '../mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
export function SecurityPage() {
|
||||
const [keys, setKeys] = useState<PasskeyRow[]>(MOCK_PASSKEYS)
|
||||
@@ -72,7 +72,7 @@ export function SecurityPage() {
|
||||
<PageTab value="acc" label="Учётная запись" />
|
||||
</PageTabsList>
|
||||
<TabsContent value="keys" className="flex min-w-0 flex-col gap-4">
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Мои passkeys"
|
||||
description="Вход без пароля: отпечаток, Face ID, PIN или ключ безопасности · удаление необратимо"
|
||||
rows={view}
|
||||
@@ -137,7 +137,7 @@ export function SecurityPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={addOpen}
|
||||
onOpenChange={setAddOpen}
|
||||
title="Добавить passkey"
|
||||
@@ -169,9 +169,9 @@ export function SecurityPage() {
|
||||
<SignalAlert tone="in" title="Что произойдёт дальше">
|
||||
Откроется системный диалог: подтвердите отпечатком, Face ID, PIN или ключом безопасности. Закрытый ключ не покидает устройство.
|
||||
</SignalAlert>
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
|
||||
<PreviewSheet
|
||||
<PortalSheet
|
||||
open={selected !== null}
|
||||
onOpenChange={(o) => !o && setSelected(null)}
|
||||
title="Passkey"
|
||||
@@ -213,7 +213,7 @@ export function SecurityPage() {
|
||||
['Последний вход', `${selected?.lastUsed ?? ''} · 203.0.113.10`],
|
||||
]}
|
||||
/>
|
||||
</PreviewSheet>
|
||||
</PortalSheet>
|
||||
|
||||
<TypedConfirm
|
||||
open={deleting !== null}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { FingerprintIcon, ShieldIcon, UserXIcon, UsersIcon } from 'lucide-react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
|
||||
import { CreateUserPreviewSheet } from '../create-sheets'
|
||||
import { KpiStrip } from '../kpi-strip'
|
||||
import { CreateUserPortalSheet } from '../create-sheets'
|
||||
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { Tabs, TabsContent } from '@authportal/ui/components/tabs'
|
||||
import { AccessSheet } from '../access-sheet'
|
||||
import { diffAccess, type AccessValue } from '../access-model'
|
||||
import { PageHead, TypedConfirm, PageTab, PageTabsList } from '../bits'
|
||||
import { kpi } from '../kpi'
|
||||
import { PageHead, TypedConfirm, PageTab, PageTabsList } from '@/components/portal/bits'
|
||||
import { kpi } from '@/components/portal/kpi'
|
||||
import { ALL_APP_IDS } from '../mock'
|
||||
import { ExplainBanner } from '../explain-banner'
|
||||
import { ExplainBanner } from '@/components/portal/explain-banner'
|
||||
import { AccessMatrix } from '../access-matrix'
|
||||
import { UsersGrid, type UsersGridHandlers } from '../users-grid'
|
||||
import { usePreviewStore } from '../preview-store'
|
||||
@@ -131,7 +131,7 @@ export function UsersPage() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<CreateUserPreviewSheet open={createOpen} onOpenChange={setCreateOpen} onSubmit={onCreate} />
|
||||
<CreateUserPortalSheet open={createOpen} onOpenChange={setCreateOpen} onSubmit={onCreate} />
|
||||
|
||||
{accessFor ? (
|
||||
<AccessSheet
|
||||
|
||||
@@ -67,7 +67,7 @@ import {
|
||||
useSidebar,
|
||||
} from '@authportal/ui/components/sidebar'
|
||||
import { cn } from '@authportal/ui/lib/utils'
|
||||
import { Segmented } from './bits'
|
||||
import { Segmented } from '@/components/portal/bits'
|
||||
import { FRESH_BY_PAGE, MONITOR_ITEMS, NAV_GROUPS, NAV_ITEMS, type NavItem } from './mock'
|
||||
import { usePreviewStore } from './preview-store'
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { AdminUser } from '@authportal/shared'
|
||||
|
||||
export type ChangeKind = 'add' | 'chg' | 'del'
|
||||
|
||||
export type RowMark = 'new' | 'dirty' | 'deleted'
|
||||
import type { RowMark } from '@/components/portal/tone'
|
||||
export type { RowMark }
|
||||
|
||||
export type PendingChange = {
|
||||
id: string
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useMemo } from 'react'
|
||||
import type { AdminUser } from '@authportal/shared'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@authportal/ui/components/button'
|
||||
import { PendingChip, StatusBadge } from './bits'
|
||||
import { PendingChip, StatusBadge } from '@/components/portal/bits'
|
||||
import type { RowMark } from './preview-store'
|
||||
import { ALL_APP_IDS, APP_SHORT, USER_META, type Tone } from './mock'
|
||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from './section-grid'
|
||||
import { SectionGrid, TwoLineCell, type GridColumn } from '@/components/portal/section-grid'
|
||||
|
||||
function userStatus(u: AdminUser): { tone: Tone; label: string } {
|
||||
if (u.disabled) return { tone: 'mu', label: 'Отключён' }
|
||||
@@ -65,7 +65,7 @@ export function UsersGrid({ users, handlers, marks }: { users: AdminUser[]; hand
|
||||
)
|
||||
|
||||
return (
|
||||
<PreviewSectionGrid
|
||||
<SectionGrid
|
||||
title="Пользователи портала"
|
||||
description="Поиск по email и имени · фильтры: роль, статус, приложения, passkey · колонки настраиваются, вид сохраняется"
|
||||
rows={users}
|
||||
|
||||
Reference in new issue
Block a user