From 065250c1cfb789ad906d6af36a19bd23171d1cf0 Mon Sep 17 00:00:00 2001 From: Gemini Agent Date: Sun, 1 Mar 2026 07:06:58 +0000 Subject: [PATCH] Redesign: Warm Sanctuary aesthetic for core pages - Implement cohesive 'Warm Sanctuary' design system - Add Playfair Display + Source Sans 3 typography - Create paper texture background and warm color palette - Redesign Today Dashboard with elegant cards and animations - Redesign Medication Form with step-by-step visual flow - Redesign Emergency Card with clear visual hierarchy - Redesign Onboarding with floating blobs and welcoming feel - Update Tailwind config with new colors, shadows, and animations --- src/app/(app)/emergency/page.tsx | 79 ++- src/app/(app)/today/page.tsx | 237 +++++---- src/app/globals.css | 229 +++++++-- src/app/layout.tsx | 10 +- src/app/onboarding/page.tsx | 226 +++++--- src/components/emergency/EmergencyCard.tsx | 192 ++++--- src/components/medications/MedicationForm.tsx | 485 ++++++++++++------ tailwind.config.ts | 172 +++++-- 8 files changed, 1078 insertions(+), 552 deletions(-) diff --git a/src/app/(app)/emergency/page.tsx b/src/app/(app)/emergency/page.tsx index d6ab771..ff636b0 100644 --- a/src/app/(app)/emergency/page.tsx +++ b/src/app/(app)/emergency/page.tsx @@ -1,18 +1,23 @@ 'use client' import { useEffect, useState } from 'react' -import { ArrowLeft, Edit2 } from 'lucide-react' +import { ArrowLeft, Edit2, Heart } from 'lucide-react' import { useRouter } from 'next/navigation' import { useLiveQuery } from 'dexie-react-hooks' import { db } from '@/lib/sync' import { EmergencyCard } from '@/components/emergency/EmergencyCard' -import { Button, LoadingState } from '@/components/ui' +import { LoadingState } from '@/components/ui' import { useApp } from '../provider' export default function EmergencyPage() { const router = useRouter() const { currentWorkspace } = useApp() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) // Fetch workspace from IndexedDB for offline access const workspace = useLiveQuery( @@ -32,7 +37,11 @@ export default function EmergencyPage() { ) if (!workspace) { - return + return ( +
+ +
+ ) } const emergencyInfo = { @@ -56,58 +65,74 @@ export default function EmergencyPage() { })) || [] return ( -
+
{/* Header */} -
-
+
+
+ {currentWorkspace.role !== 'VIEWER' && ( )}
-
+
{hasInfo ? ( - +
+ +
) : ( -
-
- +
+
+
-

+ +

No Emergency Info Set

-

- Add important medical information for emergencies. + +

+ Add important medical information that could be crucial in an emergency situation.

+ {currentWorkspace.role !== 'VIEWER' && ( - + )}
)}
{/* Offline indicator */} -
-
-

- This information is available offline -

+
+
+
+
+

+ This information is available offline +

+
diff --git a/src/app/(app)/today/page.tsx b/src/app/(app)/today/page.tsx index cb5037a..251c382 100644 --- a/src/app/(app)/today/page.tsx +++ b/src/app/(app)/today/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState, useCallback } from 'react' import { useRouter } from 'next/navigation' import { format, isToday, isTomorrow } from 'date-fns' import { toZonedTime } from 'date-fns-tz' -import { Phone, MapPin, Clock, ChevronRight, Pill, Calendar, Plus, AlertTriangle, ClipboardCheck } from 'lucide-react' +import { Phone, MapPin, Clock, ChevronRight, Pill, Calendar, Plus, AlertTriangle, ClipboardCheck, Heart } from 'lucide-react' import { useLiveQuery } from 'dexie-react-hooks' import { db, logDose, undoDose } from '@/lib/sync' @@ -23,6 +23,11 @@ export default function TodayPage() { const [now, setNow] = useState(() => new Date()) const [quickNote, setQuickNote] = useState('') const [isAddingNote, setIsAddingNote] = useState(false) + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) // Update time every minute useEffect(() => { @@ -149,7 +154,14 @@ export default function TodayPage() { const date = toZonedTime(new Date(datetime), TIMEZONE) if (isToday(date)) return `Today at ${format(date, 'h:mm a')}` if (isTomorrow(date)) return `Tomorrow at ${format(date, 'h:mm a')}` - return format(date, 'EEE, MMM d \'at\' h:mm a') + return format(date, "EEE, MMM d 'at' h:mm a") + } + + const getGreeting = () => { + const hour = now.getHours() + if (hour < 12) return 'Good morning' + if (hour < 17) return 'Good afternoon' + return 'Good evening' } if (!appointments || !medications) { @@ -166,27 +178,41 @@ export default function TodayPage() { return ( <>
- - {/* Greeting */} -
-

