# Claude Project Context — English Learning App (TOEIC Focus)

> File này cung cấp context đầy đủ cho Claude khi làm việc với dự án.
> Cập nhật file này mỗi khi có quyết định kiến trúc mới.
> **Last updated**: Phase 2 done — thêm Phase 3 Retention & Monetization, đẩy Speaking AI và Full TOEIC sang Phase 4 & 5

---

## Tổng quan dự án

**Loại sản phẩm**: Web app học tiếng Anh / luyện thi TOEIC & IELTS cho người dùng Việt Nam
**Target users**: Sinh viên, người đi làm tại Việt Nam cần TOEIC, IELTS, hoặc học tiếng Anh tổng quát
**Focus chính**: TOEIC (mở rộng market), sau đó IELTS
**Giai đoạn hiện tại**: Phase 1 — MVP, validate market
**Roadmap**: 5 phases — MVP → Auth & Progress → Retention & Monetization → Speaking AI → Full TOEIC

---

## Tech Stack

### Frontend
| Layer | Tech | Ghi chú |
|---|---|---|
| Framework | **React** + **Vite** + **TypeScript** | |
| Routing | **TanStack Router** | File-based, type-safe |
| Server state | **TanStack Query** | Fetch, cache, sync API data |
| Client state | **Zustand** | UI state + localStorage persist |
| Styling | **Tailwind CSS** | Desktop-first |
| UI Components | **shadcn/ui** | Dùng khi cần, không bắt buộc |

### Design System (từ Stitch export)
| Token | Value |
|---|---|
| Font | Plus Jakarta Sans + Material Symbols Outlined |
| Primary | #2563EB |
| Success | #16A34A |
| Danger | #DC2626 |
| Background | #F8FAFC |
| Card | #FFFFFF |
| Border radius | 12–16px |
| Shadow | soft, subtle |

### Responsive Layout
| Breakpoint | Layout |
|---|---|
| Desktop (1280px) | Sidebar trái cố định (240px) + main content — **LAYOUT CHÍNH** |
| Tablet (768px) | Sidebar thu gọn icon-only |
| Mobile (375px) | Ẩn sidebar, hiện bottom navigation bar |

### Backend
| Phase | Tech | Ghi chú |
|---|---|---|
| Phase 1 | **Supabase** (PostgreSQL + Edge Functions + JS SDK) | Tạm thời, migrate sau |
| Phase 2+ | **NestJS** + **PostgreSQL** native | Khi có traction |

> ⚠️ Supabase chỉ dùng Phase 1. Schema PostgreSQL thiết kế chuẩn ngay từ đầu để migrate không đau.

### AI
| Layer | Tech | Ghi chú |
|---|---|---|
| Provider | **GLM (Z.ai API)** | Rẻ, OpenAI-compatible format |
| Endpoint | `open.bigmodel.cn/api/paas/v4` | |
| Model | GLM-4 / GLM-4.7 | |
| Fallback | OpenAI / Claude API | Swap dễ vì API compatible |
| Gọi từ | Supabase Edge Function (Phase 1) → NestJS service (Phase 2+) | Giấu API key |

### Deploy
| Layer | Tech |
|---|---|
| Frontend | Self-hosted server (có sẵn) |
| Backend | Self-hosted server (có sẵn) |
| Database | Supabase Cloud (Phase 1) → self-hosted PostgreSQL (Phase 2+) |

---

## Database Schema (PostgreSQL)

```sql
-- Câu hỏi TOEIC
CREATE TABLE questions (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  part        INT NOT NULL,           -- 1 đến 7
  type        TEXT,                   -- photo | q&a | incomplete_sentence | etc.
  content     TEXT NOT NULL,          -- nội dung câu hỏi / đoạn văn
  options     JSONB,                  -- ["A. ...", "B. ...", "C. ...", "D. ..."]
  answer      TEXT NOT NULL,          -- "A"
  explanation TEXT,                   -- giải thích đáp án bằng tiếng Việt
  audio_url   TEXT,                   -- Part 1–4 (listening)
  image_url   TEXT,                   -- Part 1 (photos)
  created_at  TIMESTAMPTZ DEFAULT now()
);

-- Từ vựng TOEIC
CREATE TABLE vocab (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  word        TEXT NOT NULL,
  phonetic    TEXT,                   -- /wɜːrd/
  meaning_vi  TEXT NOT NULL,          -- nghĩa tiếng Việt
  topic       TEXT NOT NULL,          -- business | office | travel | finance | hr | marketing
  example     TEXT,                   -- câu ví dụ tiếng Anh
  created_at  TIMESTAMPTZ DEFAULT now()
);

-- Phase 2+
CREATE TABLE users (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email       TEXT UNIQUE NOT NULL,
  name        TEXT NOT NULL,
  password    TEXT NOT NULL,          -- hashed
  created_at  TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE user_progress (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id       UUID REFERENCES users(id),
  type          TEXT,                 -- test | vocab | writing
  reference_id  UUID,
  data          JSONB,
  created_at    TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE writing_submissions (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     UUID REFERENCES users(id),
  content     TEXT NOT NULL,
  feedback    JSONB,
  created_at  TIMESTAMPTZ DEFAULT now()
);
```

