diff --git a/src/app/page.tsx b/src/app/page.tsx index a003fb8..2370b5a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -11,12 +11,16 @@ import { MatchmakingScreen } from "@/components/screens/MatchmakingScreen"; import { LeaderboardScreen } from "@/components/screens/LeaderboardScreen"; import { ShopScreen } from "@/components/screens/ShopScreen"; import { ChatScreen } from "@/components/screens/ChatScreen"; +import { NotificationsScreen } from "@/components/screens/NotificationsScreen"; import { AuthScreen } from "@/components/screens/AuthScreen"; import { DailyRewardModal } from "@/components/online/DailyRewardModal"; +import { NotificationToaster } from "@/components/online/NotificationToaster"; import { CapacitorBack } from "@/components/CapacitorBack"; import { useSessionStore } from "@/lib/session-store"; import { useGameStore } from "@/lib/game-store"; import { useOnlineStore } from "@/lib/online-store"; +import { useNotifStore, pushNotification } from "@/lib/notification-store"; +import { getService } from "@/lib/online/service"; import { screenFromHash, useUIStore, type Screen } from "@/lib/ui-store"; /** Transient screens can't be restored without their state — fall back to home. */ @@ -43,6 +47,20 @@ export default function Page() { useEffect(() => { init(); useUIStore.getState().initHistory(); + 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(() => {}); const onPop = (e: PopStateEvent) => { const raw = ((e.state?.screen as Screen) ?? screenFromHash()); @@ -56,6 +74,7 @@ export default function Page() { <> {renderScreen(screen)} + {loading && null} @@ -84,6 +103,8 @@ function renderScreen(screen: string) { return ; case "chat": return ; + case "notifications": + return ; default: return ; } diff --git a/src/components/online/NotificationToaster.tsx b/src/components/online/NotificationToaster.tsx new file mode 100644 index 0000000..23983f0 --- /dev/null +++ b/src/components/online/NotificationToaster.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { AnimatePresence, motion } from "framer-motion"; +import { useEffect } from "react"; +import { useNotifStore } from "@/lib/notification-store"; +import { useI18n } from "@/lib/i18n"; + +export function NotificationToaster() { + const toast = useNotifStore((s) => s.lastToast); + const dismiss = useNotifStore((s) => s.dismissToast); + const { locale } = useI18n(); + + useEffect(() => { + if (!toast) return; + const id = setTimeout(dismiss, 4000); + return () => clearTimeout(id); + }, [toast, dismiss]); + + return ( + + {toast && ( + +
+ {toast.icon} +
+
+ {locale === "fa" ? toast.titleFa : toast.titleEn} +
+ {(toast.bodyFa || toast.bodyEn) && ( +
+ {locale === "fa" ? toast.bodyFa : toast.bodyEn} +
+ )} +
+
+
+ )} +
+ ); +} diff --git a/src/components/online/TopBar.tsx b/src/components/online/TopBar.tsx index f7b506d..f831acc 100644 --- a/src/components/online/TopBar.tsx +++ b/src/components/online/TopBar.tsx @@ -1,8 +1,9 @@ "use client"; -import { Coins, Crown, Gift } from "lucide-react"; +import { Bell, Coins, Crown, Gift } from "lucide-react"; import { useSessionStore } from "@/lib/session-store"; import { useUIStore } from "@/lib/ui-store"; +import { useNotifStore } from "@/lib/notification-store"; import { useI18n } from "@/lib/i18n"; import { Avatar } from "./Avatar"; @@ -10,6 +11,7 @@ export function TopBar() { const profile = useSessionStore((s) => s.profile); const go = useUIStore((s) => s.go); const openDaily = useUIStore((s) => s.openDaily); + const unread = useNotifStore((s) => s.unread); const { t } = useI18n(); if (!profile) return null; @@ -34,6 +36,18 @@ export function TopBar() {
+