278 lines
10 KiB
Markdown
278 lines
10 KiB
Markdown
# 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.
|
||
> Cập nhật file này mỗi khi có quyết định kiến trúc mới.
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
### Frontend
|
||
| Layer | Tech | Ghi chú |
|
||
|---|---|---|
|
||
| Framework | **React** (Vite) | |
|
||
| Routing | **TanStack Router** | Type-safe routing |
|
||
| Server state | **TanStack Query** | Fetch, cache, sync API data |
|
||
| Client state | **Zustand** | UI state, localStorage sync |
|
||
| Styling | **Tailwind CSS** | Mobile-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 |
|
||
|---|---|
|
||
| Framework | **NestJS** |
|
||
| ORM | **Prisma** hoặc **TypeORM** |
|
||
| Database | **PostgreSQL** (self-hosted) |
|
||
| Auth | **JWT** + Google OAuth + Zalo OAuth |
|
||
| Mobile | **Flutter** (iOS + Android) |
|
||
|
||
### 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.
|
||
|
||
### 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 — Phase 1)
|
||
|
||
```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 sẽ thêm: users, user_progress, writing_submissions, test_sessions
|
||
```
|
||
|
||
---
|
||
|
||
## API Design
|
||
|
||
### Supabase SDK (Phase 1)
|
||
```typescript
|
||
// Lấy câu hỏi theo Part
|
||
supabase.from('questions').select('*').eq('part', 1).limit(10)
|
||
|
||
// Lấy từ vựng theo chủ đề
|
||
supabase.from('vocab').select('*').eq('topic', 'business')
|
||
```
|
||
|
||
### 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)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Cấu trúc thư mục (React)
|
||
|
||
```
|
||
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
|
||
├── components/
|
||
│ ├── QuestionCard.tsx
|
||
│ ├── FlashCard.tsx
|
||
│ ├── WritingFeedback.tsx
|
||
│ ├── ProgressBar.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)
|
||
├── lib/
|
||
│ └── supabase.ts ← Supabase client init
|
||
└── utils/
|
||
└── rateLimiter.ts ← Rate limit AI Writing (3 lần/ngày/IP, localStorage)
|
||
```
|
||
|
||
---
|
||
|
||
## Routes (TanStack Router)
|
||
|
||
```
|
||
/ ← Landing page
|
||
/toeic ← Chọn Part (1–7)
|
||
/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)
|
||
```
|
||
|
||
---
|
||
|
||
## 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ủ đề
|
||
|
||
---
|
||
|
||
## Tính năng KHÔNG có ở Phase 1
|
||
|
||
| 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+ |
|
||
|
||
---
|
||
|
||
## Timeline Phase 1 (5 tuần)
|
||
|
||
| 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 |
|
||
|
||
---
|
||
|
||
## Definition of Done — Phase 1
|
||
|
||
- [ ] ≥ 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 có critical bug sau 1 tuần beta
|
||
|
||
---
|
||
|
||
## 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-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 |
|
||
|
||
---
|
||
|
||
## Quyết định kiến trúc quan trọng
|
||
|
||
| Quyết định | Lý do |
|
||
|---|---|
|
||
| Không có 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 2–3 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 |
|
||
|
||
---
|
||
|
||
## Conventions
|
||
|
||
- **Ngôn ngữ**: Tiếng Việt cho UI người dùng, English cho code/comments
|
||
- **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, ví 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 |