- 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.
128 lines
3.9 KiB
TypeScript
128 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
import {
|
|
init,
|
|
mountMiniAppSync,
|
|
mountThemeParamsSync,
|
|
bindThemeParamsCssVars,
|
|
mountViewport,
|
|
bindViewportCssVars,
|
|
unmountViewport,
|
|
} from "@telegram-apps/sdk-react";
|
|
import { fixSurfaceContrast, useTelegramTheme } from "@/hooks/use-telegram-theme";
|
|
import { applyAndroidPerformanceClass } from "@/lib/telegram-android-perf";
|
|
import { useAppStore } from "@/store/app-store";
|
|
import { getLang } from "@/i18n/messages";
|
|
import { useTranslation } from "@/i18n/use-translation";
|
|
|
|
const EVENT_CONFIG_LOADED = "dt-config-loaded";
|
|
|
|
export interface TelegramSdkContextValue {
|
|
/** True after init() and sync mounts have run; safe to use backButton etc. */
|
|
sdkReady: boolean;
|
|
}
|
|
|
|
const TelegramSdkContext = createContext<TelegramSdkContextValue>({
|
|
sdkReady: false,
|
|
});
|
|
|
|
export function useTelegramSdkReady(): TelegramSdkContextValue {
|
|
return useContext(TelegramSdkContext);
|
|
}
|
|
|
|
/**
|
|
* Wraps the app with Telegram Mini App SDK initialization.
|
|
* Calls init(acceptCustomStyles), mounts theme params (binds --tg-theme-* CSS vars),
|
|
* mounts the mini app, then mounts viewport and binds viewport CSS vars
|
|
* (--tg-viewport-stable-height, --tg-viewport-content-safe-area-inset-*, etc.).
|
|
* Does not call ready() here — the app calls callMiniAppReadyOnce() from
|
|
* lib/telegram-ready when the first visible screen has finished loading.
|
|
* Theme is set before first paint by the inline script in layout.tsx (URL hash);
|
|
* useTelegramTheme() in the app handles ongoing theme changes.
|
|
* Syncs lang from window.__DT_LANG on mount and when config.js fires dt-config-loaded.
|
|
*/
|
|
/**
|
|
* Live theme sync: provider-owned so every route (/, /admin, not-found, error) gets
|
|
* data-theme and Mini App chrome color updates when Telegram theme changes.
|
|
*/
|
|
function ThemeSync() {
|
|
useTelegramTheme();
|
|
return null;
|
|
}
|
|
|
|
export function TelegramProvider({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [sdkReady, setSdkReady] = useState(false);
|
|
const setLang = useAppStore((s) => s.setLang);
|
|
const lang = useAppStore((s) => s.lang);
|
|
const { t } = useTranslation();
|
|
|
|
// Sync lang from backend config: on mount and when config.js has loaded (all routes, including admin).
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
setLang(getLang());
|
|
const onConfigLoaded = () => setLang(getLang());
|
|
window.addEventListener(EVENT_CONFIG_LOADED, onConfigLoaded);
|
|
return () => window.removeEventListener(EVENT_CONFIG_LOADED, onConfigLoaded);
|
|
}, [setLang]);
|
|
|
|
// Apply lang to document (title and html lang) so all routes including admin get correct title.
|
|
useEffect(() => {
|
|
if (typeof document === "undefined") return;
|
|
document.documentElement.lang = lang;
|
|
document.title = t("app.title");
|
|
}, [lang, t]);
|
|
|
|
useEffect(() => {
|
|
const cleanup = init({ acceptCustomStyles: true });
|
|
|
|
if (mountThemeParamsSync.isAvailable()) {
|
|
mountThemeParamsSync();
|
|
}
|
|
if (bindThemeParamsCssVars.isAvailable()) {
|
|
bindThemeParamsCssVars();
|
|
}
|
|
fixSurfaceContrast();
|
|
void document.documentElement.offsetHeight;
|
|
|
|
if (mountMiniAppSync.isAvailable()) {
|
|
mountMiniAppSync();
|
|
}
|
|
|
|
applyAndroidPerformanceClass();
|
|
|
|
setSdkReady(true);
|
|
|
|
let unbindViewportCssVars: (() => void) | undefined;
|
|
if (mountViewport.isAvailable()) {
|
|
mountViewport()
|
|
.then(() => {
|
|
if (bindViewportCssVars.isAvailable()) {
|
|
unbindViewportCssVars = bindViewportCssVars();
|
|
}
|
|
})
|
|
.catch(() => {
|
|
// Viewport not supported (e.g. not in Mini App); ignore.
|
|
});
|
|
}
|
|
|
|
return () => {
|
|
setSdkReady(false);
|
|
unbindViewportCssVars?.();
|
|
unmountViewport();
|
|
cleanup();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<TelegramSdkContext.Provider value={{ sdkReady }}>
|
|
<ThemeSync />
|
|
{children}
|
|
</TelegramSdkContext.Provider>
|
|
);
|
|
}
|