Files

8.7 KiB

MedMinder - Agent Guide

Project Overview

MedMinder is a medication reminder PWA (Progressive Web App) built with React 19, TypeScript, and Vite. It uses Appwrite for backend (auth, database, messaging) and Firebase Cloud Messaging for push notifications.

Key Characteristics:

  • Passwordless magic-link authentication via Appwrite
  • Brazilian Portuguese UI (all user-facing text is in pt-BR)
  • PWA with offline support via Workbox
  • Push notifications for medication reminders
  • Mobile-first design with bottom navigation

Essential Commands

# Development
npm run dev              # Start Vite dev server (includes PWA in dev mode)

# Build & Deploy
npm run build            # TypeScript compile + Vite build (outputs to dist/)
npm run preview          # Preview production build locally

# Linting
npm run lint             # ESLint with TypeScript, React Hooks, React Refresh rules

# Task (go-task)
task run-dev             # Same as npm run dev
task build               # Same as npm run build
task zip-functions       # Package Appwrite functions for deployment

Project Structure

src/
├── components/
│   ├── ui/              # shadcn/ui components (button, card, dialog, etc.)
│   └── layout/          # AppLayout, BottomNav
├── hooks/
│   └── useAuth.ts       # Authentication state hook
├── lib/
│   ├── appwrite.ts      # Appwrite client + collection constants
│   ├── auth.ts          # Magic link auth helpers
│   ├── medications.ts   # Medication CRUD + types + scheduling logic
│   ├── notifications.ts # FCM push token registration
│   ├── profiles.ts      # User profile management
│   └── utils.ts         # cn() helper for Tailwind classes
├── pages/
│   ├── Login.tsx        # Magic link login
│   ├── AuthCallback.tsx # OTP verification
│   ├── Dashboard.tsx    # Today's medication schedule
│   ├── Medications.tsx  # Medication management
│   └── Settings.tsx     # Theme, notifications, copy supplements
├── sw.ts                # Service worker (Workbox + FCM)
├── App.tsx              # Routes + protected route wrapper
└── main.tsx             # React root + PWA registration

functions/
├── send-reminders/      # Appwrite Function: cron job for push notifications
└── copy-medications/    # Appwrite Function: copy meds between users

Architecture & Data Flow

Authentication Flow

  1. User enters email on Login page
  2. sendEmailOTP() creates Appwrite email token
  3. User receives magic link with OTP
  4. verifyEmailOTP() creates session
  5. useAuth hook maintains session state
  6. ProtectedRoute component guards authenticated routes

Medication Scheduling

  • Medications stored in Appwrite with: name, dosage, frequency, times[], startDate, durationDays, timezone
  • isScheduledToday() determines if medication is due based on frequency and date range
  • updateNotificationCache() writes denormalized medication data to cache collection for the reminder function

Push Notification Flow

  1. User enables notifications → requestAndRegisterPushToken()
  2. FCM token registered with Appwrite via account.createPushTarget()
  3. send-reminders function runs every minute (cron: * * * * *)
  4. Function reads cache collection, checks local time per user timezone
  5. If medication time matches and not already taken → messaging.createPush()
  6. FCM delivers to browser, service worker handles background/foreground

Copy Medications Feature

  • User enters target email in Settings
  • copy-medications function resolves email → userId via profiles collection
  • Copies all medications from current user to target user
  • Resets startDate to today

Code Patterns & Conventions

shadcn/ui Components

  • Uses @base-ui/react primitives (not Radix)
  • Style: "base-nova" (defined in components.json)
  • All UI components in src/components/ui/
  • Import pattern: import { Button } from '@/components/ui/button'

Styling

  • Tailwind CSS v4 with @theme inline for CSS variables
  • Font: Geist Variable
  • Color scheme: Neutral base with OKLCH colors
  • Dark mode: next-themes with attribute="class"
  • Container width: w-[85%] mx-auto for mobile-first content

TypeScript Patterns

  • Types defined alongside functions in lib files (e.g., Medication, MedicationLog in medications.ts)
  • Appwrite documents cast with as unknown as Type pattern
  • React 19 types (no explicit React.FC needed)

Error Handling

  • Toast notifications via sonner for user feedback
  • Portuguese error messages
  • Try/catch with graceful degradation (e.g., Firebase not configured)

