diff --git a/webapp-next/src/app/page.tsx b/webapp-next/src/app/page.tsx index 2ed3816..52bea0a 100644 --- a/webapp-next/src/app/page.tsx +++ b/webapp-next/src/app/page.tsx @@ -5,12 +5,13 @@ "use client"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { useAppStore } from "@/store/app-store"; import { useShallow } from "zustand/react/shallow"; import { useTelegramTheme } from "@/hooks/use-telegram-theme"; import { useTelegramAuth } from "@/hooks/use-telegram-auth"; import { useAppInit } from "@/hooks/use-app-init"; +import { callMiniAppReadyOnce } from "@/lib/telegram-ready"; import { CurrentDutyView } from "@/components/current-duty/CurrentDutyView"; import { CalendarPage } from "@/components/CalendarPage"; @@ -22,29 +23,48 @@ export default function Home() { useAppInit({ isAllowed, startParam }); - const { currentView, setCurrentView, setSelectedDay } = useAppStore( - useShallow((s) => ({ - currentView: s.currentView, - setCurrentView: s.setCurrentView, - setSelectedDay: s.setSelectedDay, - })) - ); + const { currentView, setCurrentView, setSelectedDay, appContentReady } = + useAppStore( + useShallow((s) => ({ + currentView: s.currentView, + setCurrentView: s.setCurrentView, + setSelectedDay: s.setSelectedDay, + appContentReady: s.appContentReady, + })) + ); + + // When content is ready, tell Telegram to hide native loading and show our app. + useEffect(() => { + if (appContentReady) { + callMiniAppReadyOnce(); + } + }, [appContentReady]); const handleBackFromCurrentDuty = useCallback(() => { setCurrentView("calendar"); setSelectedDay(null); }, [setCurrentView, setSelectedDay]); - if (currentView === "currentDuty") { - return ( + const content = + currentView === "currentDuty" ? (
+ ) : ( + ); - } - return ; + return ( +
+ {content} +
+ ); } diff --git a/webapp-next/src/components/CalendarPage.tsx b/webapp-next/src/components/CalendarPage.tsx index 1f011f4..9a31107 100644 --- a/webapp-next/src/components/CalendarPage.tsx +++ b/webapp-next/src/components/CalendarPage.tsx @@ -16,7 +16,6 @@ import { CalendarHeader } from "@/components/calendar/CalendarHeader"; import { CalendarGrid } from "@/components/calendar/CalendarGrid"; import { DutyList } from "@/components/duty/DutyList"; import { DayDetail, type DayDetailHandle } from "@/components/day-detail"; -import { callMiniAppReadyOnce } from "@/lib/telegram-ready"; import { ErrorState } from "@/components/states/ErrorState"; import { AccessDenied } from "@/components/states/AccessDenied"; @@ -60,6 +59,7 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) { prevMonth, setCurrentMonth, setSelectedDay, + setAppContentReady, } = useAppStore( useShallow((s) => ({ currentMonth: s.currentMonth, @@ -75,6 +75,7 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) { prevMonth: s.prevMonth, setCurrentMonth: s.setCurrentMonth, setSelectedDay: s.setSelectedDay, + setAppContentReady: s.setAppContentReady, })) ); @@ -126,13 +127,13 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) { }, [setSelectedDay]); const readyCalledRef = useRef(false); - // Signal Telegram to hide loading when the first load finishes (loading goes true -> false). + // Mark content ready when first load finishes or access denied, so page can call ready() and show content. useEffect(() => { - if (!loading && !readyCalledRef.current) { + if ((!loading || accessDenied) && !readyCalledRef.current) { readyCalledRef.current = true; - callMiniAppReadyOnce(); + setAppContentReady(true); } - }, [loading]); + }, [loading, accessDenied, setAppContentReady]); return (
diff --git a/webapp-next/src/components/current-duty/CurrentDutyView.tsx b/webapp-next/src/components/current-duty/CurrentDutyView.tsx index e2a4251..d8c8117 100644 --- a/webapp-next/src/components/current-duty/CurrentDutyView.tsx +++ b/webapp-next/src/components/current-duty/CurrentDutyView.tsx @@ -20,7 +20,6 @@ import { formatHHMM, } from "@/lib/date-utils"; import { getRemainingTime, findCurrentDuty } from "@/lib/current-duty"; -import { callMiniAppReadyOnce } from "@/lib/telegram-ready"; import { ContactLinks } from "@/components/contact/ContactLinks"; import { Button } from "@/components/ui/button"; import { @@ -47,6 +46,7 @@ type ViewState = "loading" | "error" | "ready"; export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyViewProps) { const { t } = useTranslation(); const lang = useAppStore((s) => s.lang); + const setAppContentReady = useAppStore((s) => s.setAppContentReady); const { initDataRaw } = useTelegramAuth(); const [state, setState] = useState("loading"); @@ -93,12 +93,12 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi return () => controller.abort(); }, [loadTodayDuties]); - // Signal Telegram to hide loading when this view is ready (or error). + // Mark content ready when data is loaded or error, so page can call ready() and show content. useEffect(() => { if (state !== "loading") { - callMiniAppReadyOnce(); + setAppContentReady(true); } - }, [state]); + }, [state, setAppContentReady]); // Auto-update remaining time every second when there is an active duty. useEffect(() => { diff --git a/webapp-next/src/store/app-store.ts b/webapp-next/src/store/app-store.ts index 0d401c7..3e65910 100644 --- a/webapp-next/src/store/app-store.ts +++ b/webapp-next/src/store/app-store.ts @@ -28,6 +28,8 @@ export interface AppState { accessDeniedDetail: string | null; currentView: CurrentView; selectedDay: string | null; + /** True when the first visible screen has finished loading; used to hide content until ready(). */ + appContentReady: boolean; setCurrentMonth: (d: Date) => void; nextMonth: () => void; @@ -41,8 +43,9 @@ export interface AppState { setLang: (v: "ru" | "en") => void; setCurrentView: (v: CurrentView) => void; setSelectedDay: (key: string | null) => void; + setAppContentReady: (v: boolean) => void; /** Batch multiple state updates into a single re-render. */ - batchUpdate: (partial: Partial>) => void; + batchUpdate: (partial: Partial>) => void; } const now = new Date(); @@ -67,6 +70,7 @@ export const useAppStore = create((set) => ({ accessDeniedDetail: null, currentView: getInitialView(), selectedDay: null, + appContentReady: false, setCurrentMonth: (d) => set({ currentMonth: d }), nextMonth: () => @@ -86,5 +90,6 @@ export const useAppStore = create((set) => ({ setLang: (v) => set({ lang: v }), setCurrentView: (v) => set({ currentView: v }), setSelectedDay: (key) => set({ selectedDay: key }), + setAppContentReady: (v) => set({ appContentReady: v }), batchUpdate: (partial) => set(partial), }));