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/**"
|
- "/admin/**"
|
||||||
- "/api/v1/admin/**"
|
- "/api/v1/admin/**"
|
||||||
- "/trade-in/api/v1/admin/**"
|
- "/trade-in/api/v1/admin/**"
|
||||||
|
expired:
|
||||||
|
# Пробный доступ закончился — нет доступа ни к чему. Аккаунт остаётся в
|
||||||
|
# caddy/users.caddy.snippet (basic_auth), чтобы дойти до фронта и увидеть
|
||||||
|
# сообщение; /me отдаёт role=expired → RouteGuard рендерит trial-экран.
|
||||||
|
paths: []
|
||||||
|
deny:
|
||||||
|
- "/**"
|
||||||
|
|
||||||
# NB: реальные analyst-логины ДОЛЖНЫ быть добавлены и в
|
# NB: реальные analyst-логины ДОЛЖНЫ быть добавлены и в
|
||||||
# caddy/users.caddy.snippet (Caddy basic_auth) силами devops — иначе Caddy не
|
# caddy/users.caddy.snippet (Caddy basic_auth) силами devops — иначе Caddy не
|
||||||
|
|
@ -72,7 +79,7 @@ users:
|
||||||
user8: pilot
|
user8: pilot
|
||||||
user9: pilot
|
user9: pilot
|
||||||
user10: pilot
|
user10: pilot
|
||||||
praktika: pilot # пилот-аккаунт агентства «Практика» 2026-05-29
|
praktika: expired # пробный доступ закончился 2026-06-27 — см. NoAccessScreen variant="trial"
|
||||||
admintest: admin # temp QA 2026-05-26
|
admintest: admin # temp QA 2026-05-26
|
||||||
pilottest: pilot # temp QA 2026-05-26
|
pilottest: pilot # temp QA 2026-05-26
|
||||||
analysttest: analyst # temp QA 2026-06-07 (#962)
|
analysttest: analyst # temp QA 2026-06-07 (#962)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import yaml
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
Role = Literal["admin", "pilot", "analyst"]
|
Role = Literal["admin", "pilot", "analyst", "expired"]
|
||||||
|
|
||||||
|
|
||||||
class UserScope(TypedDict):
|
class UserScope(TypedDict):
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,26 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface NoAccessScreenProps {
|
interface NoAccessScreenProps {
|
||||||
variant: "user" | "path" | "session";
|
variant: "user" | "path" | "session" | "trial";
|
||||||
path?: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
||||||
|
const title =
|
||||||
|
variant === "session"
|
||||||
|
? "Сессия истекла"
|
||||||
|
: variant === "trial"
|
||||||
|
? "Пробный доступ закончился"
|
||||||
|
: "Доступа нет";
|
||||||
|
|
||||||
const subtitle =
|
const subtitle =
|
||||||
variant === "session"
|
variant === "session"
|
||||||
? "Сессия истекла или вы не авторизованы. Обновите страницу, чтобы войти снова."
|
? "Сессия истекла или вы не авторизованы. Обновите страницу, чтобы войти снова."
|
||||||
: variant === "user"
|
: variant === "user"
|
||||||
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
|
? "Учётная запись не привязана к роли. Обратитесь к администратору GenDesign — kopylov."
|
||||||
: "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору.";
|
: variant === "path"
|
||||||
|
? "У вашей роли нет доступа к этому разделу. Вернитесь на главную или обратитесь к администратору."
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
|
|
@ -56,8 +65,33 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
||||||
lineHeight: 1.25,
|
lineHeight: 1.25,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{variant === "session" ? "Сессия истекла" : "Доступа нет"}
|
{title}
|
||||||
</h1>
|
</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
|
<p
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
|
|
@ -68,6 +102,7 @@ export function NoAccessScreen({ variant, path }: NoAccessScreenProps) {
|
||||||
>
|
>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
{variant === "path" && path ? (
|
{variant === "path" && path ? (
|
||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ export function RouteGuard({ children }: RouteGuardProps) {
|
||||||
|
|
||||||
if (!data) return null;
|
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)) {
|
if (!isPathAllowed(data.allowed_paths, data.deny_paths, absolutePath)) {
|
||||||
return <NoAccessScreen variant="path" path={absolutePath} />;
|
return <NoAccessScreen variant="path" path={absolutePath} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiFetchWithStatus, HTTPError } from "@/lib/api";
|
import { apiFetchWithStatus, HTTPError } from "@/lib/api";
|
||||||
|
|
||||||
export type Role = "admin" | "pilot";
|
export type Role = "admin" | "pilot" | "expired";
|
||||||
|
|
||||||
export interface UserScope {
|
export interface UserScope {
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue