Files
duty-teller/webapp-next/src/app/page.tsx
Nikolay Tatarinov 40e2b5adc4 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.
2026-03-06 16:48:24 +03:00

84 lines
2.9 KiB
TypeScript

/**
* Main Mini App page: current duty deep link or calendar view.
* Delegates to CurrentDutyView or CalendarPage; runs theme and app init.
*/
"use client";
import { useCallback, useEffect } from "react";
import { useAppStore, type AppState } from "@/store/app-store";
import { useShallow } from "zustand/react/shallow";
import { useTelegramAuth } from "@/hooks/use-telegram-auth";
import { useAppInit } from "@/hooks/use-app-init";
import { fetchAdminMe } from "@/lib/api";
import { getLang } from "@/i18n/messages";
import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen";
import { CurrentDutyView } from "@/components/current-duty/CurrentDutyView";
import { CalendarPage } from "@/components/CalendarPage";
export default function Home() {
const { initDataRaw, startParam, isLocalhost } = useTelegramAuth();
const isAllowed = isLocalhost || !!initDataRaw;
useAppInit({ isAllowed, startParam });
const setIsAdmin = useAppStore((s) => s.setIsAdmin);
useEffect(() => {
if (!isAllowed || !initDataRaw) {
setIsAdmin(false);
return;
}
fetchAdminMe(initDataRaw, getLang()).then(({ is_admin }) => setIsAdmin(is_admin));
}, [isAllowed, initDataRaw, setIsAdmin]);
const { accessDenied, currentView, setCurrentView, setSelectedDay, appContentReady, setAppContentReady } =
useAppStore(
useShallow((s: AppState) => ({
accessDenied: s.accessDenied,
currentView: s.currentView,
setCurrentView: s.setCurrentView,
setSelectedDay: s.setSelectedDay,
appContentReady: s.appContentReady,
setAppContentReady: s.setAppContentReady,
}))
);
// When showing access-denied or current-duty view, mark content ready so ReadyGate can call miniAppReady().
useEffect(() => {
if (accessDenied || currentView === "currentDuty") {
setAppContentReady(true);
}
}, [accessDenied, currentView, setAppContentReady]);
const handleBackFromCurrentDuty = useCallback(() => {
setCurrentView("calendar");
setSelectedDay(null);
}, [setCurrentView, setSelectedDay]);
const content = accessDenied ? (
<div className="content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6">
<AccessDeniedScreen primaryAction="reload" />
</div>
) : currentView === "currentDuty" ? (
<div className="content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6">
<CurrentDutyView
onBack={handleBackFromCurrentDuty}
openedFromPin={startParam === "duty"}
/>
</div>
) : (
<CalendarPage isAllowed={isAllowed} initDataRaw={initDataRaw} />
);
return (
<div
className="min-h-[var(--tg-viewport-stable-height,100vh)]"
style={{
visibility: appContentReady ? "visible" : "hidden",
}}
>
{content}
</div>
);
}