From 89391279bb7f63a10cbaf4fb422148839735fef0 Mon Sep 17 00:00:00 2001 From: gosangam Date: Sat, 11 Jul 2026 00:31:37 +0530 Subject: [PATCH] feat: add daily vani --- app/(app)/page.tsx | 4 +- components/vani/vani-of-the-day-cta.tsx | 62 +++++++++++++++++++++++++ lib/vani.ts | 33 +++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 components/vani/vani-of-the-day-cta.tsx create mode 100644 lib/vani.ts diff --git a/app/(app)/page.tsx b/app/(app)/page.tsx index 0dafa4be..1857efc5 100644 --- a/app/(app)/page.tsx +++ b/app/(app)/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { Compass, Loader2, LogIn, Sparkles } from "lucide-react"; import { useAuth } from "@/components/auth/auth-provider"; import { SadhnaStreakCTA } from "@/components/sadhna/streak-cta"; +import { VaniOfTheDayCTA } from "@/components/vani/vani-of-the-day-cta"; import { HomeFeed } from "@/components/feed/home-feed"; /** @@ -32,8 +33,9 @@ export default function HomePage() { // mobile this max-width is wider than the viewport, so it's full-bleed // as before.
-
+
+
diff --git a/components/vani/vani-of-the-day-cta.tsx b/components/vani/vani-of-the-day-cta.tsx new file mode 100644 index 00000000..5378a6a6 --- /dev/null +++ b/components/vani/vani-of-the-day-cta.tsx @@ -0,0 +1,62 @@ +"use client"; + +/** + * VaniOfTheDayCTA — a home-page card suggesting the day's "Vāṇī of the Day": + * one prose chapter, verse, or lecture chosen by the backend (the same for + * everyone that day, never repeating within 90 days). Tapping it opens the + * item. Renders nothing until/unless the pick loads, so it never leaves an + * empty frame. Styled to match SadhnaStreakCTA. + */ + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { ChevronRight, Quote } from "lucide-react"; +import { getVaniOfTheDay, type Vani } from "@/lib/vani"; + +export function VaniOfTheDayCTA() { + const [vani, setVani] = useState(null); + + useEffect(() => { + let cancelled = false; + getVaniOfTheDay() + .then((v) => { + if (!cancelled) setVani(v); + }) + .catch(() => { + /* card simply doesn't render */ + }); + return () => { + cancelled = true; + }; + }, []); + + if (!vani) return null; + + return ( + + + + +
+

+ Vāṇī of the day + + {vani.label} + +

+

+ {vani.title} +

+ {vani.subtitle ? ( +

+ {vani.subtitle} +

+ ) : null} +
+ + + ); +} diff --git a/lib/vani.ts b/lib/vani.ts new file mode 100644 index 00000000..9c5fb838 --- /dev/null +++ b/lib/vani.ts @@ -0,0 +1,33 @@ +/** + * Client for "Vāṇī of the Day" — the home page's daily content pick. + * + * Public endpoint (no auth): the BFF returns the same pick to everyone for the + * current day, chosen from a prose chapter, verse, or transcript and not + * repeated within 90 days. + */ + +const BFF_BASE = process.env.NEXT_PUBLIC_BFF_URL || "http://localhost:8080"; + +export type Vani = { + kind: "verse" | "prose" | "transcript"; + label: string; // "Verse" | "Chapter" | "Lecture" | "Conversation" + title: string; + subtitle: string; + href: string; + book_title?: string; +}; + +type Envelope = { success?: boolean; data?: T }; + +export async function getVaniOfTheDay(): Promise { + try { + const res = await fetch(`${BFF_BASE}/api/v1/vani/today`, { + headers: { Accept: "application/json" }, + }); + if (!res.ok) return null; + const body = (await res.json().catch(() => ({}))) as Envelope; + return body.data ?? null; + } catch { + return null; + } +}