# How everything connects

This is the map. Detail for every button lives in `02`–`06`. Marketing CTAs that start these flows are in `01-marketing-pages.md`.

Access words used below:

- **public** — no login
- **logged-in** — Supabase Auth session
- **onboarded** — `profiles.onboarding_completed_at` set, or name + AUB/LAU already filled (major and campus are optional)
- **subscribed** — at least one `user_access` row with `payment_status` in `paid`/`confirmed` and `expires_at` null or in the future (admins skip this for `/my-courses`)

`proxy.ts` gates:

- `/` — if a Supabase Auth cookie is present **and** `userHasActiveSubscription` is true (`user_access` paid/confirmed, `expires_at` null or in the future) → `/dashboard`. Anonymous visitors and logged-in users with no active access still get the marketing homepage. Other public routes (`/pricing`, `/courses`, legal) are not redirected.
- `/login`, `/signup` — if already logged in → `/auth/home` (with safe `next` when present)
- `/dashboard`, `/welcome`, `/admin`, `/my-courses` — if anonymous → `/login?next=…`
- `/my-courses/{slug}` and `/dashboard/course/{slug}` — also require subscribed (else `/pricing`); `/dashboard` and the `/my-courses` home alias do **not**
- `/checkout` — session refresh; the page itself sends anonymous users to `/login?next=/checkout?…` and incomplete profiles to `/welcome?next=…`

**Active access** is not “logged in” and not a historical order. Authoritative read: `user_access` where `user_id` matches, `payment_status` in `paid`/`confirmed`, and `expires_at` is null or `> now()`. Expired or revoked rows do not count. Canonical student home is `/dashboard`.

---

## 1. Registration and first login

```mermaid
flowchart TD
  A[Anonymous: /signup or /login] --> B{Method}
  B -->|Google| C["signInWithOAuth → Google"]
  B -->|Email signup| D["signUp → auth.users"]
  B -->|Email login| E["signInWithPassword"]
  C --> G["/auth/callback?code=&next="]
  D --> H[Check your email]
  H --> G
  E --> I["/auth/home?next="]
  G --> I
  D -.->|trigger handle_new_user| P[(profiles insert)]
  I --> J{profileNeedsOnboarding?}
  J -->|yes| K["/welcome?next="]
  J -->|no| L["active access → /dashboard; else / or preserved next"]
  K --> M["completeOnboarding writes name + university"]
  M --> L
```

**Tables**

| Step | Writes | Reads |
|---|---|---|
| Google / email signup | `auth.users`; trigger inserts `profiles` (`id`, `email`, `full_name`, `avatar_url`, `nickname`; role defaults to `student`) | — |
| Email login | Auth session cookies only | `auth.users` |
| `/auth/callback` | Session cookies; optional Resend welcome email if `profiles.created_at` ≤ 5s | `profiles.created_at` |
| `/auth/home` | — | `profiles.full_name`, `university`, `onboarding_completed_at` |
| `/welcome` submit | `profiles.full_name`, `university`, optional campus/major/programs, `nickname`, `onboarding_completed_at`, `updated_at` | same fields as prefill |

**Where login is forced**

- Any `/dashboard`, `/welcome`, `/admin`, `/my-courses` hit while logged out → `/login?next=…`
- `/checkout` without a session → `/login?next=/checkout?plan=…`
- Workspace layout and checkout bounce incomplete name/university to `/welcome?next=…`

**After login, where you land**

- Default `next` is `/auth/home`
- `/auth/home` → `/welcome?next=…` or the preserved course/checkout path; default home is `/dashboard` only when `userHasActiveSubscription` is true, otherwise `/`
- Course-preview signup uses `/signup?next=/checkout?plan=focus&course={uuid}`

Phone signup is **not** offered. Phone sign-in was removed from `/login` (zero hosted identities).

Full page inventory: [02-auth-onboarding.md](./02-auth-onboarding.md)
Product rules: [../onboarding.md](../onboarding.md)

---

## 2. Checkout and access grant

```mermaid
flowchart TD
  M[Marketing: Get Focus / Get Balance / Cover my semester / Prep for Finals] --> C["/checkout?plan=focus|balance|fullload|crunch"]
  Course["/courses/slug Get access"] --> C2["/checkout?plan=focus&course=uuid"]
  C2 --> C
  C --> Auth{Signed in?}
  Auth -->|no| Login["/login?next=/checkout?…"]
  Login --> C
  Auth -->|yes| S1[Step 1 pick courses]
  S1 --> S2[Step 2 name university WhatsApp uni email terms]
  S2 --> API["POST /api/payments/whish/create"]
  API --> ORD[(orders pending)]
  API --> W[Whish collect URL]
  W --> R["/checkout/payment/success or failure?orderId="]
  R --> ST["GET /api/payments/whish/status"]
  ST --> REC[reconcileWhishOrder]
  REC --> UA[(user_access paid)]
  UA --> Packs["Go to my courses → /dashboard"]
```

