feat(design-preview): страница пользователя с рейкой вкладок (solution-users-2)

Права, вход и passkeys, сессии, активность и опасная зона. Каталог пользователей вынесен в общее
состояние макета, поэтому правки видны и в списке, и в профиле. Переходы: меню строки «Профиль»,
«Открыть профиль» в панели прав, хлебные крошки в шапке.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-18 02:35:57 +07:00
1 parent f0a257220f
commit 007ece4575
11 files changed
+447 -51

No files matched your search

@@ -1,4 +1,5 @@
import { useMemo, useState } from 'react'
import { ArrowUpRightIcon } from 'lucide-react'
import type { AdminUser } from '@authportal/shared'
import { Button } from '@authportal/ui/components/button'
import { AccessEditor } from './access-editor'
@@ -10,10 +11,12 @@ export function AccessSheet({
user,
onClose,
onSave,
onOpenProfile,
}: {
user: AdminUser
onClose: () => void
onSave: (value: AccessValue) => void
onOpenProfile?: () => void
}) {
const [value, setValue] = useState<AccessValue>({ apps: user.apps, permissions: user.permissions })
const changes = useMemo(() => diffAccess({ apps: user.apps, permissions: user.permissions }, value).length, [user, value])
@@ -28,6 +31,12 @@ export function AccessSheet({
description="Доступ к приложениям и разделам · иерархия: админ ⊃ запись ⊃ чтение"
footer={
<>
{onOpenProfile ? (
<Button variant="ghost" onClick={onOpenProfile}>
Открыть профиль
<ArrowUpRightIcon aria-hidden />
</Button>
) : null}
<span className="text-muted-foreground mr-auto self-center text-xs">
{changes > 0 ? `Изменений: ${changes}` : 'Без изменений'}
</span>
@@ -0,0 +1,304 @@
import { useMemo, useState } from 'react'
import { Link, useNavigate, useParams } from '@tanstack/react-router'
import {
ArrowLeftIcon,
FingerprintIcon,
HistoryIcon,
MonitorSmartphoneIcon,
ShieldCheckIcon,
TriangleAlertIcon,
} from 'lucide-react'
import { toast } from 'sonner'
import type { AdminUser } from '@authportal/shared'
import { AuditLogTimeline } from '@/components/reui-kit/audit-log-timeline'
import { Frame, FrameDescription, FrameFooter, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame'
import { Avatar, AvatarFallback } from '@authportal/ui/components/avatar'
import { Button } from '@authportal/ui/components/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
import { AccessEditor } from '../access-editor'
import { diffAccess, type AccessValue } from '../access-model'
import { KvFrame, PendingChip, StatusBadge, TypedConfirm } from '../bits'
import { MOCK_AUDIT, MOCK_SESSIONS, USER_META, type SessionRow } from '../mock'
import { usePreviewStore } from '../preview-store'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
import { usePatchUsers } from '../user-actions'
function initials(u: AdminUser) {
const base = (u.name || u.email).trim()
const parts = base.split(/\s+/)
return (parts.length > 1 ? `${parts[0]![0]}${parts[1]![0]}` : base.slice(0, 2)).toUpperCase()
}
function statusOf(u: AdminUser) {
if (u.disabled) return { tone: 'mu' as const, label: 'Отключён' }
if (u.passkey_count === 0) return { tone: 'wn' as const, label: 'Без passkey' }
return { tone: 'ok' as const, label: 'Активен' }
}
/** Страница пользователя (solution-users-2): рейка вкладок — права, вход, сессии, активность, опасная зона. */
export function UserProfilePage() {
const { userId } = useParams({ from: '/design-preview/_shell/users/$userId' })
const { users, setUsers, addChange, marksFor } = usePreviewStore()
const navigate = useNavigate()
const patch = usePatchUsers()
const user = users.find((u) => u.id === userId)
const mark = marksFor('users')[userId]
const [draft, setDraft] = useState<{ id: string; value: AccessValue } | null>(null)
const [sessions, setSessions] = useState<SessionRow[]>(MOCK_SESSIONS)
const [resetOpen, setResetOpen] = useState(false)
const [deleteOpen, setDeleteOpen] = useState(false)
const sessionRows = useMemo(() => sessions.filter((s) => s.user === user?.email), [sessions, user])
const events = useMemo(
() => MOCK_AUDIT.filter((e) => e.actor_email === user?.email || e.summary.includes(user?.email ?? '\0')),
[user],
)
if (!user) {
return (
<Frame spacing="sm" className="w-full">
<FramePanel className="flex flex-col items-start gap-3 py-8">
<p className="font-medium">Пользователь не найден</p>
<p className="text-muted-foreground text-sm">Возможно, учётная запись уже удалена.</p>
<Button variant="outline" render={<Link to="/design-preview/users" />} nativeButton={false}>
<ArrowLeftIcon aria-hidden />К списку пользователей
</Button>
</FramePanel>
</Frame>
)
}
const status = statusOf(user)
const base: AccessValue = { apps: user.apps, permissions: user.permissions }
const value = draft?.id === user.id ? draft.value : base
const changes = diffAccess(base, value).length
const sessionColumns: GridColumn<SessionRow>[] = [
{ id: 'ip', title: 'IP · клиент', size: 260, cell: (r) => <TwoLineCell dot={r.tone} title={r.ipClient} sub={r.note || undefined} /> },
{ id: 'created', title: 'Создана', size: 140, cell: (r) => <span className="text-sm whitespace-nowrap">{r.created}</span> },
{ id: 'exp', title: 'Истекает', size: 120, cell: (r) => <span className="text-sm whitespace-nowrap">{r.expires}</span> },
{
id: 'act',
title: '',
size: 130,
cell: (r) => (
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
setSessions((s) => s.filter((x) => x.id !== r.id))
toast.success('Сессия завершена (макет)')
}}
>
Завершить
</Button>
),
},
]
return (
<>
<Frame spacing="sm" className="w-full">
<FramePanel className="flex flex-wrap items-center gap-x-4 gap-y-3">
<Avatar className="size-12 rounded-xl">
<AvatarFallback className="rounded-xl text-sm">{initials(user)}</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 basis-60 flex-col gap-1">
<h1 className="truncate text-xl font-semibold tracking-tight">{user.email}</h1>
<p className="text-muted-foreground truncate font-mono text-[11px]">{USER_META[user.id]?.sub ?? user.name}</p>
<div className="flex flex-wrap items-center gap-1.5">
<StatusBadge tone={status.tone}>{status.label}</StatusBadge>
<StatusBadge tone={user.is_admin ? 'wn' : 'in'}>{user.is_admin ? 'Админ' : 'Пользователь'}</StatusBadge>
{mark ? <PendingChip mark={mark} /> : null}
</div>
</div>
<Button variant="outline" render={<Link to="/design-preview/users" />} nativeButton={false}>
<ArrowLeftIcon aria-hidden />К списку
</Button>
</FramePanel>
</Frame>
<Tabs defaultValue="access" orientation="vertical" className="min-w-0 flex-col gap-4 md:flex-row md:items-start">
<TabsList className="flex h-auto w-full flex-row flex-wrap justify-start md:w-56 md:shrink-0 md:flex-col md:items-stretch">
<TabsTrigger value="access" className="justify-start">
<ShieldCheckIcon aria-hidden />
Права доступа
</TabsTrigger>
<TabsTrigger value="auth" className="justify-start">
<FingerprintIcon aria-hidden />
Вход и passkeys
</TabsTrigger>
<TabsTrigger value="sessions" className="justify-start">
<MonitorSmartphoneIcon aria-hidden />
Сессии · {sessionRows.length}
</TabsTrigger>
<TabsTrigger value="activity" className="justify-start">
<HistoryIcon aria-hidden />
Активность
</TabsTrigger>
<TabsTrigger value="danger" className="justify-start">
<TriangleAlertIcon aria-hidden />
Опасная зона
</TabsTrigger>
</TabsList>
<div className="min-w-0 flex-1">
<TabsContent value="access" className="min-w-0">
<Frame spacing="sm" className="w-full">
<FrameHeader>
<FrameTitle>Права доступа</FrameTitle>
<FrameDescription>Приложения и разделы · иерархия: админ ⊃ запись ⊃ чтение</FrameDescription>
</FrameHeader>
<FramePanel>
<AccessEditor user={user} value={value} onChange={(next) => setDraft({ id: user.id, value: next })} />
</FramePanel>
<FrameFooter className="flex-wrap justify-end gap-2">
<span className="text-muted-foreground mr-auto text-xs">{changes > 0 ? `Изменений: ${changes}` : 'Без изменений'}</span>
<Button variant="outline" disabled={changes === 0} onClick={() => setDraft(null)}>
Сбросить
</Button>
<Button
disabled={changes === 0}
onClick={() => {
patch(
[user.id],
(u) => ({ ...u, apps: value.apps, permissions: value.permissions }),
`Права обновлены: ${user.email}`,
() => diffAccess(base, value),
)
setDraft(null)
}}
>
Сохранить права
</Button>
</FrameFooter>
</Frame>
</TabsContent>
<TabsContent value="auth" className="flex min-w-0 flex-col gap-4">
<KvFrame
title="Вход"
description="Способы входа учётной записи"
items={[
{ label: 'Пароль', value: 'Задан · запасной вход' },
{ label: 'Passkeys', value: user.passkey_count === 0 ? 'нет' : `${user.passkey_count} шт.` },
{ label: 'Последний вход', value: USER_META[user.id]?.activity ?? '—' },
{ label: 'Статус', value: status.label },
]}
/>
<Frame spacing="sm" className="w-full">
<FramePanel className="flex flex-wrap items-center justify-between gap-3">
<div className="flex min-w-0 flex-col">
<span className="text-sm font-medium">Сбросить passkeys…</span>
<span className="text-muted-foreground text-xs">Войти можно будет только по паролю, пока не добавлен новый ключ.</span>
</div>
<Button variant="outline" disabled={user.passkey_count === 0} onClick={() => setResetOpen(true)}>
Сбросить
</Button>
</FramePanel>
</Frame>
</TabsContent>
<TabsContent value="sessions" className="min-w-0">
<PreviewSectionGrid
title="Активные сессии"
description="Завершение сессии выкидывает пользователя из портала и приложений"
rows={sessionRows}
columns={sessionColumns}
getRowId={(r) => r.id}
emptyMessage="Нет активных сессий."
/>
</TabsContent>
<TabsContent value="activity" className="min-w-0">
<Frame spacing="sm" className="w-full">
<FrameHeader>
<FrameTitle>Активность</FrameTitle>
<FrameDescription>События пользователя из журнала портала</FrameDescription>
</FrameHeader>
<FramePanel>
{events.length === 0 ? (
<p className="text-muted-foreground text-sm">Событий нет.</p>
) : (
<AuditLogTimeline entries={events} totalCount={events.length} compact lockedUserId={user.id} />
)}
</FramePanel>
</Frame>
</TabsContent>
<TabsContent value="danger" className="min-w-0">
<Frame spacing="sm" className="w-full">
<FrameHeader>
<FrameTitle>Опасная зона</FrameTitle>
<FrameDescription>Действия попадают в «Не сохранено» и в журнал</FrameDescription>
</FrameHeader>
<FramePanel className="p-0">
<div className="flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3">
<div className="flex min-w-0 flex-col">
<span className="text-sm font-medium">{user.disabled ? 'Включить учётную запись' : 'Отключить учётную запись'}</span>
<span className="text-muted-foreground text-xs">Вход закрывается, учётная запись и права сохраняются.</span>
</div>
<Button
variant="outline"
onClick={() =>
patch([user.id], (u) => ({ ...u, disabled: !u.disabled }), `${user.disabled ? 'Включён' : 'Отключён'} ${user.email}`, (b, a) => [
`− disabled: ${String(b.disabled)}`,
`+ disabled: ${String(a.disabled)}`,
])
}
>
{user.disabled ? 'Включить' : 'Отключить'}
</Button>
</div>
<div className="flex flex-wrap items-center justify-between gap-3 px-4 py-3">
<div className="flex min-w-0 flex-col">
<span className="text-sm font-medium">Удалить пользователя…</span>
<span className="text-muted-foreground text-xs">Учётная запись и все права будут удалены безвозвратно.</span>
</div>
<Button variant="destructive" onClick={() => setDeleteOpen(true)}>
Удалить…
</Button>
</div>
</FramePanel>
</Frame>
</TabsContent>
</div>
</Tabs>
<TypedConfirm
open={resetOpen}
onOpenChange={setResetOpen}
title="Сбросить passkeys пользователя?"
description={`Все ключи ${user.email} будут удалены. Войти можно будет только по паролю. Действие попадёт в журнал.`}
expected="сбросить"
confirmLabel="Сбросить"
onConfirm={() => {
patch([user.id], (u) => ({ ...u, passkey_count: 0 }), `Сброс passkeys: ${user.email}`, (b) => [`− passkeys: ${b.passkey_count}`, '+ passkeys: 0'])
setResetOpen(false)
}}
/>
<TypedConfirm
open={deleteOpen}
onOpenChange={setDeleteOpen}
title="Удалить пользователя?"
description={`Учётная запись ${user.email} и её права будут удалены. Действие попадёт в панель «Не сохранено» и в журнал.`}
expected={user.email}
confirmLabel="Удалить"
onConfirm={() => {
addChange({
kind: 'del',
title: `Удалён ${user.email}`,
lines: [`− email: ${user.email}`, `− apps: ${user.apps.length}`],
resource: 'users',
marks: { [user.id]: 'deleted' },
commit: () => setUsers((prev) => prev.filter((u) => u.id !== user.id)),
})
setDeleteOpen(false)
void navigate({ to: '/design-preview/users' })
}}
/>
</>
)
}
@@ -1,5 +1,6 @@
import { UsersIcon, ShieldCheckIcon, FingerprintIcon, UserXIcon } from 'lucide-react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { toast } from 'sonner'
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
import { CreateUserSheet } from '@/components/reui-kit/create-user-sheet'
@@ -11,20 +12,22 @@ import { AccessSheet } from '../access-sheet'
import { diffAccess, type AccessValue } from '../access-model'
import { CodeBlock, KvList, PageHead, PreviewSheet, TypedConfirm } from '../bits'
import { kpi } from '../kpi'
import { ALL_APP_IDS, APP_SHORT, MOCK_USERS, USER_JOURNAL, type Tone } from '../mock'
import { ALL_APP_IDS, APP_SHORT, USER_JOURNAL, type Tone } from '../mock'
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
import { StatusBadge } from '../bits'
import { UsersGrid, type UsersGridHandlers } from '../users-grid'
import { usePreviewStore } from '../preview-store'
import { usePatchUsers } from '../user-actions'
export function UsersPage() {
const { addChange, marksFor } = usePreviewStore()
const { addChange, marksFor, users, setUsers } = usePreviewStore()
const marks = marksFor('users')
const [users, setUsers] = useState<AdminUser[]>(MOCK_USERS)
const usersRef = useRef(users)
useEffect(() => {
usersRef.current = users
}, [users])
const patch = usePatchUsers()
const navigate = useNavigate()
const [createOpen, setCreateOpen] = useState(false)
const [journalFor, setJournalFor] = useState<AdminUser | null>(null)
@@ -33,25 +36,6 @@ export function UsersPage() {
const [resetFor, setResetFor] = useState<AdminUser | null>(null)
const [bulkDeleteIds, setBulkDeleteIds] = useState<string[] | null>(null)
/** Меняет поля у выбранных пользователей и кладёт изменение в «Не сохранено». */
const patch = useCallback(
(ids: string[], apply: (u: AdminUser) => AdminUser, title: string, lineFor: (before: AdminUser, after: AdminUser) => string[]) => {
const before = usersRef.current.filter((u) => ids.includes(u.id))
const after = before.map(apply)
const lines = before.flatMap((b, i) => [` ${b.email}`, ...lineFor(b, after[i]!)])
setUsers((prev) => prev.map((u) => after.find((a) => a.id === u.id) ?? u))
addChange({
kind: 'chg',
title,
lines,
resource: 'users',
marks: Object.fromEntries(before.map((b) => [b.id, 'dirty' as const])),
revert: () => setUsers((prev) => prev.map((u) => before.find((b) => b.id === u.id) ?? u)),
})
},
[addChange],
)
const onDeactivate = useCallback(
(user: AdminUser) =>
patch([user.id], (u) => ({ ...u, disabled: !u.disabled }), `${user.disabled ? 'Включён' : 'Отключён'} ${user.email}`, (b, a) => [
@@ -89,7 +73,7 @@ export function UsersPage() {
setCreateOpen(false)
setAccessFor(created)
},
[addChange],
[addChange, setUsers],
)
const onDelete = useCallback((user: AdminUser) => setDeleteFor(user), [])
@@ -103,6 +87,7 @@ export function UsersPage() {
onToggleDisabled: onDeactivate,
onResetPasskeys,
onDelete,
onOpenProfile: (u) => void navigate({ to: '/design-preview/users/$userId', params: { userId: u.id } }),
onBulkDelete: (ids) => setBulkDeleteIds(ids),
onBulkDisabled: (ids, disabled) =>
patch(ids, (u) => ({ ...u, disabled }), `${disabled ? 'Отключено' : 'Включено'} пользователей: ${ids.length}`, () => [
@@ -169,6 +154,9 @@ export function UsersPage() {
key={accessFor.id}
user={accessFor}
onClose={() => setAccessFor(null)}
onOpenProfile={() => {
void navigate({ to: '/design-preview/users/$userId', params: { userId: accessFor.id } })
}}
onSave={(value: AccessValue) => {
const target = accessFor
patch(
+20 -4
View File
@@ -31,8 +31,10 @@ import { Avatar, AvatarFallback } from '@authportal/ui/components/avatar'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@authportal/ui/components/breadcrumb'
import { Button } from '@authportal/ui/components/button'
import {
@@ -472,7 +474,9 @@ function FreshnessPopover() {
export function PreviewHeader() {
const pathname = useRouterState({ select: (s) => s.location.pathname })
const { setPaletteOpen } = usePreviewStore()
const { setPaletteOpen, users } = usePreviewStore()
const profileId = /^\/design-preview\/users\/([^/]+)/.exec(pathname)?.[1]
const profileUser = profileId ? users.find((u) => u.id === profileId) : undefined
const current = NAV_ITEMS.find((i) => pathname === i.to || pathname.startsWith(`${i.to}/`))
return (
@@ -481,9 +485,21 @@ export function PreviewHeader() {
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
<Breadcrumb className="min-w-0">
<BreadcrumbList className="flex-nowrap">
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="truncate">{current?.label ?? 'Макет'}</BreadcrumbPage>
</BreadcrumbItem>
{profileUser ? (
<>
<BreadcrumbItem className="hidden @md/header:inline-flex">
<BreadcrumbLink render={<Link to="/design-preview/users" />}>Пользователи</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden @md/header:block" />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="truncate">{profileUser.email}</BreadcrumbPage>
</BreadcrumbItem>
</>
) : (
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="truncate">{current?.label ?? 'Макет'}</BreadcrumbPage>
</BreadcrumbItem>
)}
</BreadcrumbList>
</Breadcrumb>
<div className="ml-auto flex shrink-0 items-center gap-2">
@@ -1,5 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import { toast } from 'sonner'
import type { AdminUser } from '@authportal/shared'
import { MOCK_USERS } from './mock'
import {
PreviewStoreContext,
type Contrast,
@@ -16,6 +18,7 @@ import {
export function PreviewProvider({ children }: { children: ReactNode }) {
const [pending, setPending] = useState<PendingChange[]>([])
const [paletteOpen, setPaletteOpen] = useState(false)
const [users, setUsers] = useState<AdminUser[]>(MOCK_USERS)
const [density, setDensity] = useState<Density>('comfortable')
const [contrast, setContrast] = useState<Contrast>('standard')
const [pins, setPins] = useState<string[]>(() => {
@@ -109,6 +112,8 @@ export function PreviewProvider({ children }: { children: ReactNode }) {
applyAll,
paletteOpen,
setPaletteOpen,
users,
setUsers,
pins,
togglePin,
density,
@@ -116,7 +121,7 @@ export function PreviewProvider({ children }: { children: ReactNode }) {
contrast,
setContrast,
}),
[pending, marksFor, addChange, discardAll, applyAll, paletteOpen, pins, togglePin, density, contrast],
[pending, marksFor, addChange, discardAll, applyAll, paletteOpen, users, pins, togglePin, density, contrast],
)
return (
+5 -1
View File
@@ -1,4 +1,5 @@
import { createContext, useContext } from 'react'
import { createContext, useContext, type Dispatch, type SetStateAction } from 'react'
import type { AdminUser } from '@authportal/shared'
export type ChangeKind = 'add' | 'chg' | 'del'
@@ -32,6 +33,9 @@ export type PreviewStore = {
applyAll: () => void
paletteOpen: boolean
setPaletteOpen: (open: boolean) => void
/** Каталог пользователей: общий для списка и профиля. */
users: AdminUser[]
setUsers: Dispatch<SetStateAction<AdminUser[]>>
pins: string[]
togglePin: (id: string) => void
density: Density
@@ -0,0 +1,35 @@
import { useCallback, useEffect, useRef } from 'react'
import type { AdminUser } from '@authportal/shared'
import { usePreviewStore } from './preview-store'
/** Меняет поля у выбранных пользователей и кладёт правку в «Не сохранено» (с откатом). */
export function usePatchUsers() {
const { users, setUsers, addChange } = usePreviewStore()
const usersRef = useRef(users)
useEffect(() => {
usersRef.current = users
}, [users])
return useCallback(
(
ids: string[],
apply: (u: AdminUser) => AdminUser,
title: string,
lineFor: (before: AdminUser, after: AdminUser) => string[],
) => {
const before = usersRef.current.filter((u) => ids.includes(u.id))
const after = before.map(apply)
const lines = before.flatMap((b, i) => [` ${b.email}`, ...lineFor(b, after[i]!)])
setUsers((prev) => prev.map((u) => after.find((a) => a.id === u.id) ?? u))
addChange({
kind: 'chg',
title,
lines,
resource: 'users',
marks: Object.fromEntries(before.map((b) => [b.id, 'dirty' as const])),
revert: () => setUsers((prev) => prev.map((u) => before.find((b) => b.id === u.id) ?? u)),
})
},
[addChange, setUsers],
)
}
@@ -6,6 +6,7 @@ import {
ScrollTextIcon,
ShieldCheckIcon,
Trash2Icon,
UserRoundIcon,
FingerprintIcon,
} from 'lucide-react'
import type { AdminUser } from '@authportal/shared'
@@ -42,6 +43,7 @@ function passkeyLabel(n: number) {
export type UsersGridHandlers = {
onOpenAccess: (u: AdminUser) => void
onOpenProfile: (u: AdminUser) => void
onOpenJournal: (u: AdminUser) => void
onToggleDisabled: (u: AdminUser) => void
onResetPasskeys: (u: AdminUser) => void
@@ -89,6 +91,10 @@ export function UsersGrid({ users, handlers, marks }: { users: AdminUser[]; hand
<ShieldCheckIcon aria-hidden />
Права доступа
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handlers.onOpenProfile(u)}>
<UserRoundIcon aria-hidden />
Профиль
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handlers.onOpenJournal(u)}>
<ScrollTextIcon aria-hidden />
Журнал
+44 -21
View File
@@ -30,8 +30,9 @@ import { Route as DesignPreviewShellAppsRouteImport } from './routes/design-prev
import { Route as DesignPreviewShellAuditRouteImport } from './routes/design-preview._shell.audit'
import { Route as DesignPreviewShellLoginsRouteImport } from './routes/design-preview._shell.logins'
import { Route as DesignPreviewShellOidcRouteImport } from './routes/design-preview._shell.oidc'
import { Route as DesignPreviewShellUsersRouteImport } from './routes/design-preview._shell.users'
import { Route as AuthAdminUsersUserIdRouteImport } from './routes/_auth.admin.users.$userId'
import { Route as DesignPreviewShellUsersIndexRouteImport } from './routes/design-preview._shell.users.index'
import { Route as DesignPreviewShellUsersUserIdRouteImport } from './routes/design-preview._shell.users.$userId'
const IndexRoute = IndexRouteImport.update({
id: '/',
@@ -139,16 +140,23 @@ const DesignPreviewShellOidcRoute = DesignPreviewShellOidcRouteImport.update({
path: '/oidc',
getParentRoute: () => DesignPreviewShellRoute,
} as any)
const DesignPreviewShellUsersRoute = DesignPreviewShellUsersRouteImport.update({
id: '/users',
path: '/users',
getParentRoute: () => DesignPreviewShellRoute,
} as any)
const AuthAdminUsersUserIdRoute = AuthAdminUsersUserIdRouteImport.update({
id: '/users/$userId',
path: '/users/$userId',
getParentRoute: () => AuthAdminRoute,
} as any)
const DesignPreviewShellUsersIndexRoute =
DesignPreviewShellUsersIndexRouteImport.update({
id: '/users/',
path: '/users/',
getParentRoute: () => DesignPreviewShellRoute,
} as any)
const DesignPreviewShellUsersUserIdRoute =
DesignPreviewShellUsersUserIdRouteImport.update({
id: '/users/$userId',
path: '/users/$userId',
getParentRoute: () => DesignPreviewShellRoute,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
@@ -169,9 +177,10 @@ export interface FileRoutesByFullPath {
'/design-preview/audit': typeof DesignPreviewShellAuditRoute
'/design-preview/logins': typeof DesignPreviewShellLoginsRoute
'/design-preview/oidc': typeof DesignPreviewShellOidcRoute
'/design-preview/users': typeof DesignPreviewShellUsersRoute
'/admin/': typeof AuthAdminIndexRoute
'/admin/users/$userId': typeof AuthAdminUsersUserIdRoute
'/design-preview/users/$userId': typeof DesignPreviewShellUsersUserIdRoute
'/design-preview/users/': typeof DesignPreviewShellUsersIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
@@ -190,9 +199,10 @@ export interface FileRoutesByTo {
'/design-preview/audit': typeof DesignPreviewShellAuditRoute
'/design-preview/logins': typeof DesignPreviewShellLoginsRoute
'/design-preview/oidc': typeof DesignPreviewShellOidcRoute
'/design-preview/users': typeof DesignPreviewShellUsersRoute
'/admin': typeof AuthAdminIndexRoute
'/admin/users/$userId': typeof AuthAdminUsersUserIdRoute
'/design-preview/users/$userId': typeof DesignPreviewShellUsersUserIdRoute
'/design-preview/users': typeof DesignPreviewShellUsersIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -216,9 +226,10 @@ export interface FileRoutesById {
'/design-preview/_shell/audit': typeof DesignPreviewShellAuditRoute
'/design-preview/_shell/logins': typeof DesignPreviewShellLoginsRoute
'/design-preview/_shell/oidc': typeof DesignPreviewShellOidcRoute
'/design-preview/_shell/users': typeof DesignPreviewShellUsersRoute
'/_auth/admin/': typeof AuthAdminIndexRoute
'/_auth/admin/users/$userId': typeof AuthAdminUsersUserIdRoute
'/design-preview/_shell/users/$userId': typeof DesignPreviewShellUsersUserIdRoute
'/design-preview/_shell/users/': typeof DesignPreviewShellUsersIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -241,9 +252,10 @@ export interface FileRouteTypes {
| '/design-preview/audit'
| '/design-preview/logins'
| '/design-preview/oidc'
| '/design-preview/users'
| '/admin/'
| '/admin/users/$userId'
| '/design-preview/users/$userId'
| '/design-preview/users/'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -262,9 +274,10 @@ export interface FileRouteTypes {
| '/design-preview/audit'
| '/design-preview/logins'
| '/design-preview/oidc'
| '/design-preview/users'
| '/admin'
| '/admin/users/$userId'
| '/design-preview/users/$userId'
| '/design-preview/users'
id:
| '__root__'
| '/'
@@ -287,9 +300,10 @@ export interface FileRouteTypes {
| '/design-preview/_shell/audit'
| '/design-preview/_shell/logins'
| '/design-preview/_shell/oidc'
| '/design-preview/_shell/users'
| '/_auth/admin/'
| '/_auth/admin/users/$userId'
| '/design-preview/_shell/users/$userId'
| '/design-preview/_shell/users/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -448,13 +462,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DesignPreviewShellOidcRouteImport
parentRoute: typeof DesignPreviewShellRoute
}
'/design-preview/_shell/users': {
id: '/design-preview/_shell/users'
path: '/users'
fullPath: '/design-preview/users'
preLoaderRoute: typeof DesignPreviewShellUsersRouteImport
parentRoute: typeof DesignPreviewShellRoute
}
'/_auth/admin/users/$userId': {
id: '/_auth/admin/users/$userId'
path: '/users/$userId'
@@ -462,6 +469,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthAdminUsersUserIdRouteImport
parentRoute: typeof AuthAdminRoute
}
'/design-preview/_shell/users/': {
id: '/design-preview/_shell/users/'
path: '/users'
fullPath: '/design-preview/users/'
preLoaderRoute: typeof DesignPreviewShellUsersIndexRouteImport
parentRoute: typeof DesignPreviewShellRoute
}
'/design-preview/_shell/users/$userId': {
id: '/design-preview/_shell/users/$userId'
path: '/users/$userId'
fullPath: '/design-preview/users/$userId'
preLoaderRoute: typeof DesignPreviewShellUsersUserIdRouteImport
parentRoute: typeof DesignPreviewShellRoute
}
}
}
@@ -508,7 +529,8 @@ interface DesignPreviewShellRouteChildren {
DesignPreviewShellAuditRoute: typeof DesignPreviewShellAuditRoute
DesignPreviewShellLoginsRoute: typeof DesignPreviewShellLoginsRoute
DesignPreviewShellOidcRoute: typeof DesignPreviewShellOidcRoute
DesignPreviewShellUsersRoute: typeof DesignPreviewShellUsersRoute
DesignPreviewShellUsersUserIdRoute: typeof DesignPreviewShellUsersUserIdRoute
DesignPreviewShellUsersIndexRoute: typeof DesignPreviewShellUsersIndexRoute
}
const DesignPreviewShellRouteChildren: DesignPreviewShellRouteChildren = {
@@ -518,7 +540,8 @@ const DesignPreviewShellRouteChildren: DesignPreviewShellRouteChildren = {
DesignPreviewShellAuditRoute: DesignPreviewShellAuditRoute,
DesignPreviewShellLoginsRoute: DesignPreviewShellLoginsRoute,
DesignPreviewShellOidcRoute: DesignPreviewShellOidcRoute,
DesignPreviewShellUsersRoute: DesignPreviewShellUsersRoute,
DesignPreviewShellUsersUserIdRoute: DesignPreviewShellUsersUserIdRoute,
DesignPreviewShellUsersIndexRoute: DesignPreviewShellUsersIndexRoute,
}
const DesignPreviewShellRouteWithChildren =
@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { UserProfilePage } from '@/design-preview/pages/user-profile-page'
export const Route = createFileRoute('/design-preview/_shell/users/$userId')({
component: UserProfilePage,
})
@@ -1,6 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { UsersPage } from '@/design-preview/pages/users-page'
export const Route = createFileRoute('/design-preview/_shell/users')({
export const Route = createFileRoute('/design-preview/_shell/users/')({
component: UsersPage,
})