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),
}));