**Plan slugs on the URL vs database ids**

| URL `?plan=` | Display name | `PLANS` / `orders.plan` / `user_access.plan_type` |
|---|---|---|
| `focus` | Focus | `single_course` (also accepted as `focus`) |
| `balance` | Balance | `two_courses` |
| `fullload` | Full Load | `semester_access` |
| `crunch` | Crunch | `exam_crunch` |

Catalog: `lib/payments/catalog.ts`. Full Load is up to 6 courses; Crunch is up to 4. New grants do not write `schedule_verifications`.

**Live writes**

| Moment | Table |
|---|---|
| Continue to secure payment | `orders` (`status=pending`, `payment_provider=whish`, `course_ids`, `registered_credits`, `whatsapp`, `email` = uni email, `amount`, `external_id`, `collect_url`) |
| Whish paid (status poll or callback) | `orders.payment_status=paid`; `grantAccess` → `user_access` (`payment_status=paid`, `course_ids`, `expires_at`, `payment_reference`). New grants do **not** insert `schedule_verifications`. |
| `payment_events` | Server audit around create/reconcile (no UI) |

Entitlement **reads** use `user_access.course_ids`. Junction tables may be dual-written and are not the read source of truth.

**Not the live CTA path (removed)**

- Legacy reservation modal / `POST /api/reservations`
- Legacy `POST /api/orders`
- Placeholder payment sessions

`profiles` is **not** updated at checkout. University on the form is stored on `orders`, not on the profile.

Paid course lists still read `user_access.course_ids` / `orders.course_ids`. Junction tables (`access_grant_courses`, `order_courses`) are dual-written when present and are **not** the read source of truth yet.

Full page inventory: [03-checkout.md](./03-checkout.md)

---

## 3. After pay: dashboard vs my-courses

```mermaid
flowchart TD
  Pay[user_access row exists] --> D["/dashboard — student home"]
  Pay --> Alias["/my-courses and /dashboard/my-packs redirect to /dashboard"]
  D --> Course["/my-courses/{slug}"]
  Course --> Study["?module=&tab=notes|formula_sheet|flashcards|quiz|exam_review|mistake_fixes|exercises"]
  D --> Lib["/dashboard/library"]
  D --> Prog["/dashboard/progress"]
  D --> Sem["/dashboard/semester-plan"]
  D --> Set["?settings=profile|notifications|billing|security|danger|help"]
```

`/dashboard/course/{slug}/…` redirects to `/my-courses/{slug}`.  
`/my-courses/library|progress|semester-plan` redirect to the `/dashboard/…` twins.

Someone who is logged in but **has not paid** can still open `/dashboard` (empty onboarding banner). `/my-courses` without a slug redirects to `/dashboard`. Course URLs `/my-courses/{slug}` still require a paid grant.

Full page inventory: [04-dashboard-and-workspace.md](./04-dashboard-and-workspace.md)

---

## 4. Settings (every tab)

Opened over the workspace (not a standalone `/settings` page). Gear, account flyout **Settings**, or `/dashboard?settings={tab}`. Query is stripped after open. Legacy `/settings/*` redirects into these tabs.

| Tab id | Label | What it does |
|---|---|---|
| `profile` | Profile | Edit name, nickname, photo, university/campus/major; badge display |
| `notifications` | Notifications | Four email/reminder toggles on `profiles.notification_preferences` |
| `billing` | Plan & billing | Active `user_access` + Whish `orders` history; **Upgrade or change plan** → `/pricing` |
| `security` | Security | Password, Google link/unlink, phone display, backup password, this-device time |
| `danger` | Danger zone | Sign out → `/`; delete account (type `DELETE`) |
| `help` | Help & contact | WhatsApp, request a course, policy links |

Dark mode and extra languages are not shipped. Appearance and Language panes and server actions were removed. `/settings/appearance` and `/settings/language` still redirect to profile.

Full inventory: [05-settings.md](./05-settings.md)

---

## 5. Study: how a workspace is used

