feat: add webpush and sadhna tracker

This commit is contained in:
gosangam
2026-06-08 15:48:12 +05:30
parent ecb32d2e85
commit 0334e03c3c
52 changed files with 2118 additions and 545 deletions
+54
View File
@@ -91,6 +91,29 @@ export type SadhnaDashboard = {
// plus an in-progress fold for an unfinished today. Capped server-side;
// current_streak holds the true length.
vine_days: VineDay[];
// Rewards. `next_reward_date` is the projected UTC date (YYYY-MM-DD) of the
// next gift if the streak continues. `pending_reward` is a freshly earned
// gift to celebrate now (null when there's nothing to show).
next_reward_date: string | null;
pending_reward: SadhnaRewardGrant | null;
};
/** A gift item in the streak-reward catalog. */
export type SadhnaReward = {
id: string;
key: string;
name: string;
meaning: string;
image_url: string;
active: boolean;
sort: number;
};
/** A reward earned at a streak milestone (a multiple of 7 days). */
export type SadhnaRewardGrant = {
milestone: number;
granted_at: string;
reward: SadhnaReward;
};
// ─── API ───────────────────────────────────────────────────────────────
@@ -99,10 +122,41 @@ export const sadhna = {
/** Today's progress + streak + plant state. */
dashboard: () => call<SadhnaDashboard>("/sadhna/dashboard"),
/** Per-day state for an inclusive UTC date range (YYYY-MM-DD), for the
* calendar's week/month views. Range is bounded to a year server-side. */
history: (from: string, to: string) =>
call<SadhnaDay[]>(
`/sadhna/history?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`,
),
/** Save today's chanting round count; returns the recomputed dashboard. */
saveChant: (roundsCompleted: number) =>
call<SadhnaDashboard>("/sadhna/chant", {
method: "POST",
body: JSON.stringify({ rounds_completed: roundsCompleted }),
}),
/** The user's collected gifts (newest first) + giftable catalog size. */
rewards: () =>
call<{ collected: SadhnaRewardGrant[]; total: number }>("/sadhna/rewards"),
/** Acknowledge pending celebrations after showing the confetti. */
ackRewards: () => call<void>("/sadhna/rewards/ack", { method: "POST" }),
/** Whether daily sādhana reminders are enabled for the caller. */
reminderPrefs: () => call<{ enabled: boolean }>("/sadhna/reminders"),
/** Toggle daily sādhana reminders. */
setReminderPrefs: (enabled: boolean) =>
call<{ enabled: boolean }>("/sadhna/reminders", {
method: "PUT",
body: JSON.stringify({ enabled }),
}),
/** Report the browser's IANA timezone so reminders fire at local time. */
setTimezone: (timezone: string) =>
call<void>("/sadhna/timezone", {
method: "POST",
body: JSON.stringify({ timezone }),
}),
};