2026-06-04 10:11:00 +03:30
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect } from "react";
|
|
|
|
|
import { HomeScreen } from "@/components/HomeScreen";
|
|
|
|
|
import { GameScreen } from "@/components/screens/GameScreen";
|
|
|
|
|
import { ProfileScreen } from "@/components/screens/ProfileScreen";
|
|
|
|
|
import { FriendsScreen } from "@/components/screens/FriendsScreen";
|
|
|
|
|
import { OnlineLobbyScreen } from "@/components/screens/OnlineLobbyScreen";
|
|
|
|
|
import { RoomScreen } from "@/components/screens/RoomScreen";
|
|
|
|
|
import { MatchmakingScreen } from "@/components/screens/MatchmakingScreen";
|
|
|
|
|
import { LeaderboardScreen } from "@/components/screens/LeaderboardScreen";
|
|
|
|
|
import { ShopScreen } from "@/components/screens/ShopScreen";
|
2026-06-04 16:28:59 +03:30
|
|
|
import { BuyCoinsScreen } from "@/components/screens/BuyCoinsScreen";
|
2026-06-04 10:21:44 +03:30
|
|
|
import { ChatScreen } from "@/components/screens/ChatScreen";
|
2026-06-04 15:52:06 +03:30
|
|
|
import { NotificationsScreen } from "@/components/screens/NotificationsScreen";
|
2026-06-04 10:11:00 +03:30
|
|
|
import { AuthScreen } from "@/components/screens/AuthScreen";
|
|
|
|
|
import { DailyRewardModal } from "@/components/online/DailyRewardModal";
|
2026-06-04 15:52:06 +03:30
|
|
|
import { NotificationToaster } from "@/components/online/NotificationToaster";
|
2026-06-04 20:58:05 +03:30
|
|
|
import { ResumeGameBar } from "@/components/online/ResumeGameBar";
|
2026-06-04 14:31:14 +03:30
|
|
|
import { CapacitorBack } from "@/components/CapacitorBack";
|
2026-06-04 10:11:00 +03:30
|
|
|
import { useSessionStore } from "@/lib/session-store";
|
2026-06-04 12:56:14 +03:30
|
|
|
import { useGameStore } from "@/lib/game-store";
|
|
|
|
|
import { useOnlineStore } from "@/lib/online-store";
|
2026-06-04 15:52:06 +03:30
|
|
|
import { useNotifStore, pushNotification } from "@/lib/notification-store";
|
|
|
|
|
import { getService } from "@/lib/online/service";
|
2026-06-04 12:56:14 +03:30
|
|
|
import { screenFromHash, useUIStore, type Screen } from "@/lib/ui-store";
|
|
|
|
|
|
|
|
|
|
/** Transient screens can't be restored without their state — fall back to home. */
|
|
|
|
|
function resolveScreen(s: Screen): Screen {
|
|
|
|
|
switch (s) {
|
|
|
|
|
case "game":
|
|
|
|
|
return useGameStore.getState().started ? s : "home";
|
|
|
|
|
case "room":
|
|
|
|
|
return useOnlineStore.getState().room ? s : "home";
|
|
|
|
|
case "chat":
|
|
|
|
|
return useOnlineStore.getState().activeChatFriend ? s : "home";
|
|
|
|
|
case "matchmaking":
|
|
|
|
|
return useOnlineStore.getState().matchmaking.phase !== "idle" ? s : "home";
|
|
|
|
|
default:
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-04 10:11:00 +03:30
|
|
|
|
|
|
|
|
export default function Page() {
|
|
|
|
|
const screen = useUIStore((s) => s.screen);
|
|
|
|
|
const init = useSessionStore((s) => s.init);
|
|
|
|
|
const loading = useSessionStore((s) => s.loading);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
init();
|
2026-06-04 17:59:30 +03:30
|
|
|
|
|
|
|
|
// ZarinPal payment return (?pay=success&coins= / ?pay=failed)
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
const pay = params.get("pay");
|
|
|
|
|
if (pay) {
|
|
|
|
|
if (pay === "success") {
|
|
|
|
|
const coins = params.get("coins");
|
|
|
|
|
pushNotification({
|
|
|
|
|
kind: "system",
|
|
|
|
|
titleFa: "پرداخت موفق",
|
|
|
|
|
titleEn: "Payment successful",
|
|
|
|
|
bodyFa: coins ? `${coins} سکه به حساب شما اضافه شد` : undefined,
|
|
|
|
|
bodyEn: coins ? `${coins} coins added` : undefined,
|
|
|
|
|
icon: "💰",
|
|
|
|
|
});
|
|
|
|
|
useSessionStore.getState().refreshProfile();
|
|
|
|
|
} else {
|
|
|
|
|
pushNotification({ kind: "system", titleFa: "پرداخت ناموفق بود", titleEn: "Payment failed", icon: "⚠️" });
|
|
|
|
|
}
|
|
|
|
|
window.history.replaceState({}, "", window.location.pathname);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 12:56:14 +03:30
|
|
|
useUIStore.getState().initHistory();
|
2026-06-04 15:52:06 +03:30
|
|
|
useNotifStore.getState().init();
|
|
|
|
|
// surface a daily-reward notification if it's available
|
|
|
|
|
getService()
|
|
|
|
|
.getDailyState()
|
|
|
|
|
.then((d) => {
|
|
|
|
|
if (d.available)
|
|
|
|
|
pushNotification({
|
|
|
|
|
kind: "daily",
|
|
|
|
|
titleFa: "پاداش روزانه آماده است",
|
|
|
|
|
titleEn: "Daily reward is ready",
|
|
|
|
|
icon: "🎁",
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {});
|
2026-06-04 12:56:14 +03:30
|
|
|
|
2026-06-04 20:58:05 +03:30
|
|
|
// Resume an in-progress server match after a full reload: the server
|
|
|
|
|
// re-broadcasts state on (re)connect — if we're not already in a game and
|
|
|
|
|
// not mid-matchmaking, re-enter live mode (minimized) so the resume pill shows.
|
|
|
|
|
const svc = getService();
|
|
|
|
|
let resumeUnsub: (() => void) | undefined;
|
|
|
|
|
let rewardUnsub: (() => void) | undefined;
|
|
|
|
|
if (svc.live) {
|
|
|
|
|
resumeUnsub = svc.onState((s) => {
|
|
|
|
|
const gs = useGameStore.getState();
|
|
|
|
|
const scr = useUIStore.getState().screen;
|
|
|
|
|
if (
|
|
|
|
|
!gs.started &&
|
|
|
|
|
scr !== "matchmaking" &&
|
|
|
|
|
scr !== "game" &&
|
|
|
|
|
s.matchWinner == null &&
|
|
|
|
|
s.phase !== "match-over"
|
|
|
|
|
) {
|
|
|
|
|
gs.enterServerMatch(svc);
|
|
|
|
|
gs.applyServerState(s);
|
|
|
|
|
gs.minimize();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Nudge the player when a match they left finishes while they're away.
|
|
|
|
|
rewardUnsub = svc.onReward(() => {
|
|
|
|
|
if (useUIStore.getState().screen !== "game")
|
|
|
|
|
pushNotification({
|
|
|
|
|
kind: "system",
|
|
|
|
|
titleFa: "بازی به پایان رسید",
|
|
|
|
|
titleEn: "Match ended",
|
|
|
|
|
bodyFa: "نتیجه و جایزه را ببینید",
|
|
|
|
|
bodyEn: "See the result and reward",
|
|
|
|
|
icon: "🏆",
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 12:56:14 +03:30
|
|
|
const onPop = (e: PopStateEvent) => {
|
|
|
|
|
const raw = ((e.state?.screen as Screen) ?? screenFromHash());
|
|
|
|
|
useUIStore.getState().syncFromPop(resolveScreen(raw));
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener("popstate", onPop);
|
2026-06-04 20:58:05 +03:30
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("popstate", onPop);
|
|
|
|
|
resumeUnsub?.();
|
|
|
|
|
rewardUnsub?.();
|
|
|
|
|
};
|
2026-06-04 10:11:00 +03:30
|
|
|
}, [init]);
|
2026-06-04 06:09:11 +03:30
|
|
|
|
|
|
|
|
return (
|
2026-06-04 10:11:00 +03:30
|
|
|
<>
|
|
|
|
|
{renderScreen(screen)}
|
|
|
|
|
<DailyRewardModal />
|
2026-06-04 15:52:06 +03:30
|
|
|
<NotificationToaster />
|
2026-06-04 20:58:05 +03:30
|
|
|
<ResumeGameBar />
|
2026-06-04 14:31:14 +03:30
|
|
|
<CapacitorBack />
|
2026-06-04 10:11:00 +03:30
|
|
|
{loading && null}
|
|
|
|
|
</>
|
2026-06-04 06:09:11 +03:30
|
|
|
);
|
|
|
|
|
}
|
2026-06-04 10:11:00 +03:30
|
|
|
|
|
|
|
|
function renderScreen(screen: string) {
|
|
|
|
|
switch (screen) {
|
|
|
|
|
case "game":
|
|
|
|
|
return <GameScreen />;
|
|
|
|
|
case "auth":
|
|
|
|
|
return <AuthScreen />;
|
|
|
|
|
case "profile":
|
|
|
|
|
return <ProfileScreen />;
|
|
|
|
|
case "friends":
|
|
|
|
|
return <FriendsScreen />;
|
|
|
|
|
case "online":
|
|
|
|
|
return <OnlineLobbyScreen />;
|
|
|
|
|
case "room":
|
|
|
|
|
return <RoomScreen />;
|
|
|
|
|
case "matchmaking":
|
|
|
|
|
return <MatchmakingScreen />;
|
|
|
|
|
case "leaderboard":
|
|
|
|
|
return <LeaderboardScreen />;
|
|
|
|
|
case "shop":
|
|
|
|
|
return <ShopScreen />;
|
2026-06-04 16:28:59 +03:30
|
|
|
case "buycoins":
|
|
|
|
|
return <BuyCoinsScreen />;
|
2026-06-04 10:21:44 +03:30
|
|
|
case "chat":
|
|
|
|
|
return <ChatScreen />;
|
2026-06-04 15:52:06 +03:30
|
|
|
case "notifications":
|
|
|
|
|
return <NotificationsScreen />;
|
2026-06-04 10:11:00 +03:30
|
|
|
default:
|
|
|
|
|
return <HomeScreen />;
|
|
|
|
|
}
|
|
|
|
|
}
|