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:
@@ -5,8 +5,12 @@
|
||||
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import "./globals.css";
|
||||
import { getLang, translate } from "@/i18n/messages";
|
||||
import { callMiniAppReadyOnce } from "@/lib/telegram-ready";
|
||||
import { THEME_BOOTSTRAP_SCRIPT } from "@/lib/theme-bootstrap-script";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
@@ -16,6 +20,11 @@ export default function GlobalError({
|
||||
reset: () => void;
|
||||
}) {
|
||||
const lang = getLang();
|
||||
|
||||
useEffect(() => {
|
||||
callMiniAppReadyOnce();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={lang === "ru" ? "ru" : "en"}
|
||||
@@ -23,28 +32,19 @@ export default function GlobalError({
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
{/* Same theme detection as layout: hash / Telegram / prefers-color-scheme → data-theme */}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(function(){var scheme='dark';var root=document.documentElement;var hash=typeof location!=='undefined'&&location.hash?location.hash.slice(1):'';if(hash){var params={};hash.split('&').forEach(function(p){var i=p.indexOf('=');if(i===-1)return;var k=decodeURIComponent(p.slice(0,i));var v=p.slice(i+1);params[k]=v;});if(params.tgWebAppColorScheme==='light'||params.tgWebAppColorScheme==='dark')scheme=params.tgWebAppColorScheme;if(params.tgWebAppThemeParams){try{var theme=JSON.parse(decodeURIComponent(params.tgWebAppThemeParams));var tgKeys=['bg_color','text_color','hint_color','link_color','button_color','button_text_color','secondary_bg_color','header_bg_color','accent_text_color','destructive_text_color','section_bg_color','section_header_text_color','section_separator_color','subtitle_text_color','bottom_bar_bg_color'];tgKeys.forEach(function(k){var v=theme[k];if(v&&typeof v==='string')root.style.setProperty('--tg-theme-'+k.replace(/_/g,'-'),v);});Object.keys(theme).forEach(function(k){var v=theme[k];if(v&&typeof v==='string')root.style.setProperty('--tg-theme-'+k.replace(/_/g,'-'),v);});}catch(e){}}}var twa=typeof window!=='undefined'&&window.Telegram&&window.Telegram.WebApp;if(scheme!=='light'&&scheme!=='dark'&&twa&&(twa.colorScheme==='light'||twa.colorScheme==='dark'))scheme=twa.colorScheme;if(scheme!=='light'&&scheme!=='dark'){try{var s=getComputedStyle(root).getPropertyValue('--tg-color-scheme').trim();if(s==='light'||s==='dark')scheme=s;}catch(e){} }if(scheme!=='light'&&scheme!=='dark')scheme=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';root.setAttribute('data-theme',scheme);if(twa){if(twa.setBackgroundColor)twa.setBackgroundColor('bg_color');if(twa.setHeaderColor)twa.setHeaderColor('bg_color');}})();`,
|
||||
}}
|
||||
/>
|
||||
<script dangerouslySetInnerHTML={{ __html: THEME_BOOTSTRAP_SCRIPT }} />
|
||||
</head>
|
||||
<body className="antialiased">
|
||||
<div className="flex min-h-[var(--tg-viewport-stable-height,100vh)] flex-col items-center justify-center gap-4 bg-background px-4 text-foreground">
|
||||
<div className="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">
|
||||
<h1 className="text-xl font-semibold">
|
||||
{translate(lang, "error_boundary.message")}
|
||||
</h1>
|
||||
<p className="text-center text-muted-foreground">
|
||||
{translate(lang, "error_boundary.description")}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => reset()}
|
||||
className="rounded-lg bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<Button type="button" onClick={() => reset()}>
|
||||
{translate(lang, "error_boundary.reload")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user