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() {
+