19 KiB
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)
-- 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
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
// 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
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 |