gendesign/frontend/src/components/auth/NoAccessScreen.tsx
Light1YT 7a2b055b35
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m42s
Deploy Trade-In / deploy (push) Successful in 39s
Deploy / build-frontend (push) Successful in 2m55s
Deploy / deploy (push) Successful in 1m1s
feat(rbac): frontend route-guard + nav filter (#586)
PR B (frontend) to backend RBAC #585. RouteGuard wraps app, NoAccessScreen on 403 from /me, TopNav filtered by role.
2026-05-26 06:51:45 +00:00

106 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
/**
* Полноэкранный «доступа нет» — показывается в двух случаях:
* 1. /api/v1/me вернул 403 (юзер аутентифицирован Caddy basic_auth,
* но его нет в `auth/roles.yaml`). Variant: "user".
* 2. Юзер открыл URL, которого нет в его allowed_paths (или есть в
* deny_paths). Variant: "path".
*
* Tone-of-voice (см. `.claude/rules/ui-microcopy.md`): нейтральный business,
* без эмодзи, без «Ой!». Просто констатация.
*/
import { Lock } from "lucide-react";
interface NoAccessScreenProps {
/** "user" — юзер вне ролей; "path" — путь вне scope */
variant: "user" | "path";
/** Текущий путь — используется только для variant="path" в caption */
path?: string;
}
export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
const title = "Доступа нет";
const subtitle =
variant === "user"
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
: "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору.";
return (
<main
role="alert"
aria-live="polite"
style={{
minHeight: "100vh",
background: "var(--bg-app)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 24,
fontFamily: "Inter, -apple-system, 'Segoe UI', system-ui, sans-serif",
}}
>
<div
style={{
background: "var(--bg-card)",
border: "1px solid var(--border-card)",
borderRadius: 12,
padding: "32px 28px",
maxWidth: 480,
width: "100%",
textAlign: "left",
}}
>
<div
style={{
width: 40,
height: 40,
background: "var(--accent-soft)",
borderRadius: 8,
display: "flex",
alignItems: "center",
justifyContent: "center",
marginBottom: 16,
}}
>
<Lock size={20} strokeWidth={1.5} color="var(--accent)" />
</div>
<h1
style={{
margin: "0 0 8px",
fontSize: 22,
fontWeight: 600,
color: "var(--fg-primary)",
lineHeight: 1.25,
}}
>
{title}
</h1>
<p
style={{
margin: 0,
fontSize: 14,
color: "var(--fg-secondary)",
lineHeight: 1.6,
}}
>
{subtitle}
</p>
{variant === "path" && path ? (
<p
style={{
margin: "12px 0 0",
fontSize: 12,
color: "var(--fg-tertiary)",
fontFamily: "ui-monospace, SFMono-Regular, monospace",
wordBreak: "break-all",
}}
>
{path}
</p>
) : null}
</div>
</main>
);
}