"use client"; /** * /sadhna-streak — the devotee's daily sādhana home. * * • a streak calendar with week/month views and back/forward paging; each * day shows its state, today highlighted, and a lotus blooms beneath * every day the sādhana was completed * • the current/best streak counter * • two daily disciplines as action cards: finish today's chanting, and * post at least one quote from the Vāṇī — each flips to a "done" state * once complete * * Auth-gated like /notifications and /u/me. Re-fetches on focus so a quote * posted elsewhere (posting is selection-driven while reading) reflects * here when the devotee returns. */ import { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; import { BookOpenText, Check } from "lucide-react"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; import { sadhna, type SadhnaDashboard, type SadhnaRewardGrant } from "@/lib/sadhna"; import { StreakCalendar } from "@/components/sadhna/streak-calendar"; import { RewardsGallery } from "@/components/sadhna/rewards-gallery"; import { RewardCelebration } from "@/components/sadhna/reward-celebration"; import { cn } from "@/lib/utils"; export default function SadhnaStreakPage() { const { user, loading } = useAuth(); const { requireAuth } = useAuthGate(); const router = useRouter(); const [dash, setDash] = useState(null); const [state, setState] = useState<"loading" | "ready" | "error">("loading"); const [rewards, setRewards] = useState<{ collected: SadhnaRewardGrant[]; total: number; } | null>(null); // Guards the celebration from re-opening between dismiss and the dashboard // refetch (and lets a *new* milestone's gift still celebrate). const [ackedMilestone, setAckedMilestone] = useState(null); // Auth gate — same pattern as /notifications. useEffect(() => { if (loading || user) return; let cancelled = false; (async () => { const ok = await requireAuth(); if (!ok && !cancelled) router.replace("/"); })(); return () => { cancelled = true; }; }, [loading, user, requireAuth, router]); const load = useCallback(() => { sadhna .dashboard() .then((d) => { setDash(d); setState("ready"); }) .catch(() => setState("error")); }, []); const loadRewards = useCallback(() => { sadhna .rewards() .then(setRewards) .catch(() => { /* gallery is non-critical */ }); }, []); // Load once we have a user, and again whenever the tab regains focus. useEffect(() => { if (!user) return; load(); loadRewards(); const onFocus = () => { load(); loadRewards(); }; window.addEventListener("focus", onFocus); return () => window.removeEventListener("focus", onFocus); }, [user, load, loadRewards]); // A freshly earned, not-yet-celebrated gift to show confetti for. const pending = dash?.pending_reward ?? null; const showCelebration = !!pending && pending.milestone !== ackedMilestone; const closeCelebration = useCallback(async () => { if (pending) setAckedMilestone(pending.milestone); try { await sadhna.ackRewards(); } catch { /* best-effort */ } load(); // clears pending_reward loadRewards(); // the new gift now appears in the gallery }, [pending, load, loadRewards]); if (loading || !user) { return (

{loading ? "Loading…" : "Sign in to track your sādhana."}

); } return (

Daily Sādhana

{caption(dash)}

{state === "error" ? (

Could not load your sādhana. Please try again.

) : ( )} {/* Action items */}
{/* Collected gifts */} {/* Celebration when a gift is freshly earned */} {showCelebration && pending ? ( ) : null}
); } // ─── Action cards ────────────────────────────────────────────────────── function ChantAction({ dash }: { dash: SadhnaDashboard | null }) { const done = dash?.chant_done ?? false; const target = dash?.target_rounds ?? 16; const rounds = dash?.rounds_completed ?? 0; return ( } title="Finish today's chanting" subtitle={ done ? `${rounds} rounds chanted` : `${rounds} / ${target} rounds` } done={done} /> ); } function QuoteAction({ dash }: { dash: SadhnaDashboard | null }) { const done = dash?.quote_done ?? false; return ( } title="Post a quote" subtitle={ done ? "Shared today — thank you" : "Select a passage while reading, then Share to Feed" } done={done} /> ); } function ActionCard({ href, icon, title, subtitle, done, }: { href: string; icon: React.ReactNode; title: string; subtitle: string; done: boolean; }) { return ( {done ? : icon}

{title}

{subtitle}

); } // ─── Copy ────────────────────────────────────────────────────────────── function caption(dash: SadhnaDashboard | null): string { if (!dash) return "Two simple offerings each day — a lotus for every one."; if (dash.today_completed) return "Today's sādhana is complete — a lotus blooms."; if (dash.chant_done || dash.quote_done) { return "One offering done. Finish the other to complete today."; } return "Chant your rounds and post a quote to complete today."; }