feat: enhance theme handling and layout components for Telegram Mini App

- Updated theme resolution logic to utilize a shared inline script for consistent theme application across routes.
- Introduced `AppShell` and `ReadyGate` components to manage app readiness and theme synchronization, improving user experience.
- Enhanced `GlobalError` and `NotFound` pages with a unified full-screen layout for better accessibility and visual consistency.
- Refactored CSS to implement safe area insets for sticky headers and content safety, ensuring proper layout on various devices.
- Added unit tests for new functionality and improved existing tests for better coverage and reliability.
This commit is contained in:
2026-03-06 16:48:24 +03:00
parent 76bff6dc05
commit 40e2b5adc4
25 changed files with 396 additions and 131 deletions

View File

@@ -0,0 +1,51 @@
/**
* Shared full-screen state layout for fallback screens (access denied, not-found, error).
* Uses content-safe, app tokens, and consistent spacing so all fallback screens look like the same app.
*/
"use client";
export interface FullScreenStateShellProps {
/** Main heading (e.g. "Access denied", "Page not found"). */
title: React.ReactNode;
/** Optional description or message below the title. */
description?: React.ReactNode;
/** Optional extra content (e.g. server detail, secondary text). */
children?: React.ReactNode;
/** Primary action (Button or Link). */
primaryAction: React.ReactNode;
/** Wrapper role. Default "alert" for error/denied states. */
role?: "alert" | "status";
/** Optional extra class names for the wrapper. */
className?: string;
}
const WRAPPER_CLASS =
"content-safe flex min-h-[var(--tg-viewport-stable-height,100vh)] flex-col items-center justify-center gap-4 bg-background px-4 text-foreground";
/**
* Full-screen centered shell with title, optional description, and primary action.
* Use for access denied, not-found, and in-app error boundary screens.
*/
export function FullScreenStateShell({
title,
description,
children,
primaryAction,
role = "alert",
className,
}: FullScreenStateShellProps) {
return (
<div
className={className ? `${WRAPPER_CLASS} ${className}` : WRAPPER_CLASS}
role={role}
>
<h1 className="text-xl font-semibold">{title}</h1>
{description != null && (
<p className="text-center text-muted-foreground">{description}</p>
)}
{children}
{primaryAction}
</div>
);
}