Enhance Atoms and Layout Documentation
- Updated ATOMS.md to include new LoginInput and ErrorMessageLogin components with detailed usage examples and props descriptions. - Adjusted the status and created date in ATOMS.md. - Refined the layout and styling descriptions in LAYOUTS.md for Header and Footer components, including responsive behavior and accessibility improvements. - Added new sections for WiFi card and social links in the Footer documentation. - Improved code formatting and consistency across various components in the codebase.
This commit is contained in:
@@ -1,30 +1,39 @@
|
|||||||
---
|
---
|
||||||
name: agent-orchestrator
|
name: agent-orchestrator
|
||||||
description: >
|
description: >
|
||||||
Breaks down large, multi-step software tasks into structured sub-agents on the Claude Code CLI using the Task tool.
|
Breaks down large, multi-step software tasks into structured sub-agents on the
|
||||||
Use this skill whenever the user describes a complex project or feature that has multiple components - e.g., "build a login API with JWT and unit tests", "set up a CI/CD pipeline with Docker and GitHub Actions", "create a full-stack todo app with auth, REST API, and frontend".
|
Claude Code CLI using the Task tool. Use this skill whenever the user
|
||||||
Trigger this skill when the task clearly has more than one distinct sub-system, layer, or concern that could be worked on in parallel or in sequence.
|
describes a complex project or feature that has multiple components - e.g.,
|
||||||
DO NOT trigger for simple, single-step tasks like "write a function to reverse a string" or "fix this bug".
|
"build a login API with JWT and unit tests", "set up a CI/CD pipeline with
|
||||||
|
Docker and GitHub Actions", "create a full-stack todo app with auth, REST API,
|
||||||
|
and frontend". Trigger this skill when the task clearly has more than one
|
||||||
|
distinct sub-system, layer, or concern that could be worked on in parallel or
|
||||||
|
in sequence. DO NOT trigger for simple, single-step tasks like "write a
|
||||||
|
function to reverse a string" or "fix this bug".
|
||||||
---
|
---
|
||||||
|
|
||||||
# Agent Orchestrator
|
# Agent Orchestrator
|
||||||
|
|
||||||
A skill for decomposing large engineering tasks into parallel and sequential sub-agents on Claude Code CLI using the **Task tool**.
|
A skill for decomposing large engineering tasks into parallel and sequential
|
||||||
|
sub-agents on Claude Code CLI using the **Task tool**.
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
Trigger when the user's task has **multiple distinct components** - for example:
|
Trigger when the user's task has **multiple distinct components** - for example:
|
||||||
|
|
||||||
- "Build a REST API with authentication, rate limiting, and unit tests"
|
- "Build a REST API with authentication, rate limiting, and unit tests"
|
||||||
- "Set up a monorepo with shared packages, CI pipeline, and deployment configs"
|
- "Set up a monorepo with shared packages, CI pipeline, and deployment configs"
|
||||||
- "Create a data pipeline: ingest transform store visualize"
|
- "Create a data pipeline: ingest transform store visualize"
|
||||||
|
|
||||||
Do **not** trigger for single-step tasks (e.g., "rename this variable", "add a README").
|
Do **not** trigger for single-step tasks (e.g., "rename this variable", "add a
|
||||||
|
README").
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prompt Template
|
## Prompt Template
|
||||||
|
|
||||||
Copy the block below and paste it into your Agent Code CLI session. Replace the placeholder in the `TASK` variable with your actual task description.
|
Copy the block below and paste it into your Agent Code CLI session. Replace the
|
||||||
|
placeholder in the `TASK` variable with your actual task description.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -143,19 +152,25 @@ Begin now with STEP 1.
|
|||||||
|
|
||||||
## Tips for Customizing the Template
|
## Tips for Customizing the Template
|
||||||
|
|
||||||
- **Replace the TASK value** with your actual task string. Keep it on one line, in quotes.
|
- **Replace the TASK value** with your actual task string. Keep it on one line,
|
||||||
- **Add context if needed**: Append lines after `TASK:` like `STACK: "Node.js, PostgreSQL, Jest"` or `CONSTRAINTS: "Use ESM modules only"` - the orchestrator will incorporate them.
|
in quotes.
|
||||||
|
- **Add context if needed**: Append lines after `TASK:` like
|
||||||
|
`STACK: "Node.js, PostgreSQL, Jest"` or
|
||||||
|
`CONSTRAINTS: "Use ESM modules only"` - the orchestrator will incorporate
|
||||||
|
them.
|
||||||
- **For monorepos or specific file layouts**: Add a `STRUCTURE:` hint, e.g.:
|
- **For monorepos or specific file layouts**: Add a `STRUCTURE:` hint, e.g.:
|
||||||
```
|
```
|
||||||
STRUCTURE: "src/api/, src/auth/, src/tests/, docker-compose.yml"
|
STRUCTURE: "src/api/, src/auth/, src/tests/, docker-compose.yml"
|
||||||
```
|
```
|
||||||
- **To limit parallelism** (e.g., on resource-constrained machines): Add `MAX_PARALLEL_AGENTS: 2` after the TASK line.
|
- **To limit parallelism** (e.g., on resource-constrained machines): Add
|
||||||
|
`MAX_PARALLEL_AGENTS: 2` after the TASK line.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
**Input task:**
|
**Input task:**
|
||||||
|
|
||||||
> "Build a login API with JWT authentication and unit tests"
|
> "Build a login API with JWT authentication and unit tests"
|
||||||
|
|
||||||
**Expected orchestrator output (before execution):**
|
**Expected orchestrator output (before execution):**
|
||||||
@@ -180,12 +195,15 @@ Dependency Plan:
|
|||||||
Starting execution. Spawning parallel sub-agents now...
|
Starting execution. Spawning parallel sub-agents now...
|
||||||
```
|
```
|
||||||
|
|
||||||
T1 and T2 spawn immediately. T3 spawns once T1 completes. T4 and T5 spawn once T3 completes. T6 spawns last.
|
T1 and T2 spawn immediately. T3 spawns once T1 completes. T4 and T5 spawn once
|
||||||
|
T3 completes. T6 spawns last.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- This skill requires Agent Code CLI with the **Task tool** enabled.
|
- This skill requires Agent Code CLI with the **Task tool** enabled.
|
||||||
- Each spawned sub-agent has no memory of the others - pass all needed context explicitly in the Task prompt.
|
- Each spawned sub-agent has no memory of the others - pass all needed context
|
||||||
- For very large tasks (10+ sub-tasks), consider breaking the orchestration into phases and applying this skill recursively per phase.
|
explicitly in the Task prompt.
|
||||||
|
- For very large tasks (10+ sub-tasks), consider breaking the orchestration into
|
||||||
|
phases and applying this skill recursively per phase.
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ Chứa route/page/layout theo Next.js App Router.
|
|||||||
- **`(main)/`** - Main shopping interface (duyệt menu, đăng nhập, đăng ký,
|
- **`(main)/`** - Main shopping interface (duyệt menu, đăng nhập, đăng ký,
|
||||||
thanh toán)
|
thanh toán)
|
||||||
- **`(feed)/`** - Feed/discovery page (khám phá quán)
|
- **`(feed)/`** - Feed/discovery page (khám phá quán)
|
||||||
- **`(manager)/`** - Manager dashboard (quản lý sản phẩm, đơn hàng) - **[ĐANG
|
- **`(manager)/`** - Manager dashboard (quản lý sản phẩm, phân tích tài
|
||||||
PHÁT TRIỂN]**
|
chính) - **[ĐANG PHÁT TRIỂN ~70%]**
|
||||||
|
- **`(staff)/`** - Staff shift schedule (lịch làm việc nhân viên) - **[HOÀN
|
||||||
|
THÀNH]**
|
||||||
- **Tài liệu:** `app/APP.md` - Chi tiết routes, pages, CSS tokens
|
- **Tài liệu:** `app/APP.md` - Chi tiết routes, pages, CSS tokens
|
||||||
|
|
||||||
### `components/` - Reusable UI Components (Atomic Design)
|
### `components/` - Reusable UI Components (Atomic Design)
|
||||||
@@ -80,7 +82,14 @@ Sections phức tạp kết hợp molecules + atoms, có logic, data filtering:
|
|||||||
- **`cart/`** - CartFab, CartSummary, CartList
|
- **`cart/`** - CartFab, CartSummary, CartList
|
||||||
- **`product-grid/`** - ProductGrid, ProductFilters
|
- **`product-grid/`** - ProductGrid, ProductFilters
|
||||||
- **`forms/`** - LoginForm, RegisterForm, CheckoutForm, ReviewForm
|
- **`forms/`** - LoginForm, RegisterForm, CheckoutForm, ReviewForm
|
||||||
- **`modals/`** - ReviewModal, ConfirmModal
|
- **`modals/`** - ReviewModal, ConfirmModal, CashPaymentModal, QRPaymentModal,
|
||||||
|
PaymentSuccessModal
|
||||||
|
- **`manager/`** - ProductModal (image upload), ComboModal, CategoryModal,
|
||||||
|
DeleteConfirm, StatusBadge, ProductsTab, CombosTab, CategoriesTab
|
||||||
|
- **`analytics/`** - BarChart, LineChart, PieChart (SVG), ProductTable,
|
||||||
|
SummaryCard
|
||||||
|
- **`shift-schedule/`** - WeeklySchedule, MonthlyCalendar, MobileShiftView,
|
||||||
|
ShiftCreateModal, ShiftDetailModal
|
||||||
- **`shop-grid/`** - ShopGrid, ShopFilters
|
- **`shop-grid/`** - ShopGrid, ShopFilters
|
||||||
- **`hero-section/`** - HeroSection
|
- **`hero-section/`** - HeroSection
|
||||||
- **`featured-section/`** - FeaturedProducts, FeaturedShops
|
- **`featured-section/`** - FeaturedProducts, FeaturedShops
|
||||||
@@ -103,6 +112,7 @@ Page layouts, không có data cụ thể, children composition:
|
|||||||
- **`main-layout/`** - MainLayout (header + sidebar + content + footer)
|
- **`main-layout/`** - MainLayout (header + sidebar + content + footer)
|
||||||
- **`feed-layout/`** - FeedLayout
|
- **`feed-layout/`** - FeedLayout
|
||||||
- **`manager-layout/`** - ManagerLayout
|
- **`manager-layout/`** - ManagerLayout
|
||||||
|
- **`staff-layout/`** - StaffLayout (navigation shell for staff pages)
|
||||||
- **`checkout-layout/`** - CheckoutLayout
|
- **`checkout-layout/`** - CheckoutLayout
|
||||||
- **`auth-layout/`** - AuthLayout
|
- **`auth-layout/`** - AuthLayout
|
||||||
|
|
||||||
@@ -161,8 +171,12 @@ Chứa logic dùng chung, context, constants, types:
|
|||||||
- **`auth-context.tsx`** - AuthProvider + useAuth() hook (login, logout,
|
- **`auth-context.tsx`** - AuthProvider + useAuth() hook (login, logout,
|
||||||
register)
|
register)
|
||||||
- **`cart-context.tsx`** - CartProvider + useCart() hook (add, remove, quantity
|
- **`cart-context.tsx`** - CartProvider + useCart() hook (add, remove, quantity
|
||||||
operations)
|
operations, clearCart)
|
||||||
- **`menu-context.tsx`** - MenuProvider + useMenu() hook (category state)
|
- **`menu-context.tsx`** - MenuProvider + useMenu() hook (category state)
|
||||||
|
- **`manager-context.tsx`** - ManagerProvider + useManager() hook (CRUD for
|
||||||
|
products, combos, categories)
|
||||||
|
- **`shift-context.tsx`** - ShiftProvider + useShift() hook (staff shift
|
||||||
|
management: create, update, delete, date selection)
|
||||||
- **Tài liệu:** `lib/LIB.md` - Chi tiết types, constants, contexts
|
- **Tài liệu:** `lib/LIB.md` - Chi tiết types, constants, contexts
|
||||||
|
|
||||||
### `public/` - Static Assets
|
### `public/` - Static Assets
|
||||||
@@ -233,11 +247,41 @@ components/
|
|||||||
│ ├── product-grid/
|
│ ├── product-grid/
|
||||||
│ │ ├── ProductGrid.tsx
|
│ │ ├── ProductGrid.tsx
|
||||||
│ │ └── ProductGrid.types.ts
|
│ │ └── ProductGrid.types.ts
|
||||||
|
│ ├── modals/
|
||||||
|
│ │ ├── ReviewModal.tsx
|
||||||
|
│ │ ├── CashPaymentModal.tsx
|
||||||
|
│ │ ├── QRPaymentModal.tsx
|
||||||
|
│ │ └── PaymentSuccessModal.tsx
|
||||||
|
│ ├── manager/
|
||||||
|
│ │ ├── ProductModal.tsx
|
||||||
|
│ │ ├── ComboModal.tsx
|
||||||
|
│ │ ├── CategoryModal.tsx
|
||||||
|
│ │ ├── DeleteConfirm.tsx
|
||||||
|
│ │ ├── StatusBadge.tsx
|
||||||
|
│ │ ├── ProductsTab.tsx
|
||||||
|
│ │ ├── CombosTab.tsx
|
||||||
|
│ │ └── CategoriesTab.tsx
|
||||||
|
│ ├── analytics/
|
||||||
|
│ │ ├── BarChart.tsx
|
||||||
|
│ │ ├── LineChart.tsx
|
||||||
|
│ │ ├── PieChart.tsx
|
||||||
|
│ │ ├── ProductTable.tsx
|
||||||
|
│ │ └── SummaryCard.tsx
|
||||||
|
│ ├── shift-schedule/
|
||||||
|
│ │ ├── WeeklySchedule.tsx
|
||||||
|
│ │ ├── MonthlyCalendar.tsx
|
||||||
|
│ │ ├── MobileShiftView.tsx
|
||||||
|
│ │ ├── ShiftCreateModal.tsx
|
||||||
|
│ │ └── ShiftDetailModal.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
├── templates/
|
├── templates/
|
||||||
│ ├── main-layout/
|
│ ├── main-layout/
|
||||||
│ │ ├── MainLayout.tsx
|
│ │ ├── MainLayout.tsx
|
||||||
│ │ └── MainLayout.types.ts
|
│ │ └── MainLayout.types.ts
|
||||||
|
│ ├── manager-layout/
|
||||||
|
│ │ └── ManagerLayout.tsx
|
||||||
|
│ ├── staff-layout/
|
||||||
|
│ │ └── StaffLayout.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
└── ATOMIC_DESIGN.md
|
└── ATOMIC_DESIGN.md
|
||||||
```
|
```
|
||||||
@@ -314,9 +358,15 @@ components/
|
|||||||
- **Mock data first** - Use `lib/constants.ts` for development, replace with API
|
- **Mock data first** - Use `lib/constants.ts` for development, replace with API
|
||||||
later
|
later
|
||||||
- **Contexts for state sharing:**
|
- **Contexts for state sharing:**
|
||||||
- `AuthContext` (lib/auth-context.tsx) - User state + auth operations
|
- `AuthContext` (lib/auth-context.tsx) - User state + auth operations →
|
||||||
- `CartContext` (lib/cart-context.tsx) - Shopping cart + operations
|
`useAuth()`
|
||||||
- `MenuContext` (lib/menu-context.tsx) - Active category state
|
- `CartContext` (lib/cart-context.tsx) - Shopping cart + operations →
|
||||||
|
`useCart()` (includes `clearCart`)
|
||||||
|
- `MenuContext` (lib/menu-context.tsx) - Active category state → `useMenu()`
|
||||||
|
- `ManagerContext` (lib/manager-context.tsx) - Manager dashboard state (CRUD
|
||||||
|
for products/combos/categories) → `useManager()`
|
||||||
|
- `ShiftContext` (lib/shift-context.tsx) - Staff shift management (create,
|
||||||
|
update, delete shifts, date selection) → `useShift()`
|
||||||
- **localStorage keys:** `coffee-shop-user`, `coffee-shop-cart` (defined in
|
- **localStorage keys:** `coffee-shop-user`, `coffee-shop-cart` (defined in
|
||||||
contexts)
|
contexts)
|
||||||
|
|
||||||
@@ -625,21 +675,33 @@ className = "w-full sm:w-1/2 md:w-1/3 p-4 md:p-6 hidden md:block";
|
|||||||
|
|
||||||
## 5) Project Status
|
## 5) Project Status
|
||||||
|
|
||||||
|
> Last Updated: 2026-04-18
|
||||||
|
|
||||||
### ✅ Completed Features
|
### ✅ Completed Features
|
||||||
|
|
||||||
- User authentication (login, register, logout)
|
- User authentication (login, register, logout)
|
||||||
- Product grid display with category filtering
|
- Product grid display with category filtering
|
||||||
- Shopping cart with add/remove/update operations
|
- Shopping cart with add/remove/update operations
|
||||||
- Payment page with review modal
|
- Payment page with PaymentSummaryCard + review modal
|
||||||
|
- Cash payment modal (CashPaymentModal) with change calculation
|
||||||
|
- QR payment modal (QRPaymentModal) with QR code generation (`qrcode.react`) and
|
||||||
|
5-second auto-confirm countdown
|
||||||
|
- Payment success modal (PaymentSuccessModal) with cart clear
|
||||||
- Shop discovery (feed page)
|
- Shop discovery (feed page)
|
||||||
- Responsive design (mobile, tablet, desktop)
|
- Responsive design (mobile, tablet, desktop)
|
||||||
- Header + Footer with navigation
|
- Header + Footer with navigation
|
||||||
- Sidebar category filter
|
- Sidebar category filter
|
||||||
|
- Manager dashboard — Products, Combos, Categories tabs with full CRUD
|
||||||
|
- Product image upload in ProductModal
|
||||||
|
- Financial Analytics dashboard (LineChart, BarChart, PieChart, ProductTable)
|
||||||
|
- Staff shift schedule (`/staff/schedule`) — weekly/monthly views, mobile view,
|
||||||
|
CRUD shifts, department filter
|
||||||
|
|
||||||
### 🚀 In Progress
|
### 🚀 In Progress
|
||||||
|
|
||||||
|
- Manager dashboard (~70% complete) - `app/(manager)/` route group
|
||||||
|
- Remaining: order management, inventory tracking, real API integration
|
||||||
- Atomic Design refactoring (components reorganization)
|
- Atomic Design refactoring (components reorganization)
|
||||||
- Manager dashboard (quản lý sản phẩm) - `app/(manager)/` route group
|
|
||||||
|
|
||||||
### 📋 Planned Features
|
### 📋 Planned Features
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
Dự án Frontend cho hệ thống đặt món cà phê, xây dựng bằng Next.js App Router,
|
Dự án Frontend cho hệ thống đặt món cà phê, xây dựng bằng Next.js App Router,
|
||||||
React 19, TypeScript và Tailwind CSS v4.
|
React 19, TypeScript và Tailwind CSS v4.
|
||||||
|
|
||||||
|
_Last Updated: 2026-04-18_
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mô Tả Dự Án
|
## Mô Tả Dự Án
|
||||||
@@ -11,7 +13,9 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
|
|||||||
|
|
||||||
### Tính Năng Hiện Tại
|
### Tính Năng Hiện Tại
|
||||||
|
|
||||||
#### 1. **Trang Đăng Nhập & Đăng Ký** (`app/(main)/login`, `app/(main)/register`)
|
#### Tính Năng Khách Hàng (Customer Features)
|
||||||
|
|
||||||
|
##### 1. **Trang Đăng Nhập & Đăng Ký** (`app/(main)/login`, `app/(main)/register`)
|
||||||
|
|
||||||
- Form đăng nhập với validation (username, password)
|
- Form đăng nhập với validation (username, password)
|
||||||
- Form đăng ký với xác thực OTP
|
- Form đăng ký với xác thực OTP
|
||||||
@@ -19,7 +23,7 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
|
|||||||
- Hỗ trợ 3 loại tài khoản: Manager, Staff, Customer
|
- Hỗ trợ 3 loại tài khoản: Manager, Staff, Customer
|
||||||
- Hiển thị thông tin shop trong form
|
- Hiển thị thông tin shop trong form
|
||||||
|
|
||||||
#### 2. **Trang Khám Phá Quán Nước** (`app/(feed)/feed`)
|
##### 2. **Trang Khám Phá Quán Nước** (`app/(feed)/feed`)
|
||||||
|
|
||||||
- Danh sách các quán cà phê
|
- Danh sách các quán cà phê
|
||||||
- Tìm kiếm theo tên quán và địa chỉ
|
- Tìm kiếm theo tên quán và địa chỉ
|
||||||
@@ -27,9 +31,7 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
|
|||||||
- Responsive layout (grid 1/2/3 cột)
|
- Responsive layout (grid 1/2/3 cột)
|
||||||
- Empty state khi không tìm thấy quán
|
- Empty state khi không tìm thấy quán
|
||||||
|
|
||||||
#### 3. **Trang Chính - Duyệt Thực Đơn** (`app/(main)`)
|
##### 3. **Trang Chính - Duyệt Thực Đơn** (`app/(main)`)
|
||||||
|
|
||||||
Dành cho khách hàng:
|
|
||||||
|
|
||||||
- Sidebar collapsible (64px/240px) - danh mục sản phẩm
|
- Sidebar collapsible (64px/240px) - danh mục sản phẩm
|
||||||
- Grid sản phẩm responsive (1-5 cột tuỳ thiết bị)
|
- Grid sản phẩm responsive (1-5 cột tuỳ thiết bị)
|
||||||
@@ -38,41 +40,52 @@ Dành cho khách hàng:
|
|||||||
- Lọc tự động theo trạng thái available
|
- Lọc tự động theo trạng thái available
|
||||||
- Mobile menu: scrollable category tabs (< md)
|
- Mobile menu: scrollable category tabs (< md)
|
||||||
|
|
||||||
#### 4. **Trang Thanh Toán** (`app/(main)/payment`)
|
##### 4. **Trang Thanh Toán** (`app/(main)/payment`)
|
||||||
|
|
||||||
- Bảng danh sách sản phẩm trong giỏ hàng với điều chỉnh số lượng
|
- Bảng danh sách sản phẩm trong giỏ hàng với điều chỉnh số lượng
|
||||||
- Invoice aside sticky với tổng giá và nút thanh toán (Tiền mặt, QR)
|
- Invoice aside sticky với tổng giá và nút thanh toán (Tiền mặt, QR)
|
||||||
- Modal đánh giá 5 sao dành riêng cho khách hàng (ReviewModal)
|
- Modal đánh giá 5 sao dành riêng cho khách hàng (ReviewModal)
|
||||||
|
- **CashPaymentModal:** nhập tiền mặt, tính tiền thối, báo lỗi nếu tiền không đủ
|
||||||
|
- **QRPaymentModal:** tạo mã QR thanh toán (qrcode.react), tự xác nhận sau 5
|
||||||
|
giây đếm ngược
|
||||||
|
- **PaymentSuccessModal:** xác nhận thanh toán thành công, tự động xoá giỏ hàng
|
||||||
|
|
||||||
#### 5. **Hệ Thống Giỏ Hàng** (`lib/cart-context.tsx`)
|
##### 5. **Hệ Thống Giỏ Hàng** (`lib/cart-context.tsx`)
|
||||||
|
|
||||||
- Lưu trữ trạng thái giỏ trong localStorage
|
- Lưu trữ trạng thái giỏ trong localStorage
|
||||||
- Thêm/xóa/tăng/giảm số lượng sản phẩm
|
- Thêm/xóa/tăng/giảm số lượng sản phẩm
|
||||||
- Tính tổng giá và số mặt hàng
|
- Tính tổng giá và số mặt hàng
|
||||||
- Persist dữ liệu giữa các session
|
- Persist dữ liệu giữa các session
|
||||||
|
- `clearCart()` để xoá toàn bộ giỏ sau thanh toán
|
||||||
|
|
||||||
#### 6. **Hệ Thống Xác Thực** (`lib/auth-context.tsx`)
|
##### 6. **Hệ Thống Xác Thực** (`lib/auth-context.tsx`)
|
||||||
|
|
||||||
- Quản lý trạng thái người dùng (login/logout/register)
|
- Quản lý trạng thái người dùng (login/logout/register)
|
||||||
- Lưu thông tin user trong localStorage
|
- Lưu thông tin user trong localStorage
|
||||||
- Mock auth database với 3 loại tài khoản
|
- Mock auth database với 3 loại tài khoản
|
||||||
- Hỗ trợ hoàn tất đăng ký qua OTP
|
- Hỗ trợ hoàn tất đăng ký qua OTP
|
||||||
|
|
||||||
#### 7. **Hệ Thống Danh Mục** (`lib/menu-context.tsx`)
|
##### 7. **Hệ Thống Danh Mục** (`lib/menu-context.tsx`)
|
||||||
|
|
||||||
- Chia sẻ trạng thái category giữa Header mobile và Sidebar
|
- Chia sẻ trạng thái category giữa Header mobile và Sidebar
|
||||||
- Tự động clear search khi thay đổi category
|
- Tự động clear search khi thay đổi category
|
||||||
|
|
||||||
#### 8. **Manager Dashboard** (`app/(manager)/manager`)
|
#### Tính Năng Quản Lý (Manager Features)
|
||||||
|
|
||||||
Dành cho quản lý:
|
##### 8. **Manager Dashboard** (`app/(manager)/manager`) — ~70% hoàn thành
|
||||||
|
|
||||||
- Sidebar desktop: Brand, tab navigation (Thực đơn / Combo / Danh mục), link tới
|
- Sidebar desktop: Brand, tab navigation (Thực đơn / Combo / Danh mục), link tới
|
||||||
Analytics
|
Analytics
|
||||||
- CRUD sản phẩm, combo, danh mục qua modals
|
|
||||||
- Auth guard: tự động redirect non-manager về `/`
|
- Auth guard: tự động redirect non-manager về `/`
|
||||||
|
- **Products tab:** CRUD sản phẩm qua ProductModal, bao gồm:
|
||||||
|
- Upload ảnh sản phẩm với xem trước, nút "Đổi ảnh" và xoá ảnh
|
||||||
|
- Giao diện vùng kéo thả với dashed border
|
||||||
|
- Toggle trạng thái available
|
||||||
|
- Dialog xác nhận xoá
|
||||||
|
- **Combos tab:** CRUD combo qua ComboModal, toggle trạng thái, xác nhận xoá
|
||||||
|
- **Categories tab:** CRUD danh mục qua CategoryModal, xác nhận xoá
|
||||||
|
|
||||||
#### 9. **Trang Phân Tích Tài Chính** (`app/(manager)/manager/analytics`)
|
##### 9. **Trang Phân Tích Tài Chính** (`app/(manager)/manager/analytics`)
|
||||||
|
|
||||||
- Summary cards: Doanh thu, đơn hàng, lợi nhuận, giá trị đơn trung bình (so sánh
|
- Summary cards: Doanh thu, đơn hàng, lợi nhuận, giá trị đơn trung bình (so sánh
|
||||||
kỳ trước)
|
kỳ trước)
|
||||||
@@ -81,6 +94,24 @@ Dành cho quản lý:
|
|||||||
- Bảng top 5 sản phẩm và bảng chi tiết có thể sắp xếp
|
- Bảng top 5 sản phẩm và bảng chi tiết có thể sắp xếp
|
||||||
- Lọc theo danh mục
|
- Lọc theo danh mục
|
||||||
|
|
||||||
|
#### Tính Năng Nhân Viên (Staff Features)
|
||||||
|
|
||||||
|
##### 10. **Lịch Làm Việc Nhân Viên** (`app/(staff)/staff/schedule`)
|
||||||
|
|
||||||
|
- Xem lịch theo tuần và theo tháng
|
||||||
|
- Mobile shift view (danh sách ca theo ngày)
|
||||||
|
- Lọc theo phòng ban / bộ phận
|
||||||
|
- Modal tạo ca làm việc mới (ShiftCreateModal)
|
||||||
|
- Modal xem chi tiết ca (ShiftDetailModal)
|
||||||
|
- Auth guard: chỉ staff/manager mới truy cập được
|
||||||
|
|
||||||
|
#### Cải Thiện Tiếp Cận (Accessibility)
|
||||||
|
|
||||||
|
- Thuộc tính `title` trên các nút icon (tooltip cho screen reader)
|
||||||
|
- ARIA roles trên modals: `role="dialog"`, `aria-modal`, `aria-labelledby`
|
||||||
|
- Semantic HTML: nhãn form với `htmlFor` đúng chuẩn
|
||||||
|
- `alt` text đầy đủ cho ảnh sản phẩm
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cách Chạy Dự Án
|
## Cách Chạy Dự Án
|
||||||
@@ -144,9 +175,12 @@ frondend/
|
|||||||
| | +-- layout.tsx # Feed layout
|
| | +-- layout.tsx # Feed layout
|
||||||
| | +-- feed/page.tsx # Trang khám phá quán nước (/feed)
|
| | +-- feed/page.tsx # Trang khám phá quán nước (/feed)
|
||||||
| +-- (manager)/ # Manager route group
|
| +-- (manager)/ # Manager route group
|
||||||
| +-- layout.tsx # Manager layout - auth guard + ManagerProvider
|
| | +-- layout.tsx # Manager layout - auth guard + ManagerProvider
|
||||||
| +-- manager/page.tsx # Manager Dashboard (/manager)
|
| | +-- manager/page.tsx # Manager Dashboard (/manager)
|
||||||
| +-- manager/analytics/page.tsx # Financial Analytics (/manager/analytics)
|
| | +-- manager/analytics/page.tsx # Financial Analytics (/manager/analytics)
|
||||||
|
| +-- (staff)/ # Staff route group
|
||||||
|
| +-- layout.tsx # Staff layout - auth guard
|
||||||
|
| +-- staff/schedule/page.tsx # Lịch làm việc nhân viên (/staff/schedule)
|
||||||
+-- components/ # Atomic Design UI components
|
+-- components/ # Atomic Design UI components
|
||||||
| +-- atoms/ # Nguyên tử: Button, Input, Badge, Text...
|
| +-- atoms/ # Nguyên tử: Button, Input, Badge, Text...
|
||||||
| +-- molecules/ # Phân tử: ProductCard, ShopCard, SearchBar...
|
| +-- molecules/ # Phân tử: ProductCard, ShopCard, SearchBar...
|
||||||
@@ -202,8 +236,9 @@ frondend/
|
|||||||
| ---------------- | --------- | ------------------------------------- |
|
| ---------------- | --------- | ------------------------------------- |
|
||||||
| Next.js | 16.1.7 | React Framework (App Router) |
|
| Next.js | 16.1.7 | React Framework (App Router) |
|
||||||
| React | 19.2.3 | Thư viện UI |
|
| React | 19.2.3 | Thư viện UI |
|
||||||
| TypeScript | ^5 | Kiểu dữ liệu tĩnh |
|
| TypeScript | ^5.9.3 | Kiểu dữ liệu tĩnh |
|
||||||
| Tailwind CSS | ^4.2.2 | Utility-first CSS framework |
|
| Tailwind CSS | ^4.2.2 | Utility-first CSS framework |
|
||||||
|
| qrcode.react | ^4.2.0 | Tạo mã QR thanh toán |
|
||||||
| Geist Font | - | Font chữ (Google Fonts via next/font) |
|
| Geist Font | - | Font chữ (Google Fonts via next/font) |
|
||||||
| FontAwesome | 6.7.2 | Icon library (CDN) |
|
| FontAwesome | 6.7.2 | Icon library (CDN) |
|
||||||
| pnpm | - | Package manager |
|
| pnpm | - | Package manager |
|
||||||
@@ -224,14 +259,15 @@ frondend/
|
|||||||
- **Thanh Toán:** `app/(main)/payment/page.tsx`
|
- **Thanh Toán:** `app/(main)/payment/page.tsx`
|
||||||
- **Manager Dashboard:** `app/(manager)/manager/page.tsx`
|
- **Manager Dashboard:** `app/(manager)/manager/page.tsx`
|
||||||
- **Phân Tích Tài Chính:** `app/(manager)/manager/analytics/page.tsx`
|
- **Phân Tích Tài Chính:** `app/(manager)/manager/analytics/page.tsx`
|
||||||
|
- **Lịch Làm Việc Nhân Viên:** `app/(staff)/staff/schedule/page.tsx`
|
||||||
|
|
||||||
### Tài Khoản Demo
|
### Tài Khoản Demo
|
||||||
|
|
||||||
| Loại tài khoản | Tên đăng nhập | Mật khẩu |
|
| Loại tài khoản | Tên đăng nhập | Mật khẩu / OTP |
|
||||||
| -------------- | ------------- | ------------- |
|
| -------------- | ------------- | -------------------------- |
|
||||||
| Manager | admin | admin |
|
| Manager | admin | admin |
|
||||||
| Staff | Nguyễn Văn An | Nguyễn Văn An |
|
| Staff | Nguyễn Văn An | Nguyễn Văn An |
|
||||||
| Customer | 0987654321 | user1 |
|
| Customer | 0987654321 | bất kỳ 6 chữ số (mock OTP) |
|
||||||
|
|
||||||
### Design & Styling
|
### Design & Styling
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Coffee Shop Frontend - TODO
|
# Coffee Shop Frontend - TODO
|
||||||
|
|
||||||
|
_Last Updated: 2026-04-18_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Completed Features & Implementations
|
## Completed Features & Implementations
|
||||||
|
|
||||||
### A. Dead Code Removed
|
### A. Dead Code Removed
|
||||||
@@ -37,17 +41,66 @@
|
|||||||
- [x] npm run build - PASSED (Compiled successfully, TypeScript clean, static
|
- [x] npm run build - PASSED (Compiled successfully, TypeScript clean, static
|
||||||
pages generated)
|
pages generated)
|
||||||
|
|
||||||
|
### F. Cart & Payment (Completed)
|
||||||
|
|
||||||
|
- [x] Cart checkout flow — cart context with addToCart, removeFromCart,
|
||||||
|
setQuantity, clearCart
|
||||||
|
- [x] Payment page implementation (`app/(main)/payment`)
|
||||||
|
- [x] CashPaymentModal: cash input, change calculation, insufficient cash error
|
||||||
|
- [x] QRPaymentModal: QR code generation via qrcode.react, 5-second countdown
|
||||||
|
auto-confirm
|
||||||
|
- [x] PaymentSuccessModal: success confirmation, auto-clears cart
|
||||||
|
|
||||||
|
### G. Manager Dashboard — ~70% Complete (In Progress)
|
||||||
|
|
||||||
|
- [x] Manager Dashboard page (`app/(manager)/manager/page.tsx`) with auth guard
|
||||||
|
- [x] Products tab with CRUD (ProductModal)
|
||||||
|
- [x] Image upload in ProductModal: file input, preview, "Đổi ảnh" button,
|
||||||
|
remove button, dashed border drop zone
|
||||||
|
- [x] Combos tab with CRUD (ComboModal)
|
||||||
|
- [x] Categories tab with CRUD (CategoryModal)
|
||||||
|
- [x] Delete confirmation dialogs for all entities
|
||||||
|
- [x] Status toggle buttons (available/unavailable)
|
||||||
|
- [x] Sidebar navigation with link to Analytics
|
||||||
|
|
||||||
|
### H. Financial Analytics Dashboard (Completed)
|
||||||
|
|
||||||
|
- [x] Analytics page (`app/(manager)/manager/analytics`)
|
||||||
|
- [x] Summary cards: Revenue, Orders, Profit, Average Order Value (with period
|
||||||
|
comparison)
|
||||||
|
- [x] Period selector: Day / Week / Month / Year
|
||||||
|
- [x] SVG charts: LineChart, BarChart, PieChart with hover tooltips
|
||||||
|
- [x] Top 5 products table and sortable detail table
|
||||||
|
- [x] Category filter
|
||||||
|
|
||||||
|
### I. Staff Shift Schedule (Completed)
|
||||||
|
|
||||||
|
- [x] Staff schedule module (`app/(staff)/staff/schedule`)
|
||||||
|
- [x] Weekly calendar view
|
||||||
|
- [x] Monthly calendar view
|
||||||
|
- [x] Mobile shift view (list per day)
|
||||||
|
- [x] ShiftCreateModal: create new shifts
|
||||||
|
- [x] ShiftDetailModal: view shift details
|
||||||
|
- [x] Department/role filtering
|
||||||
|
- [x] Auth guard for staff/manager only
|
||||||
|
|
||||||
|
### J. Accessibility Improvements (Completed)
|
||||||
|
|
||||||
|
- [x] title attributes on icon buttons (tooltips for screen readers)
|
||||||
|
- [x] ARIA roles on modals: role="dialog", aria-modal, aria-labelledby
|
||||||
|
- [x] Semantic HTML: form labels with correct htmlFor
|
||||||
|
- [x] Image alt text for product images
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Pending Features (Future Work)
|
## Pending Features (Future Work)
|
||||||
|
|
||||||
### Cart & Ordering
|
### Cart & Ordering
|
||||||
|
|
||||||
- [ ] Implement cart checkout flow (app/(main)/cart or modal)
|
- [ ] Cart sidebar/modal with item list and total (quick-access without going to
|
||||||
- [ ] Cart sidebar/modal with item list and total
|
payment page)
|
||||||
- [ ] Order submission API integration
|
- [ ] Order submission API integration
|
||||||
- [ ] Payment page implementation (app/(main)/payment)
|
- [ ] Order history/tracking page for customers
|
||||||
- [ ] Order history/tracking page
|
|
||||||
- [ ] Toast notifications for cart actions
|
- [ ] Toast notifications for cart actions
|
||||||
|
|
||||||
### Authentication & User Management
|
### Authentication & User Management
|
||||||
@@ -58,22 +111,22 @@
|
|||||||
- [ ] Password reset/recovery flow
|
- [ ] Password reset/recovery flow
|
||||||
- [ ] Session management and token refresh
|
- [ ] Session management and token refresh
|
||||||
|
|
||||||
### Manager Features
|
### Manager Features (Remaining ~30%)
|
||||||
|
|
||||||
- [ ] Manager dashboard page (app/(manager)/page.tsx)
|
- [ ] Order management & tracking for manager
|
||||||
- [ ] Product management (add/edit/delete)
|
- [ ] Inventory management beyond available toggle (stock counts, low-stock
|
||||||
- [ ] Category management
|
alerts)
|
||||||
- [ ] Order management & tracking
|
- [ ] Staff management tab in Manager Dashboard
|
||||||
- [ ] Sales analytics/dashboard
|
- [ ] Shift approval workflow (manager approves staff shift requests)
|
||||||
- [ ] Inventory management
|
|
||||||
|
|
||||||
### Backend Integration
|
### Backend Integration
|
||||||
|
|
||||||
- [ ] Replace MOCK_PRODUCTS with API calls (GET /api/products)
|
- [ ] Replace MOCK_PRODUCTS with API calls (GET /api/products)
|
||||||
- [ ] Replace MOCK_SHOPS with API calls (GET /api/shops)
|
- [ ] Replace MOCK_SHOPS with API calls (GET /api/shops)
|
||||||
- [ ] Replace MOCK_USERS with real authentication (POST /api/auth/login)
|
- [ ] Replace MOCK_USERS with real authentication (POST /api/auth/login)
|
||||||
- [ ] Real product images (replace placeholder.jpg)
|
- [ ] Real product images hosted on server/CDN (replace public/imgs/products/)
|
||||||
- [ ] Image upload for products
|
- [ ] Real analytics data from backend (GET /api/analytics)
|
||||||
|
- [ ] Real shift/schedule data from backend
|
||||||
|
|
||||||
### UX Improvements
|
### UX Improvements
|
||||||
|
|
||||||
@@ -85,11 +138,12 @@
|
|||||||
- [ ] Filter by price range
|
- [ ] Filter by price range
|
||||||
- [ ] Quantity selector in product card
|
- [ ] Quantity selector in product card
|
||||||
- [ ] Related products suggestions
|
- [ ] Related products suggestions
|
||||||
|
- [ ] Push notifications for order status updates
|
||||||
|
|
||||||
### Performance & SEO
|
### Performance & SEO
|
||||||
|
|
||||||
- [ ] Dynamic route generation for products (app/(main)/product/[id]/page.tsx)
|
- [ ] Dynamic route generation for products (`app/(main)/product/[id]/page.tsx`)
|
||||||
- [ ] Dynamic route generation for shops (app/(feed)/shop/[id]/page.tsx)
|
- [ ] Dynamic route generation for shops (`app/(feed)/shop/[id]/page.tsx`)
|
||||||
- [ ] Meta tags and Open Graph for SEO
|
- [ ] Meta tags and Open Graph for SEO
|
||||||
- [ ] Image optimization and lazy loading
|
- [ ] Image optimization and lazy loading
|
||||||
- [ ] Code splitting and dynamic imports
|
- [ ] Code splitting and dynamic imports
|
||||||
@@ -97,7 +151,7 @@
|
|||||||
### Accessibility & Testing
|
### Accessibility & Testing
|
||||||
|
|
||||||
- [ ] Keyboard navigation testing
|
- [ ] Keyboard navigation testing
|
||||||
- [ ] ARIA labels audit
|
- [ ] Full ARIA labels audit (WCAG 2.1 AA)
|
||||||
- [ ] Unit tests for contexts
|
- [ ] Unit tests for contexts (auth, cart, menu, manager)
|
||||||
- [ ] E2E tests for user flows
|
- [ ] E2E tests for user flows (checkout, login, manager CRUD)
|
||||||
- [ ] Accessibility audit (WCAG 2.1 AA)
|
- [ ] Accessibility audit tool (axe, Lighthouse)
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ export default function PaymentPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function ManagerPage() {
|
|||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`flex w-full cursor-pointer items-center gap-3 rounded-xl border-none px-3 py-2.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 ${
|
className={`flex w-full cursor-pointer items-center gap-3 rounded-xl border-none px-3 py-2.5 text-sm font-medium transition-all focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 focus-visible:outline-none ${
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? "bg-(--color-primary) text-white shadow-sm"
|
? "bg-(--color-primary) text-white shadow-sm"
|
||||||
: "hover:bg-background bg-transparent text-(--color-text-secondary) hover:text-(--color-primary-dark)"
|
: "hover:bg-background bg-transparent text-(--color-text-secondary) hover:text-(--color-primary-dark)"
|
||||||
@@ -82,7 +82,7 @@ export default function ManagerPage() {
|
|||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/manager/analytics"
|
href="/manager/analytics"
|
||||||
className="hover:bg-background flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) no-underline transition-all hover:text-(--color-primary-dark) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-primary)/50"
|
className="hover:bg-background flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) no-underline transition-all hover:text-(--color-primary-dark) focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-chart-line w-4 text-center"></i>
|
<i className="fa-solid fa-chart-line w-4 text-center"></i>
|
||||||
<span className="flex-1 text-left">Tài chính</span>
|
<span className="flex-1 text-left">Tài chính</span>
|
||||||
@@ -92,7 +92,7 @@ export default function ManagerPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/staff/schedule"
|
href="/staff/schedule"
|
||||||
className="hover:bg-background flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) no-underline transition-all hover:text-(--color-primary-dark) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-primary)/50"
|
className="hover:bg-background flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium text-(--color-text-secondary) no-underline transition-all hover:text-(--color-primary-dark) focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-calendar-days w-4 text-center"></i>
|
<i className="fa-solid fa-calendar-days w-4 text-center"></i>
|
||||||
<span className="flex-1 text-left">Ca làm</span>
|
<span className="flex-1 text-left">Ca làm</span>
|
||||||
@@ -125,7 +125,7 @@ export default function ManagerPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className="flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-xl border-none bg-transparent py-2 text-xs font-medium text-red-500 transition hover:bg-red-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-400/50"
|
className="flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-xl border-none bg-transparent py-2 text-xs font-medium text-red-500 transition hover:bg-red-50 focus-visible:ring-2 focus-visible:ring-red-400/50 focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-right-from-bracket"></i>
|
<i className="fa-solid fa-right-from-bracket"></i>
|
||||||
Đăng xuất
|
Đăng xuất
|
||||||
@@ -158,7 +158,7 @@ export default function ManagerPage() {
|
|||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`flex cursor-pointer items-center gap-1.5 rounded-xl border-none px-3 py-2 text-xs font-medium transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 ${
|
className={`flex cursor-pointer items-center gap-1.5 rounded-xl border-none px-3 py-2 text-xs font-medium transition focus-visible:ring-2 focus-visible:ring-(--color-primary)/50 focus-visible:outline-none ${
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? "bg-(--color-primary) text-white"
|
? "bg-(--color-primary) text-white"
|
||||||
: "bg-background text-(--color-text-secondary) hover:text-(--color-primary)"
|
: "bg-background text-(--color-text-secondary) hover:text-(--color-primary)"
|
||||||
|
|||||||
+109
-18
@@ -24,12 +24,16 @@ app/
|
|||||||
│ ├── layout.tsx # Feed layout
|
│ ├── layout.tsx # Feed layout
|
||||||
│ └── feed/
|
│ └── feed/
|
||||||
│ └── page.tsx # Trang khám phá quán (/feed)
|
│ └── page.tsx # Trang khám phá quán (/feed)
|
||||||
└── (manager)/
|
├── (manager)/
|
||||||
├── layout.tsx # Manager layout - auth guard + ManagerProvider
|
│ ├── layout.tsx # Manager layout - auth guard + ManagerProvider
|
||||||
└── manager/
|
│ └── manager/
|
||||||
├── page.tsx # Manager Dashboard - quản lý thực đơn (/manager)
|
│ ├── page.tsx # Manager Dashboard - quản lý thực đơn (/manager)
|
||||||
└── analytics/
|
│ └── analytics/
|
||||||
└── page.tsx # Financial Analytics (/manager/analytics)
|
│ └── page.tsx # Financial Analytics (/manager/analytics)
|
||||||
|
└── (staff)/
|
||||||
|
└── staff/
|
||||||
|
└── schedule/
|
||||||
|
└── page.tsx # Staff Shift Schedule (/staff/schedule)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -241,7 +245,7 @@ registration flow. Two-step process: phone verification → account creation.
|
|||||||
|
|
||||||
**Route:** `/payment` **Type:** Client component **Description:**
|
**Route:** `/payment` **Type:** Client component **Description:**
|
||||||
Payment/checkout page. Shows cart items in a table with quantity controls and a
|
Payment/checkout page. Shows cart items in a table with quantity controls and a
|
||||||
summary aside with payment actions.
|
summary aside (PaymentSummaryCard) with payment actions and modal flows.
|
||||||
|
|
||||||
#### Key Features
|
#### Key Features
|
||||||
|
|
||||||
@@ -251,27 +255,46 @@ summary aside with payment actions.
|
|||||||
- Empty state message when cart is empty
|
- Empty state message when cart is empty
|
||||||
- Horizontal scroll on small screens (min-w-190)
|
- Horizontal scroll on small screens (min-w-190)
|
||||||
|
|
||||||
- **Invoice Aside:**
|
- **PaymentSummaryCard (Invoice Aside):**
|
||||||
- Sticky on desktop (top offset = header height + 1rem)
|
- Sticky on desktop (top offset = header height + 1rem)
|
||||||
- Shows total price
|
- Shows all cart items with subtotals and grand total
|
||||||
- Payment buttons: Tiền mặt, QR Code (UI-only; trigger review modal for
|
- Payment method buttons: Tiền mặt (Cash), QR Code
|
||||||
customers)
|
|
||||||
- **"Đánh giá" button** — only visible when `user.role === "customer"`; opens
|
- **"Đánh giá" button** — only visible when `user.role === "customer"`; opens
|
||||||
ReviewModal
|
ReviewModal
|
||||||
- **"Quay về" button** — links back to `/`; spans full width when review
|
- **"Quay về" button** — links back to `/`; spans full width when review
|
||||||
button is absent
|
button is absent
|
||||||
|
|
||||||
- **Review Modal (ReviewModal):**
|
#### Payment Modal Flow
|
||||||
- Opened when a customer clicks "Đánh giá" OR clicks a payment button (Tiền
|
|
||||||
mặt/QR)
|
- **Cash Payment (CashPaymentModal):**
|
||||||
- Closed via "Quay lại" button, backdrop click, or after submitting and
|
- Opened when user clicks "Tiền mặt"
|
||||||
closing
|
- Input: cash received amount
|
||||||
|
- Shows change due or insufficient cash error inline
|
||||||
|
- Displays invoice item list
|
||||||
|
- Confirm → opens PaymentSuccessModal
|
||||||
|
|
||||||
|
- **QR Payment (QRPaymentModal):**
|
||||||
|
- Opened when user clicks "QR Code"
|
||||||
|
- Generates QR code via `qrcode.react` (`QRCodeSVG` component)
|
||||||
|
- 5-second countdown timer — auto-confirms on expiry
|
||||||
|
- Displays invoice item list
|
||||||
|
- Confirm → opens PaymentSuccessModal
|
||||||
|
|
||||||
|
- **PaymentSuccessModal:**
|
||||||
|
- Shown after confirming Cash or QR payment
|
||||||
|
- Displays success confirmation message
|
||||||
|
- Clears cart via `clearCart()` from CartContext on close/confirm
|
||||||
|
|
||||||
|
- **ReviewModal:**
|
||||||
|
- Opened when a customer clicks "Đánh giá"
|
||||||
|
- 5-star rating + textarea for comment
|
||||||
|
- Closed via "Quay lại" button, backdrop click, or after submitting
|
||||||
- See `components/ReviewModal.tsx` for full documentation
|
- See `components/ReviewModal.tsx` for full documentation
|
||||||
|
|
||||||
#### Context Usage
|
#### Context Usage
|
||||||
|
|
||||||
- **useCart()** — items, totalPrice, increaseQty, decreaseQty, removeFromCart,
|
- **useCart()** — items, totalPrice, increaseQty, decreaseQty, removeFromCart,
|
||||||
setQuantity
|
setQuantity, clearCart
|
||||||
- **useAuth()** — user (to check `user.role === "customer"` for review button
|
- **useAuth()** — user (to check `user.role === "customer"` for review button
|
||||||
visibility)
|
visibility)
|
||||||
|
|
||||||
@@ -279,6 +302,9 @@ summary aside with payment actions.
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
isReviewOpen: boolean; // Controls ReviewModal visibility
|
isReviewOpen: boolean; // Controls ReviewModal visibility
|
||||||
|
isCashModalOpen: boolean; // Controls CashPaymentModal visibility
|
||||||
|
isQRModalOpen: boolean; // Controls QRPaymentModal visibility
|
||||||
|
isSuccessModalOpen: boolean; // Controls PaymentSuccessModal visibility
|
||||||
isCustomer: boolean; // Derived from user.role === "customer"
|
isCustomer: boolean; // Derived from user.role === "customer"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -502,13 +528,78 @@ categoryFilter: string; // category id or "all"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## (staff) Route Group
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
**Description:** Route group for staff-specific pages. Uses `StaffLayout` from
|
||||||
|
`components/templates/staff-layout/` which provides the staff navigation shell.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Staff Shift Schedule (app/(staff)/staff/schedule/page.tsx)
|
||||||
|
|
||||||
|
**Route:** `/staff/schedule` **Type:** Client component **Description:** Weekly
|
||||||
|
and monthly shift schedule management for staff members.
|
||||||
|
|
||||||
|
#### Key Features
|
||||||
|
|
||||||
|
- **Weekly Schedule View (WeeklySchedule.tsx):**
|
||||||
|
- Grid-based view of shifts for each day of the current week
|
||||||
|
- Shows shift time, role, and department per cell
|
||||||
|
- Click a shift to open ShiftDetailModal
|
||||||
|
|
||||||
|
- **Monthly Calendar View (MonthlyCalendar.tsx):**
|
||||||
|
- Full month calendar grid
|
||||||
|
- Shifts rendered as colored chips per day
|
||||||
|
- Navigate between months
|
||||||
|
|
||||||
|
- **Mobile Shift View (MobileShiftView.tsx):**
|
||||||
|
- Optimized list-based view for small screens
|
||||||
|
- Shows upcoming shifts for the current/selected date
|
||||||
|
|
||||||
|
- **ShiftCreateModal:**
|
||||||
|
- Form to create a new shift: date, start/end time, role, department
|
||||||
|
- Validates time range before submitting
|
||||||
|
|
||||||
|
- **ShiftDetailModal:**
|
||||||
|
- Shows full details of a selected shift
|
||||||
|
- Options to edit or delete the shift
|
||||||
|
|
||||||
|
- **Department Filter:**
|
||||||
|
- Dropdown to filter schedule view by department
|
||||||
|
|
||||||
|
#### Context Usage
|
||||||
|
|
||||||
|
- **useShift()** (lib/shift-context.tsx) — shifts, createShift, updateShift,
|
||||||
|
deleteShift, selectedDate, setSelectedDate
|
||||||
|
|
||||||
|
#### State Management
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
activeView: "weekly" | "monthly"; // Toggle between calendar views
|
||||||
|
isCreateModalOpen: boolean; // Controls ShiftCreateModal
|
||||||
|
selectedShift: Shift | null; // Shift passed to ShiftDetailModal
|
||||||
|
departmentFilter: string; // Currently selected department
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Responsive Behavior
|
||||||
|
|
||||||
|
- **Mobile (<768px):** MobileShiftView displayed instead of grid calendars
|
||||||
|
- **Tablet/Desktop (768px+):** WeeklySchedule or MonthlyCalendar displayed
|
||||||
|
- **Department filter:** Sticky top bar on all screen sizes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|
||||||
- [x] Payment page implementation
|
- [x] Payment page implementation
|
||||||
- [x] Customer review modal (ReviewModal) with 5-star rating + textarea
|
- [x] Customer review modal (ReviewModal) with 5-star rating + textarea
|
||||||
- [x] Manager dashboard (menu management)
|
- [x] Manager dashboard (menu management)
|
||||||
- [x] Financial Analytics dashboard (charts, profit analysis)
|
- [x] Financial Analytics dashboard (charts, profit analysis)
|
||||||
|
- [x] Staff shift schedule (weekly/monthly views, CRUD, department filter)
|
||||||
|
- [x] Cash and QR payment modals with invoice display
|
||||||
|
- [x] Image upload for products in manager dashboard
|
||||||
- [ ] Order history/tracking page
|
- [ ] Order history/tracking page
|
||||||
- [ ] User profile page
|
- [ ] User profile page
|
||||||
- [ ] Cart checkout flow
|
|
||||||
- [ ] Real backend API integration
|
- [ ] Real backend API integration
|
||||||
|
|||||||
+180
-7
@@ -7,14 +7,31 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
components/
|
components/
|
||||||
├── atoms/ # Basic building blocks (Button, Input, Text, Badge...)
|
├── atoms/ # Basic building blocks — 14 atoms (Button, Input, Text, Badge...) — see atoms/ATOMS.md
|
||||||
├── molecules/ # Groups of atoms (ProductCard, SearchBar...)
|
├── molecules/ # Groups of atoms — 5 molecules (ProductCard, ShopCard, ShiftCard, SearchBar, PaymentSummaryCard)
|
||||||
├── organisms/ # Complex UI sections (CategorySidebar, CartFab, ProductGrid, ReviewModal, analytics charts...)
|
├── organisms/ # Complex UI sections — 27 organisms (analytics, cart, forms, manager, modals, navigation, grids, shift-schedule)
|
||||||
└── templates/ # Page layout structures (MainLayout, AuthLayout...)
|
└── templates/ # Page layout structures — 5 templates (MainLayout, AuthLayout, FeedLayout, ManagerLayout, StaffLayout)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ATOMS
|
||||||
|
|
||||||
|
See [`atoms/ATOMS.md`](atoms/ATOMS.md) for full atom documentation (14 atoms).
|
||||||
|
|
||||||
|
**Summary of atom groups:**
|
||||||
|
|
||||||
|
| Folder | Atoms |
|
||||||
|
| ----------- | -------------------------------------------- |
|
||||||
|
| buttons/ | Button, IconButton |
|
||||||
|
| inputs/ | TextInput, SearchInput, Textarea, LoginInput |
|
||||||
|
| typography/ | Heading, Text, Caption |
|
||||||
|
| badges/ | Badge, PriceBadge |
|
||||||
|
| dividers/ | Divider |
|
||||||
|
| errors/ | ErrorMessageLogin |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## MOLECULES
|
## MOLECULES
|
||||||
|
|
||||||
### ProductCard (`molecules/cards/ProductCard.tsx`)
|
### ProductCard (`molecules/cards/ProductCard.tsx`)
|
||||||
@@ -25,16 +42,58 @@ parent grid (w-full).
|
|||||||
|
|
||||||
**Atomic level:** Molecule (composed of Button, Caption, Text atoms)
|
**Atomic level:** Molecule (composed of Button, Caption, Text atoms)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### ShopCard (`molecules/cards/ShopCard.tsx`)
|
### ShopCard (`molecules/cards/ShopCard.tsx`)
|
||||||
|
|
||||||
**Description:** Shop discovery card. Displays shop image, name, address, and a
|
**Description:** Shop discovery card. Displays shop image, name, address, and a
|
||||||
link to the menu.
|
link to the menu.
|
||||||
|
|
||||||
|
**Atomic level:** Molecule
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ShiftCard (`molecules/cards/ShiftCard.tsx`)
|
||||||
|
|
||||||
|
**Description:** Card displaying a single shift slot. Shows start/end time,
|
||||||
|
duration, wage, status badge, and registered staff list. Supports a compact mode
|
||||||
|
for use in calendar grids.
|
||||||
|
|
||||||
|
**Atomic level:** Molecule
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ShiftCardProps {
|
||||||
|
shift: ShiftSlot; // Shift slot data (status, times, staff, etc.)
|
||||||
|
compact?: boolean; // Compact mode for calendar cells (default: false)
|
||||||
|
onClick?: (shift: ShiftSlot) => void; // Click handler
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status variants:** `available` (blue), `registered` (dark blue),
|
||||||
|
`approved_leave` (purple), `absent` (red)
|
||||||
|
|
||||||
|
**Used in:** `WeeklySchedule`, `MobileShiftView`, `MonthlyCalendar`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### SearchBar (`molecules/search-bar/SearchBar.tsx`)
|
### SearchBar (`molecules/search-bar/SearchBar.tsx`)
|
||||||
|
|
||||||
**Description:** Search input with clear button. Controlled component — value
|
**Description:** Search input with clear button. Controlled component — value
|
||||||
and onChange come from parent.
|
and onChange come from parent.
|
||||||
|
|
||||||
|
**Atomic level:** Molecule (wraps SearchInput atom)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### PaymentSummaryCard (`molecules/cards/PaymentSummaryCard.tsx`)
|
||||||
|
|
||||||
|
**Description:** Summary card displayed on the payment page. Shows cart items,
|
||||||
|
subtotal, and payment method selection. Triggers cash or QR payment modals.
|
||||||
|
|
||||||
|
**Atomic level:** Molecule
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ORGANISMS
|
## ORGANISMS
|
||||||
@@ -44,25 +103,35 @@ and onChange come from parent.
|
|||||||
**Description:** Left sidebar with collapsible category filter. Sticky below
|
**Description:** Left sidebar with collapsible category filter. Sticky below
|
||||||
header, full viewport height minus header.
|
header, full viewport height minus header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### CartFab (`organisms/cart/CartFab.tsx`)
|
### CartFab (`organisms/cart/CartFab.tsx`)
|
||||||
|
|
||||||
**Description:** Floating Action Button displaying cart item count. Links to
|
**Description:** Floating Action Button displaying cart item count. Links to
|
||||||
/payment page.
|
/payment page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### ProductGrid (`organisms/product-grid/ProductGrid.tsx`)
|
### ProductGrid (`organisms/product-grid/ProductGrid.tsx`)
|
||||||
|
|
||||||
**Description:** Full product grid organism with mobile category tabs,
|
**Description:** Full product grid organism with mobile category tabs,
|
||||||
filtering, and empty state. Uses useCart and useMenu contexts.
|
filtering, and empty state. Uses useCart and useMenu contexts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### ShopGrid (`organisms/shop-grid/ShopGrid.tsx`)
|
### ShopGrid (`organisms/shop-grid/ShopGrid.tsx`)
|
||||||
|
|
||||||
**Description:** Responsive grid of ShopCard molecules for the feed/discovery
|
**Description:** Responsive grid of ShopCard molecules for the feed/discovery
|
||||||
page.
|
page.
|
||||||
|
|
||||||
### ReviewModal (`organisms/modals/ReviewModal.tsx`)
|
---
|
||||||
|
|
||||||
**Description:** Modal for customer reviews. Shows 5-star rating and textarea.
|
### LoginForm (`organisms/forms/LoginForm.tsx`)
|
||||||
After submission, displays thank-you message.
|
|
||||||
|
**Description:** Login form organism. Uses LoginInput and ErrorMessageLogin
|
||||||
|
atoms. Handles admin/staff/customer login flows via useAuth context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Analytics Charts (`organisms/analytics/`)
|
### Analytics Charts (`organisms/analytics/`)
|
||||||
|
|
||||||
@@ -81,6 +150,101 @@ dashboard. All components are interactive with hover tooltips.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Manager Organisms (`organisms/manager/`)
|
||||||
|
|
||||||
|
Components for the manager dashboard product/category/combo management.
|
||||||
|
|
||||||
|
| Component | File | Description |
|
||||||
|
| ---------------- | ----------------- | ------------------------------------------------------- |
|
||||||
|
| CategoriesTab | CategoriesTab.tsx | Category list with add/edit/delete actions |
|
||||||
|
| CategoryModal | CategoryModal.tsx | Modal form to create/edit a category |
|
||||||
|
| ComboModal | ComboModal.tsx | Modal form to create/edit a combo |
|
||||||
|
| CombosTab | CombosTab.tsx | Combo list with add/edit/delete actions |
|
||||||
|
| DeleteConfirm | DeleteConfirm.tsx | Generic delete confirmation dialog |
|
||||||
|
| ProductModal | ProductModal.tsx | Modal form to create/edit a product (with image upload) |
|
||||||
|
| ProductsTab | ProductsTab.tsx | Product list with add/edit/delete actions |
|
||||||
|
| StatusBadge | StatusBadge.tsx | Badge showing active/inactive status |
|
||||||
|
| Manager.types.ts | Manager.types.ts | Shared TypeScript interfaces for manager components |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Modals (`organisms/modals/`)
|
||||||
|
|
||||||
|
| Component | File | Description |
|
||||||
|
| ------------------- | ----------------------- | --------------------------------------------------------- |
|
||||||
|
| ReviewModal | ReviewModal.tsx | 5-star rating and textarea review submission modal |
|
||||||
|
| CashPaymentModal | CashPaymentModal.tsx | Cash payment — input received amount, calculate change |
|
||||||
|
| QRPaymentModal | QRPaymentModal.tsx | QR code payment — generates QR, 5s countdown auto-confirm |
|
||||||
|
| PaymentSuccessModal | PaymentSuccessModal.tsx | Success screen — auto-redirects to home after 1 second |
|
||||||
|
| Modal.types.ts | Modal.types.ts | Shared TypeScript interfaces for all modal components |
|
||||||
|
|
||||||
|
#### CashPaymentModal
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CashPaymentModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
items: InvoiceItem[]; // { name, quantity, price }[]
|
||||||
|
totalAmount: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- ARIA: `role="dialog"`, `aria-modal="true"`,
|
||||||
|
`aria-labelledby="cash-modal-title"`
|
||||||
|
- Shows invoice list, cash received input, change due calculation, insufficient
|
||||||
|
cash error
|
||||||
|
|
||||||
|
#### QRPaymentModal
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface QRPaymentModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
items: InvoiceItem[];
|
||||||
|
totalAmount: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Uses `qrcode.react` (`QRCodeSVG`) to render a QR code
|
||||||
|
- 5-second countdown timer — auto-calls `onConfirm` when it reaches 0
|
||||||
|
- ARIA: `role="dialog"`, `aria-modal="true"`, `aria-labelledby="qr-modal-title"`
|
||||||
|
|
||||||
|
#### PaymentSuccessModal
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface PaymentSuccessModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Auto-redirects to `/` after 1 second via `useRouter`
|
||||||
|
- ARIA: `role="dialog"`, `aria-modal="true"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Shift Schedule (`organisms/shift-schedule/`)
|
||||||
|
|
||||||
|
Components for the staff shift scheduling feature.
|
||||||
|
|
||||||
|
| Component | File | Description |
|
||||||
|
| ---------------- | -------------------- | ----------------------------------------------------- |
|
||||||
|
| WeeklySchedule | WeeklySchedule.tsx | Weekly calendar grid showing shifts per day |
|
||||||
|
| MonthlyCalendar | MonthlyCalendar.tsx | Monthly calendar view with shift indicators per day |
|
||||||
|
| MobileShiftView | MobileShiftView.tsx | Mobile-optimized list/card view of shifts |
|
||||||
|
| ShiftCreateModal | ShiftCreateModal.tsx | Form modal for registering for or creating a shift |
|
||||||
|
| ShiftDetailModal | ShiftDetailModal.tsx | Detail view of a shift with its registered staff list |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TEMPLATES
|
## TEMPLATES
|
||||||
|
|
||||||
### MainLayout (`templates/main-layout/MainLayout.tsx`)
|
### MainLayout (`templates/main-layout/MainLayout.tsx`)
|
||||||
@@ -102,6 +266,11 @@ login/register pages.
|
|||||||
**Description:** Manager layout template — auth guard + ManagerProvider. Used by
|
**Description:** Manager layout template — auth guard + ManagerProvider. Used by
|
||||||
the manager route group.
|
the manager route group.
|
||||||
|
|
||||||
|
### StaffLayout (`templates/staff-layout/`)
|
||||||
|
|
||||||
|
**Description:** Staff layout template — wraps staff-facing pages (shift
|
||||||
|
schedule, etc.). Used by the staff route group.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Contexts Documentation
|
## Contexts Documentation
|
||||||
@@ -125,3 +294,7 @@ mobile menu and CategorySidebar selection.
|
|||||||
|
|
||||||
Manages menu CRUD state (products, combos, categories) for the manager
|
Manages menu CRUD state (products, combos, categories) for the manager
|
||||||
dashboard.
|
dashboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2026-04-18
|
||||||
|
|||||||
+131
-35
@@ -1,7 +1,7 @@
|
|||||||
# Atoms Components Library
|
# Atoms Components Library
|
||||||
|
|
||||||
**Status:** ✅ Phase 1 Implementation Complete **Created:** 2026-04-03
|
**Status:** ✅ Complete — 14 atoms **Created:** 2026-04-03 **Foundation:**
|
||||||
**Foundation:** Atomic Design Pattern
|
Atomic Design Pattern
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ potentially other atoms).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Atoms Created
|
## 🎯 Atoms Created (14 total)
|
||||||
|
|
||||||
### Buttons (`buttons/`)
|
### Buttons (`buttons/`)
|
||||||
|
|
||||||
@@ -181,6 +181,58 @@ import { Textarea } from "@/components/atoms";
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### **LoginInput.tsx**
|
||||||
|
|
||||||
|
Specialized input for the login form. Supports username/phone and password
|
||||||
|
fields. Password fields include a built-in show/hide toggle button. Displays a
|
||||||
|
user icon on desktop viewports.
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface LoginInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
label: string; // Field label (rendered as <label>)
|
||||||
|
type: string; // Input type — "text" | "password"
|
||||||
|
name: string; // Input id and name (used for label htmlFor)
|
||||||
|
value: string; // Controlled value
|
||||||
|
errors?: string; // Error string — adds red border when set
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import LoginInput from "@/components/atoms/inputs/LoginInput";
|
||||||
|
|
||||||
|
<LoginInput
|
||||||
|
label="Tên đăng nhập"
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
value={username}
|
||||||
|
errors={errors.username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LoginInput
|
||||||
|
label="Mật khẩu"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
value={password}
|
||||||
|
errors={errors.password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
- Password type shows a show/hide toggle button (ARIA-labelled)
|
||||||
|
- Desktop: renders a `fa-user` icon at left via `lg:block`
|
||||||
|
- Error state: red border via `border-red-400` when `errors` is set
|
||||||
|
- Used in: `LoginForm.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Typography (`typography/`)
|
### Typography (`typography/`)
|
||||||
|
|
||||||
#### **Heading.tsx**
|
#### **Heading.tsx**
|
||||||
@@ -346,6 +398,50 @@ import { Divider } from "@/components/atoms";
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Errors (`errors/`)
|
||||||
|
|
||||||
|
#### **ErrorMessageLogin.tsx**
|
||||||
|
|
||||||
|
Displays error messages in the login form. Two visual styles controlled by the
|
||||||
|
`type` prop: a prominent alert box (`primary`) and a compact inline message
|
||||||
|
(`secondary`). Both include a `fa-circle-exclamation` icon.
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ErrorMessageLoginProps {
|
||||||
|
message: string; // Error message text to display
|
||||||
|
type?: string; // "primary" (default) | "secondary"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Variants:**
|
||||||
|
|
||||||
|
- `primary` (default): Rounded alert box — red border, red-50 background,
|
||||||
|
`text-red-600`, `text-sm`. Used for form-level errors (e.g. wrong password).
|
||||||
|
- `secondary`: Inline paragraph — `text-red-500`, `text-xs`. Used for
|
||||||
|
field-level errors below individual inputs.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import ErrorMessageLogin from "@/components/atoms/errors/ErrorMessageLogin";
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Form-level error */
|
||||||
|
}
|
||||||
|
<ErrorMessageLogin message="Tên đăng nhập hoặc mật khẩu không đúng" />;
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Field-level inline error */
|
||||||
|
}
|
||||||
|
<ErrorMessageLogin message="Vui lòng nhập mật khẩu" type="secondary" />;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Used in:** `LoginForm.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🎨 Theming
|
## 🎨 Theming
|
||||||
|
|
||||||
All atoms use CSS variables for consistent theming:
|
All atoms use CSS variables for consistent theming:
|
||||||
@@ -378,7 +474,8 @@ Change one variable in `globals.css` to update all atoms across the app.
|
|||||||
import { Badge, PriceBadge } from "@/components/atoms/badges";
|
import { Badge, PriceBadge } from "@/components/atoms/badges";
|
||||||
import { Button } from "@/components/atoms/buttons";
|
import { Button } from "@/components/atoms/buttons";
|
||||||
import { Divider } from "@/components/atoms/dividers";
|
import { Divider } from "@/components/atoms/dividers";
|
||||||
import { SearchInput, TextInput } from "@/components/atoms/inputs";
|
import ErrorMessageLogin from "@/components/atoms/errors/ErrorMessageLogin";
|
||||||
|
import { LoginInput, SearchInput, TextInput } from "@/components/atoms/inputs";
|
||||||
import { Caption, Heading, Text } from "@/components/atoms/typography";
|
import { Caption, Heading, Text } from "@/components/atoms/typography";
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -400,6 +497,9 @@ import {
|
|||||||
} from "@/components/atoms";
|
} from "@/components/atoms";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Note: `ErrorMessageLogin` and `LoginInput` are imported directly from their
|
||||||
|
> subfolders as they are auth-specific and not re-exported from the root barrel.
|
||||||
|
|
||||||
### Type Imports
|
### Type Imports
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
@@ -483,50 +583,46 @@ All atoms include:
|
|||||||
|
|
||||||
```
|
```
|
||||||
components/atoms/
|
components/atoms/
|
||||||
├── buttons/
|
|
||||||
│ ├── Button.tsx
|
|
||||||
│ ├── Button.types.ts
|
|
||||||
│ ├── IconButton.tsx
|
|
||||||
│ └── index.ts
|
|
||||||
├── inputs/
|
|
||||||
│ ├── TextInput.tsx
|
|
||||||
│ ├── SearchInput.tsx
|
|
||||||
│ ├── Textarea.tsx
|
|
||||||
│ ├── Input.types.ts
|
|
||||||
│ └── index.ts
|
|
||||||
├── typography/
|
|
||||||
│ ├── Heading.tsx
|
|
||||||
│ ├── Text.tsx
|
|
||||||
│ ├── Caption.tsx
|
|
||||||
│ ├── Typography.types.ts
|
|
||||||
│ └── index.ts
|
|
||||||
├── badges/
|
├── badges/
|
||||||
│ ├── Badge.tsx
|
│ ├── Badge.tsx
|
||||||
│ ├── Badge.types.ts
|
│ ├── Badge.types.ts
|
||||||
│ ├── PriceBadge.tsx
|
│ ├── PriceBadge.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
|
├── buttons/
|
||||||
|
│ ├── Button.tsx
|
||||||
|
│ ├── Button.types.ts
|
||||||
|
│ ├── IconButton.tsx
|
||||||
|
│ └── index.ts
|
||||||
├── dividers/
|
├── dividers/
|
||||||
│ ├── Divider.tsx
|
│ ├── Divider.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
|
├── errors/ ← auth error display
|
||||||
|
│ ├── Error.types.ts
|
||||||
|
│ └── ErrorMessageLogin.tsx
|
||||||
|
├── inputs/
|
||||||
|
│ ├── Input.types.ts
|
||||||
|
│ ├── LoginInput.tsx ← auth-specific input
|
||||||
|
│ ├── SearchInput.tsx
|
||||||
|
│ ├── Textarea.tsx
|
||||||
|
│ ├── TextInput.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
├── typography/
|
||||||
|
│ ├── Caption.tsx
|
||||||
|
│ ├── Heading.tsx
|
||||||
|
│ ├── Text.tsx
|
||||||
|
│ ├── Typography.types.ts
|
||||||
|
│ └── index.ts
|
||||||
├── index.ts (barrel export)
|
├── index.ts (barrel export)
|
||||||
└── ATOMS.md (this file)
|
└── ATOMS.md (this file)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Next Steps
|
## 🚀 Status
|
||||||
|
|
||||||
Once atoms are comfortable, the next phase is creating **Molecules** -
|
Atoms are stable and in use across molecules and organisms. Molecules are
|
||||||
combinations of atoms:
|
implemented — see `components/COMPONENTS.md` for details. See
|
||||||
|
`OPTIMIZATION_PLAN.md` for the full roadmap.
|
||||||
- **ProductCard** - Image + Text + Badge + Button
|
|
||||||
- **FormField** - Label + Input + Error Text
|
|
||||||
- **SearchBar** - SearchInput + Button
|
|
||||||
- **RatingInput** - Interactive 5-star rating
|
|
||||||
- **PriceTag** - Price display with formatting
|
|
||||||
- And more...
|
|
||||||
|
|
||||||
See `OPTIMIZATION_PLAN.md` for the full roadmap.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -540,5 +636,5 @@ See `OPTIMIZATION_PLAN.md` for the full roadmap.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Status:** ✅ Complete & Ready for Use **Last Updated:** 2026-04-03 **Phase:**
|
**Status:** ✅ Complete & Ready for Use **Last Updated:** 2026-04-18 **Atoms:**
|
||||||
1 of 5
|
14
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Product, UserRole } from "@/lib/types";
|
|
||||||
import type { CartItem } from "@/lib/cart-context";
|
import type { CartItem } from "@/lib/cart-context";
|
||||||
|
import type { Product, UserRole } from "@/lib/types";
|
||||||
|
|
||||||
export interface ProductCardProps {
|
export interface ProductCardProps {
|
||||||
image: string;
|
image: string;
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export default function ProductModal({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
className="absolute bottom-2 right-2 cursor-pointer rounded-lg border-none bg-black/50 px-3 py-1 text-xs text-white transition hover:bg-black/70"
|
className="absolute right-2 bottom-2 cursor-pointer rounded-lg border-none bg-black/50 px-3 py-1 text-xs text-white transition hover:bg-black/70"
|
||||||
>
|
>
|
||||||
Đổi ảnh
|
Đổi ảnh
|
||||||
</button>
|
</button>
|
||||||
@@ -197,9 +197,7 @@ export default function ProductModal({
|
|||||||
className="flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-(--color-border) bg-transparent px-4 py-6 text-(--color-text-muted) transition hover:border-(--color-primary) hover:text-(--color-primary)"
|
className="flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-(--color-border) bg-transparent px-4 py-6 text-(--color-text-muted) transition hover:border-(--color-primary) hover:text-(--color-primary)"
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-cloud-arrow-up text-2xl"></i>
|
<i className="fa-solid fa-cloud-arrow-up text-2xl"></i>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">Nhấn để chọn ảnh</span>
|
||||||
Nhấn để chọn ảnh
|
|
||||||
</span>
|
|
||||||
<span className="text-xs">PNG, JPG, WEBP (tối đa 5MB)</span>
|
<span className="text-xs">PNG, JPG, WEBP (tối đa 5MB)</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Button from "@/components/atoms/buttons/Button";
|
import Button from "@/components/atoms/buttons/Button";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import type { CashPaymentModalProps } from "./Modal.types";
|
import type { CashPaymentModalProps } from "./Modal.types";
|
||||||
|
|
||||||
const formatPrice = (value: number) =>
|
const formatPrice = (value: number) =>
|
||||||
@@ -41,27 +42,25 @@ export default function CashPaymentModal({
|
|||||||
<div className="relative w-full max-w-lg rounded-2xl border border-(--color-border-light) bg-white p-6 shadow-xl">
|
<div className="relative w-full max-w-lg rounded-2xl border border-(--color-border-light) bg-white p-6 shadow-xl">
|
||||||
<h2
|
<h2
|
||||||
id="cash-modal-title"
|
id="cash-modal-title"
|
||||||
className="mb-4 text-xl font-bold text-foreground"
|
className="text-foreground mb-4 text-xl font-bold"
|
||||||
>
|
>
|
||||||
Thanh toán tiền mặt
|
Thanh toán tiền mặt
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Invoice list */}
|
{/* Invoice list */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-2 text-sm font-semibold text-(--color-text-secondary) uppercase tracking-wide">
|
<h3 className="mb-2 text-sm font-semibold tracking-wide text-(--color-text-secondary) uppercase">
|
||||||
Hóa đơn
|
Hóa đơn
|
||||||
</h3>
|
</h3>
|
||||||
<div className="rounded-xl border border-(--color-border-light) overflow-hidden">
|
<div className="overflow-hidden rounded-xl border border-(--color-border-light)">
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-between px-4 py-2.5 border-b border-(--color-border-light) last:border-b-0"
|
className="flex items-center justify-between border-b border-(--color-border-light) px-4 py-2.5 last:border-b-0"
|
||||||
>
|
>
|
||||||
<span className="font-medium text-foreground">
|
<span className="text-foreground font-medium">{item.name}</span>
|
||||||
{item.name}
|
|
||||||
</span>
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-(--color-text-muted) text-sm">
|
<span className="text-sm text-(--color-text-muted)">
|
||||||
x{item.quantity}
|
x{item.quantity}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-(--color-primary)">
|
<span className="font-semibold text-(--color-primary)">
|
||||||
@@ -70,10 +69,8 @@ export default function CashPaymentModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="flex items-center justify-between px-4 py-3 bg-(--color-border-light)/30">
|
<div className="flex items-center justify-between bg-(--color-border-light)/30 px-4 py-3">
|
||||||
<span className="font-bold text-foreground">
|
<span className="text-foreground font-bold">Tổng cộng</span>
|
||||||
Tổng cộng
|
|
||||||
</span>
|
|
||||||
<span className="text-lg font-bold text-(--color-primary)">
|
<span className="text-lg font-bold text-(--color-primary)">
|
||||||
{formatPrice(totalAmount)}
|
{formatPrice(totalAmount)}
|
||||||
</span>
|
</span>
|
||||||
@@ -97,7 +94,7 @@ export default function CashPaymentModal({
|
|||||||
value={cashReceived}
|
value={cashReceived}
|
||||||
onChange={(e) => setCashReceived(e.target.value)}
|
onChange={(e) => setCashReceived(e.target.value)}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
className="w-full rounded-lg border border-(--color-border) bg-transparent px-3 py-2 text-right text-lg font-semibold focus:outline-none focus:ring-2 focus:ring-(--color-primary)"
|
className="w-full rounded-lg border border-(--color-border) bg-transparent px-3 py-2 text-right text-lg font-semibold focus:ring-2 focus:ring-(--color-primary) focus:outline-none"
|
||||||
/>
|
/>
|
||||||
{isInsufficient && (
|
{isInsufficient && (
|
||||||
<p className="mt-1.5 text-sm text-red-500">
|
<p className="mt-1.5 text-sm text-red-500">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
interface PaymentSuccessModalProps {
|
interface PaymentSuccessModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -41,7 +41,7 @@ export default function PaymentSuccessModal({
|
|||||||
<div className="relative w-full max-w-sm rounded-2xl border border-(--color-border-light) bg-white p-8 shadow-xl">
|
<div className="relative w-full max-w-sm rounded-2xl border border-(--color-border-light) bg-white p-8 shadow-xl">
|
||||||
<div className="flex flex-col items-center gap-4 text-center">
|
<div className="flex flex-col items-center gap-4 text-center">
|
||||||
<i className="fa-solid fa-circle-check text-5xl text-(--color-primary)"></i>
|
<i className="fa-solid fa-circle-check text-5xl text-(--color-primary)"></i>
|
||||||
<h2 className="text-xl font-bold text-foreground">
|
<h2 className="text-foreground text-xl font-bold">
|
||||||
Thanh toán thành công!
|
Thanh toán thành công!
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-(--color-text-muted)">
|
<p className="text-sm text-(--color-text-muted)">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import type { QRPaymentModalProps } from "./Modal.types";
|
import type { QRPaymentModalProps } from "./Modal.types";
|
||||||
|
|
||||||
const formatPrice = (value: number) =>
|
const formatPrice = (value: number) =>
|
||||||
@@ -9,7 +10,13 @@ const formatPrice = (value: number) =>
|
|||||||
|
|
||||||
const COUNTDOWN_SECONDS = 5;
|
const COUNTDOWN_SECONDS = 5;
|
||||||
|
|
||||||
export default function QRPaymentModal({ isOpen, onClose, onConfirm, items, totalAmount }: QRPaymentModalProps) {
|
export default function QRPaymentModal({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
items,
|
||||||
|
totalAmount,
|
||||||
|
}: QRPaymentModalProps) {
|
||||||
const [qrValue, setQrValue] = useState("");
|
const [qrValue, setQrValue] = useState("");
|
||||||
const [timeLeft, setTimeLeft] = useState(COUNTDOWN_SECONDS);
|
const [timeLeft, setTimeLeft] = useState(COUNTDOWN_SECONDS);
|
||||||
|
|
||||||
@@ -47,32 +54,46 @@ export default function QRPaymentModal({ isOpen, onClose, onConfirm, items, tota
|
|||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="qr-modal-title"
|
aria-labelledby="qr-modal-title"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" aria-hidden="true" onClick={onClose} />
|
<div
|
||||||
|
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||||
|
aria-hidden="true"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
<div className="relative w-full max-w-lg rounded-2xl border border-(--color-border-light) bg-white p-6 shadow-xl">
|
<div className="relative w-full max-w-lg rounded-2xl border border-(--color-border-light) bg-white p-6 shadow-xl">
|
||||||
|
<h2
|
||||||
<h2 id="qr-modal-title" className="mb-4 text-xl font-bold text-foreground">
|
id="qr-modal-title"
|
||||||
|
className="text-foreground mb-4 text-xl font-bold"
|
||||||
|
>
|
||||||
Thanh toán QR Code
|
Thanh toán QR Code
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Invoice list */}
|
{/* Invoice list */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-2 text-sm font-semibold uppercase tracking-wide text-(--color-text-secondary)">Hóa đơn</h3>
|
<h3 className="mb-2 text-sm font-semibold tracking-wide text-(--color-text-secondary) uppercase">
|
||||||
|
Hóa đơn
|
||||||
|
</h3>
|
||||||
<div className="overflow-hidden rounded-xl border border-(--color-border-light)">
|
<div className="overflow-hidden rounded-xl border border-(--color-border-light)">
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-between border-b border-(--color-border-light) px-4 py-2.5 last:border-b-0"
|
className="flex items-center justify-between border-b border-(--color-border-light) px-4 py-2.5 last:border-b-0"
|
||||||
>
|
>
|
||||||
<span className="font-medium text-foreground">{item.name}</span>
|
<span className="text-foreground font-medium">{item.name}</span>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-sm text-(--color-text-muted)">x{item.quantity}</span>
|
<span className="text-sm text-(--color-text-muted)">
|
||||||
<span className="font-semibold text-(--color-primary)">{formatPrice(item.price * item.quantity)}</span>
|
x{item.quantity}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold text-(--color-primary)">
|
||||||
|
{formatPrice(item.price * item.quantity)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="flex items-center justify-between bg-(--color-border-light)/30 px-4 py-3">
|
<div className="flex items-center justify-between bg-(--color-border-light)/30 px-4 py-3">
|
||||||
<span className="font-bold text-foreground">Tổng cộng</span>
|
<span className="text-foreground font-bold">Tổng cộng</span>
|
||||||
<span className="text-lg font-bold text-(--color-primary)">{formatPrice(totalAmount)}</span>
|
<span className="text-lg font-bold text-(--color-primary)">
|
||||||
|
{formatPrice(totalAmount)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,10 +106,10 @@ export default function QRPaymentModal({ isOpen, onClose, onConfirm, items, tota
|
|||||||
{qrValue && <QRCodeSVG value={qrValue} size={180} />}
|
{qrValue && <QRCodeSVG value={qrValue} size={180} />}
|
||||||
<p className="text-sm text-(--color-text-muted)">
|
<p className="text-sm text-(--color-text-muted)">
|
||||||
Tự động xác nhận sau{" "}
|
Tự động xác nhận sau{" "}
|
||||||
<span className="font-bold text-(--color-primary)">{timeLeft}</span> giây...
|
<span className="font-bold text-(--color-primary)">{timeLeft}</span>{" "}
|
||||||
|
giây...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+218
-270
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
> Tài liệu chi tiết về layout components (Header, Footer) và responsive behavior
|
> Tài liệu chi tiết về layout components (Header, Footer) và responsive behavior
|
||||||
|
|
||||||
|
> **Last Updated:** 2026-04-18
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -21,180 +23,197 @@ layouts/
|
|||||||
|
|
||||||
## Header (layouts/header.tsx)
|
## Header (layouts/header.tsx)
|
||||||
|
|
||||||
**File:** layouts/header.tsx **Type:** Client component **Description:** Sticky
|
**File:** layouts/header.tsx **Type:** Client component (`"use client"`)
|
||||||
top navigation bar with brand info and authentication status display.
|
**Description:** Sticky top navigation bar with brand info and authentication
|
||||||
|
status display. Uses `useAuth()` context for real auth state and `useRouter()`
|
||||||
|
for navigation.
|
||||||
|
|
||||||
### Layout
|
### Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────┐
|
||||||
│ [Logo] [Shop Name] [Tagline] │ [Auth Status] │
|
│ [Logo] [Shop Name] [Tagline] │ [Auth Button(s)] │
|
||||||
└─────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────┘
|
||||||
height: 72px (var(--spacing-header-height))
|
height: var(--spacing-header-height) (72px)
|
||||||
```
|
```
|
||||||
|
|
||||||
**2-Column Layout:**
|
**2-Column Layout:**
|
||||||
|
|
||||||
- **Left:** Shop branding (logo + name + tagline)
|
- **Left:** Shop branding (logo + name + tagline) — wrapped in `<Link href="/">`
|
||||||
- **Right:** Auth status indicator
|
- **Right:** Auth status indicator (varies by user role)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
#### Logo & Branding
|
#### Logo & Branding
|
||||||
|
|
||||||
- Shop logo (40x40px)
|
- Shop logo rendered with `next/image` (fill, 40x40px / 44x44px on md+)
|
||||||
- Shop name (bold text)
|
- Shop name (bold, color transitions on hover)
|
||||||
- Tagline (hidden on mobile < md, visible md+)
|
- Tagline (hidden on mobile < md, visible md+)
|
||||||
|
- Entire left section is a link to `/`
|
||||||
|
|
||||||
**Responsive:** | Breakpoint | Logo | Name | Tagline |
|
**Responsive:**
|
||||||
|-----------|------|------|---------| | < sm (640px) | Yes | Yes | Hidden | |
|
|
||||||
sm-md | Yes | Yes | Hidden | | md+ (768px) | Yes | Yes | Visible |
|
| Breakpoint | Logo | Name | Tagline |
|
||||||
|
| ------------ | --------- | ---- | ------- |
|
||||||
|
| < sm (640px) | h-10/w-10 | Yes | Hidden |
|
||||||
|
| sm-md | h-10/w-10 | Yes | Hidden |
|
||||||
|
| md+ (768px) | h-11/w-11 | Yes | Visible |
|
||||||
|
|
||||||
#### Authentication Status
|
#### Authentication Status
|
||||||
|
|
||||||
The header cycles through 3 auth states (for UI demo):
|
The header reads from `useAuth()` and reflects the actual logged-in user role.
|
||||||
|
|
||||||
**State 1: Guest (Not logged in)**
|
**State 1: Guest (user = null)**
|
||||||
|
|
||||||
```
|
```
|
||||||
[brown button] "Dang nhap"
|
[brown button] "Đăng nhập" ← icon always shown, label hidden on xs
|
||||||
```
|
```
|
||||||
|
|
||||||
- Guest state (user = null)
|
- `title="Đăng nhập"` on button
|
||||||
- Brown primary button
|
- Clicking navigates to `/login`
|
||||||
- Click: toggles to next state
|
- Icon: `fa-solid fa-right-to-bracket`
|
||||||
|
|
||||||
**State 2: Manager**
|
**State 2: Manager (user.role === "manager")**
|
||||||
|
|
||||||
```
|
```
|
||||||
[caramel/gold badge] "👔 Quản lý"
|
[Dashboard link] [Logout icon button]
|
||||||
```
|
```
|
||||||
|
|
||||||
- Shows manager role
|
- Dashboard link → `/manager`
|
||||||
- Gold/caramel colored badge
|
- Icon: `fa-solid fa-user-tie`
|
||||||
- User tie icon
|
- Label "Dashboard" hidden on xs, visible sm+
|
||||||
- Name: Nguyễn Văn An
|
- Logout button: `title="Đăng xuất"`, `aria-label="Đăng xuất"`
|
||||||
|
- Logout icon: `fa-solid fa-right-from-bracket`
|
||||||
|
- Hover: red tint on logout button
|
||||||
|
|
||||||
**State 3: Staff**
|
**State 3: Staff (user.role === "staff")**
|
||||||
|
|
||||||
```
|
```
|
||||||
[border button] "👤 [Name]"
|
[Ca làm link] [Avatar + Name button (logout on click)]
|
||||||
```
|
```
|
||||||
|
|
||||||
- Shows staff role with avatar placeholder
|
- Ca làm link → `/staff/schedule`
|
||||||
- Bordered button style
|
- Icon: `fa-solid fa-calendar-check`
|
||||||
- Name: Trần Thị Bình
|
- Label "Ca làm" hidden on xs, visible sm+
|
||||||
- Click: toggles back to Guest state
|
- Staff button: `title="Nhấn để đăng xuất"`, `aria-label="Đăng xuất"`
|
||||||
|
- Avatar: colored circle with `fa-solid fa-user`
|
||||||
|
- Name hidden on xs, visible sm+
|
||||||
|
|
||||||
|
**State 4: Customer (user.role === "customer")**
|
||||||
|
|
||||||
|
```
|
||||||
|
[Customer icon + "Khách hàng" button (logout on click)]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title="Khách hàng - {user.phone} - Nhấn để đăng xuất"`
|
||||||
|
- Avatar circle with `fa-solid fa-user`
|
||||||
|
- Label "Khách hàng" hidden on xs, visible sm+
|
||||||
|
|
||||||
### Responsive Behavior
|
### Responsive Behavior
|
||||||
|
|
||||||
| Breakpoint | Layout |
|
| Breakpoint | Layout |
|
||||||
| ------------- | ------------------------------- |
|
| ------------- | -------------------------------------- |
|
||||||
| < sm | Stacked vertically |
|
| < sm (640px) | Button labels hidden, icons only |
|
||||||
| sm-md (640px) | Side-by-side, reduced spacing |
|
| sm-md (640px) | Button labels visible, reduced spacing |
|
||||||
| md+ (768px) | Full width, comfortable spacing |
|
| md+ (768px) | Full layout including tagline |
|
||||||
|
|
||||||
**Mobile (<640px):**
|
**Mobile (<640px):**
|
||||||
|
|
||||||
- Logo smaller
|
- Logo 40x40px
|
||||||
- Name only (no tagline)
|
- Shop name only (no tagline)
|
||||||
- Auth button text hidden, icon shown
|
- Auth button shows icon only (label hidden via `hidden sm:inline`)
|
||||||
- Touch-friendly padding
|
- Touch-friendly padding
|
||||||
|
|
||||||
**Desktop (≥640px):**
|
**Desktop (≥768px):**
|
||||||
|
|
||||||
- Normal logo size
|
- Logo 44x44px
|
||||||
- Full layout with tagline
|
- Full layout with tagline
|
||||||
- Button with text and icon
|
- Buttons show icon + text label
|
||||||
- Comfortable spacing
|
- Comfortable spacing
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
|
||||||
|
- Logo image: `alt="Logo {SHOP_INFO.name}"`
|
||||||
|
- Guest button: `title="Đăng nhập"`
|
||||||
|
- Manager logout button: `title="Đăng xuất"`, `aria-label="Đăng xuất"`
|
||||||
|
- Staff logout button: `title="Nhấn để đăng xuất"`, `aria-label="Đăng xuất"`
|
||||||
|
- Customer button: `title="Khách hàng - {phone} - Nhấn để đăng xuất"`
|
||||||
|
- Icons are presentational (no aria-hidden needed — buttons have
|
||||||
|
title/aria-label)
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
| Element | Classes |
|
| Element | Classes |
|
||||||
| -------------------- | --------------------------------------------------------- |
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Header wrapper | `sticky top-0 z-50 bg-white shadow-md` |
|
| Header wrapper | `sticky top-0 z-50 h-(--spacing-header-height) w-full border-b bg-(--color-bg-header) shadow-[...]` |
|
||||||
| Container | `max-w-7xl mx-auto px-4 md:px-6 h-72px flex items-center` |
|
| Container | `mx-auto flex h-full max-w-screen-2xl items-center justify-between gap-6 px-6 md:px-8 lg:px-12` |
|
||||||
| Left section (brand) | `flex items-center gap-3 flex-1` |
|
| Left section (brand) | `group flex shrink-0 items-center gap-4 no-underline` (Link) |
|
||||||
| Right section (auth) | `flex items-center gap-3 shrink-0` |
|
| Logo wrapper | `relative h-10 w-10 shrink-0 md:h-11 md:w-11` |
|
||||||
| Logo image | `w-10 h-10 md:w-12 md:h-12` |
|
| Brand text | `text-base font-bold text-(--color-primary-dark) md:text-lg` |
|
||||||
| Brand text | `font-bold text-sm md:text-base` |
|
| Tagline | `hidden text-xs text-(--color-text-muted) md:block` |
|
||||||
| Tagline | `hidden md:block text-xs text-muted-foreground` |
|
| Right section (auth) | `flex shrink-0 items-center gap-3` |
|
||||||
| Auth button | `px-4 py-2 rounded-lg text-sm font-medium` |
|
| Guest button | `flex items-center gap-2.5 rounded-xl bg-(--color-primary) px-5 py-2.5 text-sm font-semibold text-white` |
|
||||||
|
| Manager link | `flex items-center gap-2 rounded-xl border border-(--color-accent) bg-(--color-accent-light)` |
|
||||||
|
| Logout button | `flex items-center gap-2 rounded-xl border border-(--color-border) bg-transparent px-3 py-2.5 hover:border-red-300 hover:bg-red-50 hover:text-red-500` |
|
||||||
|
| Staff button | `flex items-center gap-2.5 rounded-xl border border-(--color-border) bg-background` |
|
||||||
|
| Customer button | `flex items-center gap-2.5 rounded-xl bg-(--color-primary-light) px-4 py-2 text-white` |
|
||||||
|
|
||||||
### CSS Variables Used
|
### CSS Variables Used
|
||||||
|
|
||||||
```css
|
```css
|
||||||
--spacing-header-height: 72px;
|
--spacing-header-height /* 72px — header height */
|
||||||
--color-primary: brown;
|
--color-bg-header /* header background */
|
||||||
--color-primary-dark: darker brown;
|
--color-border /* border color */
|
||||||
--color-text-primary: text color;
|
--color-shadow-sm /* subtle shadow */
|
||||||
--color-text-muted: gray text;
|
--color-primary /* brand primary (brown) */
|
||||||
--color-shadow-sm: subtle shadow;
|
--color-primary-dark /* darker brown */
|
||||||
|
--color-primary-light /* lighter brown */
|
||||||
|
--color-accent /* accent/gold color */
|
||||||
|
--color-accent-light /* light accent background */
|
||||||
|
--color-text-muted /* gray/muted text */
|
||||||
|
--color-text-secondary /* secondary text color */
|
||||||
|
--color-border-light /* light border */
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
- **next/image** - Logo image rendering
|
- **next/image** — Logo image rendering
|
||||||
- **next/link** - Navigation links
|
- **next/link** — Brand link, dashboard link, schedule link
|
||||||
- **lib/constants:** SHOP_INFO, MOCK_USERS
|
- **next/navigation** — `useRouter` for programmatic navigation
|
||||||
- **lib/types:** User
|
- **lib/auth-context:** `useAuth` — user state, logout
|
||||||
- **FontAwesome icons:** fa-sign-in-alt, fa-sign-out-alt, fa-user-tie,
|
- **lib/constants:** `SHOP_INFO`
|
||||||
fa-user-circle
|
|
||||||
|
|
||||||
### Key Code Snippets
|
|
||||||
|
|
||||||
**Auth Demo State Machine:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
|
||||||
|
|
||||||
const handleAuthClick = () => {
|
|
||||||
if (!user) {
|
|
||||||
setUser(MOCK_USERS.manager);
|
|
||||||
} else if (user.role === "manager") {
|
|
||||||
setUser(MOCK_USERS.staff);
|
|
||||||
} else {
|
|
||||||
setUser(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Responsive Brand Text:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-bold text-sm md:text-base">{SHOP_INFO.name}</span>
|
|
||||||
<span className="hidden md:block text-xs text-muted-foreground">
|
|
||||||
{SHOP_INFO.tagline}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Footer (layouts/footer.tsx)
|
## Footer (layouts/footer.tsx)
|
||||||
|
|
||||||
**File:** layouts/footer.tsx **Type:** Client component (uses constants)
|
**File:** layouts/footer.tsx **Type:** Server component (no `"use client"`)
|
||||||
**Description:** Site footer with shop information, social links, and WiFi
|
**Description:** Site footer with shop information, social links, and WiFi
|
||||||
details.
|
details. Copyright year is dynamic via `new Date().getFullYear()`.
|
||||||
|
|
||||||
### Layout
|
### Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────────────┐
|
||||||
│ [Brand Info] │ [Social + WiFi] │
|
│ [Brand Info] │ [Social + WiFi] │
|
||||||
├────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────────────────┤
|
||||||
│ © 2024 Coffee Shop. Made with ❤️ in VN │
|
│ © {year} Coffee Shop. All rights reserved. | Powered by ... │
|
||||||
└────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**12-Column Grid Layout:**
|
**12-Column Grid Layout:**
|
||||||
|
|
||||||
| Breakpoint | Brand Info | Social + WiFi |
|
| Breakpoint | Brand Info | Right column (Social + WiFi) |
|
||||||
| ------------ | ----------- | ------------- |
|
| ------------ | ----------- | -------------------------------- |
|
||||||
| Mobile | col-span-12 | col-span-12 |
|
| Mobile | col-span-12 | col-span-12 |
|
||||||
| md (768px) | col-span-6 | col-span-6 |
|
| md (768px) | col-span-6 | col-span-6 |
|
||||||
| lg+ (1024px) | col-span-6 | col-span-6 |
|
| lg+ (1024px) | col-span-8 | col-span-4 |
|
||||||
|
| xl+ | col-span-6 | col-span-6 (with 2-col sub-grid) |
|
||||||
|
|
||||||
|
The right column (Social + WiFi) uses an internal sub-grid:
|
||||||
|
|
||||||
|
- Mobile/lg: stacked (1 column)
|
||||||
|
- xl+: side-by-side (2 columns)
|
||||||
|
|
||||||
### Sections
|
### Sections
|
||||||
|
|
||||||
@@ -202,179 +221,114 @@ details.
|
|||||||
|
|
||||||
**Content:**
|
**Content:**
|
||||||
|
|
||||||
- Shop logo (40x40px)
|
- Shop logo (40x40px, `next/image`)
|
||||||
- Shop name (bold)
|
- Shop name (bold, accent color)
|
||||||
- Tagline (gray text)
|
- Tagline (gray, 75% opacity)
|
||||||
- Address with location icon
|
- Address with `fa-solid fa-location-dot`
|
||||||
- Phone with phone icon
|
- Phone (clickable `tel:` link) with `fa-solid fa-phone`
|
||||||
- Email with envelope icon
|
- Email (clickable `mailto:` link) with `fa-solid fa-envelope`
|
||||||
- Open hours with clock icon
|
- Open hours with `fa-solid fa-clock`
|
||||||
|
|
||||||
**Responsive:**
|
#### 2. Social Links
|
||||||
|
|
||||||
```
|
**Links (heading: "Kết nối"):**
|
||||||
Mobile:
|
|
||||||
└─ Logo
|
|
||||||
└─ Name & Tagline (stacked)
|
|
||||||
└─ Address
|
|
||||||
└─ Phone
|
|
||||||
└─ Email
|
|
||||||
└─ Open Hours
|
|
||||||
|
|
||||||
Desktop:
|
- Facebook → `SOCIAL_LINKS.facebook` (branded blue icon, opens `_blank`)
|
||||||
└─ Logo + Name/Tagline (flexbox, side-by-side)
|
- TikTok → `SOCIAL_LINKS.tiktok` (black icon, opens `_blank`)
|
||||||
└─ Address, Phone, Email, Hours (stacked below)
|
- Website → `SOCIAL_LINKS.website` (primary color icon, uses `next/link`)
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Social Links (Top Right)
|
|
||||||
|
|
||||||
**Links:**
|
|
||||||
|
|
||||||
- Facebook icon → SHOP_INFO.facebook
|
|
||||||
- TikTok icon → SHOP_INFO.tiktok
|
|
||||||
- Website icon → SHOP_INFO.website
|
|
||||||
|
|
||||||
**Styling:**
|
**Styling:**
|
||||||
|
|
||||||
- Icons in circle backgrounds
|
- Each item: icon badge (8x8 rounded-lg) + label text
|
||||||
- Hover effect (color change)
|
- Hover: accent color + full opacity
|
||||||
- Flex row layout
|
|
||||||
- Centered alignment
|
|
||||||
|
|
||||||
#### 3. WiFi Card (Bottom Right)
|
#### 3. WiFi Card
|
||||||
|
|
||||||
**Content:**
|
**Content (heading: "WiFi Miễn Phí"):**
|
||||||
|
|
||||||
- "📶 WiFi miễn phí" label
|
- WiFi icon (`fa-solid fa-wifi`)
|
||||||
- Network name (monospace)
|
- Network name (monospace, accent-bordered)
|
||||||
- Password (monospace, can be hidden/shown)
|
- Password (monospace, accent-bordered, always visible — no toggle)
|
||||||
|
|
||||||
|
**Note:** Current implementation shows password in plain text (no hide/show
|
||||||
|
toggle). This differs from older documentation.
|
||||||
|
|
||||||
**Styling:**
|
**Styling:**
|
||||||
|
|
||||||
- Light gray background
|
- Semi-transparent dark panel (`bg-(--color-primary-dark) bg-opacity-30`)
|
||||||
- Rounded borders
|
- Rounded xl border
|
||||||
|
- Stacked label + value rows (no overflow risk)
|
||||||
- Monospace font for credentials
|
- Monospace font for credentials
|
||||||
- Eye icon toggle for password visibility
|
|
||||||
|
|
||||||
**Example:**
|
#### 4. Bottom Bar
|
||||||
|
|
||||||
```
|
|
||||||
📶 WiFi miễn phí
|
|
||||||
┌────────────────────────┐
|
|
||||||
│ Network: CoffeeShop │
|
|
||||||
│ Password: •••••••• │
|
|
||||||
│ (eye icon) │
|
|
||||||
└────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. Copyright Bar (Bottom)
|
|
||||||
|
|
||||||
**Content:**
|
**Content:**
|
||||||
|
|
||||||
- Copyright text: "© 2024 Coffee Shop"
|
- Left: `© {year} {SHOP_INFO.name}. All rights reserved.`
|
||||||
- "Made with ❤️ in Vietnam"
|
- Right: `Được vận hành ❤ bằng Drinkool`
|
||||||
- Centered
|
- Responsive: stacked on mobile, side-by-side on sm+
|
||||||
|
|
||||||
**Styling:**
|
|
||||||
|
|
||||||
- Small text
|
|
||||||
- Gray color
|
|
||||||
- Subtle separator line above
|
|
||||||
|
|
||||||
### Responsive Behavior
|
### Responsive Behavior
|
||||||
|
|
||||||
**Mobile (<768px):**
|
**Mobile (<768px):**
|
||||||
|
|
||||||
- Single column layout
|
- Single column layout
|
||||||
- Stacked sections
|
- Stacked sections (brand, then social+WiFi)
|
||||||
- Full width
|
- Full width
|
||||||
- Padding around edges
|
|
||||||
|
|
||||||
**Tablet (768px):**
|
**Tablet (768px):**
|
||||||
|
|
||||||
- 2-column layout
|
- 2-column: brand (6 cols) | social+WiFi (6 cols)
|
||||||
- Brand info left, Social + WiFi right
|
- Social and WiFi stacked within right column
|
||||||
- Equal width columns
|
|
||||||
|
|
||||||
**Desktop (≥768px):**
|
**Desktop (≥1024px):**
|
||||||
|
|
||||||
- 2-column layout with more spacing
|
- Brand 8 cols, right 4 cols
|
||||||
- Brand info slightly smaller
|
- Social and WiFi stacked in right col
|
||||||
- Comfortable padding
|
|
||||||
|
**XL (≥1280px):**
|
||||||
|
|
||||||
|
- Brand 6 cols, right 6 cols
|
||||||
|
- Social and WiFi side-by-side in right col sub-grid
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
| Element | Classes |
|
| Element | Classes |
|
||||||
| -------------- | --------------------------------------------------------- |
|
| ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Footer wrapper | `bg-gray-100 border-t` |
|
| Footer wrapper | `w-full overflow-x-hidden bg-(--color-bg-footer) text-(--color-text-on-dark)` |
|
||||||
| Container | `max-w-7xl mx-auto px-4 py-8` |
|
| Container | `mx-auto max-w-screen-2xl px-4 py-10 md:px-6 lg:px-8` |
|
||||||
| Grid | `grid grid-cols-12 gap-8` |
|
| Grid | `grid grid-cols-12 gap-x-8 gap-y-8` |
|
||||||
| Brand section | `col-span-12 md:col-span-6` |
|
| Brand section | `col-span-12 md:col-span-6 lg:col-span-8 xl:col-span-6` |
|
||||||
| Social section | `col-span-12 md:col-span-6` |
|
| Right section | `col-span-12 grid grid-cols-1 gap-6 md:col-span-6 lg:col-span-4 xl:col-span-6 xl:grid-cols-2` |
|
||||||
| Logo | `w-10 h-10` |
|
| Logo | `relative h-10 w-10 shrink-0` |
|
||||||
| Info text | `text-sm text-gray-600` |
|
| Shop name | `text-lg font-bold text-(--color-accent)` |
|
||||||
| Social icons | `w-10 h-10 rounded-full flex items-center justify-center` |
|
| Tagline | `text-sm leading-relaxed opacity-75` |
|
||||||
| WiFi card | `bg-white p-4 rounded-lg border` |
|
| Contact list | `flex flex-col gap-2 text-sm opacity-80` |
|
||||||
| Copyright bar | `border-t mt-8 pt-6 text-center text-xs text-gray-500` |
|
| Social icon badge | `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg` |
|
||||||
|
| WiFi card | `rounded-xl border border-(--color-primary-light) bg-(--color-primary-dark) p-4` |
|
||||||
|
| WiFi value | `rounded border border-(--color-accent) px-2 py-1 font-mono font-bold text-(--color-accent)` |
|
||||||
|
| Bottom bar | `border-t border-white border-opacity-10` |
|
||||||
|
| Copyright row | `mx-auto flex max-w-screen-2xl flex-col sm:flex-row items-center justify-between gap-2 px-4 py-4 text-xs opacity-50` |
|
||||||
|
|
||||||
### CSS Variables Used
|
### CSS Variables Used
|
||||||
|
|
||||||
```css
|
```css
|
||||||
--color-primary: brand color;
|
--color-bg-footer /* dark footer background */
|
||||||
--color-text-secondary: secondary text color;
|
--color-text-on-dark /* light text for dark backgrounds */
|
||||||
--color-text-muted: muted/gray text color;
|
--color-accent /* accent/gold color */
|
||||||
--color-accent: accent color;
|
--color-primary /* brand primary (brown) */
|
||||||
--color-border: border color;
|
--color-primary-dark /* darker brown */
|
||||||
--color-bg-*: background colors;
|
--color-primary-light /* lighter brown */
|
||||||
--color-shadow-sm: subtle shadow;
|
--color-border /* border color */
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
- **next/image** - Shop logo
|
- **next/image** — Shop logo
|
||||||
- **next/link** - Social links
|
- **next/link** — Website social link
|
||||||
- **lib/constants:** SHOP_INFO, SOCIAL_LINKS
|
- **lib/constants:** `SHOP_INFO`, `SOCIAL_LINKS`
|
||||||
- **FontAwesome icons:** location-dot, phone, envelope, clock, facebook, tiktok,
|
- **FontAwesome icons:** `location-dot`, `phone`, `envelope`, `clock`,
|
||||||
globe, wifi, eye, eye-slash
|
`fa-brands fa-facebook-f`, `fa-brands fa-tiktok`, `globe`, `wifi`, `heart`
|
||||||
|
|
||||||
### Key Code Snippets
|
|
||||||
|
|
||||||
**Brand Info Section:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<Image src={SHOP_INFO.logo} alt={SHOP_INFO.name} width={40} height={40} />
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold">{SHOP_INFO.name}</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">{SHOP_INFO.tagline}</p>
|
|
||||||
<p className="text-sm mt-2">{SHOP_INFO.address}</p>
|
|
||||||
{/* etc */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Social Links:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<Link href={SOCIAL_LINKS.facebook} target="_blank" rel="noopener">
|
|
||||||
<i className="fab fa-facebook" />
|
|
||||||
</Link>
|
|
||||||
{/* etc */}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**WiFi Display:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<div className="bg-white p-4 rounded-lg">
|
|
||||||
<p className="font-semibold text-sm">📶 WiFi miễn phí</p>
|
|
||||||
<p className="font-mono text-xs mt-2">{SHOP_INFO.wifi.name}</p>
|
|
||||||
<p className="font-mono text-xs">
|
|
||||||
{showPassword ? SHOP_INFO.wifi.password : "••••••••"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -398,7 +352,7 @@ export default function RootLayout({ children }) {
|
|||||||
|
|
||||||
**Result:**
|
**Result:**
|
||||||
|
|
||||||
- Header: Always at top (sticky)
|
- Header: Always at top (sticky, z-50)
|
||||||
- Content: Takes full width between header/footer
|
- Content: Takes full width between header/footer
|
||||||
- Footer: Always at bottom
|
- Footer: Always at bottom
|
||||||
|
|
||||||
@@ -409,30 +363,22 @@ export default function RootLayout({ children }) {
|
|||||||
### Header Responsive Pattern
|
### Header Responsive Pattern
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* Mobile first */
|
/* Mobile first — icon-only auth buttons */
|
||||||
.header {
|
.header-auth-label {
|
||||||
flex-direction: column;
|
display: none; /* hidden on xs */
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tablet+ */
|
/* sm+ — show button labels */
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
.header {
|
.header-auth-label {
|
||||||
flex-direction: row;
|
display: inline; /* hidden sm:inline */
|
||||||
justify-content: space-between;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Desktop+ */
|
/* md+ — show tagline */
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.header {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagline {
|
.tagline {
|
||||||
display: block; /* Show tagline on desktop */
|
display: block; /* hidden md:block */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -440,17 +386,14 @@ export default function RootLayout({ children }) {
|
|||||||
### Footer Responsive Pattern
|
### Footer Responsive Pattern
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* Mobile: stacked */
|
/* Mobile: single column */
|
||||||
.footer-grid {
|
.footer-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: repeat(12, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tablet: 2 columns */
|
/* md: brand 6 cols, right 6 cols */
|
||||||
@media (min-width: 768px) {
|
/* lg: brand 8 cols, right 4 cols */
|
||||||
.footer-grid {
|
/* xl: brand 6 cols, right 6 cols — right col uses xl:grid-cols-2 */
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -483,33 +426,38 @@ CSS variables are set up for dark mode support. To enable:
|
|||||||
|
|
||||||
### Header
|
### Header
|
||||||
|
|
||||||
- Logo has alt text
|
- Logo image has descriptive `alt` text: `"Logo {SHOP_INFO.name}"`
|
||||||
- Auth button has aria-label
|
- Guest button: `title="Đăng nhập"`
|
||||||
- Icons have semantic meaning
|
- Manager logout button: `title="Đăng xuất"` + `aria-label="Đăng xuất"`
|
||||||
- Good contrast ratios
|
- Staff logout button: `title="Nhấn để đăng xuất"` + `aria-label="Đăng xuất"`
|
||||||
|
- Customer button: `title="Khách hàng - {phone} - Nhấn để đăng xuất"`
|
||||||
|
- Interactive elements have hover and active states (visual feedback)
|
||||||
|
- Touch targets are sized appropriately (px-4/5 + py-2/2.5)
|
||||||
|
|
||||||
### Footer
|
### Footer
|
||||||
|
|
||||||
- Headings use semantic tags
|
- Phone and email are clickable `<a>` links (`tel:`, `mailto:`)
|
||||||
- Links have descriptive text
|
- Social links open in `_blank` with `rel="noopener noreferrer"`
|
||||||
- Icons are decorative (aria-hidden)
|
- Section headings use `<h3>` semantic tags
|
||||||
- Monospace font for technical info (WiFi credentials)
|
- Icons are decorative within labelled list items
|
||||||
|
- WiFi credentials in monospace font for readability
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
- Touch targets ≥ 48px on mobile
|
- Touch targets ≥ 44px (py-2.5 + px-4/5 buttons)
|
||||||
- Sufficient color contrast
|
- `active:scale-95` on interactive buttons for tactile feedback
|
||||||
- Semantic HTML structure
|
- Semantic HTML structure (`<header>`, `<footer>`, `<nav>` implicit via links)
|
||||||
- ARIA labels where needed
|
- ARIA labels on icon-only buttons (logout buttons)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Keep layouts simple:** Avoid complex nested layouts
|
1. **Keep layouts simple:** Avoid complex nested layouts
|
||||||
2. **Responsive first:** Use mobile-first CSS approach
|
2. **Responsive first:** Use mobile-first CSS approach (`hidden sm:inline`)
|
||||||
3. **Reuse components:** Use Header/Footer across all pages
|
3. **Reuse components:** Use Header/Footer across all pages
|
||||||
4. **Props over hardcoding:** Use lib/constants for data
|
4. **Props over hardcoding:** Use lib/constants for data
|
||||||
5. **Type safety:** Use TypeScript for component props
|
5. **Type safety:** Use TypeScript for component props
|
||||||
6. **Performance:** Optimize images with next/image
|
6. **Performance:** Optimize images with next/image (`fill`, `sizes`,
|
||||||
7. **Accessibility:** Add ARIA labels and semantic HTML
|
`priority`)
|
||||||
|
7. **Accessibility:** Add title/aria-label on icon-only buttons
|
||||||
|
|||||||
Reference in New Issue
Block a user