---

## TypeScript Interfaces

```typescript
interface Question {
  id: string
  part: number
  type: string
  content: string
  options: string[]
  answer: string
  explanation: string
  audioUrl?: string
  imageUrl?: string
}

interface VocabWord {
  id: string
  word: string
  phonetic: string
  meaningVi: string
  topic: 'business' | 'office' | 'travel' | 'finance' | 'hr' | 'marketing'
  example: string
}

interface TestResult {
  testId: string
  part: number
  score: number
  total: number
  duration: number
  answers: { questionId: string; selected: string; correct: boolean }[]
  completedAt: Date
}

interface WritingFeedback {
  score: string
  grammar: string[]
  vocabulary: string[]
  structure: string
  improvedVersion: string
  summary: string
}

// Phase 2+
interface User {
  id: string
  email: string
  name: string
  createdAt: Date
}
```

---

## Cấu trúc thư mục

```
src/
├── routes/
│   ├── index.tsx              ← Trang chủ (/)
│   ├── toeic/
│   │   ├── index.tsx          ← Chọn Part (/toeic)
│   │   ├── part.$partId.tsx   ← Config số câu (/toeic/part/$partId)
│   │   ├── session.tsx        ← Làm bài (/toeic/session)
│   │   └── result.tsx         ← Kết quả + đáp án (/toeic/result)
│   ├── writing.tsx            ← AI Writing Checker (/writing)
│   ├── vocab.tsx              ← Flashcard (/vocab)
│   └── auth/                  ← Phase 2
│       ├── login.tsx          ← Đăng nhập (/auth/login)
│       └── register.tsx       ← Đăng ký (/auth/register)
├── components/
│   ├── layout/
│   │   ├── Sidebar.tsx        ← Desktop sidebar
│   │   └── BottomNav.tsx      ← Mobile bottom nav
│   ├── QuestionCard.tsx
│   ├── FlashCard.tsx
│   ├── WritingFeedback.tsx
│   ├── ProgressRing.tsx
│   └── Timer.tsx
├── store/
│   ├── testStore.ts           ← Zustand: trạng thái bài thi
│   ├── vocabStore.ts          ← Zustand: flashcard progress (persist localStorage)
│   └── authStore.ts           ← Zustand: user session (Phase 2)
├── hooks/
│   ├── useQuestions.ts        ← TanStack Query
│   ├── useVocab.ts            ← TanStack Query
│   └── useWritingCheck.ts     ← TanStack Mutation → Edge Function
├── lib/
│   └── supabase.ts            ← Supabase client init
└── utils/
    └── rateLimiter.ts         ← Rate limit 3 lần/ngày/IP (localStorage)
```

---

## Supabase Edge Function — AI Writing Checker

```typescript
// supabase/functions/writing-check/index.ts
import { serve } from "https://deno.land/std/http/server.ts"

serve(async (req) => {
  const { content } = await req.json()

  const response = await fetch("https://open.bigmodel.cn/api/paas/v4/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${Deno.env.get("GLM_API_KEY")}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "glm-4",
      messages: [
        {
          role: "system",
          content: `You are an expert English writing evaluator for TOEIC/IELTS.
Evaluate the writing and return ONLY valid JSON:
{
  "score": "estimated band score",
  "grammar": ["error + fix"],
  "vocabulary": ["suggestion"],
  "structure": "feedback in Vietnamese",
  "improved_version": "rewritten version",
  "summary": "overall feedback in Vietnamese"
}`
        },
        { role: "user", content }
      ]
    })
  })

  const data = await response.json()
  const result = JSON.parse(data.choices[0].message.content)
  return new Response(JSON.stringify(result), {
    headers: { "Content-Type": "application/json" }
  })
})
```

