From da1cfd06d498c09201ff6bebd311dafd404da5d4 Mon Sep 17 00:00:00 2001 From: Kanika Singhal Date: Fri, 3 Jul 2026 08:01:00 +0530 Subject: [PATCH 01/11] feat(wellness): compact dashboard card + WellnessSheet drawer (section 7.7) - Replace dense 270-line inline card with compact score/weather/mood view - Add WellnessWeatherIcon SVG (sun/cloud/storm) driven by wellness.status - Add WellnessSheet bottom drawer: AI coach, driver attribution bar, signal progress bars, gamified Today's Focus, reset actions, mood check-in with contextual AI response, Care Plan CTA - Support resources moved to collapsible 'Talk to Someone' section inside sheet; phone numbers hidden behind a 'Call' button (not shown inline) - Mood pick now sets contextual in-card moodResponse message; 'stretched' auto-opens the sheet - Add state: wellnessSheetOpen, moodResponse, todayFocusDone - No backend changes needed (all data already in API response) --- .../src/routes/_authenticated/dashboard.tsx | 921 +++++++++++++++--- 1 file changed, 789 insertions(+), 132 deletions(-) diff --git a/frontend/src/routes/_authenticated/dashboard.tsx b/frontend/src/routes/_authenticated/dashboard.tsx index 187c194..6cc1da1 100644 --- a/frontend/src/routes/_authenticated/dashboard.tsx +++ b/frontend/src/routes/_authenticated/dashboard.tsx @@ -6,7 +6,9 @@ import { AppShell, MobileMenuButton } from "@/components/AppShell"; import { PlatformIcon } from "@/components/PlatformIcon"; import { Plus, ChevronRight, AlertTriangle, Users, Utensils, ShoppingBag, - Bus, Receipt, MoreHorizontal, Wallet, Timer, MessageSquare, Phone, Mail, MapPin, ExternalLink, Compass, TrendingDown + Bus, Receipt, MoreHorizontal, Wallet, Timer, MessageSquare, Phone, Mail, MapPin, ExternalLink, Compass, TrendingDown, + Sparkles, Flame, Coffee, GraduationCap, Check, ArrowRight, + Smile, Meh, Frown, HeartPulse, Wind, ShieldCheck, Loader2, Moon } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -46,7 +48,10 @@ import { getDashboardInsights, getCampusIntel, getWingFeed, - getWellnessInsights, + getWellnessCheckin, + getWellnessCoach, + getWellnessCarePlan, + submitWellnessCheckin, updateTransaction, getCatalog, addCatalogItem, @@ -58,6 +63,587 @@ import { } from "@/lib/api/db.functions"; +const RESET_ICONS: Record> = { + utensils: Utensils, + wallet: Wallet, + users: Users, + compass: Compass, + coffee: Coffee, +}; + +const MOODS = [ + { key: "good", label: "Good", Icon: Smile }, + { key: "okay", label: "Okay", Icon: Meh }, + { key: "stretched", label: "Stretched", Icon: Frown }, +] as const; + +// ── Guided box-breathing (4-4-4-4) — an interactive, calming reset ────────── +const BREATH_PHASES = [ + { key: "in", label: "Breathe in" }, + { key: "hold1", label: "Hold" }, + { key: "out", label: "Breathe out" }, + { key: "hold2", label: "Hold" }, +] as const; + +function BreathingExercise() { + const [active, setActive] = useState(false); + const [phaseIdx, setPhaseIdx] = useState(0); + const [count, setCount] = useState(4); + const [cycles, setCycles] = useState(0); + + useEffect(() => { + if (!active) return; + const t = setInterval(() => { + setCount((c) => { + if (c > 1) return c - 1; + setPhaseIdx((p) => { + const next = (p + 1) % BREATH_PHASES.length; + if (next === 0) setCycles((n) => n + 1); + return next; + }); + return 4; + }); + }, 1000); + return () => clearInterval(t); + }, [active]); + + const phase = BREATH_PHASES[phaseIdx]; + const scale = phase.key === "in" || phase.key === "hold1" ? 1 : 0.62; + + function toggle() { + if (active) { + setActive(false); + } else { + setPhaseIdx(0); + setCount(4); + setCycles(0); + setActive(true); + } + } + + return ( +
+
+
+
+

{active ? phase.label : "Ready?"}

+ {active &&

{count}

} +
+
+

+ {active ? `Cycle ${cycles + 1} · box breathing 4-4-4-4` : "A one-minute calm reset — follow the circle"} +

+ +
+ ); +} + +function WellnessCarePlanDialog({ + open, + onOpenChange, + plan, + loading, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; + plan: any; + loading: boolean; +}) { + const [done, setDone] = useState>(new Set()); + const toggleStep = (i: number) => + setDone((s) => { + const n = new Set(s); + n.has(i) ? n.delete(i) : n.add(i); + return n; + }); + + const tips = plan + ? [ + { Icon: Utensils, label: "Meals", text: plan.meal_tip }, + { Icon: Moon, label: "Rest", text: plan.rest_tip }, + { Icon: Wallet, label: "Money", text: plan.money_tip }, + ].filter((t) => t.text) + : []; + + return ( + + + + + + AI Care Plan + {plan?.source === "bedrock" && ( + + AI + + )} + +

+ + Supportive guidance from your own patterns — not medical advice. +

+
+ + {loading ? ( +
+ + + +
+ ) : plan ? ( +
+ {/* Affirmation */} +
+

{plan.affirmation}

+
+ + {/* Today's focus */} + {plan.focus && ( +
+

Today's focus

+

{plan.focus}

+
+ )} + + {/* Interactive micro-steps */} + {plan.steps?.length > 0 && ( +
+

Three small steps

+
+ {plan.steps.map((s: string, i: number) => ( + + ))} +
+
+ )} + + {/* Tips */} + {tips.length > 0 && ( +
+ {tips.map((t, i) => ( +
+
+ +
+
+

{t.label}

+

{t.text}

+
+
+ ))} +
+ )} + + {/* Guided breathing */} +
+

+ Quick calm +

+ +
+ + {/* Talk to someone */} + {plan.show_support && ( +
+
+ +
+
+

Tele-MANAS · free & confidential

+

24x7 national student mental-health support

+
+ + 14416 + +
+ )} +
+ ) : ( +

Couldn't load your plan. Please try again.

+ )} +
+
+ ); +} + +// ── Wellness Weather Icon ───────────────────────────────────────────────── +function WellnessWeatherIcon({ weather, size = 32 }: { weather: string; size?: number }) { + if (weather === "stormy") return ( + + + + + ); + if (weather === "cloudy") return ( + + + + + + ); + return ( + + + + + ); +} + +// ── Support Resources Panel (expandable, inside Wellness Sheet only) ─────── +function SupportResourcesPanel({ + resources, redCheckinText, setRedCheckinText, redCheckinSubmitting, handleRedCheckinSubmit, +}: { + resources: any[]; + redCheckinText: string; + setRedCheckinText: (v: string) => void; + redCheckinSubmitting: boolean; + handleRedCheckinSubmit: (e: any) => void; +}) { + const [expanded, setExpanded] = useState(false); + return ( +
+
+

Share How You're Feeling

+