- Replaced the previous webapp with a new Mini App built using Next.js, improving performance and maintainability. - Updated the `.gitignore` to exclude Next.js build artifacts and node modules. - Revised documentation in `AGENTS.md`, `README.md`, and `architecture.md` to reflect the new Mini App structure and technology stack. - Enhanced Dockerfile to support the new build process for the Next.js application. - Updated CI workflow to build and test the Next.js application. - Added new configuration options for the Mini App, including `MINI_APP_SHORT_NAME` for improved deep linking. - Refactored frontend testing setup to accommodate the new structure and testing framework. - Removed legacy webapp files and dependencies to streamline the project.
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import {
|
|
useSignal,
|
|
isThemeParamsDark,
|
|
setMiniAppBackgroundColor,
|
|
setMiniAppHeaderColor,
|
|
} from "@telegram-apps/sdk-react";
|
|
|
|
/**
|
|
* Resolves color scheme when Telegram theme is not available (SSR or non-TWA).
|
|
* Uses --tg-color-scheme (if set by Telegram) then prefers-color-scheme.
|
|
*/
|
|
export function getFallbackScheme(): "dark" | "light" {
|
|
if (typeof window === "undefined") return "dark";
|
|
try {
|
|
const cssScheme = getComputedStyle(document.documentElement)
|
|
.getPropertyValue("--tg-color-scheme")
|
|
.trim();
|
|
if (cssScheme === "light" || cssScheme === "dark") return cssScheme;
|
|
} catch {
|
|
// ignore
|
|
}
|
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
|
|
return "light";
|
|
}
|
|
|
|
/**
|
|
* Ensure --surface differs from --bg so cards/cells are visible.
|
|
* iOS OLED sends secondary_bg === bg (#000000) while section_bg differs;
|
|
* PC desktop sends section_bg === bg (#17212b) while secondary_bg differs.
|
|
* When the CSS-resolved --surface equals --bg, override with whichever
|
|
* Telegram color provides contrast, or a synthesized lighter fallback.
|
|
*/
|
|
export function fixSurfaceContrast(): void {
|
|
const root = document.documentElement;
|
|
const cs = getComputedStyle(root);
|
|
const bg = cs.getPropertyValue("--bg").trim();
|
|
const surface = cs.getPropertyValue("--surface").trim();
|
|
if (!bg || !surface || bg !== surface) return;
|
|
const sectionBg = cs.getPropertyValue("--tg-theme-section-bg-color").trim();
|
|
if (sectionBg && sectionBg !== bg) {
|
|
root.style.setProperty("--surface", sectionBg);
|
|
return;
|
|
}
|
|
const secondaryBg = cs.getPropertyValue("--tg-theme-secondary-bg-color").trim();
|
|
if (secondaryBg && secondaryBg !== bg) {
|
|
root.style.setProperty("--surface", secondaryBg);
|
|
return;
|
|
}
|
|
root.style.setProperty("--surface", `color-mix(in srgb, ${bg}, white 8%)`);
|
|
}
|
|
|
|
/**
|
|
* Applies theme: sets data-theme, forces reflow, fixes surface contrast,
|
|
* then Mini App background/header.
|
|
* Shared by TelegramProvider (initial + delayed) and useTelegramTheme.
|
|
* @param scheme - If provided, use it; otherwise resolve via getFallbackScheme().
|
|
*/
|
|
export function applyTheme(scheme?: "dark" | "light"): void {
|
|
const resolved = scheme ?? getFallbackScheme();
|
|
document.documentElement.setAttribute("data-theme", resolved);
|
|
void document.documentElement.offsetHeight; // force reflow so WebView repaints
|
|
fixSurfaceContrast();
|
|
if (setMiniAppBackgroundColor.isAvailable()) {
|
|
setMiniAppBackgroundColor("bg_color");
|
|
}
|
|
if (setMiniAppHeaderColor.isAvailable()) {
|
|
setMiniAppHeaderColor("bg_color");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps Telegram theme params to data-theme and Mini App background/header.
|
|
* Subscribes to theme changes via SDK signals.
|
|
* Ported from webapp/js/theme.js applyTheme / initTheme.
|
|
*/
|
|
export function useTelegramTheme(): "dark" | "light" {
|
|
const signalDark = useSignal(isThemeParamsDark);
|
|
const isDark =
|
|
typeof signalDark === "boolean" ? signalDark : getFallbackScheme() === "dark";
|
|
|
|
useEffect(() => {
|
|
applyTheme(isDark ? "dark" : "light");
|
|
}, [isDark]);
|
|
|
|
return isDark ? "dark" : "light";
|
|
}
|