# Phase 2 — React Frontend Migration Playbook

This is the plan for the **next** chat after you confirm the PHP backend works.

## Strategy: shim, don't rewrite

Instead of rewriting every page, create a thin shim that mimics the Supabase JS API surface but calls our REST endpoints under the hood. This keeps pages unchanged.

### Step 1 — new API client (`src/integrations/api/client.ts`)

```typescript
const BASE = import.meta.env.VITE_API_URL; // e.g. https://api.yourdomain.com
const TOKEN_KEY = 'lifeos_jwt';

export const api = {
  get token() { return localStorage.getItem(TOKEN_KEY); },
  set token(v: string | null) { v ? localStorage.setItem(TOKEN_KEY, v) : localStorage.removeItem(TOKEN_KEY); },
  async req(path: string, opts: RequestInit = {}) {
    const r = await fetch(BASE + path, {
      ...opts,
      headers: {
        'Content-Type': 'application/json',
        ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
        ...(opts.headers || {}),
      },
    });
    const j = await r.json();
    if (!j.ok) throw new Error(j.error || 'request_failed');
    return j.data;
  },
  get:    (p: string) => api.req(p),
  post:   (p: string, body: any) => api.req(p, { method: 'POST', body: JSON.stringify(body) }),
  patch:  (p: string, body: any) => api.req(p, { method: 'PATCH', body: JSON.stringify(body) }),
  del:    (p: string) => api.req(p, { method: 'DELETE' }),
};
```

### Step 2 — replace AuthContext

Swap Supabase calls for:
- `signUp` → `api.post('/auth/signup', { email, password })` then `api.token = res.token`
- `signIn` → `api.post('/auth/login', ...)` same flow
- `signOut` → `api.token = null`
- `getSession` → `api.token ? api.get('/auth/me') : null`
- Drop Google OAuth UI (per your decision)

### Step 3 — page-by-page swap

For each Supabase call, the mapping is mechanical:
| Supabase | New |
|---|---|
| `supabase.from('bills').select('*')` | `api.get('/bills')` |
| `supabase.from('bills').insert(row)` | `api.post('/bills', row)` |
| `supabase.from('bills').update(...).eq('id', id)` | `api.patch('/bills/' + id, fields)` |
| `supabase.from('bills').delete().eq('id', id)` | `api.del('/bills/' + id)` |
| `supabase.auth.getUser()` | `api.get('/auth/me')` |
| `supabase.storage.from('avatars').upload(...)` | `fetch(BASE + '/settings/avatar', { method:'POST', body: formData })` |

### Step 4 — delete what doesn't migrate
- `src/pages/Community.tsx` and the `community/` folder
- `src/components/community/`, `src/hooks/useCommunityShare.ts`
- All Supabase Realtime subscription code
- The `src/integrations/supabase/` folder (last)

### Step 5 — env var
Add `VITE_API_URL=https://api.yourdomain.com` to your hosting env. The Lovable Cloud connection can be disabled after Phase 2 is deployed.

## Time estimate
~1 full chat session for an experienced agent. Don't try to do it in one go — go page by page so you can test each one.

---

## Phase 1.5 additions — Tasks, Goals, Habits

### Mapping localStorage → REST

| Old localStorage key (DailyPriorities/Goals/HabitTracker) | New |
|---|---|
| `dailyPriorities[date]` array | `api.get('/tasks?date=' + date)` / `api.post('/tasks', {...})` |
| `goals` array | `api.get('/goals')` / `api.post('/goals', {...})` |
| `habits` array | `api.get('/habits')` |
| `habitLogs[habitId][date]` toggle | `api.post('/habits/' + id + '/logs', { log_date, count: 1 })` |
| Untoggle a habit day | `api.del('/habits/' + id + '/logs?date=' + date)` |

### One-time migration on first login

After the user logs into the new backend for the first time, run this once:

```typescript
async function migrateLocalToServer() {
  if (localStorage.getItem('lifeos_migrated_v1') === 'yes') return;
  const payload = {
    goals: JSON.parse(localStorage.getItem('goals') || '[]')
            .map((g: any) => ({ ...g, client_id: g.id })),
    tasks: Object.entries(JSON.parse(localStorage.getItem('dailyPriorities') || '{}'))
            .flatMap(([date, items]: [string, any]) =>
              (items as any[]).map(t => ({ ...t, task_date: date, goal_client_id: t.goalId }))),
    habits: JSON.parse(localStorage.getItem('habits') || '[]').map((h: any) => ({
      ...h,
      logs: Object.entries(JSON.parse(localStorage.getItem('habitLogs_' + h.id) || '{}'))
              .filter(([, v]) => v).map(([date]) => ({ log_date: date, count: 1 })),
    })),
  };
  const res = await api.post('/import/local', payload);
  console.log('Migrated', res);
  localStorage.setItem('lifeos_migrated_v1', 'yes');
  // Optional: clear old keys after successful import
  ['goals', 'dailyPriorities', 'habits'].forEach(k => localStorage.removeItem(k));
}
```

Call it once in your `AuthContext` after `signIn`. The import is idempotent (`INSERT IGNORE`) so re-running is harmless.
