This commit is contained in:
2026-04-12 18:54:59 +07:00
parent 28e866a64e
commit ec3d400e8a
71 changed files with 7888 additions and 333 deletions

460
Claude.md
View File

@@ -1,7 +1,8 @@
# Claude Project Context — English Learning App (TOEIC Focus)
> File này dùng để cung cấp context đầy đủ cho Claude khi làm việc với dự án.
> 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**: Merged all decisions — Phase 1 scaffold done, Phase 24 planned
---
@@ -11,15 +12,7 @@
**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
---
## Mục tiêu Phase 1
- Ra sản phẩm web dùng thử được, **không cần đăng nhập**
- Validate 2 tính năng cốt lõi: luyện đề TOEIC + AI writing checker
- Test xem có người dùng thật, có traction không
- **Không over-engineer** — Supabase tạm thời, đổi sau nếu có traction
**Roadmap**: 4 phases — MVP → Auth & Progress → Speaking AI → Full TOEIC
---
@@ -28,59 +21,66 @@
### Frontend
| Layer | Tech | Ghi chú |
|---|---|---|
| Framework | **React** (Vite) | |
| Routing | **TanStack Router** | Type-safe routing |
| 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 sync |
| Styling | **Tailwind CSS** | Mobile-first |
| 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 |
### Backend (Phase 1 — tạm thời)
| Layer | Tech | Ghi chú |
|---|---|---|
| Database | **Supabase** (PostgreSQL managed) | Free tier, migrate sau |
| API | **Supabase JS SDK** | Gọi thẳng từ React |
| Server functions | **Supabase Edge Functions** (Deno) | Xử lý AI API call, giấu key |
> ⚠️ **Supabase chỉ dùng Phase 1** để ra sản phẩm nhanh.
> Phase 2 migrate sang **NestJS + PostgreSQL native** khi có traction.
> Schema PostgreSQL thiết kế chuẩn ngay từ đầu để migrate không đau.
### Backend (Phase 2 — kế hoạch)
| Layer | Tech |
### Design System (từ Stitch export)
| Token | Value |
|---|---|
| Framework | **NestJS** |
| ORM | **Prisma** hoặc **TypeORM** |
| Database | **PostgreSQL** (self-hosted) |
| Auth | **JWT** + Google OAuth + Zalo OAuth |
| Mobile | **Flutter** (iOS + Android) |
| Font | Plus Jakarta Sans + Material Symbols Outlined |
| Primary | #2563EB |
| Success | #16A34A |
| Danger | #DC2626 |
| Background | #F8FAFC |
| Card | #FFFFFF |
| Border radius | 1216px |
| 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)**`open.bigmodel.cn` | Rẻ, OpenAI-compatible |
| Model | **GLM-4** hoặc **GLM-4.7** | Test chất lượng chấm writing |
| Fallback | OpenAI / Claude API | Nếu GLM không đủ chất lượng |
> GLM API tương thích OpenAI format → swap provider không cần đổi code.
| 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) |
| 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 — Phase 1)
## 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.
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"
@@ -101,158 +101,286 @@ CREATE TABLE vocab (
created_at TIMESTAMPTZ DEFAULT now()
);
-- Phase 2 sẽ thêm: users, user_progress, writing_submissions, test_sessions
-- 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()
);
```
---
## API Design
## TypeScript Interfaces
### Supabase SDK (Phase 1)
```typescript
// Lấy câu hỏi theo Part
supabase.from('questions').select('*').eq('part', 1).limit(10)
interface Question {
id: string
part: number
type: string
content: string
options: string[]
answer: string
explanation: string
audioUrl?: string
imageUrl?: string
}
// Lấy từ vựng theo chủ đề
supabase.from('vocab').select('*').eq('topic', 'business')
```
interface VocabWord {
id: string
word: string
phonetic: string
meaningVi: string
topic: 'business' | 'office' | 'travel' | 'finance' | 'hr' | 'marketing'
example: string
}
### Supabase Edge Functions
```
POST /functions/v1/writing-check
Body : { content: string }
Return : {
score: string, // band score ước tính
grammar: string[], // lỗi ngữ pháp + gợi ý sửa
vocabulary: string[], // nhận xét từ vựng
structure: string, // nhận xét bố cục (tiếng Việt)
improved_version: string, // bài viết lại tốt hơn
summary: string // tổng nhận xét (tiếng Việt)
}
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 (React)
## Cấu trúc thư mục
```
src/
├── pages/
│ ├── Home.tsx Landing page + CTA
│ ├── ToeicPractice.tsx ← Chọn Part để luyện
│ ├── TestSession.tsx ← Làm bài (timer + câu hỏi)
│ ├── TestResult.tsx ← Kết quả + giải thích đáp án
│ ├── WritingChecker.tsx ← AI Writing Checker
│ └── Vocabulary.tsx ← Flashcard từ vựng
├── 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
│ ├── ProgressBar.tsx
│ ├── ProgressRing.tsx
│ └── Timer.tsx
├── hooks/
│ ├── useQuestions.ts ← TanStack Query: fetch questions
│ ├── useVocab.ts ← TanStack Query: fetch vocab
│ └── useWritingCheck.ts ← TanStack Mutation: gọi Edge Function
├── store/
│ ├── testStore.ts ← Zustand: trạng thái bài thi
── vocabStore.ts ← Zustand: progress flashcard (sync localStorage)
── 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 AI Writing (3 lần/ngày/IP, localStorage)
└── rateLimiter.ts ← Rate limit 3 lần/ngày/IP (localStorage)
```
---
## Routes (TanStack Router)
## Supabase Edge Function — AI Writing Checker
```
/ ← Landing page
/toeic ← Chọn Part (17)
/toeic/part/$partId ← Config bài thi (số câu)
/toeic/session ← Làm bài
/toeic/result ← Kết quả + đáp án
/writing ← AI Writing Checker
/vocab ← Flashcard (filter theo topic)
```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" }
})
})
```
---
## Tính năng Phase 1 (đã chốt)
### ✅ 1. Luyện đề TOEIC
- Mini test theo từng Part (Part 1 → Part 7)
- Chọn số câu: 10 / 20 / full part
- Làm bài có đếm giờ
- Submit → xem điểm + đáp án đúng/sai + giải thích tiếng Việt
- Lịch sử kết quả lưu **localStorage** (Zustand persist)
- Thống kê điểm yếu theo Part
### ✅ 2. AI Writing Checker (Core Differentiator)
- Nhập bài writing tự do (TOEIC email, IELTS task, general)
- Gọi GLM qua Supabase Edge Function (API key an toàn)
- Feedback JSON: band score, lỗi ngữ pháp, từ vựng, cấu trúc, bài mẫu
- Rate limit: **3 lần / ngày / IP** (không cần login)
- Hiển thị feedback có highlight, dễ đọc trên mobile
### ✅ 3. Flashcard Từ vựng TOEIC
- 6 chủ đề: Business, Office, Travel, Finance, HR, Marketing
- Mỗi card: từ + phiên âm + nghĩa Việt + câu ví dụ
- Flip card animation
- Đánh dấu: Đã thuộc / Cần ôn
- Progress lưu localStorage (Zustand persist)
- Filter theo chủ đề
## Roadmap — 4 Phases
---
## Tính năng KHÔNG có ở Phase 1
### PHASE 1 — MVP (Hiện tại) 🚧
| Tính năng | Khi nào có |
|---|---|
| Đăng nhập / Auth | Phase 2 |
| Progress sync server | Phase 2 |
| Flutter mobile app | Phase 2 |
| NestJS backend | Phase 2 |
| Full TOEIC mock test | Phase 2 |
| Thanh toán | Phase 2 |
| Speaking / Pronunciation AI | Phase 3+ |
| Cộng đồng / Forum | Phase 3+ |
| Lớp học / Giáo viên | Phase 3+ |
**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 17)
- ✅ 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 17)
- 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
---
## Timeline Phase 1 (5 tuần)
### PHASE 2 — Auth & Progress
| Tuần | Công việc |
|---|---|
| **1** | Setup Supabase schema + seed đề TOEIC (≥50 câu/part) + React + Vite + Tailwind + TanStack + Zustand |
| **2** | UI luyện đề: chọn Part → làm bài → kết quả + giải thích |
| **3** | Supabase Edge Function + GLM API → AI Writing Checker + rate limit |
| **4** | Flashcard UI + Zustand persist + mobile polish + landing page |
| **5** | Bug fix + test mobile thật + deploy lên server + beta ~20 người |
**Mục tiêu**: Giữ chân user, sync progress server-side, hiểu behavior trước khi monetize
**Trigger**: Phase 1 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 để 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
**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 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 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
---
## Definition of Done — Phase 1
### PHASE 3 — Speaking AI
- [ ] ≥ 50 câu hỏi mỗi Part (Part 17)
- [ ] 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
**Mục tiêu**: Tăng differentiation, cover kỹ năng Speaking cho IELTS/TOEIC
**Trigger**: Phase 2 ổn định, user quay lại đều đặn
**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 điểm số 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)
---
## Rủi ro đã nhận diện
### PHASE 4 — Full TOEIC Mock Test
| Rủi ro | Mức độ | Xử |
|---|---|---|
| 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-compatible nếu cần |
| Latency GLM từ VN cao | 🟡 TB | Benchmark thực tế tuần 3 |
| User mất progress (localStorage) | 🟡 TB | Chấp nhận MVP, auth Phase 2 giải quyết |
| Bản quyền đề TOEIC crawl | 🟡 TB | Dùng để seed nhanh, thay bằng nội dung tự soạn |
**Mục tiêu**: Platform luyện TOEIC toàn diện chuẩn ETS
**Trigger**: Phase 3 xong, cần nội dung premium
**Tính năng**:
- Full TOEIC test chuẩn ETS: 200 câu, 120 phút
- Audio Listening chuẩn cho Part 14
- Auto-score: tính điểm 10990 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 ý
---
@@ -260,19 +388,43 @@ src/
| Quyết định | do |
|---|---|
| Không Auth Phase 1 | Giảm scope, user dùng thử không cần tạo tài khoản |
| Supabase thay NestJS tạm | Ra nhanh hơn 23 tuần, schema chuẩn để migrate sau |
| GLM thay OpenAI/Claude | Rẻ hơn đáng kể, API compatible, đ để test |
| Web-only, không Flutter | Tập trung 1 platform, Flutter Phase 2 reuse API |
| TanStack Query + Zustand | TanStack cho server state, Zustand cho client/local state |
| localStorage cho progress | Đủ cho MVP, không cần backend phức tạp |
| 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 3+ |
| Không xác thực email Phase 2 | MVP giảm friction đăng tối đa |
| Chỉ 3 field đăng (tên/email/pass) | Friction thấp nhất, đủ để identify user |
| Guest chỉ xem preview, không làm được | Buộc đăng để 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, mobile app khi traction ràng |
| Supabase tạm Phase 1 | Ra nhanh hơn 23 tuần, schema chuẩn để migrate sau |
| GLM thay OpenAI/Claude | Rẻ hơn, OpenAI-compatible, swap dễ |
| Desktop-first (không mobile-first) | Target TOEIC learner hay dùng máy tính |
| TanStack Query + Zustand | Server state tách biệt client state ràng |
| localStorage Phase 1 | Đủ cho MVP, không cần backend phức tạp |
| NestJS Phase 2 | Supabase đủ để validate, NestJS khi scale |
| Speaking AI Phase 3 | Cần infra ổn định trước khi làm realtime audio |
| Full mock test Phase 4 | 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
- **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**: Mix Việt-Anh (nhận xét tổng thể tiếng Việt, dụ sửa tiếng Anh)
- **Mobile-first**: Test trên màn hình 375px trước, desktop sau
- **YAGNI / KISS**: Không build thứ chưa cần, Phase 1 xong mới nghĩ Phase 2
- **AI feedback**: Nhận xét tổng thể tiếng Việt, dụ sửa tiếng Anh
- **Desktop-first**: Design 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ùng Supabase, PostgreSQL schema phải production-ready
---
## Rủi ro đã nhận diện
| Rủi ro | Mức độ | Xử |
|---|---|---|
| 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 |
| Audio quality Phase 4 | 🟡 TB | Budget cho studio recording hoặc TTS premium |