# Daily Reminder Feature - Complete Setup & Context

**Date:** 2026-04-20  
**Status:** ✅ Enabled & Ready for Testing

---

## Overview

The Daily Reminder feature is a **fully implemented Firebase Cloud Messaging (FCM)** system that sends push notifications to remind users to journal. It works even when the browser is closed (on mobile PWA).

**Key Point:** All code was already in place but disabled in the UI. This document captures the setup and what was changed to enable it.

---

## Architecture

### Frontend Flow

**Files:** `src/hooks/useReminder.ts`, `src/hooks/reminderApi.ts`, `src/pages/SettingsPage.tsx`

1. User opens Settings → clicks "Daily Reminder" button
2. Modal opens with time picker (`ClockTimePicker` component)
3. User selects time (e.g., 08:00) → clicks "Save"
4. `enableReminder()` is called:
    - Requests browser notification permission (`Notification.requestPermission()`)
    - Gets FCM token from service worker
    - Sends token to backend: `POST /api/notifications/fcm-token`
    - Sends settings to backend: `PUT /api/notifications/reminder/{userId}`
    - Stores time + enabled state in localStorage

**Message Handling:**

- `listenForegroundMessages()` called on app mount (in `src/main.tsx`)
- When app is **focused**: Firebase SDK triggers `onMessage()` → shows notification manually
- When app is **closed**: Service worker (`public/sw.js`) handles it via `onBackgroundMessage()` → shows notification

### Backend Flow

**Files:** `backend/scheduler.py`, `backend/routers/notifications.py`, `backend/main.py`

**Initialization:**

- `start_scheduler()` called in FastAPI app lifespan
- Initializes Firebase Admin SDK (requires `FIREBASE_SERVICE_ACCOUNT_JSON`)
- Starts APScheduler cron job

**Every Minute:**

1. Find all users with `reminder.enabled=true` and FCM tokens
2. For each user:
    - Convert UTC time → user's timezone (stored in DB)
    - Check if current HH:MM matches `reminder.time` (e.g., "08:00")
    - Check if already notified today (via `reminder.lastNotifiedDate`)
    - Check if user has written a journal entry today
    - **If NOT written yet:** Send FCM push via `firebase_admin.messaging.send_each_for_multicast()`
    - Auto-prune stale tokens on failure
    - Mark as notified today

**Database Structure (MongoDB):**

```js
users collection {
  _id: ObjectId,
  fcmTokens: [token1, token2, ...],  // per device
  reminder: {
    enabled: boolean,
    time: "HH:MM",                    // 24-hour format
    timezone: "Asia/Kolkata",         // IANA timezone
    lastNotifiedDate: "2026-04-16"   // prevents duplicates today
  }
}
```

---

## Changes Made (2026-04-20)

### 1. Updated Frontend Environment (`.env.local`)

**Changed:** Firebase credentials from mentor's project → personal test project

```env
VITE_FIREBASE_API_KEY=AIzaSyAjGq7EFrp1mE_8Ni2iZz8LNk7ySVz-lX8
VITE_FIREBASE_AUTH_DOMAIN=react-test-8cb04.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=react-test-8cb04
VITE_FIREBASE_MESSAGING_SENDER_ID=1036594341832
VITE_FIREBASE_APP_ID=1:1036594341832:web:9db6fa337e9cd2e953c2fd
VITE_FIREBASE_VAPID_KEY=BLXhAWY-ms-ACW4PFpqnPak3VZobBIruylVE8Jt-Gm4x53g4aAzEhQzjTvGW8O7dX76-ZoUjlBV15b-EODr1IaY
```

### 2. Updated Backend Environment (`backend/.env`)

**Changed:** Added Firebase service account JSON (from personal test project)

```env
FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"react-test-8cb04",...}
```

### 3. Deleted Service Account JSON File

- Removed: `service account.json` (no longer needed — credentials now in env var)

### 4. Enabled Reminder UI (`src/pages/SettingsPage.tsx`)

**Before:**

```tsx
<div className="settings-item" style={{ opacity: 0.5 }}>
    <label className="settings-toggle">
        <input type="checkbox" checked={false} disabled readOnly />
    </label>
</div>
```

**After:**

```tsx
<button
    type="button"
    className="settings-item settings-item-button"
    onClick={handleOpenReminderModal}
>
    <div className="settings-item-content">
        <h4 className="settings-item-title">Daily Reminder</h4>
        <p className="settings-item-subtitle">
            {reminderEnabled && reminderTime
                ? `Set for ${reminderTime}`
                : "Set a daily reminder"}
        </p>
    </div>
</button>
```

- Changed from disabled toggle → interactive button
- Shows current reminder time or "Set a daily reminder"
- Clicking opens time picker modal

### 5. Removed Type Ignore Comment

**Before:**

```tsx
// @ts-ignore — intentionally unused, reminder is disabled (coming soon)
const handleReminderToggle = async () => {
```

**After:**

```tsx
const handleReminderToggle = async () => {
```

---

## Critical Code Files

| File                               | Purpose                                                                                                      |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `src/hooks/useReminder.ts`         | `enableReminder()`, `disableReminder()`, `reenableReminder()`, `getFcmToken()`, `listenForegroundMessages()` |
| `src/hooks/reminderApi.ts`         | `saveFcmToken()`, `saveReminderSettings()`                                                                   |
| `backend/scheduler.py`             | `send_reminder_notifications()`, `_process_user()`, `_send_push()`, `init_firebase()`                        |
| `backend/routers/notifications.py` | `POST /fcm-token`, `PUT /reminder/{user_id}` endpoints                                                       |
| `public/sw.js`                     | Service worker background message handler                                                                    |
| `src/pages/SettingsPage.tsx`       | UI: time picker modal, reminder state mgmt                                                                   |
| `src/main.tsx`                     | Calls `listenForegroundMessages()` on mount                                                                  |
| `backend/main.py`                  | Scheduler initialization in app lifespan                                                                     |

---

## How to Test

### Prerequisites

- ✅ Backend `.env` has Firebase service account JSON
- ✅ Frontend `.env.local` has Firebase web config + VAPID key
- ✅ UI is enabled (button visible in Settings)

### Steps

1. **Restart the backend** (so it picks up new `FIREBASE_SERVICE_ACCOUNT_JSON`)

    ```bash
    docker-compose down
    docker-compose up
    ```

2. **Open the app** and go to **Settings**

3. **Click "Daily Reminder"** → time picker modal opens

4. **Pick a time** (e.g., 14:30 for testing: pick a time 1-2 minutes in the future)

5. **Click "Save"**
    - Browser asks for notification permission → Accept
    - Time is saved locally + sent to backend

6. **Monitor backend logs:**

    ```bash
    docker logs grateful-journal-backend-1 -f
    ```

    Look for: `Reminder sent to user {user_id}: X ok, 0 failed`

7. **At the reminder time:**
    - If browser is open: notification appears in-app
    - If browser is closed: PWA/OS notification appears (mobile)

### Troubleshooting

| Issue                                               | Solution                                                                           |
| --------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Browser asks for notification permission repeatedly | Check `Notification.permission === 'default'` in browser console                   |
| FCM token is null                                   | Check `VITE_FIREBASE_VAPID_KEY` is correct; browser may not support FCM            |
| Scheduler doesn't run                               | Restart backend; check `FIREBASE_SERVICE_ACCOUNT_JSON` is valid JSON               |
| Notification doesn't appear                         | Check `reminder.lastNotifiedDate` in MongoDB; trigger time must match exactly      |
| Token registration fails                            | Check backend logs; 400 error means invalid userId format (must be valid ObjectId) |

---

## Environment Variables Reference

### Frontend (`.env.local`)

```
VITE_FIREBASE_API_KEY              # Firebase API key
VITE_FIREBASE_AUTH_DOMAIN          # Firebase auth domain
VITE_FIREBASE_PROJECT_ID           # Firebase project ID
VITE_FIREBASE_MESSAGING_SENDER_ID  # Firebase sender ID
VITE_FIREBASE_APP_ID               # Firebase app ID
VITE_FIREBASE_VAPID_KEY            # FCM Web Push VAPID key (from Firebase Console → Messaging)
VITE_API_URL                       # Backend API URL (e.g., http://localhost:8001/api)
```

### Backend (`backend/.env`)

```
FIREBASE_SERVICE_ACCOUNT_JSON  # Entire Firebase service account JSON (minified single line)
MONGODB_URI                    # MongoDB connection string
MONGODB_DB_NAME                # Database name
API_PORT                       # Backend port
ENVIRONMENT                    # production/development
FRONTEND_URL                   # Frontend URL for CORS
```

---

## Next Steps

### For Production

- Switch back to mentor's Firebase credentials (remove personal test project)
- Update `.env.local` and `backend/.env` with production Firebase values

### Future Improvements

- Add UI toggle to enable/disable without removing settings
- Show timezone in Settings (currently auto-detected)
- Show last notification date in UI
- Add snooze button to notifications
- Let users set multiple reminder times

### Resetting to Disabled State

If you need to disable reminders again:

1. Revert `.env.local` and `backend/.env` to mentor's credentials
2. Revert `src/pages/SettingsPage.tsx` to show "Coming soon" UI
3. Add back `@ts-ignore` comment

---

## Technical Notes

### Why This Approach?

- **FCM:** Works on web, mobile, PWA; no polling needed
- **Service Worker:** Handles background notifications even when browser closed
- **Timezone:** Stores user's IANA timezone to support global users
- **Duplicate Prevention:** Tracks `lastNotifiedDate` per user
- **Smart Timing:** Only notifies if user hasn't written today (no spam)

### Security Considerations

- Firebase service account JSON should never be in git (only in env vars)
- FCM tokens are device-specific; backend stores them securely
- All reminder data is encrypted end-to-end (matches app's crypto design)

### Known Limitations

- Reminder check runs every minute (not more frequent)
- FCM token refresh is handled by Firebase SDK automatically
- Stale tokens are auto-pruned on failed sends
- Timezone must be valid IANA format (not GMT±X)

---

## Quick Reference Commands

**Check backend scheduler logs:**

```bash
docker logs grateful-journal-backend-1 -f | grep -i "reminder\|firebase"
```

**View user reminders in MongoDB:**

```bash
docker exec grateful-journal-mongo-1 mongosh grateful_journal --eval "db.users.findOne({_id: ObjectId('...')})" --username admin --password internvps
```

**Clear FCM tokens for a user (testing):**

```bash
docker exec grateful-journal-mongo-1 mongosh grateful_journal --eval "db.users.updateOne({_id: ObjectId('...')}, {\$set: {fcmTokens: []}})" --username admin --password internvps
```

---

## Support

For questions about:

- **Reminders:** Check daily_reminder_feature.md in memory
- **FCM:** Firebase Cloud Messaging docs
- **APScheduler:** APScheduler documentation
- **Firebase Admin SDK:** Firebase Admin SDK for Python docs
