feat(tradein/auth): expire praktika trial access + trial-ended screen (contact Artem)
praktika pilot→expired role in roles.yaml (paths:[], deny:/**); RouteGuard short-circuits on role=expired with a dedicated trial-ended screen (not the generic path-deny path). NoAccessScreen gains variant="trial" with title «Пробный доступ закончился» and a Telegram link to @ArtemKopylov87. Backend Role Literal and frontend Role type extended to include "expired". kopylov (pilot) unaffected.
This commit is contained in:
parent
b60df61f6a
commit
703e411a17
5 changed files with 63 additions and 16 deletions
|
|
@ -54,6 +54,13 @@ roles:
|
|||
- "/admin/**"
|
||||
- "/api/v1/admin/**"
|
||||
- "/trade-in/api/v1/admin/**"
|
||||
expired:
|
||||
# Пробный доступ закончился — нет доступа ни к чему. Аккаунт остаётся в
|
||||
# caddy/users.caddy.snippet (basic_auth), чтобы дойти до фронта и увидеть
|
||||
# сообщение; /me отдаёт role=expired → RouteGuard рендерит trial-экран.
|
||||
paths: []
|
||||
deny:
|
||||
- "/**"
|
||||
|
||||
# NB: реальные analyst-логины ДОЛЖНЫ быть добавлены и в
|
||||
# caddy/users.caddy.snippet (Caddy basic_auth) силами devops — иначе Caddy не
|
||||
|
|
@ -72,7 +79,7 @@ users:
|
|||
user8: pilot
|
||||
user9: pilot
|
||||
user10: pilot
|
||||
praktika: pilot # пилот-аккаунт агентства «Практика» 2026-05-29
|
||||
praktika: expired # пробный доступ закончился 2026-06-27 — см. NoAccessScreen variant="trial"
|
||||
admintest: admin # temp QA 2026-05-26
|
||||
pilottest: pilot # temp QA 2026-05-26
|
||||
analysttest: analyst # temp QA 2026-06-07 (#962)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import yaml
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Role = Literal["admin", "pilot", "analyst"]
|
||||
Role = Literal["admin", "pilot", "analyst", "expired"]
|
||||
|
||||
|
||||
class UserScope(TypedDict):
|
||||
|
|
|
|||
|
|
@ -10,17 +10,26 @@
|
|||
*/
|
||||
|
||||
interface NoAccessScreenProps {
|
||||
variant: "user" | "path" | "session";
|
||||
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
|
||||
|
|
@ -56,18 +65,44 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
|||
lineHeight: 1.25,
|
||||
}}
|
||||
>
|
||||
{variant === "session" ? "Сессия истекла" : "Доступа нет"}
|
||||
{title}
|
||||
</h1>
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 14,
|
||||
color: "var(--fg-secondary)",
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
{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={{
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ export function RouteGuard({ children }: RouteGuardProps) {
|
|||
|
||||
if (!data) return null;
|
||||
|
||||
// Пробный доступ закончился (#praktika): role=expired → спец-экран, а не generic path-deny.
|
||||
if (data.role === "expired") {
|
||||
return <NoAccessScreen variant="trial" />;
|
||||
}
|
||||
|
||||
if (!isPathAllowed(data.allowed_paths, data.deny_paths, absolutePath)) {
|
||||
return <NoAccessScreen variant="path" path={absolutePath} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||
|
||||
import { apiFetchWithStatus, HTTPError } from "@/lib/api";
|
||||
|
||||
export type Role = "admin" | "pilot";
|
||||
export type Role = "admin" | "pilot" | "expired";
|
||||
|
||||
export interface UserScope {
|
||||
username: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue