gendesign/tradein-mvp/frontend/src/components/auth/NoAccessScreen.tsx
bot-backend a9dfe44f5c
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 51s
CI / openapi-codegen-check (pull_request) Successful in 1m56s
fix(tradein/auth): always-available logout button on NoAccessScreen
Locked/no-access users (403/denied path/expired session/trial ended) were
trapped: NoAccessScreen had no logout control, and «Выйти» lived only inside
UserMenu, which does not render on the no-access screen. They could not switch
accounts without clearing browser basic-auth manually.

- Extract logout() into shared `lib/logout.ts` (single source of truth).
- UserMenu now imports it instead of a local copy (behaviour unchanged).
- NoAccessScreen renders an always-available «Выйти» accent button for every
  variant.

Mirrored across both frontends (tradein-mvp/frontend + main frontend) to keep
the MIRROR invariant in sync.
2026-06-27 15:16:42 +03:00

144 lines
4.3 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";
/**
* MIRROR of main frontend `frontend/src/components/auth/NoAccessScreen.tsx`
* — keep in sync manually. Tradein-mvp бандлится отдельно (basePath=/trade-in),
* нет общего workspace package.
*
* Fullscreen «доступа нет» — для 403 от /me или для denied path.
* Token-based styling per `.claude/rules/ui-tokens.md` (см. globals.css).
*/
import { logout } from "@/lib/logout";
interface NoAccessScreenProps {
variant: "user" | "path" | "session" | "trial";
path?: string;
}
export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
const title =
variant === "session"
? "Сессия истекла"
: variant === "trial"
? "Пробный доступ закончился"
: "Доступа нет";
const subtitle =
variant === "session"
? "Сессия истекла или вы не авторизованы. Обновите страницу, чтобы войти снова."
: variant === "user"
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
: variant === "path"
? "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору."
: null;
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%",
}}
>
<h1
style={{
margin: "0 0 8px",
fontSize: 22,
fontWeight: 600,
color: "var(--fg-primary)",
lineHeight: 1.25,
}}
>
{title}
</h1>
{variant === "trial" ? (
<p
style={{
margin: 0,
fontSize: 14,
color: "var(--fg-secondary)",
lineHeight: 1.6,
}}
>
Пробный доступ к сервису «Мера» закончился. Чтобы продлить доступ
или получить полную версию напишите Артёму Копылову:{" "}
<a
href="https://t.me/ArtemKopylov87"
target="_blank"
rel="noreferrer"
style={{
color: "var(--accent)",
textDecoration: "underline",
}}
>
@ArtemKopylov87
</a>
.
</p>
) : (
<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}
{/* Always-available logout — иначе locked/no-access юзер заперт:
UserMenu (где обычно «Выйти») на этом экране не рендерится. */}
<button
type="button"
onClick={() => logout()}
style={{
marginTop: 20,
background: "var(--accent)",
color: "#FFFFFF",
border: "none",
borderRadius: 8,
padding: "10px 16px",
fontSize: 14,
fontWeight: 500,
cursor: "pointer",
fontFamily: "inherit",
}}
>
Выйти
</button>
</div>
</main>
);
}