From 8bbaab62e401d42e7911ef942ca3dc2d0173b709 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 18 Oct 2026 03:11:43 +0700 Subject: [PATCH] =?UTF-8?q?fix(design-preview):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=83?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20=E2=80=94=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BB=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=BF=D1=80=D0=B0=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Права, сброс passkeys, отключение и удаление уже есть в панели прав и меню строки, сессии и события — в «Входах» и «Изменениях». Страница откроется из коммита 007ece4. Co-Authored-By: Claude Sonnet 5.5 --- apps/web/src/design-preview/access-sheet.tsx | 9 - .../pages/user-profile-page.tsx | 304 ------------------ .../src/design-preview/pages/users-page.tsx | 6 - .../web/src/design-preview/preview-chrome.tsx | 24 +- apps/web/src/design-preview/users-grid.tsx | 6 - apps/web/src/routeTree.gen.ts | 22 -- .../design-preview._shell.users.$userId.tsx | 6 - 7 files changed, 4 insertions(+), 373 deletions(-) delete mode 100644 apps/web/src/design-preview/pages/user-profile-page.tsx delete mode 100644 apps/web/src/routes/design-preview._shell.users.$userId.tsx diff --git a/apps/web/src/design-preview/access-sheet.tsx b/apps/web/src/design-preview/access-sheet.tsx index 6f3ac46..08e8d11 100644 --- a/apps/web/src/design-preview/access-sheet.tsx +++ b/apps/web/src/design-preview/access-sheet.tsx @@ -1,5 +1,4 @@ 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' @@ -11,12 +10,10 @@ export function AccessSheet({ user, onClose, onSave, - onOpenProfile, }: { user: AdminUser onClose: () => void onSave: (value: AccessValue) => void - onOpenProfile?: () => void }) { const [value, setValue] = useState({ apps: user.apps, permissions: user.permissions }) const changes = useMemo(() => diffAccess({ apps: user.apps, permissions: user.permissions }, value).length, [user, value]) @@ -31,12 +28,6 @@ export function AccessSheet({ description="Доступ к приложениям и разделам · иерархия: админ ⊃ запись ⊃ чтение" footer={ <> - {onOpenProfile ? ( - - ) : null} {changes > 0 ? `Изменений: ${changes}` : 'Без изменений'} diff --git a/apps/web/src/design-preview/pages/user-profile-page.tsx b/apps/web/src/design-preview/pages/user-profile-page.tsx deleted file mode 100644 index 5b4cc38..0000000 --- a/apps/web/src/design-preview/pages/user-profile-page.tsx +++ /dev/null @@ -1,304 +0,0 @@ -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(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 ( - - -

Пользователь не найден

-

Возможно, учётная запись уже удалена.