- {format(toZonedTime(now, TIMEZONE), 'EEEE, MMMM d')} -

+ + {/* Greeting Section with decorative elements */} +
+ {/* Decorative blob */} +
+ +
+

+ {format(toZonedTime(now, TIMEZONE), 'EEEE, MMMM d')} +

+

+ {getGreeting()} +

+

+ + Take it one step at a time +

+
- {/* Emergency & Call Clinic Buttons */} -
+ {/* Emergency & Call Clinic Buttons - Floating cards */} +
{/* Emergency Info Button */} @@ -194,26 +220,28 @@ export default function TodayPage() { {currentWorkspace.clinicPhone && ( -
- -
-
- {/* Next Appointment */} -
-
-

Next Appointment

+ {/* Next Appointment - Hero Card */} +
+
+

Next Appointment

{nextAppointment ? ( - router.push(`/appointments/${nextAppointment.id}`)} > -
-
- +
+
+
-

+

{nextAppointment.title}

-

- +

+ {formatAppointmentDate(nextAppointment.datetime)}

{nextAppointment.location && ( -

- +

+ {nextAppointment.location}

)}
- +
{nextAppointment.mapUrl && ( e.stopPropagation()} - className="inline-flex items-center gap-1.5 mt-3 text-sm text-primary-600 font-medium hover:text-primary-700" + className="inline-flex items-center gap-1.5 mt-4 text-sm text-primary-600 font-medium hover:text-primary-700 hover:underline" > Open in Maps )} - +
) : ( - - -

No upcoming appointments

- -
+ +
)}
@@ -283,26 +311,26 @@ export default function TodayPage() { ) if (tomorrowAppt) { return ( -
- +
router.push(`/appointments/${tomorrowAppt.id}/prep`)} > -
-
- +
+
+
-

+

Prepare for tomorrow

-

+

{tomorrowAppt.title} at {format(toZonedTime(new Date(tomorrowAppt.datetime), TIMEZONE), 'h:mm a')}

- +
- +
) } @@ -311,23 +339,25 @@ export default function TodayPage() { {/* Refill Alerts */} {medications && medications.length > 0 && ( - ({ - id: m.id, - name: m.name, - pillCount: m.pillCount, - refillThreshold: m.refillThreshold, - }))} - /> +
+ ({ + id: m.id, + name: m.name, + pillCount: m.pillCount, + refillThreshold: m.refillThreshold, + }))} + /> +
)} {/* Meds Due */} -
-
-

Medications

+
+
+

Medications

