fix(tradein/auth): honest 401/error screens in RouteGuard instead of misleading text #2516

Merged
lekss361 merged 1 commit from fix/tradein-routeguard-401-screen into main 2026-07-13 20:19:13 +00:00
2 changed files with 61 additions and 29 deletions

View file

@ -12,7 +12,7 @@
import { logout } from "@/lib/logout";
interface NoAccessScreenProps {
variant: "user" | "path" | "session" | "trial";
variant: "user" | "path" | "session" | "trial" | "error";
path?: string;
}
@ -22,16 +22,20 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
? "Сессия истекла"
: variant === "trial"
? "Пробный доступ закончился"
: "Доступа нет";
: variant === "error"
? "Не удалось проверить доступ"
: "Доступа нет";
const subtitle =
variant === "session"
? "Сессия истекла или вы не авторизованы. Обновите страницу, чтобы войти снова."
: variant === "user"
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
: variant === "path"
? "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору."
: null;
? "Сессия истекла или вы не авторизованы. Нажмите «Войти снова», чтобы пройти авторизацию заново."
: variant === "error"
? "Возникла техническая проблема при проверке доступа. Проверьте подключение к интернету и обновите страницу. Если ошибка повторяется — обратитесь к администратору GenDesign."
: variant === "user"
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
: variant === "path"
? "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору."
: null;
return (
<main
@ -118,26 +122,51 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
{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>
{/* Always-available primary action иначе locked/no-access юзер заперт:
UserMenu (где обычно «Выйти») на этом экране не рендерится.
session/error сбой временный (истёкшая сессия / сетевой сбой),
поэтому CTA повторить попытку через reload, а не logout.
basic_auth Caddy: reload на top-level navigation триггерит
повторный auth-challenge браузера при отклонённых creds. */}
{variant === "session" || variant === "error" ? (
<button
type="button"
onClick={() => window.location.reload()}
style={{
marginTop: 20,
background: "var(--accent)",
color: "#FFFFFF",
border: "none",
borderRadius: 8,
padding: "10px 16px",
fontSize: 14,
fontWeight: 500,
cursor: "pointer",
fontFamily: "inherit",
}}
>
{variant === "session" ? "Войти снова" : "Обновить страницу"}
</button>
) : (
<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>
);

View file

@ -58,7 +58,10 @@ export function RouteGuard({ children }: RouteGuardProps) {
}
if (error) {
return <NoAccessScreen variant="user" />;
// Не 401/403 — 500 / сетевой сбой / таймаут. НЕ переиспользуем variant="user"
// (текст «аккаунт не привязан к роли» вводит в заблуждение при техническом
// сбое) — отдельный экран с честной формулировкой + reload CTA.
return <NoAccessScreen variant="error" />;
}
if (!data) return null;