Date/Time Handling

  • All dates stored as ISO strings
  • todayString() returns YYYY-MM-DD in local time
  • Timezone stored per medication for accurate reminder scheduling
  • Server function converts UTC to user's local timezone for comparisons

Appwrite Collections Schema

medications

  • name: string
  • dosage: string
  • frequency: enum ('once_daily' | 'twice_daily' | 'three_daily' | 'weekly' | 'as_needed')
  • times: string[] (HH:MM format)
  • startDate: string (YYYY-MM-DD)
  • durationDays: number | null
  • timezone: string (IANA timezone, e.g., 'America/Sao_Paulo')
  • userId: string

logs (dose confirmations)

  • medicationId: string
  • userId: string
  • takenAt: string (ISO timestamp)
  • scheduledDate: string (YYYY-MM-DD)
  • scheduledTime: string | undefined (HH:MM)

profiles

  • userId: string (document ID)
  • email: string (lowercase, for lookup)

cache

  • userId: string (document ID)
  • data: string (JSON array of medications)

Permissions: All collections use row-level security. Create/Read for Users, Update/Delete for document owner only.


Environment Variables

All required env vars are in .env.example:

Appwrite:

  • VITE_APPWRITE_ENDPOINT
  • VITE_APPWRITE_PROJECT_ID
  • VITE_APPWRITE_DATABASE_ID
  • VITE_APPWRITE_MEDICATIONS_COLLECTION_ID
  • VITE_APPWRITE_LOGS_COLLECTION_ID
  • VITE_APPWRITE_PROFILES_COLLECTION_ID
  • VITE_APPWRITE_CACHE_COLLECTION_ID
  • VITE_APPWRITE_COPY_FUNCTION_ID
  • VITE_APPWRITE_FCM_PROVIDER_ID

Firebase (for push notifications):

  • VITE_FIREBASE_API_KEY
  • VITE_FIREBASE_AUTH_DOMAIN
  • VITE_FIREBASE_PROJECT_ID
  • VITE_FIREBASE_STORAGE_BUCKET
  • VITE_FIREBASE_MESSAGING_SENDER_ID
  • VITE_FIREBASE_APP_ID
  • VITE_FIREBASE_VAPID_KEY

Build:

  • BASE_URL: Set to /medminder/ for GitHub Pages deployment

Important Gotchas

Service Worker & Firebase

  • sw.ts is the custom service worker using Workbox injectManifest strategy
  • Firebase is conditionally initialized (checks for API key presence)
  • Background messages: Only shows notification for data-only payloads (no notification field) to avoid duplicates
  • Foreground messages: Handled in App.tsx via listenForegroundMessages(), displays toast

Notification Cache

  • Every medication mutation (create/update/delete) calls updateNotificationCache()
  • Cache is a JSON string in the cache collection for fast reads by the reminder function
  • Cache updates are fire-and-forget (errors logged but not blocking)

Copy Medications Permissions

  • When copying, permissions must include both fromUserId (for the write to succeed under row security) and toUserId (for access)
  • See copyMedications() in medications.ts for the permission pattern

Timezone Handling

  • Reminder function uses Intl.DateTimeFormat with user's timezone to get local time
  • Sleeping hours filter hardcoded for America/Sao_Paulo (11 PM - 6 AM local = 2 AM - 9 AM UTC)
  • If expanding to other timezones, this needs to be made configurable

Push Target Storage

  • Push target ID stored in localStorage (medminder_push_target_id)
  • On token refresh, attempts to update existing target before creating new one

PWA in Development

  • vite-plugin-pwa has devOptions.enabled: true so service worker runs in dev
  • TypeScript types for virtual modules come from vite-plugin-pwa/client

Testing Considerations

  • No test framework currently configured
  • For manual testing: Use browser DevTools → Application → Service Workers to inspect SW
  • Push notifications require HTTPS or localhost
  • Firebase config can be omitted for development (notifications gracefully disabled)

Deployment

GitHub Pages (configured in .github/workflows/deploy.yml):

  • Triggered on push to main
  • Requires all env vars as GitHub Secrets
  • Outputs to dist/ with .nojekyll for proper SPA routing
  • Base URL set to /medminder/

Appwrite Functions:

  • Must be manually packaged with task zip-functions and uploaded to Appwrite Console
  • send-reminders requires cron schedule: * * * * *
  • Both functions need env vars from .env.example (without VITE_ prefix)