```mermaid
flowchart TD
  Card[Dashboard course card] --> OV["/my-courses/{slug} Overview"]
  OV --> Notes["Notes markdown"]
  OV --> Form["Formula sheet"]
  OV --> FC["Flashcards"]
  OV --> Quiz["Quiz / Exam"]
  OV --> ER["Exam review"]
  OV --> MF["Mistake fixes"]
  OV --> EX["Exercises"]
  Notes --> CP[(content_progress)]
  FC --> FS[(flashcard_sessions / card_states / review_logs)]
  Quiz --> QA[(quiz_attempts + content_progress)]
  Notes --> Lib[(library_items save)]
  Heartbeat[CourseStudyHeartbeat] --> SS[(study_sessions)]
  SS --> Streak[Sidebar streak / Progress]
```

Course settings (`/my-courses/{slug}/settings`) writes `flashcard_deck_settings` and can upload `syllabuses`. Exam dates are read from `course_events`.

Full inventory: [06-study-views.md](./06-study-views.md)

---

## 6. End-to-end student path (one sitting)

1. Land on `/` or `/pricing` → **Get Focus** (or another plan CTA).
2. `/checkout?plan=focus` → if logged out, **Sign in** / **Create account** with `next` back to checkout.
3. New account: verify email if needed → `/auth/home` → `/welcome` (name, AUB/LAU, major) → `/dashboard` (still no courses).
4. Return to checkout (`next`) → pick course(s) → details → **Continue to secure payment** → Whish OTP.
5. `/checkout/payment/success` polls until **Payment confirmed** → **Go to my courses**.
6. Dashboard shows the course card → open `/my-courses/{slug}` → Notes / Flashcards / Quiz.
7. Gear → Settings: nickname, photo, notification toggles, billing (active access), security.
8. Sidebar Progress / Library / Semester Plan read the same study writes (`content_progress`, `study_sessions`, `library_items`, `exam_schedules`, `course_events`). Syllabus upload is optional and private.

---

## 7. Connection cheat sheet

| From | To | How |
|---|---|---|
| Header **Get access** / **Add more courses** | `/checkout` | `navCheckoutHref` |
| Header **Sign in** | `/login` | — |
| Header **Dashboard** (mobile logged-in) | `/dashboard` | — |
| Account flyout **Upgrade plan** | `/pricing` | then back into checkout |
| Account flyout **Settings** / **Get help** | Settings modal or `/dashboard?settings=` | workspace vs marketing header |
| Checkout success **Go to my courses** | `/dashboard` | after `user_access` grant |
| Dashboard course card | `/my-courses/{slug}` | `buildCourseOverviewHref` |
| Course **Get access** (public catalog) | `/checkout?plan=focus&course={id}` | preselects that course |
| Non-subscriber `/my-courses` | `/dashboard` (free-state home) | alias redirect; course slugs still gated |
| Non-subscriber `/my-courses/{slug}` | `/pricing` | proxy |
| Incomplete profile any workspace page | `/welcome` | layout |
| Logout | `/` | Auth sign-out |
| Delete account | `/` (or `/login` if unauthorized) | `auth.admin.deleteUser` |

---

## 8. Tables the student product actually uses

| Table | Written by | Read by |
|---|---|---|
| `auth.users` | signup, password change, delete | login, settings security |
| `profiles` | signup trigger, welcome, profile/avatar/notifications, nickname | header, dashboard greeting, settings, `/auth/home` |
| `orders` | Whish checkout create + reconcile | payment return status, settings billing |
| `user_access` | `grantAccess` after paid Whish | dashboard courses, billing, proxy `/my-courses`, nav CTA |
| `courses` / `course_packs` / `universities` / `course_modules` / `module_pack_items` | admin | catalog, checkout picker, study |
| `content_progress` | notes, quiz complete, flashcard scheduled review | overview, resume, progress |
| `flashcard_sessions`, `flashcard_card_states`, `flashcard_review_logs`, `flashcard_deck_settings` | flashcard UI / course settings | flashcards + progress |
| `quiz_attempts` | quiz complete | progress quiz notes |
| `study_sessions` | study heartbeat | streak, study hours, progress |
| `library_items` | save note / upload | library |
| `syllabuses` | syllabus modal | semester plan, course settings |
| `course_events` | admin / syllabus parse | exam banner, semester plan, course settings |
| `notifications` | server triggers | dashboard bell |
| `user_badges` | streak/badge jobs | settings Profile “Showing up” |
| `user_streaks` | streak compute | sidebar streak card |
