From 271fdb25212728697c6a6141b63a8c2c60f80fb6 Mon Sep 17 00:00:00 2001 From: namdh861 Date: Sat, 22 Aug 2026 08:24:38 +0700 Subject: [PATCH] feat: add guide --- CLAUDE.md | 79 +++++ docs/ARCHITECTURE.md | 127 ++++++++ docs/CONVENTIONS.md | 82 +++++ docs/DESIGN.md | 722 +++++++++++++++++++++++++++++++++++++++++++ docs/PHASES.md | 222 +++++++++++++ docs/SETUP.md | 78 +++++ 6 files changed, 1310 insertions(+) create mode 100644 CLAUDE.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/CONVENTIONS.md create mode 100644 docs/DESIGN.md create mode 100644 docs/PHASES.md create mode 100644 docs/SETUP.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..17d6791 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project + +`aplp.frontend.react` — React frontend for APLP (Adaptive Personal Learning Platform), the learner-facing app (`aplp-web`). Thin client only: it calls the `aplp.backend.spring` REST API and holds no domain business rules (mastery/progress/adaptive decisions all come from the backend). + +## Commands + +```bash +npm run dev # Vite dev server +npm run build # tsc -b (typecheck) + vite build +npm run typecheck # tsc -b --noEmit +npm run lint # oxlint +npm run lint:fix +npm run format # prettier --write . +npm run format:check +npm test # vitest run (single run) +npm run test:watch # vitest watch mode +npm run test:coverage +``` + +Run a single test file: `npx vitest run src/features/auth/store/authStore.test.ts` (or `npx vitest src/path/to/File.test.tsx` for watch mode on one file). + +Backend must be running locally for the app to function against real APIs (default `VITE_API_BASE_URL=http://localhost:8080/api/v1`, see `.env.example`). + +## Architecture + +``` +src/ +├── app/ # entry, router (App.tsx), layouts, top-level pages, providers (QueryProvider) +├── api/ # axios client (src/api/client.ts), tokenStorage +├── features/ # vertical feature modules: auth (built), learner/course/learning/assessment/adaptive/... (planned per docs/PHASES.md) +│ └── / +│ ├── api/ # calls to backend endpoints for this feature +│ ├── components/ +│ ├── hooks/ +│ ├── pages/ # route-level views +│ ├── store/ # zustand store, if the feature needs one +│ └── types.ts +├── shared/ # shared components (src/shared/components), config (src/shared/config/env.ts) +└── types/ # cross-feature types / DTO contracts (src/types/api.ts) +``` + +Path alias `@/` → `src/` (configured in `vite.config.ts` and `tsconfig`). + +### Data flow + +UI component → hooks (React Query for server state, Zustand for cross-app client state, `useState` for local state) → `src/api` axios wrapper → backend REST API. + +- Server state (fetched/cached data) always goes through TanStack Query — never duplicate it into a Zustand store. +- All backend calls go through `src/api`; don't call axios/fetch directly from components. + +### Auth flow + +- Access token: in-memory only (`src/api/tokenStorage.ts`), never persisted — XSS mitigation. Cleared on reload. +- Refresh token: currently stored in `localStorage` by `tokenStorage.ts` (note: this differs from `docs/ARCHITECTURE.md`, which documents the original design as an HttpOnly cookie managed entirely by the backend — treat the code as source of truth, not the doc, if the two disagree). +- The axios client (`src/api/client.ts`) auto-handles `401`s: on a non-auth-endpoint 401 it calls the registered `RefreshHandler` once (deduped via a shared `refreshPromise`), retries the original request with the new token, and calls `UnauthorizedHandler` (logout) if refresh fails. Auth endpoints (`/auth/*`) are excluded from this retry to avoid loops. +- `registerAuthHandlers()` wires the auth feature's refresh/logout logic into the generic api client — the api layer stays auth-feature-agnostic. +- Route protection via `ProtectedRoute` (`src/features/auth/components/ProtectedRoute.tsx`), wrapping routes in `App.tsx`. +- `useAuthStore.getState().init()` runs once on app mount (`App.tsx`) to hydrate auth state. + +## Conventions (from docs/CONVENTIONS.md) + +- TypeScript everywhere; avoid `any`. Props via `interface`. +- Naming: components PascalCase, hooks `useX` camelCase, API functions camelCase, types/interfaces PascalCase. +- Test files sit next to source: `Component.test.tsx`. +- React Query keys: array form, e.g. `['course', id]`. +- Every fetch/save view handles loading, empty, error, success states, using the shared components in `src/shared/components` (`Spinner`, `EmptyState`, `ErrorState`, `ErrorBoundary`). +- Errors surfaced to users must be friendly messages derived from the backend's `ApiError` shape (`src/types/api.ts`) — never raw stack traces. + +## Docs + +`docs/ARCHITECTURE.md`, `docs/CONVENTIONS.md`, `docs/SETUP.md`, `docs/PHASES.md` — the latter maps each upcoming feature phase (learner profile, course, learning, assessment, adaptive, recommendation, assistance, account) to frontend work; check it before scaffolding a new `features/` module. + +## Design + +Alway using `docs/DESIGN.md` file to code everything relevant to style and UI \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..bf19e38 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,127 @@ +# APLP Frontend — Architecture + +| | | +| --- | --- | +| **Module** | `aplp.frontend.react` | +| **Status** | Phase 0 hoàn tất | + +## 1. Role + +Client mỏng cho `aplp.web`: gọi API của backend `aplp.backend.spring`, tập trung vào UI/UX. + +**Frontend không**: +- Tính toán master/progress/adaptive (mọi quyết định từ backend). +- Chứa business rules. +- Lưu trữ dữ liệu business ngoài cache phục vụ UI. + +## 2. Tech Stack (confirmed tại Phase 0) + +| Lĩnh vực | Lựa chọn | +| --- | --- | +| Framework | React 19+ | +| Language | TypeScript | +| Build | Vite 8 | +| Routing | React Router 7 | +| HTTP client | Axios (abstraction) | +| Server-state | TanStack Query (React Query) | +| Client-state | Zustand | +| Styling | Tailwind CSS 4 | +| Testing | Vitest + Testing Library | + +## 3. Folder Structure + +```text +aplp.frontend.react/ +├── docs/ +├── public/ +└── src/ + ├── app/ # app entry, router, providers, layout + ├── api/ # api client, endpoints + ├── features/ # feature modules (vertical per phase) + │ ├── auth/ + │ ├── learner/ + │ ├── course/ + │ ├── learning/ + │ ├── assessment/ + │ ├── adaptive/ + │ ├── recommendation/ + │ ├── assistance/ + │ └── account/ + ├── shared/ # shared components, hooks, utils + └── types/ # shared types / DTO contracts +``` + +## 4. Feature Structure + +Mỗi feature module nên tự đóng gói: + +```text +src/features// +├── api/ # calls backend endpoints +├── components/ +├── hooks/ +├── pages/ # route-level views +└── types.ts # feature-local types +``` + +## 5. Data Flow + +``` +UI (component) + ↓ hooks (React Query / Zustand) + ↓ api/ axios wrapper (auth header, refresh token, error mapping) + ↓ Backend REST API +``` + +- **Read**: React Query quản lý fetch/cache/server-state. +- **Write**: mutation qua React Query; optimistic update nếu cần. +- **Runtime UI state**: local/Zustand. +- Token: access token **in-memory** (`src/api/tokenStorage.ts`), refresh token HttpOnly cookie do backend quản lý (đã confirm Phase 0). + +## 6. Auth Flow + +``` +Login → lưu access token → attach Authorization header +Access token hết hạn → gọi /auth/refresh → retry request +Logout → xóa token → redirect +``` + +- Trung tâm qua api client, không scatter trong từng page. +- Route bảo vệ qua ProtectedRoute / auth guard. +- Chi tiết (JWT/session) theo backend (OQ-010 / ARC-002). + +## 7. Error Handling + +- Api client chuẩn hóa lỗi (`ApiError` từ backend) → user-friendly message. +- Error boundary cho crash cấp tree. +- Loading/empty/error states component tái sử dụng. + +## 8. Styling & Design + +- Theme tokens dùng chung (colors, spacing, typography). +- Component pattern thống nhất (candidate: component library quyết định Phase 0). + +## 9. Deployment + +- Build tĩnh (Vite output) serve qua CDN/nginx hoặc tách container. +- Env qua `VITE_*` variables; không commit secret vào build config. + +## 10. Guardrails + +- **Không tính toán domain phía client** (adaptive, mastery, progress). +- **Không bypass auth guard**. +- **Không gọi backend API trực tiếp ngoài `src/api`** (except khi có lý do rõ). +- Loại bỏ dead code, không dùng type `any` tùy tiện (TypeScript). +- Dùng semantic versions; document trong repo. + +## 11. Open Questions + +| ID | Question | Impact | Status | +| --- | --- | --- | --- | +| FE-001 | Vite hay framework khác? | Setup | **Resolved: Vite** | +| FE-002 | TypeScript bắt buộc? | Quality | **Resolved: có** | +| FE-003 | Component library? | UX | **Resolved: Tailwind CSS** | +| FE-004 | Client-state tool? | Data flow | **Resolved: Zustand** | +| FE-005 | SSR/SSG? | SEO | **Resolved: không** | +| FE-008 | Cách quản lý token (memory/localStorage/refresh)? | Security | **Resolved: access token in-memory + refresh token HttpOnly cookie (backend)** | +| FE-009 | Có cần end-to-end test (Playwright)? | Testing | Open | \ No newline at end of file diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md new file mode 100644 index 0000000..7dcfec6 --- /dev/null +++ b/docs/CONVENTIONS.md @@ -0,0 +1,82 @@ +# APLP Frontend — Conventions + +| | | +| --- | --- | +| **Status** | Phase 0 hoàn tất | + +## 1. Language & Tooling + +- **TypeScript** cho toàn bộ code. Không dùng `any` tùy tiện; ưu tiên explicit types. +- Lint: **oxlint** (đã confirm Phase 0); format: **Prettier**. +- Convention: file `.tsx` cho React components; kiểu dùng interface cho props. + +## 2. Naming + +| Loại | Quy ước | +| --- | --- | +| Component | PascalCase (`CourseCard.tsx`) | +| Hook | camelCase, prefix `use` (`useLearningProgress.ts`) | +| Page/route | PascalCase trong `pages/` | +| API function | camelCase (`fetchCourseDetail`) | +| Types/interface | PascalCase (`CourseDetailDto`, `ApiError`) | + +## 3. Component Rules + +- Component nhỏ, single responsibility. +- Props dùng interface; optional props explicit. +- Logic phức tạp nên tách vào hooks. +- Components dùng chung đặt `src/shared/components`; feature-specific vào `features//components`. +- Không đặt business logic vào JSX. + +## 4. State Management + +- **Server state** → TanStack Query (query keys chuẩn: `['course', id]`). +- **UI/local state** → `useState`; nếu cần chia sẻ toàn app → Zustand store. +- Không đặt server data vào Zustand khi đã có React Query (tránh trùng source of truth). +- Tên store: domain concept (`useLearnerStore`). + +## 5. API Layer + +- Gọi backend qua `src/api` wrapper. Không gọi `fetch`/axios trực tiếp tại component. +- Response mapping/error handling nằm trong api layer. +- Có hằng số endpoint riêng, không đặt URL rải rác. + +## 6. Error & Loading States + +- Mọi view fetch/save cần xử lý: loading, empty, error, success. +- Dùng shared components (Spinner, EmptyState, ErrorState / ErrorBoundary). +- Hiển thị lỗi user-friendly từ `ApiError` của backend; không hiện stack trace. + +## 7. Styling + +- Dùng design tokens/theme (xác nhận stack Phase 0). +- Không hard-code color/margin lộn xộn; ưu tiên class utility/component thống nhất. +- Responsive theo grid/breakpoints chuẩn. + +## 8. Testing + +- Unit test components/hooks: Vitest. +- Render test với testing-library. +- E2E (Playwright) — optional, đánh giá sau. +- Test file đặt cạnh source (`Component.test.tsx`) hoặc `__tests__/` — chọn 1 để thống nhất. + +## 9. Git & Collaboration + +- Commit nhỏ, message rõ (theo style repo khi setup lại). +- Dùng feature branch + review. +- Lint + typecheck + test chạy qua (lệnh chuẩn `npm run lint`, `npm run typecheck`, `npm test`) trước khi merge. + +## 10. Security + +- **Không** lưu secret/token vào code hay log. +- Không put credentials vào env file committed (`.env` → `.env.example`). +- Không render user content bằng `dangerouslySetInnerHTML` trừ khi đã sanatize. + +## 11. Open Questions + +| ID | Question | Status | +| --- | --- | --- | +| CON-001 | ESLint config (airbnb/custom)? | **Resolved: oxlint (default config)** | +| CON-002 | Path alias (`@/`)? | **Resolved: có (`@` → `src`)** | +| CON-003 | Test file location convention? | **Resolved: đặt cạnh source (`Component.test.tsx`)** | +| CON-004 | Storybook có cần? | Open | \ No newline at end of file diff --git a/docs/DESIGN.md b/docs/DESIGN.md new file mode 100644 index 0000000..30bfab8 --- /dev/null +++ b/docs/DESIGN.md @@ -0,0 +1,722 @@ +--- +version: alpha +name: Gamified Learning UI +description: A playful, game-like design system for an English learning web application. Built around a bright learning world, chunky tactile 3D controls, soft rounded surfaces, clear progress signals, and lightweight game mechanics such as XP, levels, streaks, badges, and rewards. The interface feels like a friendly game rather than a traditional LMS — energetic and motivating without becoming childish or visually noisy. + +colors: + primary: "#5B5FEF" + primary-dark: "#4548C9" + primary-light: "#E8E9FF" + + secondary: "#FFB84D" + secondary-dark: "#E59A28" + secondary-light: "#FFF1D6" + + success: "#35C978" + success-dark: "#239B58" + success-light: "#DDF7E8" + + accent: "#FF6B6B" + accent-light: "#FFE3E3" + + sky: "#55B9F3" + sky-light: "#E2F4FF" + + canvas: "#F7F8FC" + surface: "#FFFFFF" + surface-soft: "#F0F2F8" + surface-hover: "#E9EBF5" + + ink: "#20233A" + ink-secondary: "#62677F" + ink-muted: "#969BAE" + ink-inverse: "#FFFFFF" + + border: "#E2E5EF" + + shadow-color: "#252A52" + +typography: + display-xl: + fontFamily: Nunito + fontSize: 64px + fontWeight: 900 + lineHeight: 1.0 + letterSpacing: -1px + + display-lg: + fontFamily: Nunito + fontSize: 48px + fontWeight: 900 + lineHeight: 1.05 + letterSpacing: -0.5px + + display-md: + fontFamily: Nunito + fontSize: 36px + fontWeight: 800 + lineHeight: 1.1 + letterSpacing: -0.25px + + heading-lg: + fontFamily: Nunito + fontSize: 28px + fontWeight: 800 + lineHeight: 1.2 + letterSpacing: 0 + + heading-md: + fontFamily: Nunito + fontSize: 22px + fontWeight: 800 + lineHeight: 1.25 + letterSpacing: 0 + + heading-sm: + fontFamily: Nunito + fontSize: 18px + fontWeight: 800 + lineHeight: 1.3 + letterSpacing: 0 + + body-lg: + fontFamily: Nunito + fontSize: 18px + fontWeight: 600 + lineHeight: 1.5 + letterSpacing: 0 + + body: + fontFamily: Nunito + fontSize: 16px + fontWeight: 500 + lineHeight: 1.5 + letterSpacing: 0 + + body-sm: + fontFamily: Nunito + fontSize: 14px + fontWeight: 500 + lineHeight: 1.45 + letterSpacing: 0 + + button: + fontFamily: Nunito + fontSize: 16px + fontWeight: 800 + lineHeight: 1.2 + letterSpacing: 0 + + button-lg: + fontFamily: Nunito + fontSize: 18px + fontWeight: 800 + lineHeight: 1.2 + letterSpacing: 0 + + label: + fontFamily: Nunito + fontSize: 13px + fontWeight: 800 + lineHeight: 1.2 + letterSpacing: 0.2px + +rounded: + xs: 8px + sm: 12px + md: 16px + lg: 20px + xl: 28px + xxl: 36px + pill: 999px + full: 50% + +spacing: + xxs: 4px + xs: 8px + sm: 12px + md: 16px + lg: 20px + xl: 24px + xxl: 32px + xxxl: 40px + section: 64px + hero: 96px + +components: + + nav-bar: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + typography: "{typography.body}" + padding: "{spacing.md} {spacing.xl}" + borderBottom: "1px solid {colors.border}" + + button-primary: + backgroundColor: "{colors.primary}" + textColor: "{colors.ink-inverse}" + typography: "{typography.button-lg}" + rounded: "{rounded.md}" + padding: "14px {spacing.xl}" + shadow: "0 5px 0 {colors.primary-dark}" + minHeight: "52px" + + button-secondary: + backgroundColor: "{colors.secondary}" + textColor: "{colors.ink}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: "12px {spacing.xl}" + shadow: "0 4px 0 {colors.secondary-dark}" + minHeight: "48px" + + button-success: + backgroundColor: "{colors.success}" + textColor: "{colors.ink-inverse}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: "12px {spacing.xl}" + shadow: "0 4px 0 {colors.success-dark}" + minHeight: "48px" + + button-outline: + backgroundColor: "{colors.surface}" + textColor: "{colors.primary}" + borderColor: "{colors.primary}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: "11px {spacing.xl}" + minHeight: "48px" + + button-ghost: + backgroundColor: "transparent" + textColor: "{colors.ink-secondary}" + typography: "{typography.button}" + rounded: "{rounded.sm}" + padding: "{spacing.sm} {spacing.md}" + + icon-button: + backgroundColor: "{colors.surface-soft}" + textColor: "{colors.ink}" + rounded: "{rounded.md}" + minSize: "44px" + + hero: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.display-xl}" + padding: "{spacing.hero}" + decoration: "soft game-world shapes and floating 3D objects" + + game-card: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.xl}" + padding: "{spacing.xl}" + borderColor: "{colors.border}" + shadow: "0 6px 0 rgba(37,42,82,0.08)" + + game-card-primary: + backgroundColor: "{colors.primary}" + textColor: "{colors.ink-inverse}" + rounded: "{rounded.xl}" + padding: "{spacing.xl}" + shadow: "0 6px 0 {colors.primary-dark}" + + lesson-card: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.xl}" + borderColor: "{colors.border}" + + lesson-tile: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + minHeight: "160px" + shadow: "0 5px 0 rgba(37,42,82,0.08)" + + progress-card: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + borderColor: "{colors.border}" + + xp-card: + backgroundColor: "{colors.secondary-light}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + + streak-card: + backgroundColor: "{colors.accent-light}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + + level-card: + backgroundColor: "{colors.primary-light}" + textColor: "{colors.ink}" + rounded: "{rounded.xl}" + padding: "{spacing.xl}" + + reward-card: + backgroundColor: "{colors.secondary}" + textColor: "{colors.ink}" + rounded: "{rounded.xl}" + padding: "{spacing.xl}" + shadow: "0 6px 0 {colors.secondary-dark}" + + badge: + backgroundColor: "{colors.primary-light}" + textColor: "{colors.primary-dark}" + typography: "{typography.label}" + rounded: "{rounded.pill}" + padding: "6px 12px" + + badge-success: + backgroundColor: "{colors.success-light}" + textColor: "{colors.success-dark}" + typography: "{typography.label}" + rounded: "{rounded.pill}" + padding: "6px 12px" + + xp-badge: + backgroundColor: "{colors.secondary}" + textColor: "{colors.ink}" + typography: "{typography.label}" + rounded: "{rounded.pill}" + padding: "6px 12px" + + level-badge: + backgroundColor: "{colors.primary}" + textColor: "{colors.ink-inverse}" + typography: "{typography.label}" + rounded: "{rounded.pill}" + padding: "6px 12px" + + progress-bar: + trackColor: "{colors.surface-soft}" + fillColor: "{colors.primary}" + height: "12px" + rounded: "{rounded.pill}" + + progress-bar-success: + trackColor: "{colors.surface-soft}" + fillColor: "{colors.success}" + height: "12px" + rounded: "{rounded.pill}" + + answer-option: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + borderColor: "{colors.border}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + minHeight: "64px" + shadow: "0 4px 0 rgba(37,42,82,0.06)" + + answer-option-correct: + backgroundColor: "{colors.success-light}" + textColor: "{colors.success-dark}" + borderColor: "{colors.success}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + + answer-option-wrong: + backgroundColor: "{colors.accent-light}" + textColor: "{colors.accent}" + borderColor: "{colors.accent}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + + mascot-card: + backgroundColor: "{colors.sky-light}" + textColor: "{colors.ink}" + rounded: "{rounded.xxl}" + padding: "{spacing.xl}" + + daily-challenge: + backgroundColor: "{colors.primary}" + textColor: "{colors.ink-inverse}" + rounded: "{rounded.xl}" + padding: "{spacing.xxl}" + shadow: "0 7px 0 {colors.primary-dark}" + + empty-state: + backgroundColor: "{colors.surface-soft}" + textColor: "{colors.ink-secondary}" + rounded: "{rounded.xl}" + padding: "{spacing.xxxl}" + + modal-card: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.xl}" + padding: "{spacing.xxl}" + shadow: "0 20px 60px rgba(32,35,58,0.18)" + + toast: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.md}" + padding: "{spacing.sm} {spacing.md}" + shadow: "0 8px 24px rgba(32,35,58,0.15)" + + footer: + backgroundColor: "{colors.surface-soft}" + textColor: "{colors.ink-secondary}" + typography: "{typography.body}" + padding: "{spacing.section}" + + + # ─────────────────────────────────────────────────────────── + # GAME MECHANICS + # ─────────────────────────────────────────────────────────── + + xp-display: + icon: "⭐" + color: "{colors.secondary}" + typography: "{typography.heading-sm}" + + streak-display: + icon: "🔥" + color: "{colors.accent}" + typography: "{typography.heading-sm}" + + level-display: + icon: "🏆" + color: "{colors.primary}" + typography: "{typography.heading-sm}" + + reward-popup: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.xl}" + padding: "{spacing.xxl}" + shadow: "0 12px 40px rgba(32,35,58,0.18)" + animation: "scale-in + bounce" + + completion-state: + backgroundColor: "{colors.success-light}" + textColor: "{colors.success-dark}" + rounded: "{rounded.xl}" + padding: "{spacing.xxxl}" + animation: "celebration" + + +# ───────────────────────────────────────────────────────────── +# OVERVIEW +# ───────────────────────────────────────────────────────────── + +## Overview + +This design system transforms an English learning website into a lightweight game-like learning experience. + +The product should feel like a friendly game that happens to teach English — not like a traditional LMS, school management system, or enterprise dashboard. + +The visual language is built around: + +- Playful 3D / tactile controls +- Large, obvious actions +- Soft rounded surfaces +- Bright but controlled colors +- Strong progress visualization +- XP, levels, streaks, badges, and rewards +- Friendly illustrations and optional mascot characters +- Minimal navigation +- Short learning interactions +- Clear success and failure feedback + +The interface should be energetic enough to motivate children and students while remaining mature enough for teenagers and adults who are starting English from zero. + +The core principle is: + +> **Game-like, not childish.** + +The interface should communicate: + +> "Let's play and learn." + +rather than: + +> "Here is your educational dashboard." + + +# ───────────────────────────────────────────────────────────── +# DESIGN LANGUAGE +# ───────────────────────────────────────────────────────────── + +## Design Direction + +Primary style: + +**Gamified Learning UI** + +Visual style: + +**Playful 3D / Tactile UI** + +UX philosophy: + +**Game-like learning** + +Tone: + +**Friendly · Encouraging · Simple · Rewarding** + +The UI should make actions feel physical. + +Buttons should appear slightly raised from the surface. When pressed, they should move downward and visually lose their elevation. + +Cards should feel like game tiles. + +Progress should feel like progression through a game world. + +Rewards should feel immediate and satisfying. + + +## Core Principles + +### 1. Action First + +Every major screen should have one obvious primary action. + +Examples: + +- Continue +- Start Lesson +- Practice +- Check Answer +- Continue +- Claim Reward + +Avoid screens where five actions compete for attention. + +### 2. Game Before Dashboard + +Do not expose complex LMS structures unless necessary. + +Prefer: + +Home → Activity → Result → Reward → Next Activity + +over: + +Dashboard → Course → Chapter → Lesson → Content → Quiz + +### 3. Chunky Controls + +Interactive elements should be larger than typical SaaS interfaces. + +Recommended minimum: + +- Primary CTA: 48–52px +- Secondary CTA: 44–48px +- Answer option: 60–72px +- Icon button: 44px minimum + +### 4. Tactile Feedback + +Buttons should have visible depth. + +Default: + +`translateY(0)` + +Pressed: + +`translateY(4px)` + +The shadow should reduce proportionally. + +This creates a physical button effect without requiring heavy 3D rendering. + +### 5. Reward Progress + +Learning actions should produce visible progress. + +Examples: + +- +10 XP +- +1 streak +- Level progress +- Lesson completion +- Badge unlocked +- Daily challenge completed + +Avoid rewarding every tiny interaction. Rewards should reinforce meaningful learning actions. + + +# ───────────────────────────────────────────────────────────── +# COLORS +# ───────────────────────────────────────────────────────────── + +The palette moves away from Discord's dark gaming aesthetic. + +The learning environment uses a bright neutral canvas with a small number of recognizable game colors. + +### Primary + +**Purple / Indigo** + +`#5B5FEF` + +Used for: + +- Primary CTA +- Active navigation +- Main progress +- Level indicators +- Important interactive states + +### Secondary + +**Warm Yellow** + +`#FFB84D` + +Used for: + +- XP +- Stars +- Rewards +- Achievements +- Celebration + +### Success + +**Green** + +`#35C978` + +Used for: + +- Correct answers +- Completed lessons +- Positive progress +- Success actions + +### Accent + +**Coral** + +`#FF6B6B` + +Used for: + +- Streak +- Attention +- Wrong answer +- Limited-time challenge indicators + +### Sky + +**Light Blue** + +`#55B9F3` + +Used for: + +- Friendly illustrations +- Secondary learning areas +- Background decorations + +### Canvas + +`#F7F8FC` + +The primary application background. + +It should remain visually quiet so game elements stand out. + +### Surface + +`#FFFFFF` + +Used for cards, lesson tiles, dialogs, and interactive surfaces. + + +# ───────────────────────────────────────────────────────────── +# TYPOGRAPHY +# ───────────────────────────────────────────────────────────── + +Typography should feel friendly rather than corporate. + +Recommended primary family: + +**Nunito** + +Alternative: + +**Plus Jakarta Sans** + +Display text should be bold and rounded. + +Body text should remain highly readable. + +Avoid: + +- Condensed display fonts +- Aggressive gaming fonts +- Excessive ALL CAPS +- Decorative fonts for learning content + +Unlike Discord, headlines do not need to shout. + +A friendly: + +> Learn 5 new words today + +is preferable to: + +> MASTER YOUR ENGLISH NOW! + + +# ───────────────────────────────────────────────────────────── +# LAYOUT +# ───────────────────────────────────────────────────────────── + +## Grid + +Use a centered application container. + +Recommended maximum width: + +`1200px` + +Preferred content width: + +`960–1200px` + +The layout should feel spacious. + +Avoid dense dashboard layouts. + +## Home Layout + +Recommended hierarchy: + +```text +Top Navigation + +Greeting +Current Level / XP + +Continue Learning + ↓ +Main Lesson Card + +Daily Challenge + ↓ +Learning Activities + +Vocabulary +Listening +Speaking +Reading + +Achievements / Streak \ No newline at end of file diff --git a/docs/PHASES.md b/docs/PHASES.md new file mode 100644 index 0000000..d1dd7bd --- /dev/null +++ b/docs/PHASES.md @@ -0,0 +1,222 @@ +# APLP Frontend (React) — Phases + +| | | +| --- | --- | +| **Module** | `aplp.frontend.react` | +| **Role** | Web application cho learner (`aplp-web`) | +| **Status** | Draft | +| **Backend** | `aplp.backend.spring` (Java/Spring Boot) | + +Frontend triển khai theo các phase của [APLP-Project-Phases](../../aplp.backend.spring/APLP-Project-Phases.md). Document này ánh xạ từng phase sang công việc frontend. + +> Nguyên tắc: frontend là client mỏng (thin client), gọi API của backend. Frontend **không** chứa business rules của domain; chỉ chứa UX/UI logic. + +--- + +## 1. Phase 0 — Foundation (Frontend) + +### Objective + +Dựng khung ứng dụng, auth flow, routing, chuẩn hóa giao tiếp API với backend. + +> ✅ Phase 0 hoàn tất. Stack đã confirm (Vite, React 19, TS, React Router, axios, React Query, Zustand, Tailwind). Auth flow + api client + ProtectedRoute đang hoạt động. + +### Features + +| Feature | Priority | +| --- | --- | +| Khởi tạo React app (Vite \[candidate\] + TypeScript) | Must | +| Routing (react-router) | Must | +| Auth flow: login/logout/refresh + token storage | Must | +| API client layer (axios/fetch wrapper, error handling) | Must | +| Layout/App shell & ProtectedRoute | Must | +| Theme & UI base (candidate: Tailwind / component lib) | Should | +| State management base (candidate: Zustand/React Query) | Should | +| Environment config (`VITE_*`) | Must | + +### Completion Criteria + +- Đăng nhập/đăng xuất chạy được, token quản lý đúng, route bảo vệ hoạt động. +- API layer chuẩn, gọi được backend local. + +--- + +## 2. Phase 1 — Identity & Learner + +### Objective + +Hiển thị và cho học viên quản lý hồ sơ, preferences, learning goals. + +### Features + +| Feature | Priority | +| --- | --- | +| Trang hồ sơ learner (view/edit) | Must | +| Form learning preferences | Should | +| Quản lý learning goals (CRUD) | Should | + +--- + +## 3. Phase 2 — Content & Course + +### Objective + +Browsing + chi tiết course từ content model (backend/LMS). + +### Features + +| Feature | Priority | +| --- | --- | +| Course catalog & search cơ bản | Must | +| Course detail page (structure module/lesson) | Must | +| Enroll/select course | Should | + +--- + +## 4. Phase 3 — Learning Experience + +### Objective + +Trải nghiệm học từ đầu tới cuối trong UI. + +### Features + +| Feature | Priority | +| --- | --- | +| Player/lesson view cho learning activity | Must | +| Start / continue / resume đúng lesson | Must | +| Navigation giữa lessons (next/prev, course map) | Must | +| Đánh dấu hoàn thành & feedback ngay | Must | +| Progress thanh / module state | Should | + +--- + +## 5. Phase 4 — Assessment & Progress + +### Objective + +Learner làm assessment, xem kết quả và tiến độ trong UI. + +### Features + +| Feature | Priority | +| --- | --- | +| Màn hình làm assessment (câu hỏi, lựa chọn) | Must | +| Submit & hiển thị kết quả/score | Must | +| Hiển thị learning progress & history | Must | +| Retake flow (nếu business cho phép — OQ-005) | Should | + +--- + +## 6. Phase 5 — Adaptive Learning + +### Objective + +Hiển thị learner state, mastery và hoạt động được đề xuất tiếp theo từ backend. + +### Features + +| Feature | Priority | +| --- | --- | +| Hiển thị mastery / knowledge state | Must | +| Hiển thị "next activity" đề xuất | Must | +| Prerequisite lock/unlock state trong UI | Should | + +> Frontend chỉ **hiển thị** giao diện theo quyết định của backend; **không** tính toán adaptive bên client. + +--- + +## 7. Phase 6 — Personalization & Recommendation + +### Objective + +Hiển thị các đề xuất/path được cá nhân hóa. + +### Features + +| Feature | Priority | +| --- | --- | +| Block "Recommended for you" | Should | +| Personalized path view | Could | +| Cho phép learner xác nhận/bỏ qua đề xuất | Should | + +--- + +## 8. Phase 7 — Learning Assistance + +### Objective + +UI cho AI assistance: hint, explanation, Q&A, feedback (backend-driven). + +### Features + +| Feature | Priority | +| --- | --- | +| Hint/explanation panel trong lesson | Should | +| Chat/Q&A box (RAG backend) | Could | +| Feedback hiển thị sau assessment | Should | + +--- + +## 9. Phase 8 — Productization + +### Objective + +UI cho subscription/plan, notification, account. + +### Features + +| Feature | Priority | +| --- | --- | +| Trang plan/subscription | Should | +| Notification UI | Should | +| Account management | Should | + +--- + +## 10. Phase 9 — Scale & Evolution + +### Objective + +Performance, caching client, observability. + +### Features + +| Feature | Priority | +| --- | --- | +| Data fetching caching (React Query) | Should | +| Code splitting / lazy loading | Should | +| Error boundary & monitoring (sentry-like) | Should | + +--- + +## 11. MVP (Frontend) + +> **Một learner có thể dùng UI này học một course hoàn chỉnh.** + +MVP frontend bao gồm: + +``` +Auth + App shell ++ Course catalog/detail ++ Lesson player ++ Progress display ++ Assessment UI ++ (adaptive simple — optional) +``` + +Ngoài MVP: AI assistance, recommendation nâng cao, subscription UI. + +## 12. Open Questions (Frontend-specific) + +> Phase 0 đã confirm: Vite, TypeScript, React Router, axios, TanStack Query + Zustand, Tailwind CSS, Vitest + Testing Library, no SSR, no PWA. + +| ID | Question | Impact | Status | +| --- | --- | --- | --- | +| FE-001 | Vite hay CRA / framework khác? | Setup | **Resolved: Vite** | +| FE-002 | TypeScript có bắt buộc? | Quality | **Resolved: có** | +| FE-003 | Component library nào (Tailwind/MUI/etc)? | UX consistency | **Resolved: Tailwind CSS** | +| FE-004 | State management: React Query + Zustand/Redux? | Data flow | **Resolved: React Query + Zustand** | +| FE-005 | SSR/SSG có cần (SEO)? | Architecture | **Resolved: không cho MVP** | +| FE-006 | I18n cho UI? | UX | Open | +| FE-007 | Có cần PWA/offline? | UX | **Resolved: không cho MVP** | \ No newline at end of file diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 0000000..5db2caa --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,78 @@ +# APLP Frontend — Setup + +| | | +| --- | --- | +| **Status** | Phase 0 hoàn tất — repo đã khởi tạo Vite + React + TS | + +## 1. Prerequisites + +- Node.js 18+ (chọn LTS, xác nhận khi khởi tạo) +- npm/pnpm (chọn 1 — candidate npm) +- Backend chạy local (xem `docs/SETUP.md` của `aplp.backend.spring`) + +## 2. Cấu trúc dự án dự kiến + +```text +aplp.frontend.react/ +├── README.md +├── docs/ # frontend docs +│ ├── PHASES.md # công việc frontend theo phase APLP +│ ├── ARCHITECTURE.md +│ ├── CONVENTIONS.md +│ └── SETUP.md +├── index.html +├── vite.config.ts +├── package.json +├── tsconfig.json +├── .env.example +├── public/ +└── src/ + ├── app/ + ├── api/ + ├── features/... + ├── shared/ + └── types/ +``` + +## 3. Khởi tạo (Phase 0) + +> Repo hiện tại là template trống từ GitLab. Các bước dưới sẽ thực hiện khi bắt đầu Phase 0. + +1. Khởi tạo React + TypeScript app (candidate: Vite): `npm create vite@latest . -- --template react-ts` +2. Thêm `react-router-dom`, axios, TanStack Query, Zustand, tooling lint/test. +3. Tạo config env mẫu `.env.example`: `VITE_API_BASE_URL=http://localhost:8080/api` +4. Dựng api client + auth flow + ProtectedRoute (an toàn tương ứng Phase 0). +5. Tạo folder structure theo `docs/ARCHITECTURE.md`. +6. Viết README chính thức thay template GitLab. +7. Kiểm tra: `npm run lint`, `npm run typecheck`, `npm test`, `npm run dev` build ok. + +> ✅ Đã thực hiện xong. Xem `README.md` cho cấu trúc và scripts hiện tại. + +## 4. Local Dev Flow + +- Backend chạy local (port 8080 ví dụ). +- Chạy frontend: `npm run dev` → mở địa chỉ Vite. +- Env qua file `.env` (không commit; chỉ commit `.env.example`). + +## 5. Environment Example + +```env +VITE_API_BASE_URL=http://localhost:8080/api +VITE_APP_NAME=APLP +``` + +## 6. Verification Checklist (khi có code) + +- [ ] `npm run lint` pass +- [ ] `npm run typecheck` pass +- [ ] `npm test` pass +- [ ] `npm run build` pass +- [ ] Login → protected route đúng, refresh token hoạt động + +## 7. Open Questions + +| ID | Question | Status | +| --- | --- | --- | +| SET-001 | npm hay pnpm? | Open (candidate npm) | +| SET-002 | Node version quản lý (nvm)? | Open | +| SET-003 | Deploy target (nginx/container/Vercel)? | Open | \ No newline at end of file