{medsDueSoon.length > 0 ? ( -
- {medsDueSoon.map((status) => ( +
+ {medsDueSoon.map((status, index) => ( handleTakeMed(status)} + index={index} /> ))}
) : medications.length > 0 ? ( - - -

All caught up! No meds due soon.

-
+
+
+ +
+

All caught up!

+

No medications due soon

+
) : ( {/* Quick Note */} -
-

Quick Note

- -
+
+

Quick Note

+
+
setQuickNote(e.target.value)} placeholder="Jot down a thought..." - className="flex-1 px-3 py-2.5 border border-border rounded-button text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500" + className="input-sanctuary flex-1" onKeyDown={(e) => { if (e.key === 'Enter' && quickNote.trim()) { handleAddQuickNote() @@ -384,11 +418,12 @@ export default function TodayPage() { onClick={handleAddQuickNote} disabled={!quickNote.trim() || isAddingNote} loading={isAddingNote} + className="btn-primary whitespace-nowrap" > Add
- +
@@ -399,9 +434,10 @@ interface MedicationCardProps { status: MedicationDueStatus now: Date onTake: () => void + index: number } -function MedicationCard({ status, now, onTake }: MedicationCardProps) { +function MedicationCard({ status, now, onTake, index }: MedicationCardProps) { const { medication, isOverdue, isPRN, prnAvailable, prnAvailableAt, nextDueAt } = status const getTimeLabel = () => { @@ -426,35 +462,38 @@ function MedicationCard({ status, now, onTake }: MedicationCardProps) { const canTake = !isPRN || prnAvailable return ( - -
-
- +
+
+
+
-

{medication.name}

-

+

{medication.name}

+

{getTimeLabel()} {isPRN && ' • As needed'}

- +
{medication.instructions && ( -

+

{medication.instructions}

)} - +
) } diff --git a/src/app/globals.css b/src/app/globals.css index e160773..07abbdc 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,10 +4,14 @@ @tailwind components; @tailwind utilities; +/* Google Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=Source+Sans+3:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap'); + @layer base { :root { - --background: 250 251 252; - --foreground: 31 38 49; + --background: 250 247 242; + --foreground: 38 35 32; + --surface: 255 255 255; } html { @@ -17,6 +21,16 @@ body { @apply bg-background text-secondary-900 antialiased; font-feature-settings: 'rlig' 1, 'calt' 1; + font-family: 'Source Sans 3', system-ui, sans-serif; + } + + /* Paper texture background */ + .paper-texture { + background-color: #faf7f2; + background-image: + url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); + background-blend-mode: soft-light; + background-size: 200px 200px; } /* Large text mode */ @@ -38,12 +52,12 @@ } .large-text .text-sm { - font-size: 1rem; /* text-base equivalent */ + font-size: 1rem; line-height: 1.5rem; } .large-text .text-xs { - font-size: 0.875rem; /* text-sm equivalent */ + font-size: 0.875rem; line-height: 1.25rem; } @@ -56,9 +70,9 @@ padding-bottom: env(safe-area-inset-bottom); } - /* Focus styles for accessibility */ + /* Focus styles for accessibility - warm glow */ *:focus-visible { - @apply outline-none ring-2 ring-primary-500 ring-offset-2; + @apply outline-none ring-2 ring-primary-300 ring-offset-2 ring-offset-background; } /* Better touch targets */ @@ -79,64 +93,151 @@ } @layer components { - /* Primary taken button */ - .btn-taken { + /* Warm sanctuary card styles */ + .card-sanctuary { + @apply bg-surface rounded-card shadow-card; + @apply border border-cream-200/50; + @apply transition-all duration-300 ease-sanctuary; + } + + .card-sanctuary:hover { + @apply shadow-card-hover; + @apply border-cream-300; + } + + /* Primary action button - warm green */ + .btn-primary { @apply bg-primary-500 hover:bg-primary-600 text-white font-semibold; - @apply py-3 px-6 rounded-button min-h-touch; - @apply shadow-button transition-all duration-200; - @apply active:scale-95; + @apply py-3.5 px-6 rounded-button min-h-touch; + @apply shadow-button transition-all duration-300 ease-sanctuary; + @apply active:scale-[0.98] hover:shadow-button-hover; } - /* Card styles */ + /* Secondary button - cream */ + .btn-secondary { + @apply bg-cream-100 hover:bg-cream-200 text-secondary-800 font-medium; + @apply py-3.5 px-6 rounded-button min-h-touch; + @apply border border-cream-300; + @apply transition-all duration-300 ease-sanctuary; + @apply active:scale-[0.98]; + } + + /* Accent button - terracotta */ + .btn-accent { + @apply bg-accent-500 hover:bg-accent-600 text-white font-semibold; + @apply py-3.5 px-6 rounded-button min-h-touch; + @apply shadow-button transition-all duration-300 ease-sanctuary; + @apply active:scale-[0.98] hover:shadow-button-hover; + } + + /* Ghost button */ + .btn-ghost { + @apply bg-transparent hover:bg-cream-100 text-secondary-700 font-medium; + @apply py-3.5 px-6 rounded-button min-h-touch; + @apply transition-all duration-300 ease-sanctuary; + @apply active:scale-[0.98]; + } + + /* Taken button */ + .btn-taken { + @apply btn-primary; + } + + /* Appointment card */ .card-appointment { - @apply bg-surface rounded-card shadow-card p-4; - @apply border-l-4 border-primary-500; + @apply card-sanctuary p-5; + @apply border-l-[6px] border-l-primary-400; } + /* Medication card */ .card-medication { - @apply bg-surface rounded-card shadow-card p-4; - @apply flex items-center justify-between; + @apply card-sanctuary p-5; } - /* Overdue styles */ + /* Overdue styles - warm terracotta */ .overdue { - @apply border-l-4 border-red-500 bg-red-50; + @apply border-l-[6px] border-l-accent-500; + @apply bg-accent-50/50; + } + + /* Emergency card - alert red but softened */ + .card-emergency { + @apply bg-alert-50 border-2 border-alert-200 rounded-card-lg; + @apply overflow-hidden; + } + + /* Section styling */ + .section-warm { + @apply bg-surface rounded-card-lg shadow-soft p-6; + @apply border border-cream-200/60; + } + + /* Input styling */ + .input-sanctuary { + @apply bg-surface border border-cream-300 rounded-button; + @apply px-4 py-3.5 text-secondary-900 placeholder:text-secondary-400; + @apply focus:outline-none focus:border-primary-400 focus:ring-2 focus:ring-primary-200; + @apply transition-all duration-200; } /* Timeline styles */ .timeline-item { - @apply relative pl-6 pb-4; + @apply relative pl-8 pb-6; } .timeline-item::before { content: ''; - @apply absolute left-0 top-2 w-2 h-2 rounded-full bg-primary-400; + @apply absolute left-0 top-2 w-3 h-3 rounded-full bg-primary-300; + @apply ring-4 ring-primary-100; } .timeline-item::after { content: ''; - @apply absolute left-[3px] top-4 w-0.5 h-full bg-border; + @apply absolute left-[5px] top-5 w-0.5 h-full bg-cream-300; } .timeline-item:last-child::after { @apply hidden; } + + /* Glass effect for overlays */ + .glass { + @apply bg-surface/80 backdrop-blur-md; + @apply border border-cream-200/50; + } + + /* Decorative blob shapes */ + .blob { + @apply absolute rounded-full blur-3xl opacity-30 pointer-events-none; + } + + .blob-primary { + @apply bg-primary-200; + } + + .blob-accent { + @apply bg-accent-200; + } + + .blob-cream { + @apply bg-cream-300; + } } @layer utilities { /* Animation utilities */ .animate-in { - animation: animateIn 0.2s ease-out; + animation: animateIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .animate-out { - animation: animateOut 0.15s ease-in forwards; + animation: animateOut 0.2s ease-in forwards; } @keyframes animateIn { from { opacity: 0; - transform: translateY(8px); + transform: translateY(12px); } to { opacity: 1; @@ -151,55 +252,46 @@ } to { opacity: 0; - transform: translateY(8px); + transform: translateY(12px); } } - .slide-in-from-bottom-4 { - animation: slideInFromBottom 0.2s ease-out; - } - - .slide-out-to-bottom-4 { - animation: slideOutToBottom 0.15s ease-in forwards; - } - - @keyframes slideInFromBottom { - from { - opacity: 0; - transform: translateY(16px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - @keyframes slideOutToBottom { - from { - opacity: 1; - transform: translateY(0); - } - to { - opacity: 0; - transform: translateY(16px); - } - } + /* Stagger animation delays */ + .stagger-1 { animation-delay: 0.05s; } + .stagger-2 { animation-delay: 0.1s; } + .stagger-3 { animation-delay: 0.15s; } + .stagger-4 { animation-delay: 0.2s; } + .stagger-5 { animation-delay: 0.25s; } + .stagger-6 { animation-delay: 0.3s; } + /* Fade utilities */ .fade-in { - animation: fadeIn 0.2s ease-out; + animation: fadeIn 0.3s ease-out forwards; } @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + .fade-up { + animation: fadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; + } + + @keyframes fadeUp { from { opacity: 0; + transform: translateY(20px); } to { opacity: 1; + transform: translateY(0); } } + /* Scale utilities */ .zoom-in-95 { - animation: zoomIn 0.2s ease-out; + animation: zoomIn 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; } @keyframes zoomIn { @@ -212,4 +304,29 @@ transform: scale(1); } } + + /* Soft pulse for live elements */ + .pulse-soft { + animation: pulseSoft 3s ease-in-out infinite; + } + + @keyframes pulseSoft { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } + } + + /* Gradient text */ + .gradient-text { + @apply bg-clip-text text-transparent; + background-image: linear-gradient(135deg, #528252 0%, #3f663f 100%); + } + + /* Hide scrollbar but keep functionality */ + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + .scrollbar-hide::-webkit-scrollbar { + display: none; + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2959772..9e0bc12 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,7 @@ import type { Metadata, Viewport } from 'next' -import { Inter } from 'next/font/google' import './globals.css' import { Toaster } from '@/components/ui' -const inter = Inter({ subsets: ['latin'] }) - export const metadata: Metadata = { title: 'Next Step - Health Management', description: 'A calm, reliable app to help manage appointments, medications, and notes for health care.', @@ -21,7 +18,7 @@ export const viewport: Viewport = { initialScale: 1, maximumScale: 1, userScalable: false, - themeColor: '#3a9563', + themeColor: '#528252', } export default function RootLayout({ @@ -33,8 +30,11 @@ export default function RootLayout({ + {/* Preconnect to Google Fonts for performance */} + + - + {children} diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx index 4d57391..47a9d8d 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/page.tsx @@ -1,9 +1,9 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' -import { Heart, Shield, ArrowRight } from 'lucide-react' -import { Button, Input, Card, showToast } from '@/components/ui' +import { Heart, Shield, ArrowRight, Sparkles, Users, Bell } from 'lucide-react' +import { Button, Input, showToast } from '@/components/ui' export default function OnboardingPage() { const router = useRouter() @@ -12,6 +12,11 @@ export default function OnboardingPage() { const [clinicPhone, setClinicPhone] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState('') + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) const handleAcceptDisclaimer = () => { setStep('workspace') @@ -45,7 +50,7 @@ export default function OnboardingPage() { }) } - showToast('All set! Welcome to Next Step.', 'success') + showToast('Welcome to Next Step', 'success') router.push('/today') router.refresh() } catch (err) { @@ -57,101 +62,176 @@ export default function OnboardingPage() { if (step === 'disclaimer') { return ( -
+
-
-
- -
-

Important Notice

+ {/* Decorative blobs */} +
+
+
+
- -
-

- Next Step is a tracking tool only. It helps you and your family - stay organized with appointments and medications. +

+ {/* Logo/Icon */} +
+
+ +
+ +

+ Next Step +

+

+ Supporting you through every step

+
-

- This app does not provide medical advice.{' '} - Always consult your healthcare team for medical decisions. -

+ {/* Disclaimer Card */} +
+
+
+ +
+

Important Notice

+
-

- For emergencies: Call 000 (Australia) or your local emergency - services immediately. -

+
+
+ +

+ Next Step is a tracking tool only.{' '} + It helps you and your family stay organized with appointments and medications. +

+
-

- If you have questions about your treatment, contact your clinic directly using the - button we'll help you set up. -

+
+
+ ! +
+

+ This app does not provide medical advice.{' '} + Always consult your healthcare team for medical decisions. +

+
-
-

- By continuing, you acknowledge that Next Step is for tracking purposes only and - does not replace professional medical advice. +

+
+ 000 +
+

+ For emergencies: Call 000 (Australia) or your local emergency services immediately. +

+
+ +
+ +

+ Have questions about your treatment? Contact your clinic directly using the button we'll help you set up. +

+
+
+ +
+

+ By continuing, you acknowledge that Next Step is for tracking purposes only and does not replace professional medical advice.

- - + +
) } return ( -
-
-
-
- -
-

Set Up Your Plan

-

Create a workspace to get started

+
+
+ {/* Decorative blobs */} +
+
+
- -
- setWorkspaceName(e.target.value)} - placeholder="e.g., Grace's Plan" - helperText="This is how family members will identify this workspace" - required - /> - setClinicPhone(e.target.value)} - placeholder="e.g., 08 9400 1234" - helperText="We'll add a 'Call Clinic' button for quick access" - /> +
+ {/* Header */} +
+
+ +
+ +

+ Set Up Your Plan +

+

Create a workspace to get started

+
+ + {/* Form */} + +
+ + setWorkspaceName(e.target.value)} + placeholder="e.g., Grace's Plan" + className="input-sanctuary w-full" + required + /> +

+ This is how family members will identify this workspace +

+
+ +
+ +
+ + setClinicPhone(e.target.value)} + placeholder="e.g., 08 9400 1234" + className="input-sanctuary w-full pl-10" + /> +
+

+ We'll add a quick "Call Clinic" button for easy access +

+
{error && ( -

- {error} -

+
+

{error}

+
)} - + - -

- You can add family members later from Settings -

+

+ You can add family members later from Settings +

+
) diff --git a/src/components/emergency/EmergencyCard.tsx b/src/components/emergency/EmergencyCard.tsx index 31fc7ba..727e7b2 100644 --- a/src/components/emergency/EmergencyCard.tsx +++ b/src/components/emergency/EmergencyCard.tsx @@ -1,6 +1,6 @@ 'use client' -import { Phone, User, Droplets, AlertTriangle, Activity, Stethoscope } from 'lucide-react' +import { Phone, User, Droplets, AlertTriangle, Activity, Stethoscope, HeartPulse, FileText } from 'lucide-react' import { format } from 'date-fns' interface EmergencyInfo { @@ -39,49 +39,66 @@ export function EmergencyCard({ info, medications, variant = 'full' }: Emergency } return ( -
+
{/* Header */} -
-
- -

Emergency Information

+
+
+
+ +
+
+

Emergency Information

+

Critical medical details for emergencies

+
-
+
{/* Patient Info */} {info.patientName && ( -
- -
-

Patient Name

-

{info.patientName}

+
+
+ +
+
+

+ Patient +

+

{info.patientName}

{info.patientDOB && ( -

DOB: {formatDate(info.patientDOB)}

+

+ Born: {formatDate(info.patientDOB)} +

)}
)} - {/* Blood Type */} + {/* Blood Type - Large and prominent */} {info.bloodType && ( -
- +
+
+ +
-

Blood Type

-

{info.bloodType}

+

+ Blood Type +

+

{info.bloodType}

)} {/* Allergies - High visibility */} {info.allergies && ( -
+
- +
-

Allergies

-

{info.allergies}

+

+ Allergies +

+

{info.allergies}

@@ -89,10 +106,14 @@ export function EmergencyCard({ info, medications, variant = 'full' }: Emergency {/* Medical Conditions */} {info.medicalConditions && ( -
- -
-

Medical Conditions

+
+
+ +
+
+

+ Medical Conditions +

{info.medicalConditions}

@@ -100,34 +121,49 @@ export function EmergencyCard({ info, medications, variant = 'full' }: Emergency {/* Current Medications */} {variant === 'full' && medications && medications.length > 0 && ( -
-

Current Medications

-
    - {medications.map((med, i) => ( -
  • - {med.name} - {med.instructions && ( - - {med.instructions} - )} -
  • - ))} -
+
+
+ +

+ Current Medications +

+
+
+
    + {medications.map((med, i) => ( +
  • + +
    + {med.name} + {med.instructions && ( + — {med.instructions} + )} +
    +
  • + ))} +
+
)} {/* Doctor Info */} {info.primaryPhysician && ( -
-
- -
-

Primary Physician

-

{info.primaryPhysician}

+
+
+
+ +
+
+

+ Primary Physician +

+

{info.primaryPhysician}

{info.physicianPhone && ( + {info.physicianPhone} )} @@ -138,38 +174,42 @@ export function EmergencyCard({ info, medications, variant = 'full' }: Emergency {/* Emergency Contacts */} {(info.clinicPhone || info.emergencyPhone) && ( -
-

Emergency Contacts

+
+

+ + Emergency Contacts +

+
)}
diff --git a/src/components/medications/MedicationForm.tsx b/src/components/medications/MedicationForm.tsx index 84c0090..d0cc360 100644 --- a/src/components/medications/MedicationForm.tsx +++ b/src/components/medications/MedicationForm.tsx @@ -2,26 +2,27 @@ import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' -import { Button, Input, Textarea, Select, Card, showToast } from '@/components/ui' +import { Clock, Calendar, Repeat, Pill, Package, ChevronDown, Plus, X } from 'lucide-react' +import { Button, Input, Textarea, Select, showToast } from '@/components/ui' import { useApp } from '@/app/(app)/provider' type ScheduleType = 'FIXED_TIMES' | 'INTERVAL' | 'WEEKDAYS' | 'PRN' const scheduleTypeOptions = [ - { value: 'FIXED_TIMES', label: 'Fixed times daily' }, - { value: 'INTERVAL', label: 'Every X hours' }, - { value: 'WEEKDAYS', label: 'Specific days of week' }, - { value: 'PRN', label: 'As needed (PRN)' }, + { value: 'FIXED_TIMES', label: 'Fixed times daily', icon: Clock, desc: 'Same times every day' }, + { value: 'INTERVAL', label: 'Every X hours', icon: Repeat, desc: 'Regular intervals' }, + { value: 'WEEKDAYS', label: 'Specific days', icon: Calendar, desc: 'Certain days of the week' }, + { value: 'PRN', label: 'As needed (PRN)', icon: Pill, desc: 'When you need it' }, ] const weekdays = [ - { value: 0, label: 'Sun' }, - { value: 1, label: 'Mon' }, - { value: 2, label: 'Tue' }, - { value: 3, label: 'Wed' }, - { value: 4, label: 'Thu' }, - { value: 5, label: 'Fri' }, - { value: 6, label: 'Sat' }, + { value: 0, label: 'Sun', full: 'Sunday' }, + { value: 1, label: 'Mon', full: 'Monday' }, + { value: 2, label: 'Tue', full: 'Tuesday' }, + { value: 3, label: 'Wed', full: 'Wednesday' }, + { value: 4, label: 'Thu', full: 'Thursday' }, + { value: 5, label: 'Fri', full: 'Friday' }, + { value: 6, label: 'Sat', full: 'Saturday' }, ] interface MedicationFormProps { @@ -42,6 +43,7 @@ interface MedicationFormProps { export function MedicationForm({ initialData, isEditing = false }: MedicationFormProps) { const router = useRouter() const { currentWorkspace, refreshData } = useApp() + const [mounted, setMounted] = useState(false) const [name, setName] = useState(initialData?.name || '') const [instructions, setInstructions] = useState(initialData?.instructions || '') @@ -72,9 +74,12 @@ export function MedicationForm({ initialData, isEditing = false }: MedicationFor const [error, setError] = useState('') useEffect(() => { - // Reset defaults if switching types and no initial data for that type + setMounted(true) + }, []) + + useEffect(() => { if (initialData?.scheduleType !== scheduleType) { - // Keep current state if user is just switching around in new mode + // Keep current state if user is just switching around in new mode } }, [scheduleType, initialData]) @@ -134,13 +139,11 @@ export function MedicationForm({ initialData, isEditing = false }: MedicationFor scheduleType, scheduleData: buildScheduleData(), active: true, - // Refill tracking ...(trackRefills && pillCount !== '' && { pillCount: Number(pillCount), pillsPerDose, refillThreshold, }), - // Explicitly nullify if disabled during edit ...(isEditing && !trackRefills && { pillCount: null, pillsPerDose: null, @@ -164,197 +167,337 @@ export function MedicationForm({ initialData, isEditing = false }: MedicationFor } } + const currentScheduleOption = scheduleTypeOptions.find(opt => opt.value === scheduleType) + const ScheduleIcon = currentScheduleOption?.icon || Clock + return ( - -
- setName(e.target.value)} - placeholder="e.g., Paracetamol 500mg" - required - /> +
+ {/* Form Header */} +
+
+ +
+

+ {isEditing ? 'Edit Medication' : 'Add Medication'} +

+

+ {isEditing ? 'Update your medication details' : 'Keep track of your medications'} +

+
-