- -
- - ) - } - - 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[] = [ - { id: 'ip', title: 'IP · клиент', size: 260, cell: (r) => }, - { id: 'created', title: 'Создана', size: 140, cell: (r) => {r.created} }, - { id: 'exp', title: 'Истекает', size: 120, cell: (r) => {r.expires} }, - { - id: 'act', - title: '', - size: 130, - cell: (r) => ( - - ), - }, - ] - - return ( - <> - - - - {initials(user)} - -
-

{user.email}

-

{USER_META[user.id]?.sub ?? user.name}

-
- {status.label} - {user.is_admin ? 'Админ' : 'Пользователь'} - {mark ? : null} -
-
- -
- - - - - - - Права доступа - - - - Вход и passkeys - - - - Сессии · {sessionRows.length} - - - - Активность - - - - Опасная зона - - - -
- - - - Права доступа - Приложения и разделы · иерархия: админ ⊃ запись ⊃ чтение - - - setDraft({ id: user.id, value: next })} /> - - - {changes > 0 ? `Изменений: ${changes}` : 'Без изменений'} - - - - - - - - - - -
- Сбросить passkeys… - Войти можно будет только по паролю, пока не добавлен новый ключ. -
- -
- -
- - - r.id} - emptyMessage="Нет активных сессий." - /> - - - - - - Активность - События пользователя из журнала портала - - - {events.length === 0 ? ( -

Событий нет.

- ) : ( - - )} -
- -
- - - - - Опасная зона - Действия попадают в «Не сохранено» и в журнал - - -
-
- {user.disabled ? 'Включить учётную запись' : 'Отключить учётную запись'} - Вход закрывается, учётная запись и права сохраняются. -
- -
-
-
- Удалить пользователя… - Учётная запись и все права будут удалены безвозвратно. -
- -
-
- -
-
-
- - { - patch([user.id], (u) => ({ ...u, passkey_count: 0 }), `Сброс passkeys: ${user.email}`, (b) => [`− passkeys: ${b.passkey_count}`, '+ passkeys: 0']) - setResetOpen(false) - }} - /> - { - 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' }) - }} - /> - - ) -} diff --git a/apps/web/src/design-preview/pages/users-page.tsx b/apps/web/src/design-preview/pages/users-page.tsx index 0d8edd3..85f8714 100644 --- a/apps/web/src/design-preview/pages/users-page.tsx +++ b/apps/web/src/design-preview/pages/users-page.tsx @@ -1,6 +1,5 @@ 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' @@ -26,7 +25,6 @@ export function UsersPage() { usersRef.current = users }, [users]) const patch = usePatchUsers() - const navigate = useNavigate() const [createOpen, setCreateOpen] = useState(false) const [journalFor, setJournalFor] = useState(null) @@ -86,7 +84,6 @@ 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}`, () => [ @@ -151,9 +148,6 @@ 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( diff --git a/apps/web/src/design-preview/preview-chrome.tsx b/apps/web/src/design-preview/preview-chrome.tsx index 968f897..50c365f 100644 --- a/apps/web/src/design-preview/preview-chrome.tsx +++ b/apps/web/src/design-preview/preview-chrome.tsx @@ -31,10 +31,8 @@ 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 { @@ -474,9 +472,7 @@ function FreshnessPopover() { export function PreviewHeader() { const pathname = useRouterState({ select: (s) => s.location.pathname }) - const { setPaletteOpen, users } = usePreviewStore() - const profileId = /^\/design-preview\/users\/([^/]+)/.exec(pathname)?.[1] - const profileUser = profileId ? users.find((u) => u.id === profileId) : undefined + const { setPaletteOpen } = usePreviewStore() const current = NAV_ITEMS.find((i) => pathname === i.to || pathname.startsWith(`${i.to}/`)) return ( @@ -485,21 +481,9 @@ export function PreviewHeader() { - {profileUser ? ( - <> - - }>Пользователи - - - - {profileUser.email} - - - ) : ( - - {current?.label ?? 'Макет'} - - )} + + {current?.label ?? 'Макет'} +
diff --git a/apps/web/src/design-preview/users-grid.tsx b/apps/web/src/design-preview/users-grid.tsx index 12c8125..abcc677 100644 --- a/apps/web/src/design-preview/users-grid.tsx +++ b/apps/web/src/design-preview/users-grid.tsx @@ -6,7 +6,6 @@ import { ScrollTextIcon, ShieldCheckIcon, Trash2Icon, - UserRoundIcon, FingerprintIcon, } from 'lucide-react' import type { AdminUser } from '@authportal/shared' @@ -43,7 +42,6 @@ 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 @@ -91,10 +89,6 @@ export function UsersGrid({ users, handlers, marks }: { users: AdminUser[]; hand Права доступа - handlers.onOpenProfile(u)}> - - Профиль - handlers.onOpenJournal(u)}> Журнал diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 36d1e78..370dfc0 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -32,7 +32,6 @@ import { Route as DesignPreviewShellLoginsRouteImport } from './routes/design-pr import { Route as DesignPreviewShellOidcRouteImport } from './routes/design-preview._shell.oidc' 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: '/', @@ -151,12 +150,6 @@ const DesignPreviewShellUsersIndexRoute = 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 @@ -179,7 +172,6 @@ export interface FileRoutesByFullPath { '/design-preview/oidc': typeof DesignPreviewShellOidcRoute '/admin/': typeof AuthAdminIndexRoute '/admin/users/$userId': typeof AuthAdminUsersUserIdRoute - '/design-preview/users/$userId': typeof DesignPreviewShellUsersUserIdRoute '/design-preview/users/': typeof DesignPreviewShellUsersIndexRoute } export interface FileRoutesByTo { @@ -201,7 +193,6 @@ export interface FileRoutesByTo { '/design-preview/oidc': typeof DesignPreviewShellOidcRoute '/admin': typeof AuthAdminIndexRoute '/admin/users/$userId': typeof AuthAdminUsersUserIdRoute - '/design-preview/users/$userId': typeof DesignPreviewShellUsersUserIdRoute '/design-preview/users': typeof DesignPreviewShellUsersIndexRoute } export interface FileRoutesById { @@ -228,7 +219,6 @@ export interface FileRoutesById { '/design-preview/_shell/oidc': typeof DesignPreviewShellOidcRoute '/_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 { @@ -254,7 +244,6 @@ export interface FileRouteTypes { | '/design-preview/oidc' | '/admin/' | '/admin/users/$userId' - | '/design-preview/users/$userId' | '/design-preview/users/' fileRoutesByTo: FileRoutesByTo to: @@ -276,7 +265,6 @@ export interface FileRouteTypes { | '/design-preview/oidc' | '/admin' | '/admin/users/$userId' - | '/design-preview/users/$userId' | '/design-preview/users' id: | '__root__' @@ -302,7 +290,6 @@ export interface FileRouteTypes { | '/design-preview/_shell/oidc' | '/_auth/admin/' | '/_auth/admin/users/$userId' - | '/design-preview/_shell/users/$userId' | '/design-preview/_shell/users/' fileRoutesById: FileRoutesById } @@ -476,13 +463,6 @@ declare module '@tanstack/react-router' { 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 - } } } @@ -529,7 +509,6 @@ interface DesignPreviewShellRouteChildren { DesignPreviewShellAuditRoute: typeof DesignPreviewShellAuditRoute DesignPreviewShellLoginsRoute: typeof DesignPreviewShellLoginsRoute DesignPreviewShellOidcRoute: typeof DesignPreviewShellOidcRoute - DesignPreviewShellUsersUserIdRoute: typeof DesignPreviewShellUsersUserIdRoute DesignPreviewShellUsersIndexRoute: typeof DesignPreviewShellUsersIndexRoute } @@ -540,7 +519,6 @@ const DesignPreviewShellRouteChildren: DesignPreviewShellRouteChildren = { DesignPreviewShellAuditRoute: DesignPreviewShellAuditRoute, DesignPreviewShellLoginsRoute: DesignPreviewShellLoginsRoute, DesignPreviewShellOidcRoute: DesignPreviewShellOidcRoute, - DesignPreviewShellUsersUserIdRoute: DesignPreviewShellUsersUserIdRoute, DesignPreviewShellUsersIndexRoute: DesignPreviewShellUsersIndexRoute, } diff --git a/apps/web/src/routes/design-preview._shell.users.$userId.tsx b/apps/web/src/routes/design-preview._shell.users.$userId.tsx deleted file mode 100644 index a20380f..0000000 --- a/apps/web/src/routes/design-preview._shell.users.$userId.tsx +++ /dev/null @@ -1,6 +0,0 @@ -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, -})