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.
144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
"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>
|
||
);
|
||
}
|