diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/english.iml b/.idea/english.iml
new file mode 100644
index 0000000..c956989
--- /dev/null
+++ b/.idea/english.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..766912b
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..33f2143
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Claude.md b/Claude.md
index 1198153..3d689b9 100644
--- a/Claude.md
+++ b/Claude.md
@@ -2,7 +2,7 @@
> 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 2–4 planned
+> **Last updated**: Phase 2 done — thêm Phase 3 Retention & Monetization, đẩy Speaking AI và Full TOEIC sang Phase 4 & 5
---
@@ -12,7 +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
-**Roadmap**: 4 phases — MVP → Auth & Progress → Speaking AI → Full TOEIC
+**Roadmap**: 5 phases — MVP → Auth & Progress → Retention & Monetization → Speaking AI → Full TOEIC
---
@@ -349,11 +349,136 @@ Evaluate the writing and return ONLY valid JSON:
---
-### PHASE 3 — Speaking AI
+### 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 2 ổn định, user quay lại đều đặn
+**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
@@ -364,15 +489,15 @@ Evaluate the writing and return ONLY valid JSON:
**Tech mới**:
- Speech-to-text: Whisper API hoặc Google Speech-to-Text
- Text-to-speech: native audio
-- WebRTC / MediaRecorder API (web)
+- WebRTC / MediaRecorder API (web) + Flutter audio recording
---
-### PHASE 4 — Full TOEIC Mock Test
+### PHASE 5 — Full TOEIC Mock Test
**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
+**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
@@ -389,20 +514,23 @@ Evaluate the writing and return ONLY valid JSON:
| 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 3+ |
+| 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, mobile app khi có traction rõ ràng |
+| 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 (không mobile-first) | Target TOEIC learner hay dùng máy tính |
+| 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 |
-| 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 |
+| 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 |
---
@@ -414,6 +542,7 @@ Evaluate the writing and return ONLY valid JSON:
- **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
---
@@ -427,4 +556,6 @@ Evaluate the writing and return ONLY valid JSON:
| 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 |
\ No newline at end of file
+| 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 |
\ No newline at end of file
diff --git a/src/components/MobileNav.tsx b/src/components/MobileNav.tsx
index d9d1d2f..0896c05 100644
--- a/src/components/MobileNav.tsx
+++ b/src/components/MobileNav.tsx
@@ -3,9 +3,10 @@ import { cn } from '@/lib/utils'
const NAV_ITEMS = [
{ to: '/', label: 'Home', icon: 'home', matchPrefix: '/', exact: true },
+ { to: '/dashboard', label: 'Thành tích', icon: 'emoji_events', matchPrefix: '/dashboard', exact: false },
{ to: '/toeic', label: 'Luyện đề', icon: 'assignment', matchPrefix: '/toeic', exact: false },
{ to: '/writing', label: 'Writing', icon: 'edit_note', matchPrefix: '/writing', exact: false },
- { to: '/vocab', label: 'Từ vựng', icon: 'menu_book', matchPrefix: '/vocab', exact: false },
+ { to: '/settings', label: 'Cài đặt', icon: 'settings', matchPrefix: '/settings', exact: false },
]
function isActive(pathname: string, prefix: string, exact: boolean) {
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 19fab5e..c2e83a5 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -5,9 +5,11 @@ import { useAuthModalStore } from '@/store/auth-modal-store'
const NAV_ITEMS = [
{ to: '/', label: 'Trang chủ', icon: 'home', matchPrefix: '/', exact: true },
+ { to: '/dashboard', label: 'Thành tích', icon: 'emoji_events', matchPrefix: '/dashboard', exact: false },
{ to: '/toeic', label: 'Luyện đề TOEIC', icon: 'assignment', matchPrefix: '/toeic', exact: false },
{ to: '/writing', label: 'AI Writing', icon: 'edit_note', matchPrefix: '/writing', exact: false },
{ to: '/vocab', label: 'Từ vựng', icon: 'menu_book', matchPrefix: '/vocab', exact: false },
+ { to: '/settings', label: 'Cài đặt', icon: 'settings', matchPrefix: '/settings', exact: false },
]
function isActive(pathname: string, prefix: string, exact: boolean) {
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..485ba26
--- /dev/null
+++ b/src/pages/Dashboard.tsx
@@ -0,0 +1,68 @@
+import { Link } from '@tanstack/react-router'
+import { useAuthStore } from '@/store/auth-store'
+import { useAuthModalStore } from '@/store/auth-modal-store'
+import { loadGamification } from './dashboard/gamification-store'
+import { StatsRow } from './dashboard/StatsRow'
+import { XpProgressCard } from './dashboard/XpProgressCard'
+import { WeeklySection } from './dashboard/WeeklySection'
+import { XuEconomyCard } from './dashboard/XuEconomyCard'
+import { LeaderboardCard } from './dashboard/LeaderboardCard'
+
+export function Dashboard() {
+ const user = useAuthStore((s) => s.user)
+ const openModal = useAuthModalStore((s) => s.open)
+
+ if (!user) {
+ return (
+
+ emoji_events
+
Bảng thành tích
+
+ Đăng nhập để xem streak, XP, Xu và bảng xếp hạng của bạn.
+