"use client"; /** * /sadhna-streak/chant — the japa counter. * * Shows the full opening sequence — Śrīla Prabhupāda Praṇāma (×1), * Pañca-tattva Praṇāma (×1), then the Hare Kṛṣṇa mahā-mantra (×108) — with a * big tap-to-count round counter running toward the devotee's daily target * (profile's daily_chanting_rounds, falling back to 16). Progress saves * automatically (debounced); reaching the target marks today's chanting * complete. The mantra script toggles between English (IAST) and Sanskrit * (Devanāgarī), and Śrīla Prabhupāda's recorded japa can be played alongside. * * Auth-gated like the rest of /sadhna-streak; seeds from today's saved * progress so a devotee can chant in sittings. */ import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { ArrowLeft, Check, Loader2, Minus, Pause, Play, RotateCcw, } from "lucide-react"; import { toast } from "sonner"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; import { useAudioPlayer, type AudioTrack } from "@/components/audio-player"; import { sadhna } from "@/lib/sadhna"; import { cn } from "@/lib/utils"; type Lang = "en" | "sa"; const FALLBACK_TARGET = 16; const LANG_KEY = "sadhna.mantra.lang"; // Śrīla Prabhupāda's recorded japa — played through the global player so it // survives navigation and pauses any other audio. const JAPA_TRACK: AudioTrack = { url: "https://s3.prabhupadasays.com/sp-audio/Japa/Srila%20Prabhupada%20-%20Chanting%20Japa.mp3", title: "Śrīla Prabhupāda — Japa", subtitle: "Chanting the Hare Kṛṣṇa mahā-mantra", type: "kirtan", }; // The opening praṇāma mantras, recited once before the rounds of japa. const PRELUDE: { title: string; times: string; en: string[]; sa: string[] }[] = [ { title: "Śrīla Prabhupāda Praṇāma", times: "×1", en: [ "nama oṁ viṣṇu-pādāya kṛṣṇa-preṣṭhāya bhū-tale", "śrīmate bhaktivedānta-svāmin iti nāmine", "namas te sārasvate deve gaura-vāṇī-pracāriṇe", "nirviśeṣa-śūnyavādi-pāścātya-deśa-tāriṇe", ], sa: [ "नम ॐ विष्णु-पादाय कृष्ण-प्रेष्ठाय भूतले", "श्रीमते भक्तिवेदांत-स्वामिन् इति नामिने ।", "नमस्ते सारस्वते देवे गौर-वाणी-प्रचारिणे", "निर्विशेष-शून्यवादि-पाश्चात्य-देश-तारिणे ॥", ], }, { title: "Pañca-tattva Praṇāma", times: "×1", en: [ "(jaya) śrī-kṛṣṇa-caitanya prabhu-nityānanda", "śrī-advaita gadādhara śrīvāsādi-gaura-bhakta-vṛnda", ], sa: [ "जय श्री कृष्ण-चैतन्य प्रभु-नित्यानंद", "श्री-अद्वैत गदाधर श्रीवास आदि गौर-भक्त-वृंद ।", ], }, ]; const MAHAMANTRA: Record = { en: [ "Hare Kṛṣṇa Hare Kṛṣṇa", "Kṛṣṇa Kṛṣṇa Hare Hare", "Hare Rāma Hare Rāma", "Rāma Rāma Hare Hare", ], sa: [ "हरे कृष्ण हरे कृष्ण", "कृष्ण कृष्ण हरे हरे ।", "हरे राम हरे राम", "राम राम हरे हरे ॥", ], }; export default function ChantPage() { const { user, loading } = useAuth(); const { requireAuth } = useAuthGate(); const router = useRouter(); const [target, setTarget] = useState(FALLBACK_TARGET); const [count, setCount] = useState(0); const [state, setState] = useState<"loading" | "ready" | "error">("loading"); const [saveStatus, setSaveStatus] = useState< "idle" | "saving" | "saved" | "error" >("idle"); // Lazy initializer reads the saved script preference. Guarded for SSR; the // mantra UI only renders client-side (after auth resolves), so this can't // cause a hydration mismatch. const [lang, setLang] = useState(() => { if (typeof window === "undefined") return "en"; const stored = window.localStorage.getItem(LANG_KEY); return stored === "sa" || stored === "en" ? stored : "en"; }); const { currentTrack, isPlaying, play, togglePlayPause } = useAudioPlayer(); const japaActive = currentTrack?.url === JAPA_TRACK.url; const japaPlaying = japaActive && isPlaying; const savedRef = useRef(0); // last count persisted to the server const completedRef = useRef(false); // so we toast completion only once // Auth gate. 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]); // Seed from today's saved progress. useEffect(() => { if (!user) return; let cancelled = false; sadhna .dashboard() .then((d) => { if (cancelled) return; setTarget(d.target_rounds || FALLBACK_TARGET); setCount(d.rounds_completed || 0); savedRef.current = d.rounds_completed || 0; completedRef.current = !!d.chant_done; setState("ready"); }) .catch(() => !cancelled && setState("error")); return () => { cancelled = true; }; }, [user]); const persist = useCallback(async (value: number) => { setSaveStatus("saving"); try { const d = await sadhna.saveChant(value); savedRef.current = value; setTarget(d.target_rounds || FALLBACK_TARGET); setSaveStatus("saved"); if (d.chant_done && !completedRef.current) { completedRef.current = true; toast.success("Today's chanting is complete 🙏"); } if (!d.chant_done) completedRef.current = false; } catch { setSaveStatus("error"); } }, []); // Auto-save: debounce a save whenever the count drifts from what's stored. useEffect(() => { if (state !== "ready" || count === savedRef.current) return; const t = window.setTimeout(() => void persist(count), 700); return () => window.clearTimeout(t); }, [count, state, persist]); const inc = useCallback( () => setCount((c) => Math.min(target, c + 1)), [target], ); const dec = useCallback(() => setCount((c) => Math.max(0, c - 1)), []); const reset = useCallback(() => setCount(0), []); const chooseLang = useCallback((l: Lang) => { setLang(l); window.localStorage.setItem(LANG_KEY, l); }, []); const toggleAudio = useCallback(() => { if (japaActive) togglePlayPause(); else play(JAPA_TRACK); }, [japaActive, togglePlayPause, play]); if (loading || !user) { return (

{loading ? "Loading…" : "Sign in to chant."}

); } const complete = count >= target; const pct = target > 0 ? Math.min(100, (count / target) * 100) : 0; return (
Sādhana
{/* Script toggle + japa audio */}
{(["en", "sa"] as Lang[]).map((l) => ( ))}
{/* Opening praṇāma mantras */}

Before you begin

{PRELUDE.map((m) => (

{m.title}

{m.times}
{m[lang].map((line, i) => (

{line}

))}
))}
{/* Mahā-mantra */}

The Mahā-mantra

×108
{MAHAMANTRA[lang].map((line, i) => (

{line}

))}
{/* Big tap-to-count dial */}
{/* Auto-save status */}
{state === "error" ? ( Could not load today's progress. ) : saveStatus === "saving" ? ( <> Saving… ) : saveStatus === "error" ? ( Couldn't save — keep chanting, we'll retry. ) : saveStatus === "saved" ? ( <> Progress saved ) : ( One round is 108 names on the mālā )}
); }