---

## Roadmap — 4 Phases

---

### PHASE 1 — MVP (Hiện tại) 🚧

**Mục tiêu**: Ra sản phẩm web dùng thử không cần login, validate market

**Stack**: React + Vite + TypeScript + TanStack + Zustand + Tailwind + Supabase + GLM

**Tính năng**:
- ✅ Luyện đề TOEIC mini test theo từng Part (Part 1–7)
- ✅ Chọn số câu: 10 / 20 / full part, có đếm giờ
- ✅ Submit → xem điểm + đáp án + giải thích tiếng Việt
- ✅ Lịch sử kết quả + thống kê điểm yếu theo Part (localStorage)
- ✅ AI Writing Checker (GLM, 3 lần/ngày/IP, không cần login)
- ✅ Flashcard từ vựng TOEIC (6 chủ đề, localStorage progress)

**Không có**:
- ❌ Auth / login
- ❌ Progress sync server
- ❌ Thanh toán
- ❌ Flutter / mobile app
- ❌ Full mock test

**Timeline**: 5 tuần

**Done khi**:
- ≥ 50 câu hỏi mỗi Part (Part 1–7)
- AI Writing Checker phản hồi < 5 giây
- Không lỗi hiển thị trên Chrome mobile
- 20+ người dùng thật đã dùng thử
- Không critical bug sau 1 tuần beta

---

### PHASE 2 — Auth & Progress

**Mục tiêu**: Giữ chân user, sync progress server-side, hiểu behavior trước khi monetize

**Trigger**: Phase 1 có traction — 200+ MAU hoặc feedback tích cực

**Stack thay đổi**:
- Migrate Supabase → **NestJS + PostgreSQL native**
- Thêm **Redis** cho cache + session

**Guest Access (chưa đăng ký)**:
- Xem preview 1 bài test dạng read-only (thấy câu hỏi, không làm được)
- Không cho submit đáp án, không xem kết quả
- Hiện modal "Đăng ký để luyện thi" khi cố tương tác
- Flashcard: xem vài card đầu, bị chặn sau đó
- AI Writing: không dùng được, hiện CTA đăng ký

**Auth — Đăng ký**:
- Form: **Tên + Email + Password** (chỉ 3 field)
- Không xác thực email, không OTP, không confirm
- Đăng ký xong → **redirect home luôn**

**Auth — Đăng nhập**:
- Email + Password
- Redirect về trang trước hoặc home

**Sau khi đăng nhập**:
- Làm bài không giới hạn
- Progress sync server-side: lịch sử thi, từ vựng, writing submissions
- Dashboard cá nhân: streak, biểu đồ điểm, điểm yếu theo Part
- AI Writing: 10 lần/ngày

**Không có ở Phase 2**:
- ❌ Xác thực email
- ❌ Quên mật khẩu
- ❌ Google / Zalo OAuth
- ❌ Flutter / mobile app
- ❌ Thanh toán / freemium
- ❌ Notification

---

### PHASE 3 — Xu System & Gamification ← Tiếp theo

**Mục tiêu**: Tạo thói quen học hàng ngày, giữ chân user bằng Xu economy + gamification
**Platform**: Web only (không Flutter ở phase này)
**Trigger**: Phase 2 có user đăng ký, cần convert sang returning users

---

#### Xu Economy — Trung tâm của Phase 3

```
Học hàng ngày / xem ads → kiếm Xu
           ↓
    Xu → dùng tính năng premium
           ↓
       Hết Xu → xem ads thêm
           (nạp tiền thật → Phase 4)
```

**Kiếm Xu (miễn phí)**:
| Hành động | Xu nhận |
|---|---|
| Đăng ký lần đầu (welcome bonus) | 50 Xu |
| Hoàn thành daily goal | 10 Xu |
| Streak milestone (7 / 30 / 100 ngày) | 20 / 50 / 100 Xu |
| Xem rewarded ads trên web | 5 Xu / video (tối đa 5 video/ngày) |

**Dùng Xu**:
| Tính năng | Chi phí |
|---|---|
| Streak freeze (bảo vệ 1 ngày) | 20 Xu |
| Thêm 5 lượt AI Writing (GLM-4.7) | 30 Xu |
| 1 lượt AI Writing cao cấp (GPT-4o) | 15 Xu |
| Mở thêm bài thi khi hết giới hạn free | 10 Xu |

