feat: add guide
This commit is contained in:
@@ -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)
|
||||
│ └── <feature>/
|
||||
│ ├── 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/<name>` module.
|
||||
|
||||
## Design
|
||||
|
||||
Alway using `docs/DESIGN.md` file to code everything relevant to style and UI
|
||||
@@ -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/<feature>/
|
||||
├── 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 |
|
||||
@@ -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/<feature>/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 |
|
||||
+722
@@ -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
|
||||
+222
@@ -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** |
|
||||
@@ -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 |
|
||||
Reference in New Issue
Block a user