"use client"; /** * /sadhna-streak — the devotee's daily sādhana home. * * • a thin 7-day streak strip across the top * • 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 * • a plant that grows from a seedling toward a tree as the streak holds, * and withers back through its stages (eventually dying) when it breaks * * 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 { BookOpenText, Check, Flame, BadgePlus } from "lucide-react"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; import { sadhna, type SadhnaDashboard, type SadhnaDay } from "@/lib/sadhna"; import { SadhnaVine } from "@/components/sadhna/sadhna-vine"; 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"); // 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")); }, []); // Load once we have a user, and again whenever the tab regains focus. useEffect(() => { if (!user) return; load(); const onFocus = () => load(); window.addEventListener("focus", onFocus); return () => window.removeEventListener("focus", onFocus); }, [user, load]); if (loading || !user) { return (

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

); } return (
{/* Streak strip */}

Daily Sādhana

{vineCaption(dash)}

{/* Action items */}
{/* The creeper — climbs the pole, one fold per day of the streak */}
); } // ─── Streak strip ──────────────────────────────────────────────────────── function StreakStrip({ dash, loading, }: { dash: SadhnaDashboard | null; loading: boolean; }) { const week = dash?.week ?? placeholderWeek(); const streak = dash?.current_streak ?? 0; return (
0 ? "text-orange-500" : "text-muted-foreground", )} /> {streak} day{streak === 1 ? "" : "s"}
{week.map((day, i) => ( ))}
); } function DayDot({ day, isToday }: { day: SadhnaDay; isToday: boolean }) { const partial = !day.completed && (day.chant_done || day.quote_done); return (
{day.completed ? : null}
{weekdayLetter(day.date)}
); } // ─── 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} cta={rounds > 0 ? "Continue" : "Begin"} /> ); } function QuoteAction({ dash }: { dash: SadhnaDashboard | null }) { const done = dash?.quote_done ?? false; return ( } title="Post a quote from the Vāṇī" subtitle={ done ? "Shared today — thank you" : "Select a passage while reading, then Share to Feed" } done={done} cta="Find a verse" /> ); } function ActionCard({ href, icon, title, subtitle, done, cta, }: { href: string; icon: React.ReactNode; title: string; subtitle: string; done: boolean; cta: string; }) { return (
{done ? : icon} {done ? ( Done ) : ( {cta} → )}

{title}

{subtitle}

); } // ─── Vine panel ─────────────────────────────────────────────────────── function VinePanel({ dash, error, }: { dash: SadhnaDashboard | null; error: boolean; }) { return (
{error ? (

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

) : dash ? ( ) : (

Loading your vine…

)}
); } // ─── Copy + helpers ──────────────────────────────────────────────────── function vineCaption(dash: SadhnaDashboard | null): string { if (!dash) return "Two simple offerings each day. Keep them up and watch it climb."; if (dash.current_streak === 0 && !dash.alive) { return "Begin today, and a new vine starts climbing the pole."; } if (dash.today_completed) { return "Today's fold is complete. The vine climbs higher."; } if (dash.chant_done || dash.quote_done) { return "One offering done — finish the other to complete today's fold."; } return "Each day's sādhana winds another fold around the pole."; } /** Single-letter weekday (UTC, matching the backend's day buckets). */ function weekdayLetter(date: string): string { const d = new Date(`${date}T00:00:00Z`); if (Number.isNaN(d.getTime())) return ""; return ["S", "M", "T", "W", "T", "F", "S"][d.getUTCDay()]; } /** A blank 7-day strip for the initial render before data arrives. */ function placeholderWeek(): SadhnaDay[] { const out: SadhnaDay[] = []; const now = new Date(); const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()); for (let i = 6; i >= 0; i--) { const d = new Date(today - i * 86400000); out.push({ date: d.toISOString().slice(0, 10), chant_done: false, quote_done: false, completed: false, }); } return out; }