> ⚠️ Chưa có nạp Xu bằng tiền thật ở Phase 3 — chỉ kiếm qua học + ads.
> Nạp tiền thật (VNPay/MoMo) → Phase 4.

---

#### AI Model Tier
| Tier | Model | Free limit | Dùng Xu |
|---|---|---|---|
| Free | GLM-4 base | 3 lần/ngày | — |
| Standard | GLM-4.7 | — | 30 Xu / 5 lượt |
| Premium | GPT-4o / Claude | — | 15 Xu / lượt |

---

#### Gamification
- **Streak hàng ngày**: học ít nhất 1 bài/ngày giữ chuỗi
- **XP points**: mỗi bài thi / flashcard / writing check → XP
- **Cấp độ**: Beginner → Bronze → Silver → Gold → Master (theo XP tích luỹ)
- **Streak freeze**: dùng Xu bảo vệ streak khi bận — hook mạnh nhất
- **Weekly goal**: đặt mục tiêu tuần, hoàn thành → badge + thưởng Xu

#### Leaderboard
- Bảng xếp hạng tuần theo XP (reset mỗi tuần, không tích luỹ)
- Hiện top 10 + vị trí của bản thân
- Chia sẻ kết quả lên Facebook/Zalo — viral loop tự nhiên

#### Nhắc nhở
- Browser push notification (web, không cần app)
- Giờ nhắc do user tự chọn
- Message cá nhân hoá: *"Streak 7 ngày của bạn sắp mất!"*

#### Lộ trình AI cá nhân hoá
- Phân tích kết quả thi → suggest *"Tuần này tập trung Part 5 và 6"*
- Dashboard: *"Bạn yếu nhất ở Part 5 — luyện ngay"*
- Đặt ngày thi TOEIC → đếm ngược + lịch ôn gợi ý

#### Web Ads
- Google AdSense: banner dưới trang + interstitial sau kết quả bài thi
- Rewarded video ads: xem để nhận Xu
- Không ads trong lúc đang làm bài
- User có đủ Xu / premium → không hiện ads (Phase 4)

---

#### DB Schema bổ sung
```sql
CREATE TABLE user_gamification (
  user_id      UUID REFERENCES users(id) PRIMARY KEY,
  xp           INT DEFAULT 0,
  level        TEXT DEFAULT 'beginner', -- beginner | bronze | silver | gold | master
  streak       INT DEFAULT 0,
  longest_streak INT DEFAULT 0,
  last_active  DATE,
  xu           INT DEFAULT 50,          -- welcome bonus
  freeze_count INT DEFAULT 0
);

CREATE TABLE xu_transactions (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     UUID REFERENCES users(id),
  type        TEXT,   -- earn_welcome | earn_daily | earn_streak | earn_ads | spend_freeze | spend_writing | spend_test
  amount      INT,    -- dương = nhận, âm = tiêu
  balance     INT,    -- số Xu sau giao dịch (để audit)
  description TEXT,
  created_at  TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE weekly_leaderboard (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     UUID REFERENCES users(id),
  week_start  DATE,
  xp_earned   INT DEFAULT 0,
  rank        INT
);
```

**Tech mới**:
- Google AdSense (banner + rewarded video)
- Browser Push Notification API
- Cron job: reset leaderboard mỗi tuần, check streak hàng ngày

**Không có ở Phase 3**:
- ❌ Nạp tiền thật (VNPay / MoMo) → Phase 4
- ❌ Flutter / mobile app → Phase 4
- ❌ Subscription / Premium plan → Phase 4

**Timeline**: 5–6 tuần

---

### PHASE 4 — Speaking AI

**Mục tiêu**: Tăng differentiation, cover kỹ năng Speaking cho IELTS/TOEIC

**Trigger**: Phase 3 ổn định, có doanh thu đều

**Tính năng**:
- AI Speaking Coach: record giọng → AI chấm phát âm + so sánh native speaker
- Luyện IELTS Speaking Part 1/2/3 (mock interview với AI)
- Shadow reading: nghe → lặp lại → AI so sánh độ chính xác
- Pronunciation scoring có điểm số và progress chart

**Tech mới**:
- Speech-to-text: Whisper API hoặc Google Speech-to-Text
- Text-to-speech: native audio
- WebRTC / MediaRecorder API (web) + Flutter audio recording

---

### PHASE 5 — Full TOEIC Mock Test

**Mục tiêu**: Platform luyện TOEIC toàn diện chuẩn ETS

**Trigger**: Phase 4 xong, cần nội dung premium cao cấp hơn

**Tính năng**:
- Full TOEIC test chuẩn ETS: 200 câu, 120 phút
- Audio Listening chuẩn cho Part 1–4
- Auto-score: tính điểm 10–990 theo bảng quy đổi ETS
- Phân tích chi tiết: điểm mạnh/yếu từng Part, so sánh lần trước
- Bộ đề theo năm: ETS 2023, 2024, Actual Test...
- Đếm ngược đến ngày thi + lịch ôn tập gợi ý

---

## Quyết định kiến trúc quan trọng

| Quyết định | Lý do |
|---|---|
| Không auth Phase 1 | Giảm scope, validate market trước |
| Email only Phase 2, không OAuth | Đơn giản nhất để build, OAuth Phase 4+ |
| Không xác thực email Phase 2 | MVP — giảm friction đăng ký tối đa |
| Chỉ 3 field đăng ký (tên/email/pass) | Friction thấp nhất, đủ để identify user |
| Guest chỉ xem preview, không làm được | Buộc đăng ký để dùng, giúp thu thập user data |
| Không thanh toán Phase 2 | Hiểu behavior trước khi charge tiền |
| Không Flutter Phase 2 | Web đã responsive, Flutter Phase 3 khi có traction |
| Coins tên "Xu" | Gần gũi user VN hơn "Credits" hay "Points" |
| Pay-as-you-go thay subscription | User VN ít cam kết dài hạn, mua theo nhu cầu dễ convert hơn |
| AI model tier theo Xu | Tạo upsell tự nhiên — user thấy feedback tốt hơn khi dùng model cao hơn |
| Rewarded ads mobile, banner ads web | Phù hợp từng platform — mobile chịu video, web chịu banner |
| Supabase tạm Phase 1 | Ra nhanh hơn 2–3 tuần, schema chuẩn để migrate sau |
| GLM thay OpenAI/Claude | Rẻ hơn, OpenAI-compatible, swap dễ |
| Desktop-first | Target TOEIC learner hay dùng máy tính |
| TanStack Query + Zustand | Server state tách biệt client state rõ ràng |
| NestJS Phase 2 | Supabase đủ để validate, NestJS khi scale |
| Speaking AI Phase 4 | Cần infra + monetization ổn định trước khi làm realtime audio |
| Full mock test Phase 5 | Cần content team + audio, không phải tech problem |

---

## Conventions

- **Ngôn ngữ**: Tiếng Việt cho UI người dùng, English cho code/comments/type names
- **Giải thích đáp án**: Luôn bằng tiếng Việt
- **AI feedback**: Nhận xét tổng thể tiếng Việt, ví dụ sửa tiếng Anh
- **Desktop-first**: Design và test trên 1280px trước, mobile sau
- **YAGNI / KISS**: Không build thứ chưa cần, từng Phase giải quyết từng vấn đề
- **Schema chuẩn ngay từ đầu**: Dù dùng Supabase, PostgreSQL schema phải production-ready
- **Coins = "Xu"**: Dùng nhất quán trong code lẫn UI

---

## Rủi ro đã nhận diện

| Rủi ro | Mức độ | Xử lý |
|---|---|---|
| Content đề TOEIC chất lượng thấp (crawl) | 🔴 Cao | Crawl ít + clean kỹ, tự soạn dần thay thế |
| GLM chấm writing không đủ tin cậy | 🟡 TB | Test prompt kỹ, fallback OpenAI nếu cần |
| Latency GLM từ VN cao | 🟡 TB | Benchmark thực tế, cache response nếu cần |
| User mất progress (localStorage Phase 1) | 🟡 TB | Chấp nhận Phase 1, auth Phase 2 giải quyết |
| Bản quyền đề TOEIC crawl | 🟡 TB | Seed nhanh, thay bằng nội dung tự soạn dần |
| Supabase free tier limit | 🟢 Thấp | 500MB đủ Phase 1, migrate trước khi hit limit |
| AdSense bị block (adblocker) | 🟡 TB | Rewarded ads mobile bù lại, không phụ thuộc 1 nguồn |
| VNPay/MoMo integration phức tạp | 🟡 TB | Dùng payment gateway trung gian (Stripe VN, PayOS) |
| Audio quality Phase 5 | 🟡 TB | Budget cho studio recording hoặc TTS premium |