update
This commit is contained in:
@@ -1,34 +1,34 @@
|
||||
# Chức năng 6: Quên Mật Khẩu (Forgot Password) - Hoàn Thành ✅
|
||||
# Function 6: Forgot Password - Completed ✅
|
||||
|
||||
## 📦 Files Đã Tạo/Cập Nhật
|
||||
## 📦 Files Created/Updated
|
||||
|
||||
### Frontend
|
||||
1. **`client/src/pages/auth/ForgotPasswordPage.tsx`** - Component form quên mật khẩu
|
||||
1. **`client/src/pages/auth/ForgotPasswordPage.tsx`** - Forgot password form component
|
||||
2. **`client/src/pages/auth/index.ts`** - Export ForgotPasswordPage
|
||||
3. **`client/src/App.tsx`** - Route `/forgot-password`
|
||||
|
||||
### Backend
|
||||
4. **`server/src/controllers/authController.js`** - forgotPassword() & resetPassword()
|
||||
5. **`server/src/routes/authRoutes.js`** - Routes cho forgot/reset password
|
||||
5. **`server/src/routes/authRoutes.js`** - Routes for forgot/reset password
|
||||
|
||||
## ✨ Tính Năng Chính
|
||||
## ✨ Main Features
|
||||
|
||||
### 1. Form State (Initial)
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏨 Hotel Icon (Blue) │
|
||||
│ Quên mật khẩu? │
|
||||
│ Nhập email để nhận link... │
|
||||
│ Forgot password? │
|
||||
│ Enter email to receive link... │
|
||||
├─────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Email │ │
|
||||
│ │ [📧 email@example.com ] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ [📤 Gửi link đặt lại MK] │ │
|
||||
│ │ [📤 Send reset link] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ ← Quay lại đăng nhập │ │
|
||||
│ │ ← Back to login │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ Chưa có tài khoản? Đăng ký ngay │
|
||||
│ Don't have an account? Sign up now │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -37,23 +37,23 @@
|
||||
┌─────────────────────────────────────┐
|
||||
│ ✅ Success Icon │
|
||||
│ │
|
||||
│ Email đã được gửi! │
|
||||
│ Chúng tôi đã gửi link đến │
|
||||
│ Email has been sent! │
|
||||
│ We have sent a link to │
|
||||
│ user@example.com │
|
||||
├─────────────────────────────────────┤
|
||||
│ ℹ️ Lưu ý: │
|
||||
│ • Link có hiệu lực trong 1 giờ │
|
||||
│ • Kiểm tra cả thư mục Spam/Junk │
|
||||
│ • Nếu không nhận được, thử lại │
|
||||
│ ℹ️ Note: │
|
||||
│ • Link is valid for 1 hour │
|
||||
│ • Check Spam/Junk folder │
|
||||
│ • If not received, try again │
|
||||
├─────────────────────────────────────┤
|
||||
│ [📧 Gửi lại email] │
|
||||
│ [← Quay lại đăng nhập] │
|
||||
│ [📧 Resend email] │
|
||||
│ [← Back to login] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3. Two-State Design Pattern
|
||||
✅ **Form State** - Nhập email
|
||||
✅ **Success State** - Hiển thị xác nhận & hướng dẫn
|
||||
✅ **Form State** - Enter email
|
||||
✅ **Success State** - Display confirmation & instructions
|
||||
|
||||
State management:
|
||||
```typescript
|
||||
@@ -61,19 +61,19 @@ const [isSuccess, setIsSuccess] = useState(false);
|
||||
const [submittedEmail, setSubmittedEmail] = useState('');
|
||||
```
|
||||
|
||||
## 🔧 Features Chi Tiết
|
||||
## 🔧 Detailed Features
|
||||
|
||||
### 1. Validation (Yup Schema)
|
||||
```typescript
|
||||
email:
|
||||
- Required: "Email là bắt buộc"
|
||||
- Valid format: "Email không hợp lệ"
|
||||
- Required: "Email is required"
|
||||
- Valid format: "Invalid email format"
|
||||
- Trim whitespace
|
||||
```
|
||||
|
||||
### 2. Form Field
|
||||
- **Email input** với Mail icon
|
||||
- Auto-focus khi load page
|
||||
- **Email input** with Mail icon
|
||||
- Auto-focus when page loads
|
||||
- Validation real-time
|
||||
- Error message inline
|
||||
|
||||
@@ -82,12 +82,12 @@ email:
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="animate-spin" />
|
||||
Đang xử lý...
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send />
|
||||
Gửi link đặt lại mật khẩu
|
||||
Send reset link
|
||||
</>
|
||||
)}
|
||||
```
|
||||
@@ -104,15 +104,15 @@ email:
|
||||
- Can resend email
|
||||
|
||||
✅ **Action Buttons**
|
||||
- "Gửi lại email" - Reset to form state
|
||||
- "Quay lại đăng nhập" - Navigate to /login
|
||||
- "Resend email" - Reset to form state
|
||||
- "Back to login" - Navigate to /login
|
||||
|
||||
### 5. Help Section
|
||||
```tsx
|
||||
<div className="bg-white rounded-lg shadow-sm border">
|
||||
<h3>Cần trợ giúp?</h3>
|
||||
<h3>Need help?</h3>
|
||||
<p>
|
||||
Liên hệ: support@hotel.com hoặc 1900-xxxx
|
||||
Contact: support@hotel.com or 1900-xxxx
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
@@ -267,7 +267,7 @@ CREATE TABLE password_reset_tokens (
|
||||
↓
|
||||
2. Enter email address
|
||||
↓
|
||||
3. Click "Gửi link đặt lại mật khẩu"
|
||||
3. Click "Send reset link"
|
||||
↓
|
||||
4. Frontend validation (Yup)
|
||||
↓
|
||||
@@ -286,7 +286,7 @@ CREATE TABLE password_reset_tokens (
|
||||
↓
|
||||
10. Click link → /reset-password/:token
|
||||
↓
|
||||
11. Enter new password (Chức năng 7)
|
||||
11. Enter new password (Function 7)
|
||||
```
|
||||
|
||||
## 🧪 Test Scenarios
|
||||
@@ -305,7 +305,7 @@ Expected:
|
||||
```
|
||||
Input: email = "notanemail"
|
||||
Expected:
|
||||
- Validation error: "Email không hợp lệ"
|
||||
- Validation error: "Invalid email format"
|
||||
- Form not submitted
|
||||
```
|
||||
|
||||
@@ -313,7 +313,7 @@ Expected:
|
||||
```
|
||||
Input: email = ""
|
||||
Expected:
|
||||
- Validation error: "Email là bắt buộc"
|
||||
- Validation error: "Email is required"
|
||||
- Form not submitted
|
||||
```
|
||||
|
||||
@@ -323,12 +323,12 @@ Action: Submit form
|
||||
Expected:
|
||||
- Button disabled
|
||||
- Spinner shows
|
||||
- Text: "Đang xử lý..."
|
||||
- Text: "Processing..."
|
||||
```
|
||||
|
||||
### Test Case 5: Resend email
|
||||
```
|
||||
Action: Click "Gửi lại email" in success state
|
||||
Action: Click "Resend email" in success state
|
||||
Expected:
|
||||
- Return to form state
|
||||
- Email field cleared
|
||||
@@ -337,7 +337,7 @@ Expected:
|
||||
|
||||
### Test Case 6: Back to login
|
||||
```
|
||||
Action: Click "Quay lại đăng nhập"
|
||||
Action: Click "Back to login"
|
||||
Expected:
|
||||
- Navigate to /login
|
||||
```
|
||||
@@ -482,7 +482,7 @@ never expose raw reset tokens in logs. To enable email sending:
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Chức năng 6 hoàn thành
|
||||
**Next:** Chức năng 7 - Reset Password (form to change password with token)
|
||||
**Status:** ✅ Function 6 completed
|
||||
**Next:** Function 7 - Reset Password (form to change password with token)
|
||||
**Test URL:** http://localhost:5173/forgot-password
|
||||
**API:** POST /api/auth/forgot-password
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
# Layout Components - Chức năng 1
|
||||
# Layout Components - Function 1
|
||||
|
||||
## Tổng quan
|
||||
Đã triển khai thành công **Chức năng 1: Layout cơ bản** bao gồm:
|
||||
## Overview
|
||||
Successfully implemented **Function 1: Basic Layout** including:
|
||||
|
||||
### Components đã tạo
|
||||
### Components Created
|
||||
|
||||
#### 1. **Header** (`src/components/layout/Header.tsx`)
|
||||
- Logo và tên ứng dụng
|
||||
- Sticky header với shadow
|
||||
- Logo and application name
|
||||
- Sticky header with shadow
|
||||
- Responsive design
|
||||
- Links cơ bản (Trang chủ, Phòng, Đặt phòng)
|
||||
- Basic links (Home, Rooms, Bookings)
|
||||
|
||||
#### 2. **Footer** (`src/components/layout/Footer.tsx`)
|
||||
- Thông tin công ty
|
||||
- Quick links (Liên kết nhanh)
|
||||
- Support links (Hỗ trợ)
|
||||
- Contact info (Thông tin liên hệ)
|
||||
- Company information
|
||||
- Quick links
|
||||
- Support links
|
||||
- Contact information
|
||||
- Social media icons
|
||||
- Copyright info
|
||||
- Copyright information
|
||||
- Fully responsive (4 columns → 2 → 1)
|
||||
|
||||
#### 3. **Navbar** (`src/components/layout/Navbar.tsx`)
|
||||
- **Trạng thái chưa đăng nhập**:
|
||||
- Hiển thị nút "Đăng nhập" và "Đăng ký"
|
||||
- **Trạng thái đã đăng nhập**:
|
||||
- Hiển thị avatar/tên user
|
||||
- Dropdown menu với "Hồ sơ", "Quản trị" (admin), "Đăng xuất"
|
||||
- Mobile menu với hamburger icon
|
||||
- Responsive cho desktop và mobile
|
||||
- **Not logged in state**:
|
||||
- Display "Login" and "Register" buttons
|
||||
- **Logged in state**:
|
||||
- Display avatar/user name
|
||||
- Dropdown menu with "Profile", "Admin" (admin), "Logout"
|
||||
- Mobile menu with hamburger icon
|
||||
- Responsive for desktop and mobile
|
||||
|
||||
#### 4. **SidebarAdmin** (`src/components/layout/SidebarAdmin.tsx`)
|
||||
- Chỉ hiển thị cho role = "admin"
|
||||
- Collapsible sidebar (mở/đóng)
|
||||
- Only displays for role = "admin"
|
||||
- Collapsible sidebar (open/close)
|
||||
- Menu items: Dashboard, Users, Rooms, Bookings, Payments, Services, Promotions, Banners, Reports, Settings
|
||||
- Active state highlighting
|
||||
- Responsive design
|
||||
|
||||
#### 5. **LayoutMain** (`src/components/layout/LayoutMain.tsx`)
|
||||
- Tích hợp Header, Navbar, Footer
|
||||
- Sử dụng `<Outlet />` để render nội dung động
|
||||
- Integrates Header, Navbar, Footer
|
||||
- Uses `<Outlet />` to render dynamic content
|
||||
- Props: `isAuthenticated`, `userInfo`, `onLogout`
|
||||
- Min-height 100vh với flex layout
|
||||
- Min-height 100vh with flex layout
|
||||
|
||||
### Cấu trúc thư mục
|
||||
### Directory Structure
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
@@ -62,13 +62,13 @@ src/
|
||||
└── main.tsx
|
||||
```
|
||||
|
||||
### Cách sử dụng
|
||||
### Usage
|
||||
|
||||
#### 1. Import Layout vào App
|
||||
#### 1. Import Layout into App
|
||||
```tsx
|
||||
import LayoutMain from './components/layout/LayoutMain';
|
||||
|
||||
// Trong Routes
|
||||
// In Routes
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
@@ -80,11 +80,11 @@ import LayoutMain from './components/layout/LayoutMain';
|
||||
}
|
||||
>
|
||||
<Route index element={<HomePage />} />
|
||||
{/* Các route con khác */}
|
||||
{/* Other child routes */}
|
||||
</Route>
|
||||
```
|
||||
|
||||
#### 2. Sử dụng SidebarAdmin cho trang Admin
|
||||
#### 2. Use SidebarAdmin for Admin Pages
|
||||
```tsx
|
||||
import SidebarAdmin from '../components/layout/SidebarAdmin';
|
||||
|
||||
@@ -98,78 +98,78 @@ const AdminLayout = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Tính năng đã hoàn thành ✅
|
||||
### Completed Features ✅
|
||||
|
||||
- [x] Tạo thư mục `src/components/layout/`
|
||||
- [x] Header.tsx với logo và navigation
|
||||
- [x] Footer.tsx với thông tin đầy đủ
|
||||
- [x] Navbar.tsx với logic đăng nhập/đăng xuất động
|
||||
- [x] SidebarAdmin.tsx chỉ hiển thị với role admin
|
||||
- [x] LayoutMain.tsx sử dụng `<Outlet />`
|
||||
- [x] Navbar thay đổi theo trạng thái đăng nhập
|
||||
- [x] Giao diện responsive, tương thích desktop/mobile
|
||||
- [x] Tích hợp TailwindCSS cho styling
|
||||
- [x] Export tất cả components qua index.ts
|
||||
- [x] Create `src/components/layout/` directory
|
||||
- [x] Header.tsx with logo and navigation
|
||||
- [x] Footer.tsx with complete information
|
||||
- [x] Navbar.tsx with dynamic login/logout logic
|
||||
- [x] SidebarAdmin.tsx only displays with admin role
|
||||
- [x] LayoutMain.tsx uses `<Outlet />`
|
||||
- [x] Navbar changes based on login state
|
||||
- [x] Responsive interface, compatible with desktop/mobile
|
||||
- [x] TailwindCSS integration for styling
|
||||
- [x] Export all components via index.ts
|
||||
|
||||
### Demo Routes đã tạo
|
||||
### Demo Routes Created
|
||||
|
||||
**Public Routes** (với LayoutMain):
|
||||
- `/` - Trang chủ
|
||||
- `/rooms` - Danh sách phòng
|
||||
- `/bookings` - Đặt phòng
|
||||
- `/about` - Giới thiệu
|
||||
**Public Routes** (with LayoutMain):
|
||||
- `/` - Home
|
||||
- `/rooms` - Room list
|
||||
- `/bookings` - Bookings
|
||||
- `/about` - About
|
||||
|
||||
**Auth Routes** (không có layout):
|
||||
- `/login` - Đăng nhập
|
||||
- `/register` - Đăng ký
|
||||
- `/forgot-password` - Quên mật khẩu
|
||||
**Auth Routes** (no layout):
|
||||
- `/login` - Login
|
||||
- `/register` - Register
|
||||
- `/forgot-password` - Forgot password
|
||||
|
||||
**Admin Routes** (với SidebarAdmin):
|
||||
**Admin Routes** (with SidebarAdmin):
|
||||
- `/admin/dashboard` - Dashboard
|
||||
- `/admin/users` - Quản lý người dùng
|
||||
- `/admin/rooms` - Quản lý phòng
|
||||
- `/admin/bookings` - Quản lý đặt phòng
|
||||
- `/admin/payments` - Quản lý thanh toán
|
||||
- `/admin/services` - Quản lý dịch vụ
|
||||
- `/admin/promotions` - Quản lý khuyến mãi
|
||||
- `/admin/banners` - Quản lý banner
|
||||
- `/admin/users` - User Management
|
||||
- `/admin/rooms` - Room Management
|
||||
- `/admin/bookings` - Booking Management
|
||||
- `/admin/payments` - Payment Management
|
||||
- `/admin/services` - Service Management
|
||||
- `/admin/promotions` - Promotion Management
|
||||
- `/admin/banners` - Banner Management
|
||||
|
||||
### Chạy ứng dụng
|
||||
### Run Application
|
||||
|
||||
```bash
|
||||
# Di chuyển vào thư mục client
|
||||
# Navigate to client directory
|
||||
cd client
|
||||
|
||||
# Cài đặt dependencies (nếu chưa cài)
|
||||
# Install dependencies (if not installed)
|
||||
npm install
|
||||
|
||||
# Chạy development server
|
||||
# Run development server
|
||||
npm run dev
|
||||
|
||||
# Mở trình duyệt tại: http://localhost:5173
|
||||
# Open browser at: http://localhost:5173
|
||||
```
|
||||
|
||||
### Các bước tiếp theo
|
||||
### Next Steps
|
||||
|
||||
**Chức năng 2**: Cấu hình Routing (react-router-dom)
|
||||
**Function 2**: Routing Configuration (react-router-dom)
|
||||
- ProtectedRoute component
|
||||
- AdminRoute component
|
||||
- Redirect logic
|
||||
|
||||
**Chức năng 3**: useAuthStore (Zustand Store)
|
||||
- Quản lý authentication state
|
||||
**Function 3**: useAuthStore (Zustand Store)
|
||||
- Manage authentication state
|
||||
- Login/Logout functions
|
||||
- Persist state trong localStorage
|
||||
- Persist state in localStorage
|
||||
|
||||
**Chức năng 4-8**: Auth Forms
|
||||
**Function 4-8**: Auth Forms
|
||||
- LoginPage
|
||||
- RegisterPage
|
||||
- ForgotPasswordPage
|
||||
- ResetPasswordPage
|
||||
|
||||
### Notes
|
||||
- Layout components được thiết kế để tái sử dụng
|
||||
- Props-based design cho flexibility
|
||||
- Sẵn sàng tích hợp với Zustand store
|
||||
- Tailwind classes tuân thủ 80 ký tự/dòng
|
||||
- Icons sử dụng lucide-react (đã có trong dependencies)
|
||||
- Layout components designed for reusability
|
||||
- Props-based design for flexibility
|
||||
- Ready to integrate with Zustand store
|
||||
- Tailwind classes follow 80 characters/line
|
||||
- Icons use lucide-react (already in dependencies)
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
# Chức năng 4: Form Đăng Nhập - Hướng Dẫn Sử Dụng
|
||||
# Function 4: Login Form - Usage Guide
|
||||
|
||||
## 📋 Tổng Quan
|
||||
## 📋 Overview
|
||||
|
||||
Form đăng nhập đã được triển khai đầy đủ với:
|
||||
- ✅ Validation form bằng React Hook Form + Yup
|
||||
- ✅ Hiển thị/ẩn mật khẩu
|
||||
- ✅ Checkbox "Nhớ đăng nhập" (7 ngày)
|
||||
- ✅ Loading state trong quá trình đăng nhập
|
||||
- ✅ Hiển thị lỗi từ server
|
||||
- ✅ Redirect sau khi đăng nhập thành công
|
||||
- ✅ UI đẹp với Tailwind CSS và Lucide Icons
|
||||
Login form has been fully implemented with:
|
||||
- ✅ Form validation with React Hook Form + Yup
|
||||
- ✅ Show/hide password
|
||||
- ✅ "Remember me" checkbox (7 days)
|
||||
- ✅ Loading state during login process
|
||||
- ✅ Display errors from server
|
||||
- ✅ Redirect after successful login
|
||||
- ✅ Beautiful UI with Tailwind CSS and Lucide Icons
|
||||
- ✅ Responsive design
|
||||
|
||||
## 🗂️ Các File Đã Tạo/Cập Nhật
|
||||
## 🗂️ Files Created/Updated
|
||||
|
||||
### 1. **LoginPage.tsx** - Component form đăng nhập
|
||||
**Đường dẫn:** `client/src/pages/auth/LoginPage.tsx`
|
||||
### 1. **LoginPage.tsx** - Login form component
|
||||
**Path:** `client/src/pages/auth/LoginPage.tsx`
|
||||
|
||||
```typescript
|
||||
// Các tính năng chính:
|
||||
- React Hook Form với Yup validation
|
||||
// Main features:
|
||||
- React Hook Form with Yup validation
|
||||
- Show/hide password toggle
|
||||
- Remember me checkbox
|
||||
- Loading state với spinner
|
||||
- Loading state with spinner
|
||||
- Error handling
|
||||
- Redirect với location state
|
||||
- Redirect with location state
|
||||
```
|
||||
|
||||
### 2. **index.ts** - Export module
|
||||
**Đường dẫn:** `client/src/pages/auth/index.ts`
|
||||
**Path:** `client/src/pages/auth/index.ts`
|
||||
|
||||
```typescript
|
||||
export { default as LoginPage } from './LoginPage';
|
||||
```
|
||||
|
||||
### 3. **App.tsx** - Đã cập nhật routing
|
||||
**Đường dẫn:** `client/src/App.tsx`
|
||||
### 3. **App.tsx** - Routing updated
|
||||
**Path:** `client/src/App.tsx`
|
||||
|
||||
```typescript
|
||||
// Đã thêm:
|
||||
// Added:
|
||||
import { LoginPage } from './pages/auth';
|
||||
|
||||
// Route:
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
```
|
||||
|
||||
## 🎨 Cấu Trúc UI
|
||||
## 🎨 UI Structure
|
||||
|
||||
### Layout
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏨 Hotel Icon │
|
||||
│ Đăng nhập │
|
||||
│ Chào mừng bạn trở lại... │
|
||||
│ Login │
|
||||
│ Welcome back... │
|
||||
├─────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ [Error message if any] │ │
|
||||
@@ -60,72 +60,72 @@ import { LoginPage } from './pages/auth';
|
||||
│ │ Email │ │
|
||||
│ │ [📧 email@example.com ] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Mật khẩu │ │
|
||||
│ │ Password │ │
|
||||
│ │ [🔒 •••••••• 👁️] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ ☑️ Nhớ đăng nhập │ │
|
||||
│ │ Quên mật khẩu? → │ │
|
||||
│ │ ☑️ Remember me │ │
|
||||
│ │ Forgot password? → │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ [🔐 Đăng nhập] │ │
|
||||
│ │ [🔐 Login] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ Chưa có tài khoản? Đăng ký ngay │
|
||||
│ Don't have an account? Sign up now │
|
||||
│ │
|
||||
│ Điều khoản & Chính sách bảo mật │
|
||||
│ Terms & Privacy Policy │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 🔧 Cách Sử Dụng
|
||||
## 🔧 Usage
|
||||
|
||||
### 1. Truy Cập Form
|
||||
### 1. Access Form
|
||||
|
||||
```bash
|
||||
# URL
|
||||
http://localhost:5173/login
|
||||
```
|
||||
|
||||
### 2. Các Trường Trong Form
|
||||
### 2. Form Fields
|
||||
|
||||
| Trường | Type | Bắt buộc | Validation |
|
||||
| Field | Type | Required | Validation |
|
||||
|--------|------|----------|------------|
|
||||
| Email | text | ✅ | Email hợp lệ |
|
||||
| Password | password | ✅ | Min 8 ký tự |
|
||||
| Email | text | ✅ | Valid email |
|
||||
| Password | password | ✅ | Min 8 characters |
|
||||
| Remember Me | checkbox | ❌ | Boolean |
|
||||
|
||||
### 3. Validation Rules
|
||||
|
||||
**Email:**
|
||||
```typescript
|
||||
- Required: "Email là bắt buộc"
|
||||
- Valid email format: "Email không hợp lệ"
|
||||
- Required: "Email is required"
|
||||
- Valid email format: "Invalid email format"
|
||||
- Trim whitespace
|
||||
```
|
||||
|
||||
**Password:**
|
||||
```typescript
|
||||
- Required: "Mật khẩu là bắt buộc"
|
||||
- Min 8 characters: "Mật khẩu phải có ít nhất 8 ký tự"
|
||||
- Required: "Password is required"
|
||||
- Min 8 characters: "Password must be at least 8 characters"
|
||||
```
|
||||
|
||||
### 4. Luồng Đăng Nhập
|
||||
### 4. Login Flow
|
||||
|
||||
```
|
||||
1. User nhập email + password
|
||||
2. Click "Đăng nhập"
|
||||
3. Validation form (client-side)
|
||||
4. Nếu valid:
|
||||
- Button disabled + hiển thị loading
|
||||
- Gọi useAuthStore.login()
|
||||
1. User enters email + password
|
||||
2. Click "Login"
|
||||
3. Form validation (client-side)
|
||||
4. If valid:
|
||||
- Button disabled + show loading
|
||||
- Call useAuthStore.login()
|
||||
- API POST /api/auth/login
|
||||
5. Nếu thành công:
|
||||
- Lưu token vào localStorage
|
||||
5. If successful:
|
||||
- Save token to localStorage
|
||||
- Update Zustand state
|
||||
- Redirect đến /dashboard
|
||||
6. Nếu lỗi:
|
||||
- Hiển thị error message
|
||||
- Button enabled lại
|
||||
- Redirect to /dashboard
|
||||
6. If error:
|
||||
- Display error message
|
||||
- Button enabled again
|
||||
```
|
||||
|
||||
## 🎯 Tính Năng Chính
|
||||
## 🎯 Main Features
|
||||
|
||||
### 1. Show/Hide Password
|
||||
|
||||
@@ -141,33 +141,33 @@ const [showPassword, setShowPassword] = useState(false);
|
||||
<input type={showPassword ? 'text' : 'password'} />
|
||||
```
|
||||
|
||||
### 2. Remember Me (7 ngày)
|
||||
### 2. Remember Me (7 days)
|
||||
|
||||
```typescript
|
||||
// Checkbox
|
||||
<input {...register('rememberMe')} type="checkbox" />
|
||||
|
||||
// Logic trong authService.login()
|
||||
// Logic in authService.login()
|
||||
if (rememberMe) {
|
||||
// Token sẽ được lưu trong localStorage
|
||||
// và không bị xóa khi đóng trình duyệt
|
||||
// Token will be saved in localStorage
|
||||
// and won't be deleted when closing browser
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Loading State
|
||||
|
||||
```typescript
|
||||
// Button disabled khi đang loading
|
||||
// Button disabled when loading
|
||||
<button disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="animate-spin" />
|
||||
Đang xử lý...
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LogIn />
|
||||
Đăng nhập
|
||||
Login
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -176,10 +176,10 @@ if (rememberMe) {
|
||||
### 4. Error Handling
|
||||
|
||||
```typescript
|
||||
// Error từ Zustand store
|
||||
// Error from Zustand store
|
||||
const { error } = useAuthStore();
|
||||
|
||||
// Hiển thị error message
|
||||
// Display error message
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200">
|
||||
{error}
|
||||
@@ -190,20 +190,20 @@ const { error } = useAuthStore();
|
||||
### 5. Redirect Logic
|
||||
|
||||
```typescript
|
||||
// Lấy location state từ ProtectedRoute
|
||||
// Get location state from ProtectedRoute
|
||||
const location = useLocation();
|
||||
|
||||
// Redirect về trang trước đó hoặc dashboard
|
||||
// Redirect to previous page or dashboard
|
||||
const from = location.state?.from?.pathname || '/dashboard';
|
||||
navigate(from, { replace: true });
|
||||
```
|
||||
|
||||
## 🔗 Integration với Zustand Store
|
||||
## 🔗 Integration with Zustand Store
|
||||
|
||||
```typescript
|
||||
// Hook usage
|
||||
const {
|
||||
login, // Function để login
|
||||
login, // Function to login
|
||||
isLoading, // Loading state
|
||||
error, // Error message
|
||||
clearError // Clear error
|
||||
@@ -217,7 +217,7 @@ await login({
|
||||
});
|
||||
```
|
||||
|
||||
## 🎨 Styling với Tailwind
|
||||
## 🎨 Styling with Tailwind
|
||||
|
||||
### Color Scheme
|
||||
```
|
||||
@@ -232,7 +232,7 @@ await login({
|
||||
// Container
|
||||
className="max-w-md w-full" // Max width 28rem
|
||||
|
||||
// Grid (nếu có)
|
||||
// Grid (if any)
|
||||
className="grid grid-cols-1 md:grid-cols-2"
|
||||
```
|
||||
|
||||
@@ -242,20 +242,20 @@ className="grid grid-cols-1 md:grid-cols-2"
|
||||
|
||||
**Test Case 1: Empty form**
|
||||
```
|
||||
- Input: Submit form trống
|
||||
- Expected: Hiển thị lỗi "Email là bắt buộc"
|
||||
- Input: Submit empty form
|
||||
- Expected: Display error "Email is required"
|
||||
```
|
||||
|
||||
**Test Case 2: Invalid email**
|
||||
```
|
||||
- Input: Email = "notanemail"
|
||||
- Expected: "Email không hợp lệ"
|
||||
- Expected: "Invalid email format"
|
||||
```
|
||||
|
||||
**Test Case 3: Short password**
|
||||
```
|
||||
- Input: Password = "123"
|
||||
- Expected: "Mật khẩu phải có ít nhất 8 ký tự"
|
||||
- Expected: "Password must be at least 8 characters"
|
||||
```
|
||||
|
||||
### 2. Authentication Testing
|
||||
@@ -269,13 +269,13 @@ className="grid grid-cols-1 md:grid-cols-2"
|
||||
**Test Case 5: Invalid credentials**
|
||||
```
|
||||
- Input: Wrong password
|
||||
- Expected: Error message từ server
|
||||
- Expected: Error message from server
|
||||
```
|
||||
|
||||
**Test Case 6: Network error**
|
||||
```
|
||||
- Input: Server offline
|
||||
- Expected: Error message "Có lỗi xảy ra"
|
||||
- Expected: Error message "An error occurred"
|
||||
```
|
||||
|
||||
### 3. UX Testing
|
||||
@@ -288,14 +288,14 @@ className="grid grid-cols-1 md:grid-cols-2"
|
||||
|
||||
**Test Case 8: Remember me**
|
||||
```
|
||||
- Action: Check "Nhớ đăng nhập"
|
||||
- Expected: Token persist sau khi reload
|
||||
- Action: Check "Remember me"
|
||||
- Expected: Token persists after reload
|
||||
```
|
||||
|
||||
**Test Case 9: Loading state**
|
||||
```
|
||||
- Action: Submit form
|
||||
- Expected: Button disabled, spinner hiển thị
|
||||
- Expected: Button disabled, spinner displayed
|
||||
```
|
||||
|
||||
## 🔐 Security Features
|
||||
@@ -311,18 +311,18 @@ onClick={() => setShowPassword(!showPassword)}
|
||||
|
||||
### 2. HTTPS Only (Production)
|
||||
```typescript
|
||||
// Trong .env
|
||||
// In .env
|
||||
VITE_API_URL=https://api.yourdomain.com
|
||||
```
|
||||
|
||||
### 3. Token Storage
|
||||
```typescript
|
||||
// LocalStorage cho remember me
|
||||
// LocalStorage for remember me
|
||||
if (rememberMe) {
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
// SessionStorage cho session only
|
||||
// SessionStorage for session only
|
||||
else {
|
||||
sessionStorage.setItem('token', token);
|
||||
}
|
||||
@@ -355,22 +355,22 @@ else {
|
||||
✅ Remember form state
|
||||
```
|
||||
|
||||
## 🚀 Next Steps (Chức năng 5-7)
|
||||
## 🚀 Next Steps (Function 5-7)
|
||||
|
||||
1. **Chức năng 5: Form Register**
|
||||
- Copy structure từ LoginPage
|
||||
- Thêm fields: name, phone, confirmPassword
|
||||
1. **Function 5: Register Form**
|
||||
- Copy structure from LoginPage
|
||||
- Add fields: name, phone, confirmPassword
|
||||
- Use registerSchema
|
||||
- Redirect to /login after success
|
||||
|
||||
2. **Chức năng 6: Forgot Password**
|
||||
- Simple form với email only
|
||||
2. **Function 6: Forgot Password**
|
||||
- Simple form with email only
|
||||
- Send reset link
|
||||
- Success message
|
||||
|
||||
3. **Chức năng 7: Reset Password**
|
||||
- Form với password + confirmPassword
|
||||
- Token từ URL params
|
||||
3. **Function 7: Reset Password**
|
||||
- Form with password + confirmPassword
|
||||
- Token from URL params
|
||||
- Redirect to /login after success
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
@@ -382,24 +382,24 @@ Solution: Check token expiry time
|
||||
- Refresh token: 7 days
|
||||
```
|
||||
|
||||
### Issue 2: Form không submit
|
||||
### Issue 2: Form doesn't submit
|
||||
```typescript
|
||||
Solution: Check console for validation errors
|
||||
- Open DevTools > Console
|
||||
- Look for Yup validation errors
|
||||
```
|
||||
|
||||
### Issue 3: Redirect không hoạt động
|
||||
### Issue 3: Redirect doesn't work
|
||||
```typescript
|
||||
Solution: Check location state
|
||||
console.log(location.state?.from);
|
||||
```
|
||||
|
||||
### Issue 4: Remember me không work
|
||||
### Issue 4: Remember me doesn't work
|
||||
```typescript
|
||||
Solution: Check localStorage
|
||||
- Open DevTools > Application > Local Storage
|
||||
- Check "token" và "refreshToken" keys
|
||||
- Check "token" and "refreshToken" keys
|
||||
```
|
||||
|
||||
## 📚 Resources
|
||||
@@ -428,5 +428,5 @@ Solution: Check localStorage
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Chức năng 4 hoàn thành
|
||||
**Next:** Chức năng 5 - Form Register
|
||||
**Status:** ✅ Function 4 completed
|
||||
**Next:** Function 5 - Register Form
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
# Chức năng 5: Form Đăng Ký - Hoàn Thành ✅
|
||||
# Function 5: Register Form - Completed ✅
|
||||
|
||||
## 📦 Files Đã Tạo/Cập Nhật
|
||||
## 📦 Files Created/Updated
|
||||
|
||||
### 1. **RegisterPage.tsx** - Component form đăng ký
|
||||
**Đường dẫn:** `client/src/pages/auth/RegisterPage.tsx`
|
||||
### 1. **RegisterPage.tsx** - Register form component
|
||||
**Path:** `client/src/pages/auth/RegisterPage.tsx`
|
||||
|
||||
### 2. **index.ts** - Export module
|
||||
**Đường dẫn:** `client/src/pages/auth/index.ts`
|
||||
- Đã thêm export RegisterPage
|
||||
**Path:** `client/src/pages/auth/index.ts`
|
||||
- Added export RegisterPage
|
||||
|
||||
### 3. **App.tsx** - Cập nhật routing
|
||||
**Đường dẫn:** `client/src/App.tsx`
|
||||
- Đã thêm route `/register`
|
||||
### 3. **App.tsx** - Routing updated
|
||||
**Path:** `client/src/App.tsx`
|
||||
- Added route `/register`
|
||||
|
||||
## ✨ Tính Năng Chính
|
||||
## ✨ Main Features
|
||||
|
||||
### 1. Form Fields (5 fields)
|
||||
✅ **Họ và tên** (name)
|
||||
- Required, 2-50 ký tự
|
||||
✅ **Full Name** (name)
|
||||
- Required, 2-50 characters
|
||||
- Icon: User
|
||||
- Placeholder: "Nguyễn Văn A"
|
||||
- Placeholder: "John Doe"
|
||||
|
||||
✅ **Email**
|
||||
- Required, valid email format
|
||||
- Icon: Mail
|
||||
- Placeholder: "email@example.com"
|
||||
|
||||
✅ **Số điện thoại** (phone) - Optional
|
||||
- 10-11 chữ số
|
||||
✅ **Phone Number** (phone) - Optional
|
||||
- 10-11 digits
|
||||
- Icon: Phone
|
||||
- Placeholder: "0123456789"
|
||||
|
||||
✅ **Mật khẩu** (password)
|
||||
✅ **Password** (password)
|
||||
- Required, min 8 chars
|
||||
- Must contain: uppercase, lowercase, number, special char
|
||||
- Show/hide toggle với Eye icon
|
||||
- Show/hide toggle with Eye icon
|
||||
- Icon: Lock
|
||||
|
||||
✅ **Xác nhận mật khẩu** (confirmPassword)
|
||||
✅ **Confirm Password** (confirmPassword)
|
||||
- Must match password
|
||||
- Show/hide toggle với Eye icon
|
||||
- Show/hide toggle with Eye icon
|
||||
- Icon: Lock
|
||||
|
||||
### 2. Password Strength Indicator
|
||||
✅ **Visual Progress Bar** với 5 levels:
|
||||
1. 🔴 Rất yếu (0/5)
|
||||
2. 🟠 Yếu (1/5)
|
||||
3. 🟡 Trung bình (2/5)
|
||||
4. 🔵 Mạnh (3/5)
|
||||
5. 🟢 Rất mạnh (5/5)
|
||||
✅ **Visual Progress Bar** with 5 levels:
|
||||
1. 🔴 Very weak (0/5)
|
||||
2. 🟠 Weak (1/5)
|
||||
3. 🟡 Medium (2/5)
|
||||
4. 🔵 Strong (3/5)
|
||||
5. 🟢 Very strong (5/5)
|
||||
|
||||
✅ **Real-time Requirements Checker:**
|
||||
- ✅/❌ Ít nhất 8 ký tự
|
||||
- ✅/❌ Chữ thường (a-z)
|
||||
- ✅/❌ Chữ hoa (A-Z)
|
||||
- ✅/❌ Số (0-9)
|
||||
- ✅/❌ Ký tự đặc biệt (@$!%*?&)
|
||||
- ✅/❌ At least 8 characters
|
||||
- ✅/❌ Lowercase (a-z)
|
||||
- ✅/❌ Uppercase (A-Z)
|
||||
- ✅/❌ Number (0-9)
|
||||
- ✅/❌ Special character (@$!%*?&)
|
||||
|
||||
### 3. Validation Rules (Yup Schema)
|
||||
|
||||
```typescript
|
||||
name:
|
||||
- Required: "Họ tên là bắt buộc"
|
||||
- Min 2 chars: "Họ tên phải có ít nhất 2 ký tự"
|
||||
- Max 50 chars: "Họ tên không được quá 50 ký tự"
|
||||
- Required: "Full name is required"
|
||||
- Min 2 chars: "Full name must be at least 2 characters"
|
||||
- Max 50 chars: "Full name must not exceed 50 characters"
|
||||
- Trim whitespace
|
||||
|
||||
email:
|
||||
- Required: "Email là bắt buộc"
|
||||
- Valid format: "Email không hợp lệ"
|
||||
- Required: "Email is required"
|
||||
- Valid format: "Invalid email format"
|
||||
- Trim whitespace
|
||||
|
||||
phone (optional):
|
||||
- Pattern /^[0-9]{10,11}$/
|
||||
- Error: "Số điện thoại không hợp lệ"
|
||||
- Error: "Invalid phone number"
|
||||
|
||||
password:
|
||||
- Required: "Mật khẩu là bắt buộc"
|
||||
- Required: "Password is required"
|
||||
- Min 8 chars
|
||||
- Pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/
|
||||
- Error: "Mật khẩu phải chứa chữ hoa, chữ thường, số và ký tự đặc biệt"
|
||||
- Error: "Password must contain uppercase, lowercase, number and special characters"
|
||||
|
||||
confirmPassword:
|
||||
- Required: "Vui lòng xác nhận mật khẩu"
|
||||
- Must match password: "Mật khẩu không khớp"
|
||||
- Required: "Please confirm password"
|
||||
- Must match password: "Passwords do not match"
|
||||
```
|
||||
|
||||
### 4. UX Features
|
||||
@@ -93,32 +93,32 @@ confirmPassword:
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="animate-spin" />
|
||||
Đang xử lý...
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UserPlus />
|
||||
Đăng ký
|
||||
Register
|
||||
</>
|
||||
)}
|
||||
```
|
||||
|
||||
✅ **Show/Hide Password** (2 toggles)
|
||||
- Eye/EyeOff icons
|
||||
- Separate toggle cho password và confirmPassword
|
||||
- Visual feedback khi hover
|
||||
- Separate toggle for password and confirmPassword
|
||||
- Visual feedback on hover
|
||||
|
||||
✅ **Error Display**
|
||||
- Inline validation errors dưới mỗi field
|
||||
- Global error message ở top của form
|
||||
- Red border cho fields có lỗi
|
||||
- Inline validation errors under each field
|
||||
- Global error message at top of form
|
||||
- Red border for fields with errors
|
||||
|
||||
✅ **Success Flow**
|
||||
```typescript
|
||||
1. Submit form
|
||||
2. Validation passes
|
||||
3. Call useAuthStore.register()
|
||||
4. Show toast: "Đăng ký thành công! Vui lòng đăng nhập."
|
||||
4. Show toast: "Registration successful! Please login."
|
||||
5. Navigate to /login
|
||||
```
|
||||
|
||||
@@ -135,38 +135,38 @@ confirmPassword:
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏨 Hotel Icon (Purple) │
|
||||
│ Đăng ký tài khoản │
|
||||
│ Tạo tài khoản mới để đặt phòng... │
|
||||
│ Register Account │
|
||||
│ Create a new account to book... │
|
||||
├─────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ [Error message if any] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Họ và tên │ │
|
||||
│ │ [👤 Nguyễn Văn A ] │ │
|
||||
│ │ Full Name │ │
|
||||
│ │ [👤 John Doe ] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Email │ │
|
||||
│ │ [📧 email@example.com ] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Số điện thoại (Tùy chọn) │ │
|
||||
│ │ Phone Number (Optional) │ │
|
||||
│ │ [📱 0123456789 ] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Mật khẩu │ │
|
||||
│ │ Password │ │
|
||||
│ │ [🔒 •••••••• 👁️] │ │
|
||||
│ │ ▓▓▓▓▓░░░░░ Rất mạnh │ │
|
||||
│ │ ✅ Ít nhất 8 ký tự │ │
|
||||
│ │ ✅ Chữ thường (a-z) │ │
|
||||
│ │ ✅ Chữ hoa (A-Z) │ │
|
||||
│ │ ✅ Số (0-9) │ │
|
||||
│ │ ✅ Ký tự đặc biệt │ │
|
||||
│ │ ▓▓▓▓▓░░░░░ Very strong │ │
|
||||
│ │ ✅ At least 8 characters │ │
|
||||
│ │ ✅ Lowercase (a-z) │ │
|
||||
│ │ ✅ Uppercase (A-Z) │ │
|
||||
│ │ ✅ Number (0-9) │ │
|
||||
│ │ ✅ Special character │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ Xác nhận mật khẩu │ │
|
||||
│ │ Confirm Password │ │
|
||||
│ │ [🔒 •••••••• 👁️] │ │
|
||||
│ ├───────────────────────────────┤ │
|
||||
│ │ [👤 Đăng ký] │ │
|
||||
│ │ [👤 Register] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ Đã có tài khoản? Đăng nhập ngay │
|
||||
│ Already have an account? Login now │
|
||||
│ │
|
||||
│ Điều khoản & Chính sách bảo mật │
|
||||
│ Terms & Privacy Policy │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -180,7 +180,7 @@ POST /api/auth/register
|
||||
### Request Body
|
||||
```typescript
|
||||
{
|
||||
name: string; // "Nguyễn Văn A"
|
||||
name: string; // "John Doe"
|
||||
email: string; // "user@example.com"
|
||||
password: string; // "Password123@"
|
||||
phone?: string; // "0123456789" (optional)
|
||||
@@ -195,7 +195,7 @@ POST /api/auth/register
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Nguyễn Văn A",
|
||||
"name": "John Doe",
|
||||
"email": "user@example.com",
|
||||
"phone": "0123456789",
|
||||
"role": "customer"
|
||||
@@ -235,20 +235,20 @@ Expected: Show validation errors for name, email, password
|
||||
**Test Case 2: Invalid email**
|
||||
```
|
||||
Input: email = "notanemail"
|
||||
Expected: "Email không hợp lệ"
|
||||
Expected: "Invalid email format"
|
||||
```
|
||||
|
||||
**Test Case 3: Short name**
|
||||
```
|
||||
Input: name = "A"
|
||||
Expected: "Họ tên phải có ít nhất 2 ký tự"
|
||||
Expected: "Full name must be at least 2 characters"
|
||||
```
|
||||
|
||||
**Test Case 4: Weak password**
|
||||
```
|
||||
Input: password = "abc123"
|
||||
Expected: "Mật khẩu phải chứa chữ hoa, chữ thường, số và ký tự đặc biệt"
|
||||
Password strength: Yếu/Trung bình
|
||||
Expected: "Password must contain uppercase, lowercase, number and special characters"
|
||||
Password strength: Weak/Medium
|
||||
```
|
||||
|
||||
**Test Case 5: Password mismatch**
|
||||
@@ -256,13 +256,13 @@ Password strength: Yếu/Trung bình
|
||||
Input:
|
||||
password = "Password123@"
|
||||
confirmPassword = "Password456@"
|
||||
Expected: "Mật khẩu không khớp"
|
||||
Expected: "Passwords do not match"
|
||||
```
|
||||
|
||||
**Test Case 6: Invalid phone**
|
||||
```
|
||||
Input: phone = "123"
|
||||
Expected: "Số điện thoại không hợp lệ"
|
||||
Expected: "Invalid phone number"
|
||||
```
|
||||
|
||||
### 2. UX Tests
|
||||
@@ -290,7 +290,7 @@ Action: Submit valid form
|
||||
Expected:
|
||||
- Button disabled
|
||||
- Spinner shows
|
||||
- Text changes to "Đang xử lý..."
|
||||
- Text changes to "Processing..."
|
||||
```
|
||||
|
||||
### 3. Integration Tests
|
||||
@@ -300,7 +300,7 @@ Expected:
|
||||
Input: All valid data
|
||||
Expected:
|
||||
1. API POST /api/auth/register called
|
||||
2. Toast: "Đăng ký thành công! Vui lòng đăng nhập."
|
||||
2. Toast: "Registration successful! Please login."
|
||||
3. Redirect to /login
|
||||
```
|
||||
|
||||
@@ -317,7 +317,7 @@ Expected:
|
||||
```
|
||||
Scenario: Server offline
|
||||
Expected:
|
||||
- Error message: "Đăng ký thất bại. Vui lòng thử lại."
|
||||
- Error message: "Registration failed. Please try again."
|
||||
- Toast error displayed
|
||||
```
|
||||
|
||||
@@ -335,7 +335,7 @@ function getPasswordStrength(pwd: string) {
|
||||
|
||||
return {
|
||||
strength: 0-5,
|
||||
label: ['Rất yếu', 'Yếu', 'Trung bình', 'Mạnh', 'Rất mạnh'][strength],
|
||||
label: ['Very weak', 'Weak', 'Medium', 'Strong', 'Very strong'][strength],
|
||||
color: ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-blue-500', 'bg-green-500'][strength]
|
||||
};
|
||||
}
|
||||
@@ -347,7 +347,7 @@ function getPasswordStrength(pwd: string) {
|
||||
RegisterPage/
|
||||
├── Header Section
|
||||
│ ├── Hotel Icon (purple)
|
||||
│ ├── Title: "Đăng ký tài khoản"
|
||||
│ ├── Title: "Register Account"
|
||||
│ └── Subtitle
|
||||
│
|
||||
├── Form Container (white card)
|
||||
@@ -364,7 +364,7 @@ RegisterPage/
|
||||
│ └── Submit Button (with loading)
|
||||
│
|
||||
├── Login Link
|
||||
│ └── "Đã có tài khoản? Đăng nhập ngay"
|
||||
│ └── "Already have an account? Login now"
|
||||
│
|
||||
└── Footer Links
|
||||
├── Terms of Service
|
||||
@@ -413,7 +413,7 @@ http://localhost:5173/register
|
||||
|
||||
### Example Registration
|
||||
```typescript
|
||||
Name: "Nguyễn Văn A"
|
||||
Name: "John Doe"
|
||||
Email: "nguyenvana@example.com"
|
||||
Phone: "0123456789"
|
||||
Password: "Password123@"
|
||||
@@ -481,6 +481,6 @@ to /login Show errors
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Chức năng 5 hoàn thành
|
||||
**Next:** Chức năng 6 - Forgot Password
|
||||
**Status:** ✅ Function 5 completed
|
||||
**Next:** Function 6 - Forgot Password
|
||||
**Test URL:** http://localhost:5173/register
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Route Protection Documentation
|
||||
|
||||
## Chức năng 8: Phân quyền & Bảo vệ Route
|
||||
## Function 8: Authorization & Route Protection
|
||||
|
||||
Hệ thống sử dụng 2 component để bảo vệ các route:
|
||||
- **ProtectedRoute**: Yêu cầu user phải đăng nhập
|
||||
- **AdminRoute**: Yêu cầu user phải là Admin
|
||||
The system uses 2 components to protect routes:
|
||||
- **ProtectedRoute**: Requires user to be logged in
|
||||
- **AdminRoute**: Requires user to be Admin
|
||||
|
||||
---
|
||||
|
||||
## 1. ProtectedRoute
|
||||
|
||||
### Mục đích
|
||||
Bảo vệ các route yêu cầu authentication (đăng nhập).
|
||||
### Purpose
|
||||
Protects routes requiring authentication (login).
|
||||
|
||||
### Cách hoạt động
|
||||
### How It Works
|
||||
```tsx
|
||||
// File: client/src/components/auth/ProtectedRoute.tsx
|
||||
|
||||
@@ -23,32 +23,32 @@ const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
||||
const location = useLocation();
|
||||
const { isAuthenticated, isLoading } = useAuthStore();
|
||||
|
||||
// 1. Nếu đang loading → hiển thị spinner
|
||||
// 1. If loading → display spinner
|
||||
if (isLoading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
// 2. Nếu chưa đăng nhập → redirect /login
|
||||
// 2. If not logged in → redirect /login
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Navigate
|
||||
to="/login"
|
||||
state={{ from: location }} // Lưu location để quay lại
|
||||
state={{ from: location }} // Save location to return later
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Đã đăng nhập → cho phép truy cập
|
||||
// 3. Logged in → allow access
|
||||
return <>{children}</>;
|
||||
};
|
||||
```
|
||||
|
||||
### Sử dụng trong App.tsx
|
||||
### Usage in App.tsx
|
||||
```tsx
|
||||
import { ProtectedRoute } from './components/auth';
|
||||
|
||||
// Route yêu cầu đăng nhập
|
||||
// Route requiring login
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
@@ -77,20 +77,20 @@ import { ProtectedRoute } from './components/auth';
|
||||
/>
|
||||
```
|
||||
|
||||
### Luồng hoạt động
|
||||
1. User chưa đăng nhập truy cập `/dashboard`
|
||||
2. ProtectedRoute kiểm tra `isAuthenticated === false`
|
||||
3. Redirect về `/login` và lưu `state={{ from: '/dashboard' }}`
|
||||
4. Sau khi login thành công, redirect về `/dashboard`
|
||||
### Flow
|
||||
1. User not logged in accesses `/dashboard`
|
||||
2. ProtectedRoute checks `isAuthenticated === false`
|
||||
3. Redirect to `/login` and save `state={{ from: '/dashboard' }}`
|
||||
4. After successful login, redirect to `/dashboard`
|
||||
|
||||
---
|
||||
|
||||
## 2. AdminRoute
|
||||
|
||||
### Mục đích
|
||||
Bảo vệ các route chỉ dành cho Admin (role-based access).
|
||||
### Purpose
|
||||
Protects routes for Admin only (role-based access).
|
||||
|
||||
### Cách hoạt động
|
||||
### How It Works
|
||||
```tsx
|
||||
// File: client/src/components/auth/AdminRoute.tsx
|
||||
|
||||
@@ -100,12 +100,12 @@ const AdminRoute: React.FC<AdminRouteProps> = ({
|
||||
const location = useLocation();
|
||||
const { isAuthenticated, userInfo, isLoading } = useAuthStore();
|
||||
|
||||
// 1. Nếu đang loading → hiển thị spinner
|
||||
// 1. If loading → display spinner
|
||||
if (isLoading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
// 2. Nếu chưa đăng nhập → redirect /login
|
||||
// 2. If not logged in → redirect /login
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Navigate
|
||||
@@ -116,22 +116,22 @@ const AdminRoute: React.FC<AdminRouteProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Nếu không phải admin → redirect /
|
||||
// 3. If not admin → redirect /
|
||||
const isAdmin = userInfo?.role === 'admin';
|
||||
if (!isAdmin) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
// 4. Là admin → cho phép truy cập
|
||||
// 4. Is admin → allow access
|
||||
return <>{children}</>;
|
||||
};
|
||||
```
|
||||
|
||||
### Sử dụng trong App.tsx
|
||||
### Usage in App.tsx
|
||||
```tsx
|
||||
import { AdminRoute } from './components/auth';
|
||||
|
||||
// Route chỉ dành cho Admin
|
||||
// Route for Admin only
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
@@ -148,50 +148,50 @@ import { AdminRoute } from './components/auth';
|
||||
</Route>
|
||||
```
|
||||
|
||||
### Luồng hoạt động
|
||||
### Flow
|
||||
|
||||
#### Case 1: User chưa đăng nhập
|
||||
1. Truy cập `/admin`
|
||||
2. AdminRoute kiểm tra `isAuthenticated === false`
|
||||
3. Redirect về `/login` với `state={{ from: '/admin' }}`
|
||||
4. Sau login thành công → quay lại `/admin`
|
||||
5. AdminRoute kiểm tra lại role
|
||||
#### Case 1: User not logged in
|
||||
1. Access `/admin`
|
||||
2. AdminRoute checks `isAuthenticated === false`
|
||||
3. Redirect to `/login` with `state={{ from: '/admin' }}`
|
||||
4. After successful login → return to `/admin`
|
||||
5. AdminRoute checks role again
|
||||
|
||||
#### Case 2: User đã đăng nhập nhưng không phải Admin
|
||||
1. Customer (role='customer') truy cập `/admin`
|
||||
2. AdminRoute kiểm tra `isAuthenticated === true`
|
||||
3. AdminRoute kiểm tra `userInfo.role === 'customer'` (không phải 'admin')
|
||||
4. Redirect về `/` (trang chủ)
|
||||
#### Case 2: User logged in but not Admin
|
||||
1. Customer (role='customer') accesses `/admin`
|
||||
2. AdminRoute checks `isAuthenticated === true`
|
||||
3. AdminRoute checks `userInfo.role === 'customer'` (not 'admin')
|
||||
4. Redirect to `/` (homepage)
|
||||
|
||||
#### Case 3: User là Admin
|
||||
1. Admin (role='admin') truy cập `/admin`
|
||||
2. AdminRoute kiểm tra `isAuthenticated === true`
|
||||
3. AdminRoute kiểm tra `userInfo.role === 'admin'` ✅
|
||||
4. Cho phép truy cập
|
||||
#### Case 3: User is Admin
|
||||
1. Admin (role='admin') accesses `/admin`
|
||||
2. AdminRoute checks `isAuthenticated === true`
|
||||
3. AdminRoute checks `userInfo.role === 'admin'` ✅
|
||||
4. Allow access
|
||||
|
||||
---
|
||||
|
||||
## 3. Cấu trúc Route trong App.tsx
|
||||
## 3. Route Structure in App.tsx
|
||||
|
||||
```tsx
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{/* Public Routes - Không cần đăng nhập */}
|
||||
{/* Public Routes - No login required */}
|
||||
<Route path="/" element={<LayoutMain />}>
|
||||
<Route index element={<HomePage />} />
|
||||
<Route path="rooms" element={<RoomListPage />} />
|
||||
<Route path="about" element={<AboutPage />} />
|
||||
</Route>
|
||||
|
||||
{/* Auth Routes - Không cần layout */}
|
||||
{/* Auth Routes - No layout */}
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
||||
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
|
||||
|
||||
{/* Protected Routes - Yêu cầu đăng nhập */}
|
||||
{/* Protected Routes - Login required */}
|
||||
<Route path="/" element={<LayoutMain />}>
|
||||
<Route
|
||||
path="dashboard"
|
||||
@@ -219,7 +219,7 @@ function App() {
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{/* Admin Routes - Chỉ Admin */}
|
||||
{/* Admin Routes - Admin only */}
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
@@ -251,7 +251,7 @@ function App() {
|
||||
|
||||
---
|
||||
|
||||
## 4. Tích hợp với Zustand Store
|
||||
## 4. Integration with Zustand Store
|
||||
|
||||
### useAuthStore State
|
||||
```tsx
|
||||
@@ -286,17 +286,17 @@ const useAuthStore = create<AuthStore>((set) => ({
|
||||
```
|
||||
|
||||
### User Roles
|
||||
- **admin**: Quản trị viên (full access)
|
||||
- **staff**: Nhân viên (limited access)
|
||||
- **customer**: Khách hàng (customer features only)
|
||||
- **admin**: Administrator (full access)
|
||||
- **staff**: Staff (limited access)
|
||||
- **customer**: Customer (customer features only)
|
||||
|
||||
---
|
||||
|
||||
## 5. Loading State
|
||||
|
||||
Cả 2 component đều xử lý loading state để tránh:
|
||||
- Flash of redirect (nhấp nháy khi chuyển trang)
|
||||
- Race condition (auth state chưa load xong)
|
||||
Both components handle loading state to avoid:
|
||||
- Flash of redirect (flickering when changing pages)
|
||||
- Race condition (auth state not loaded yet)
|
||||
|
||||
```tsx
|
||||
if (isLoading) {
|
||||
@@ -306,7 +306,7 @@ if (isLoading) {
|
||||
<div className="animate-spin rounded-full h-12 w-12
|
||||
border-b-2 border-indigo-600 mx-auto"
|
||||
/>
|
||||
<p className="mt-4 text-gray-600">Đang xác thực...</p>
|
||||
<p className="mt-4 text-gray-600">Authenticating...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -330,12 +330,12 @@ const LoginPage: React.FC = () => {
|
||||
try {
|
||||
await login(data);
|
||||
|
||||
// Redirect về page ban đầu hoặc /dashboard
|
||||
// Redirect to original page or /dashboard
|
||||
navigate(from, { replace: true });
|
||||
|
||||
toast.success('Đăng nhập thành công!');
|
||||
toast.success('Login successful!');
|
||||
} catch (error) {
|
||||
toast.error('Đăng nhập thất bại!');
|
||||
toast.error('Login failed!');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -348,58 +348,58 @@ const LoginPage: React.FC = () => {
|
||||
```
|
||||
|
||||
### Flow
|
||||
1. User truy cập `/bookings` (protected)
|
||||
1. User accesses `/bookings` (protected)
|
||||
2. Redirect `/login?from=/bookings`
|
||||
3. Login thành công
|
||||
4. Redirect về `/bookings` (page ban đầu)
|
||||
3. Login successful
|
||||
4. Redirect to `/bookings` (original page)
|
||||
|
||||
---
|
||||
|
||||
## 7. Testing Route Protection
|
||||
|
||||
### Test Case 1: ProtectedRoute - Unauthenticated
|
||||
**Given**: User chưa đăng nhập
|
||||
**When**: Truy cập `/dashboard`
|
||||
**Then**: Redirect về `/login`
|
||||
**And**: Lưu `from=/dashboard` trong location state
|
||||
**Given**: User not logged in
|
||||
**When**: Access `/dashboard`
|
||||
**Then**: Redirect to `/login`
|
||||
**And**: Save `from=/dashboard` in location state
|
||||
|
||||
### Test Case 2: ProtectedRoute - Authenticated
|
||||
**Given**: User đã đăng nhập
|
||||
**When**: Truy cập `/dashboard`
|
||||
**Then**: Hiển thị DashboardPage thành công
|
||||
**Given**: User logged in
|
||||
**When**: Access `/dashboard`
|
||||
**Then**: Display DashboardPage successfully
|
||||
|
||||
### Test Case 3: AdminRoute - Not Admin
|
||||
**Given**: User có role='customer'
|
||||
**When**: Truy cập `/admin`
|
||||
**Then**: Redirect về `/` (trang chủ)
|
||||
**Given**: User has role='customer'
|
||||
**When**: Access `/admin`
|
||||
**Then**: Redirect to `/` (homepage)
|
||||
|
||||
### Test Case 4: AdminRoute - Is Admin
|
||||
**Given**: User có role='admin'
|
||||
**When**: Truy cập `/admin`
|
||||
**Then**: Hiển thị AdminLayout thành công
|
||||
**Given**: User has role='admin'
|
||||
**When**: Access `/admin`
|
||||
**Then**: Display AdminLayout successfully
|
||||
|
||||
### Test Case 5: Loading State
|
||||
**Given**: Auth đang initialize
|
||||
**Given**: Auth is initializing
|
||||
**When**: isLoading === true
|
||||
**Then**: Hiển thị loading spinner
|
||||
**And**: Không redirect
|
||||
**Then**: Display loading spinner
|
||||
**And**: No redirect
|
||||
|
||||
---
|
||||
|
||||
## 8. Security Best Practices
|
||||
|
||||
### ✅ Đã Implement
|
||||
### ✅ Implemented
|
||||
1. **Client-side protection**: ProtectedRoute & AdminRoute
|
||||
2. **Token persistence**: localStorage
|
||||
3. **Role-based access**: Kiểm tra userInfo.role
|
||||
4. **Location state**: Lưu "from" để redirect về đúng page
|
||||
5. **Loading state**: Tránh flash của redirect
|
||||
6. **Replace navigation**: Không lưu lịch sử redirect
|
||||
3. **Role-based access**: Check userInfo.role
|
||||
4. **Location state**: Save "from" to redirect to correct page
|
||||
5. **Loading state**: Avoid flash of redirect
|
||||
6. **Replace navigation**: Don't save redirect history
|
||||
|
||||
### ⚠️ Lưu Ý
|
||||
- Client-side protection **không đủ** → Phải có backend validation
|
||||
- API endpoints phải kiểm tra JWT + role
|
||||
- Middleware backend: `auth`, `adminOnly`
|
||||
### ⚠️ Note
|
||||
- Client-side protection **is not enough** → Must have backend validation
|
||||
- API endpoints must check JWT + role
|
||||
- Backend middleware: `auth`, `adminOnly`
|
||||
- Never trust client-side role → Always verify on server
|
||||
|
||||
### Backend Middleware Example
|
||||
@@ -440,45 +440,45 @@ router.get('/admin/users', auth, adminOnly, getUsers);
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
### Vấn đề 1: Infinite redirect loop
|
||||
**Nguyên nhân**: ProtectedRoute check sai logic
|
||||
**Giải pháp**: Đảm bảo `replace={true}` trong Navigate
|
||||
### Issue 1: Infinite redirect loop
|
||||
**Cause**: ProtectedRoute check logic error
|
||||
**Solution**: Ensure `replace={true}` in Navigate
|
||||
|
||||
### Vấn đề 2: Flash of redirect
|
||||
**Nguyên nhân**: Không handle loading state
|
||||
**Giải pháp**: Thêm check `if (isLoading)` trước check auth
|
||||
### Issue 2: Flash of redirect
|
||||
**Cause**: Not handling loading state
|
||||
**Solution**: Add check `if (isLoading)` before auth check
|
||||
|
||||
### Vấn đề 3: Lost location state
|
||||
**Nguyên nhân**: Không pass `state={{ from: location }}`
|
||||
**Giải pháp**: Luôn lưu location khi redirect
|
||||
### Issue 3: Lost location state
|
||||
**Cause**: Not passing `state={{ from: location }}`
|
||||
**Solution**: Always save location when redirecting
|
||||
|
||||
### Vấn đề 4: Admin có thể truy cập nhưng API fail
|
||||
**Nguyên nhân**: Backend không verify role
|
||||
**Giải pháp**: Thêm middleware `adminOnly` trên API routes
|
||||
### Issue 4: Admin can access but API fails
|
||||
**Cause**: Backend doesn't verify role
|
||||
**Solution**: Add `adminOnly` middleware on API routes
|
||||
|
||||
---
|
||||
|
||||
## 10. Summary
|
||||
|
||||
### ProtectedRoute
|
||||
- ✅ Kiểm tra `isAuthenticated`
|
||||
- ✅ Redirect `/login` nếu chưa đăng nhập
|
||||
- ✅ Lưu location state để quay lại
|
||||
- ✅ Check `isAuthenticated`
|
||||
- ✅ Redirect `/login` if not logged in
|
||||
- ✅ Save location state to return
|
||||
- ✅ Handle loading state
|
||||
|
||||
### AdminRoute
|
||||
- ✅ Kiểm tra `isAuthenticated` trước
|
||||
- ✅ Kiểm tra `userInfo.role === 'admin'`
|
||||
- ✅ Redirect `/login` nếu chưa đăng nhập
|
||||
- ✅ Redirect `/` nếu không phải admin
|
||||
- ✅ Check `isAuthenticated` first
|
||||
- ✅ Check `userInfo.role === 'admin'`
|
||||
- ✅ Redirect `/login` if not logged in
|
||||
- ✅ Redirect `/` if not admin
|
||||
- ✅ Handle loading state
|
||||
|
||||
### Kết quả
|
||||
- Bảo vệ toàn bộ protected routes
|
||||
- UX mượt mà, không flash
|
||||
- Role-based access hoạt động chính xác
|
||||
- Security tốt (kết hợp backend validation)
|
||||
### Results
|
||||
- Protect all protected routes
|
||||
- Smooth UX, no flash
|
||||
- Role-based access works correctly
|
||||
- Good security (combined with backend validation)
|
||||
|
||||
---
|
||||
|
||||
**Chức năng 8 hoàn thành! ✅**
|
||||
**Function 8 completed! ✅**
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
## 📦 Files Created
|
||||
|
||||
### Core Server Files
|
||||
1. **`.env`** - Environment configuration (với mật khẩu và secrets)
|
||||
2. **`src/server.js`** - Server entry point với database connection
|
||||
3. **`src/app.js`** - Express application setup với middleware
|
||||
1. **`.env`** - Environment configuration (with passwords and secrets)
|
||||
2. **`src/server.js`** - Server entry point with database connection
|
||||
3. **`src/app.js`** - Express application setup with middleware
|
||||
|
||||
### Controllers
|
||||
4. **`src/controllers/authController.js`** - Authentication logic
|
||||
@@ -230,11 +230,11 @@ CLIENT_URL=http://localhost:5173
|
||||
|
||||
### 1. Database Setup
|
||||
```bash
|
||||
# Tạo database
|
||||
# Create database
|
||||
mysql -u root -p
|
||||
CREATE DATABASE hotel_db;
|
||||
|
||||
# Chạy migrations
|
||||
# Run migrations
|
||||
cd d:/hotel-booking/server
|
||||
npm run migrate
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Test Scenarios - Route Protection (Chức năng 8)
|
||||
# Test Scenarios - Route Protection (Function 8)
|
||||
|
||||
## Test Setup
|
||||
|
||||
@@ -205,7 +205,7 @@ Verify that loading state is displayed during auth check.
|
||||
|
||||
### Expected Result
|
||||
- ✅ Loading spinner displayed
|
||||
- ✅ Text "Đang tải..." or "Đang xác thực..." visible
|
||||
- ✅ Text "Loading..." or "Authenticating..." visible
|
||||
- ✅ No flash of redirect
|
||||
- ✅ Smooth transition after loading
|
||||
|
||||
@@ -336,7 +336,7 @@ Verify that logout clears auth and redirects properly.
|
||||
- ✅ Token removed from localStorage
|
||||
- ✅ userInfo removed from localStorage
|
||||
- ✅ Redirected to `/` or `/login`
|
||||
- ✅ Navbar shows "Đăng nhập" button
|
||||
- ✅ Navbar shows "Login" button
|
||||
- ✅ Cannot access protected routes anymore
|
||||
|
||||
### Actual Result
|
||||
@@ -430,7 +430,7 @@ Verify that non-existent routes show 404 page.
|
||||
|
||||
### Expected Result
|
||||
- ✅ 404 page displayed
|
||||
- ✅ "404 - Không tìm thấy trang" message
|
||||
- ✅ "404 - Page not found" message
|
||||
- ✅ URL shows `/non-existent-route`
|
||||
- ✅ No errors in console
|
||||
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
# 🏨 Hotel Management & Booking System
|
||||
## Bản Phân Tích Dành Cho Admin (SRS Admin Analysis)
|
||||
## Admin Analysis Document (SRS Admin Analysis)
|
||||
|
||||
---
|
||||
|
||||
## 1. Giới thiệu
|
||||
Tài liệu này phân tích các yêu cầu từ SRS của hệ thống **Hotel Management & Booking Online (e-Hotel)**, tập trung hoàn toàn vào phần **Admin / Manager / Staff** (không bao gồm khách hàng).
|
||||
Mục tiêu là nắm rõ các chức năng quản trị, vận hành và bảo mật của hệ thống.
|
||||
## 1. Introduction
|
||||
This document analyzes the requirements from the SRS of the **Hotel Management & Booking Online (e-Hotel)** system, focusing entirely on the **Admin / Manager / Staff** section (excluding customers).
|
||||
The goal is to understand the administration, operation, and security functions of the system.
|
||||
|
||||
---
|
||||
|
||||
# 2. Phân tích chức năng dành cho Admin
|
||||
# 2. Admin Functionality Analysis
|
||||
|
||||
---
|
||||
|
||||
## 2.1 Setup Module (Thiết lập hệ thống)
|
||||
## 2.1 Setup Module (System Setup)
|
||||
|
||||
### 2.1.1 Setup Rooms (Quản lý phòng)
|
||||
**Vai trò sử dụng:** Manager, Admin
|
||||
### 2.1.1 Setup Rooms (Room Management)
|
||||
**User Roles:** Manager, Admin
|
||||
|
||||
**Các chức năng:**
|
||||
- Thêm mới phòng
|
||||
- Chỉnh sửa thông tin phòng
|
||||
- Xoá phòng *(chỉ khi phòng chưa có booking)*
|
||||
- Upload hình ảnh phòng
|
||||
**Functions:**
|
||||
- Add new room
|
||||
- Edit room information
|
||||
- Delete room *(only when room has no bookings)*
|
||||
- Upload room images
|
||||
|
||||
**Thông tin phòng gồm:**
|
||||
**Room Information:**
|
||||
- RoomID
|
||||
- Description
|
||||
- Type (VIP, DELUX, SUITE, …)
|
||||
@@ -32,114 +32,114 @@ Mục tiêu là nắm rõ các chức năng quản trị, vận hành và bảo
|
||||
- Price
|
||||
- Pictures
|
||||
|
||||
**Quy tắc:**
|
||||
- Validate toàn bộ dữ liệu khi thêm/sửa
|
||||
- Không cho xoá phòng đã phát sinh booking
|
||||
**Rules:**
|
||||
- Validate all data when adding/editing
|
||||
- Do not allow deletion of rooms that have bookings
|
||||
|
||||
---
|
||||
|
||||
### 2.1.2 Setup Services (Quản lý dịch vụ)
|
||||
**Vai trò:** Manager, Admin
|
||||
### 2.1.2 Setup Services (Service Management)
|
||||
**Roles:** Manager, Admin
|
||||
|
||||
**Chức năng:**
|
||||
- Thêm dịch vụ
|
||||
- Chỉnh sửa
|
||||
- Xoá dịch vụ
|
||||
**Functions:**
|
||||
- Add service
|
||||
- Edit
|
||||
- Delete service
|
||||
|
||||
**Thông tin dịch vụ:**
|
||||
**Service Information:**
|
||||
- Service ID
|
||||
- Service Name
|
||||
- Description
|
||||
- Unit (giờ, suất, lần,…)
|
||||
- Unit (hour, portion, time, …)
|
||||
- Price
|
||||
|
||||
**Quy tắc:**
|
||||
- Validate tất cả dữ liệu nhập
|
||||
**Rules:**
|
||||
- Validate all input data
|
||||
|
||||
---
|
||||
|
||||
### 2.1.3 Promotion Management (Quản lý khuyến mãi)
|
||||
**Vai trò:** Manager, Admin
|
||||
### 2.1.3 Promotion Management
|
||||
**Roles:** Manager, Admin
|
||||
|
||||
**Chức năng:**
|
||||
**Functions:**
|
||||
- Add promotion
|
||||
- Edit promotion
|
||||
- Delete promotion
|
||||
- Promotion có thể áp dụng bằng code hoặc tự động trong booking
|
||||
- Promotion can be applied by code or automatically in booking
|
||||
|
||||
**Thông tin:**
|
||||
**Information:**
|
||||
- ID
|
||||
- Name
|
||||
- Description
|
||||
- Value (phần trăm hoặc số tiền)
|
||||
- Value (percentage or fixed amount)
|
||||
|
||||
---
|
||||
|
||||
# 2.2 Operation Module (Vận hành khách sạn)
|
||||
# 2.2 Operation Module (Hotel Operations)
|
||||
|
||||
---
|
||||
|
||||
## 2.2.1 Booking Management
|
||||
**Vai trò:** Staff, Manager, Admin
|
||||
**Roles:** Staff, Manager, Admin
|
||||
|
||||
**Chức năng:**
|
||||
- Tìm booking theo tên khách, số booking, ngày đặt
|
||||
- Xem chi tiết booking
|
||||
- Xem bill dịch vụ
|
||||
- Xử lý yêu cầu:
|
||||
- Hủy booking
|
||||
**Functions:**
|
||||
- Search booking by guest name, booking number, booking date
|
||||
- View booking details
|
||||
- View service bill
|
||||
- Process requests:
|
||||
- Cancel booking
|
||||
- Checkout
|
||||
|
||||
---
|
||||
|
||||
## 2.2.2 Check-in
|
||||
**Vai trò:** Staff, Manager
|
||||
**Roles:** Staff, Manager
|
||||
|
||||
**Quy trình check-in:**
|
||||
- Khách xuất trình Booking Number
|
||||
- Nhân viên kiểm tra thông tin booking
|
||||
- Nhập thông tin từng khách trong phòng
|
||||
- Gán số phòng thực tế
|
||||
- Thu thêm phí nếu có trẻ em hoặc extra person
|
||||
**Check-in Process:**
|
||||
- Guest presents Booking Number
|
||||
- Staff verifies booking information
|
||||
- Enter information for each guest in the room
|
||||
- Assign actual room number
|
||||
- Collect additional fees if there are children or extra persons
|
||||
|
||||
---
|
||||
|
||||
## 2.2.3 Use Services (Khách đăng ký sử dụng dịch vụ)
|
||||
**Vai trò:** Staff
|
||||
## 2.2.3 Use Services (Guest Service Registration)
|
||||
**Roles:** Staff
|
||||
|
||||
**Chức năng:**
|
||||
- Đăng ký dịch vụ cho khách dựa trên Room Number
|
||||
- In ticket nếu có yêu cầu
|
||||
**Functions:**
|
||||
- Register services for guests based on Room Number
|
||||
- Print ticket if requested
|
||||
|
||||
---
|
||||
|
||||
## 2.2.4 Check-out
|
||||
**Vai trò:** Staff, Manager
|
||||
**Roles:** Staff, Manager
|
||||
|
||||
**Chức năng:**
|
||||
- Tính toán:
|
||||
- Phí phòng
|
||||
- Phí dịch vụ
|
||||
- Phụ phí khác
|
||||
- Tạo hóa đơn (Invoice)
|
||||
- Khấu trừ tiền đã đặt cọc (booking value)
|
||||
- Khách thanh toán phần còn lại
|
||||
**Functions:**
|
||||
- Calculate:
|
||||
- Room fee
|
||||
- Service fee
|
||||
- Other surcharges
|
||||
- Create invoice
|
||||
- Deduct deposit amount (booking value)
|
||||
- Guest pays remaining amount
|
||||
|
||||
---
|
||||
|
||||
# 2.3 Report Module (Báo cáo)
|
||||
# 2.3 Report Module
|
||||
|
||||
**Vai trò:** Manager, Admin
|
||||
**Roles:** Manager, Admin
|
||||
|
||||
**Chức năng:**
|
||||
- Nhập khoảng thời gian From → To
|
||||
- Liệt kê toàn bộ booking trong khoảng thời gian
|
||||
- Tính tổng doanh thu
|
||||
- Xuất báo cáo:
|
||||
**Functions:**
|
||||
- Enter time range From → To
|
||||
- List all bookings within the time range
|
||||
- Calculate total revenue
|
||||
- Export reports:
|
||||
- Excel
|
||||
- PDF
|
||||
|
||||
**Nội dung báo cáo:**
|
||||
**Report Content:**
|
||||
- Booking ID
|
||||
- Customer Name
|
||||
- Room
|
||||
@@ -150,58 +150,58 @@ Mục tiêu là nắm rõ các chức năng quản trị, vận hành và bảo
|
||||
|
||||
---
|
||||
|
||||
# 2.4 System Administration Module (Quản trị hệ thống)
|
||||
# 2.4 System Administration Module
|
||||
|
||||
---
|
||||
|
||||
## 2.4.1 User Management
|
||||
**Vai trò:** Admin
|
||||
**Roles:** Admin
|
||||
|
||||
**Chức năng:**
|
||||
**Functions:**
|
||||
- Add user
|
||||
- Edit user
|
||||
- Delete user
|
||||
- View user detail
|
||||
- List tất cả user
|
||||
- Gán role (Admin, Manager, Staff)
|
||||
- List all users
|
||||
- Assign role (Admin, Manager, Staff)
|
||||
|
||||
---
|
||||
|
||||
## 2.4.2 Security
|
||||
**Chức năng bảo mật của hệ thống:**
|
||||
**System Security Functions:**
|
||||
|
||||
### Roles được định nghĩa:
|
||||
| Role | Quyền |
|
||||
### Defined Roles:
|
||||
| Role | Permissions |
|
||||
|------|-------|
|
||||
| **Customer** | Không cần login |
|
||||
| **Staff (Sale)** | Truy cập Operation Module |
|
||||
| **Manager** | Truy cập Setup Module |
|
||||
| **Admin** | Toàn quyền, bao gồm User & Security |
|
||||
| **Customer** | No login required |
|
||||
| **Staff (Sale)** | Access Operation Module |
|
||||
| **Manager** | Access Setup Module |
|
||||
| **Admin** | Full access, including User & Security |
|
||||
|
||||
### Quy tắc bảo mật:
|
||||
- Nhân viên & admin bắt buộc phải login
|
||||
- Quyền thao tác phụ thuộc vào role
|
||||
- Session timeout sau 30 phút không hoạt động
|
||||
### Security Rules:
|
||||
- Staff & admin must login
|
||||
- Operation permissions depend on role
|
||||
- Session timeout after 30 minutes of inactivity
|
||||
|
||||
---
|
||||
|
||||
# 3. Tóm tắt theo góc nhìn Admin
|
||||
# 3. Summary from Admin Perspective
|
||||
|
||||
| Module | Quyền Admin | Nội dung |
|
||||
| Module | Admin Permissions | Content |
|
||||
|--------|-------------|----------|
|
||||
| Room Setup | Full | CRUD phòng |
|
||||
| Service Setup | Full | CRUD dịch vụ |
|
||||
| Promotion Setup | Full | CRUD khuyến mãi |
|
||||
| Booking Management | Full | Xem, duyệt, hủy booking |
|
||||
| Check-in / Check-out | Full | Quản lý vận hành |
|
||||
| Service Usage | Full | Ghi log dịch vụ |
|
||||
| Reports | Full | Thống kê, xuất file |
|
||||
| User Management | Full | Quản lý nhân viên |
|
||||
| Security | Full | Role, phân quyền |
|
||||
| Room Setup | Full | CRUD rooms |
|
||||
| Service Setup | Full | CRUD services |
|
||||
| Promotion Setup | Full | CRUD promotions |
|
||||
| Booking Management | Full | View, approve, cancel bookings |
|
||||
| Check-in / Check-out | Full | Operations management |
|
||||
| Service Usage | Full | Service logging |
|
||||
| Reports | Full | Statistics, export files |
|
||||
| User Management | Full | Staff management |
|
||||
| Security | Full | Roles, permissions |
|
||||
|
||||
---
|
||||
|
||||
# 4. Kết luận
|
||||
Phân tích trên giúp xác định đầy đủ các chức năng cần triển khai cho **Admin / Manager / Staff** trong hệ thống quản lý khách sạn.
|
||||
Tài liệu có thể được sử dụng để xây dựng database, API, UI/UX, và phân quyền hệ thống.
|
||||
# 4. Conclusion
|
||||
The above analysis helps identify all the functions that need to be implemented for **Admin / Manager / Staff** in the hotel management system.
|
||||
This document can be used to build the database, API, UI/UX, and system permissions.
|
||||
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Authentication
|
||||
|
||||
## Chức năng 1: Layout cơ bản (Header, Footer, Navbar, SidebarAdmin)
|
||||
## Function 1: Basic Layout (Header, Footer, Navbar, SidebarAdmin)
|
||||
|
||||
### Mục tiêu
|
||||
Tạo layout nền tảng cho toàn bộ hệ thống và cấu trúc render nội dung theo route.
|
||||
### Objective
|
||||
Create a foundational layout for the entire system and structure content rendering by route.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo thư mục:
|
||||
#### Detailed Tasks
|
||||
- Create directory:
|
||||
```
|
||||
src/components/layouts/
|
||||
```
|
||||
- Bao gồm:
|
||||
- Include:
|
||||
+ Header.jsx
|
||||
+ Footer.jsx
|
||||
+ Navbar.jsx
|
||||
+ SidebarAdmin.jsx
|
||||
+ LayoutMain.jsx
|
||||
- Dùng <Outlet /> trong LayoutMain để render nội dung động.
|
||||
- Navbar thay đổi tùy trạng thái đăng nhập:
|
||||
+ Nếu chưa login → hiển thị nút “Đăng nhập / Đăng ký”.
|
||||
+ Nếu đã login → hiển thị avatar, tên user và nút “Đăng xuất”.
|
||||
- SidebarAdmin chỉ hiển thị với role = admin.
|
||||
- Use <Outlet /> in LayoutMain to render dynamic content.
|
||||
- Navbar changes based on login status:
|
||||
+ If not logged in → display "Login / Register" button.
|
||||
+ If logged in → display avatar, user name and "Logout" button.
|
||||
- SidebarAdmin only displays with role = admin.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Layout tổng thể hiển thị ổn định.
|
||||
2. Navbar hiển thị nội dung động theo trạng thái người dùng.
|
||||
3. Giao diện responsive, tương thích desktop/mobile.
|
||||
### Expected Results
|
||||
1. Overall layout displays stably.
|
||||
2. Navbar displays dynamic content based on user status.
|
||||
3. Responsive interface, compatible with desktop/mobile.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 2: Cấu hình Routing (react-router-dom)
|
||||
## Function 2: Routing Configuration (react-router-dom)
|
||||
|
||||
### Mục tiêu
|
||||
Thiết lập hệ thống định tuyến chuẩn, có bảo vệ route theo role.
|
||||
### Objective
|
||||
Set up a standard routing system with role-based route protection.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Cấu trúc route chính:
|
||||
#### Detailed Tasks
|
||||
- Main route structure:
|
||||
```
|
||||
<Route path="/" element={<LayoutMain />}>
|
||||
<Route index element={<HomePage />} />
|
||||
@@ -51,25 +51,25 @@
|
||||
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
|
||||
<Route path="/admin/*" element={<AdminRoute><AdminModule /></AdminRoute>} />
|
||||
```
|
||||
- Dùng ProtectedRoute và AdminRoute để kiểm tra:
|
||||
- Use ProtectedRoute and AdminRoute to check:
|
||||
+ isAuthenticated
|
||||
+ role === "admin"
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Người dùng không đăng nhập bị redirect về /login.
|
||||
2. AdminRoute chỉ cho phép admin truy cập.
|
||||
3. Tất cả route hoạt động mượt, không lỗi vòng lặp redirect.
|
||||
### Expected Results
|
||||
1. Unauthenticated users are redirected to /login.
|
||||
2. AdminRoute only allows admin access.
|
||||
3. All routes work smoothly, no redirect loop errors.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 3: useAuthStore (Zustand Store)
|
||||
## Function 3: useAuthStore (Zustand Store)
|
||||
|
||||
### Mục tiêu
|
||||
Quản lý trạng thái xác thực toàn cục (token, userInfo, role).
|
||||
### Objective
|
||||
Manage global authentication state (token, userInfo, role).
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo src/stores/useAuthStore.js
|
||||
- Cấu trúc:
|
||||
#### Detailed Tasks
|
||||
- Create src/stores/useAuthStore.js
|
||||
- Structure:
|
||||
```
|
||||
const useAuthStore = create((set) => ({
|
||||
token: localStorage.getItem("token") || null,
|
||||
@@ -82,132 +82,132 @@
|
||||
resetPassword: async (payload) => { ... },
|
||||
}));
|
||||
```
|
||||
- Khi đăng nhập thành công:
|
||||
+ Lưu token + userInfo vào localStorage.
|
||||
- Khi logout:
|
||||
+ Xóa localStorage và reset state.
|
||||
- When login succeeds:
|
||||
+ Save token + userInfo to localStorage.
|
||||
- When logout:
|
||||
+ Clear localStorage and reset state.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Toàn bộ thông tin user được quản lý tập trung.
|
||||
2. Duy trì đăng nhập sau khi reload trang.
|
||||
3. Dễ dàng truy cập userInfo trong mọi component.
|
||||
### Expected Results
|
||||
1. All user information is managed centrally.
|
||||
2. Maintain login after page reload.
|
||||
3. Easy access to userInfo in any component.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 4: Form Login
|
||||
## Function 4: Login Form
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng đăng nhập hệ thống.
|
||||
### Objective
|
||||
Allow users to log into the system.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo LoginPage.jsx
|
||||
- Dùng React Hook Form + Yup validate:
|
||||
+ Email hợp lệ
|
||||
+ Mật khẩu ≥ 8 ký tự
|
||||
#### Detailed Tasks
|
||||
- Create LoginPage.jsx
|
||||
- Use React Hook Form + Yup validation:
|
||||
+ Valid email
|
||||
+ Password ≥ 8 characters
|
||||
- API:
|
||||
```
|
||||
POST /api/auth/login
|
||||
```
|
||||
- Sau khi đăng nhập thành công:
|
||||
+ Lưu token vào localStorage.
|
||||
+ Gọi setUser() để cập nhật Zustand.
|
||||
+ Redirect về /dashboard.
|
||||
+ Gửi email POST /api/notify/login-success.
|
||||
- UX nâng cao:
|
||||
+ Nút loading khi đang gửi form.
|
||||
+ “Hiện/Ẩn mật khẩu”.
|
||||
+ “Nhớ đăng nhập” → lưu 7 ngày.
|
||||
- After successful login:
|
||||
+ Save token to localStorage.
|
||||
+ Call setUser() to update Zustand.
|
||||
+ Redirect to /dashboard.
|
||||
+ Send email POST /api/notify/login-success.
|
||||
- Enhanced UX:
|
||||
+ Loading button when submitting form.
|
||||
+ "Show/Hide password".
|
||||
+ "Remember me" → save for 7 days.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Đăng nhập hoạt động mượt, hiển thị thông báo lỗi rõ ràng.
|
||||
2. Email được gửi khi login thành công.
|
||||
3. Chuyển hướng đúng theo vai trò user.
|
||||
### Expected Results
|
||||
1. Login works smoothly, displays clear error messages.
|
||||
2. Email is sent when login succeeds.
|
||||
3. Redirect correctly based on user role.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 5: Form Register
|
||||
## Function 5: Register Form
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng đăng ký tài khoản mới.
|
||||
### Objective
|
||||
Allow users to register a new account.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo RegisterPage.jsx
|
||||
- Dùng React Hook Form + Yup validate:
|
||||
+ Họ tên không rỗng
|
||||
+ Email hợp lệ
|
||||
+ Mật khẩu ≥ 8 ký tự, có ký tự đặc biệt
|
||||
#### Detailed Tasks
|
||||
- Create RegisterPage.jsx
|
||||
- Use React Hook Form + Yup validation:
|
||||
+ Full name not empty
|
||||
+ Valid email
|
||||
+ Password ≥ 8 characters, contains special characters
|
||||
- API:
|
||||
```
|
||||
POST /api/auth/register
|
||||
```
|
||||
- Sau khi đăng ký thành công:
|
||||
+ Hiển thị toast “Đăng ký thành công, vui lòng đăng nhập”.
|
||||
+ Redirect về /login.
|
||||
- After successful registration:
|
||||
+ Display toast "Registration successful, please login".
|
||||
+ Redirect to /login.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Người dùng tạo tài khoản mới thành công.
|
||||
2. Validate chặt chẽ, UX mượt mà.
|
||||
3. Giao diện thống nhất với form login.
|
||||
### Expected Results
|
||||
1. Users can create new accounts successfully.
|
||||
2. Strict validation, smooth UX.
|
||||
3. Interface consistent with login form.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 6: Quên mật khẩu (Forgot Password)
|
||||
## Function 6: Forgot Password
|
||||
|
||||
### Mục tiêu
|
||||
Cung cấp chức năng gửi email reset mật khẩu.
|
||||
### Objective
|
||||
Provide functionality to send password reset email.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo ForgotPasswordPage.jsx
|
||||
#### Detailed Tasks
|
||||
- Create ForgotPasswordPage.jsx
|
||||
- API:
|
||||
```
|
||||
POST /api/auth/forgot-password
|
||||
```
|
||||
- Sau khi gửi thành công:
|
||||
+ Hiển thị thông báo “Vui lòng kiểm tra email để đặt lại mật khẩu.”
|
||||
+ Backend gửi link reset có token dạng:
|
||||
- After successful send:
|
||||
+ Display message "Please check your email to reset password."
|
||||
+ Backend sends reset link with token:
|
||||
```
|
||||
https://domain.com/reset-password/:token
|
||||
```
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Gửi email thành công.
|
||||
2. UX rõ ràng, có loading và thông báo lỗi.
|
||||
3. Giao diện thân thiện.
|
||||
### Expected Results
|
||||
1. Email sent successfully.
|
||||
2. Clear UX, with loading and error messages.
|
||||
3. User-friendly interface.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 7: Đặt lại mật khẩu (Reset Password)
|
||||
## Function 7: Reset Password
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng đổi mật khẩu thông qua link email.
|
||||
### Objective
|
||||
Allow users to change password through email link.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo ResetPasswordPage.jsx
|
||||
- Validate:
|
||||
+ Mật khẩu mới ≥ 8 ký tự, chứa ký tự đặc biệt
|
||||
+ Nhập lại mật khẩu trùng khớp
|
||||
#### Detailed Tasks
|
||||
- Create ResetPasswordPage.jsx
|
||||
- Validation:
|
||||
+ New password ≥ 8 characters, contains special characters
|
||||
+ Confirm password matches
|
||||
- API:
|
||||
```
|
||||
POST /api/auth/reset-password
|
||||
```
|
||||
- Sau khi đổi mật khẩu thành công:
|
||||
+ Gửi email xác nhận POST /api/notify/reset-success.
|
||||
+ Redirect về /login.
|
||||
- After successful password change:
|
||||
+ Send confirmation email POST /api/notify/reset-success.
|
||||
+ Redirect to /login.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Mật khẩu được cập nhật thành công.
|
||||
2. Gửi email thông báo thành công.
|
||||
3. Bảo vệ token hết hạn (invalid token → redirect về forgot-password).
|
||||
### Expected Results
|
||||
1. Password updated successfully.
|
||||
2. Success notification email sent.
|
||||
3. Protect expired token (invalid token → redirect to forgot-password).
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 8: Phân quyền & Bảo vệ route (ProtectedRoute / AdminRoute)
|
||||
## Function 8: Permissions & Route Protection (ProtectedRoute / AdminRoute)
|
||||
|
||||
### Mục tiêu
|
||||
Chặn truy cập trái phép và bảo vệ các route quan trọng.
|
||||
### Objective
|
||||
Block unauthorized access and protect important routes.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Tạo component ProtectedRoute.jsx:
|
||||
#### Detailed Tasks
|
||||
- Create ProtectedRoute.jsx component:
|
||||
```
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
const { isAuthenticated } = useAuthStore();
|
||||
@@ -216,13 +216,13 @@
|
||||
};
|
||||
```
|
||||
|
||||
- Tạo AdminRoute.jsx:
|
||||
- Create AdminRoute.jsx:
|
||||
```
|
||||
const AdminRoute = ({ children }) => {
|
||||
const { userInfo } = useAuthStore();
|
||||
return userInfo?.role === "admin" ? children : <Navigate to="/" replace />;
|
||||
};
|
||||
```
|
||||
### Kết quả mong đợi
|
||||
1. Chỉ người dùng hợp lệ mới truy cập được route quan trọng.
|
||||
2. AdminRoute đảm bảo bảo mật cho module quản trị.
|
||||
### Expected Results
|
||||
1. Only valid users can access important routes.
|
||||
2. AdminRoute ensures security for admin module.
|
||||
|
||||
@@ -1,182 +1,182 @@
|
||||
# Review System
|
||||
|
||||
## Chức năng 1: HomePage – Trang chủ hiển thị phòng nổi bật
|
||||
## Function 1: HomePage – Homepage displaying featured rooms
|
||||
|
||||
### Mục tiêu
|
||||
Tạo giao diện trang chủ giới thiệu phòng nổi bật, banner và điều hướng đến danh sách phòng.
|
||||
### Objective
|
||||
Create a homepage interface introducing featured rooms, banner and navigation to room list.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /
|
||||
2. Banner:
|
||||
```
|
||||
GET /api/banners?position=home
|
||||
```
|
||||
- Nếu không có banner → hiển thị ảnh mặc định.
|
||||
- Có thể dùng Carousel hoặc ảnh tĩnh.
|
||||
3. Phòng nổi bật:
|
||||
- If no banner → display default image.
|
||||
- Can use Carousel or static image.
|
||||
3. Featured rooms:
|
||||
```
|
||||
GET /api/rooms?featured=true
|
||||
```
|
||||
- Hiển thị 4–6 phòng bằng component RoomCard.
|
||||
- Nút “Xem tất cả phòng” → điều hướng /rooms.
|
||||
4. Loading skeleton trong khi chờ dữ liệu.
|
||||
- Display 4–6 rooms using RoomCard component.
|
||||
- "View all rooms" button → navigate to /rooms.
|
||||
4. Loading skeleton while waiting for data.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Trang chủ hiển thị banner và danh sách phòng nổi bật rõ ràng.
|
||||
2. Khi không có banner → ảnh fallback được hiển thị.
|
||||
3. Phòng nổi bật load từ API, giới hạn 4–6 phòng.
|
||||
4. UX mượt, có skeleton khi load.
|
||||
5. Nút “Xem tất cả phòng” điều hướng chính xác đến /rooms.
|
||||
### Expected Results
|
||||
1. Homepage displays banner and featured room list clearly.
|
||||
2. When no banner → fallback image is displayed.
|
||||
3. Featured rooms load from API, limited to 4–6 rooms.
|
||||
4. Smooth UX, with skeleton when loading.
|
||||
5. "View all rooms" button navigates correctly to /rooms.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 2: RoomListPage – Danh sách & Bộ lọc phòng
|
||||
## Function 2: RoomListPage – Room List & Filters
|
||||
|
||||
### Mục tiêu
|
||||
Hiển thị danh sách phòng, cho phép người dùng lọc theo loại, giá, số người và phân trang.
|
||||
### Objective
|
||||
Display room list, allow users to filter by type, price, number of guests and pagination.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /rooms
|
||||
2. Bộ lọc (component RoomFilter):
|
||||
- Trường lọc: loại phòng, giá min–max, số người.
|
||||
- Khi submit → gọi API:
|
||||
2. Filters (RoomFilter component):
|
||||
- Filter fields: room type, min–max price, number of guests.
|
||||
- On submit → call API:
|
||||
```
|
||||
GET /api/rooms?type=&minPrice=&maxPrice=&capacity=&page=
|
||||
```
|
||||
- Lưu bộ lọc vào URL query.
|
||||
- Nút “Reset” để xóa toàn bộ bộ lọc.
|
||||
3. Phân trang (Pagination component).
|
||||
4. Hiển thị danh sách bằng RoomCard.
|
||||
- Save filters to URL query.
|
||||
- "Reset" button to clear all filters.
|
||||
3. Pagination (Pagination component).
|
||||
4. Display list using RoomCard.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Danh sách phòng hiển thị chính xác theo filter.
|
||||
2. Bộ lọc hoạt động mượt, có thể reset dễ dàng.
|
||||
3. Phân trang hiển thị chính xác số trang.
|
||||
4. Filter được lưu trong URL (giúp reload không mất).
|
||||
5. Giao diện responsive, dễ đọc, không bị vỡ.
|
||||
### Expected Results
|
||||
1. Room list displays accurately according to filter.
|
||||
2. Filters work smoothly, can reset easily.
|
||||
3. Pagination displays correct page numbers.
|
||||
4. Filters saved in URL (helps reload without losing).
|
||||
5. Responsive interface, easy to read, no breakage.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 3: RoomDetailPage – Chi tiết phòng & Đánh giá
|
||||
## Function 3: RoomDetailPage – Room Details & Reviews
|
||||
|
||||
### Mục tiêu
|
||||
Tạo trang chi tiết phòng đầy đủ thông tin, hình ảnh, tiện ích và khu vực đánh giá.
|
||||
### Objective
|
||||
Create a complete room detail page with information, images, amenities and review section.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /rooms/:id
|
||||
2. Phần nội dung:
|
||||
-Thông tin phòng (ảnh, mô tả, giá, tiện ích)
|
||||
- RoomGallery: Carousel ảnh
|
||||
- RoomAmenities: danh sách tiện ích
|
||||
- Nút “Đặt ngay” → điều hướng /booking/:roomId
|
||||
2. Content section:
|
||||
- Room information (images, description, price, amenities)
|
||||
- RoomGallery: Image carousel
|
||||
- RoomAmenities: amenities list
|
||||
- "Book Now" button → navigate to /booking/:roomId
|
||||
3. Review Section:
|
||||
- Lấy danh sách review đã duyệt:
|
||||
- Get approved review list:
|
||||
```
|
||||
GET /api/rooms/:id/reviews
|
||||
```
|
||||
- Nếu người dùng đã từng đặt phòng:
|
||||
- If user has booked the room before:
|
||||
```
|
||||
POST /api/reviews
|
||||
```
|
||||
4. Component RatingStars + ReviewForm.
|
||||
5. Nếu chưa đăng nhập → hiển thị “Vui lòng đăng nhập để đánh giá”.
|
||||
6. Tính trung bình điểm review.
|
||||
7. Loading skeleton khi chờ review.
|
||||
4. RatingStars + ReviewForm component.
|
||||
5. If not logged in → display "Please login to review".
|
||||
6. Calculate average review rating.
|
||||
7. Loading skeleton when waiting for reviews.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Hiển thị đầy đủ ảnh, mô tả, tiện ích phòng.
|
||||
2. Carousel hoạt động mượt mà.
|
||||
3. Review hiển thị đúng, có trung bình số sao.
|
||||
4. Người đã đặt có thể viết review (sau duyệt).
|
||||
5. Nút “Đặt ngay” điều hướng chính xác đến form booking.
|
||||
6. Skeleton hiển thị khi chờ dữ liệu.
|
||||
### Expected Results
|
||||
1. Displays complete images, description, room amenities.
|
||||
2. Carousel works smoothly.
|
||||
3. Reviews display correctly, with average star rating.
|
||||
4. Users who have booked can write reviews (after approval).
|
||||
5. "Book Now" button navigates correctly to booking form.
|
||||
6. Skeleton displays when waiting for data.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 4: SearchRoom – Tìm phòng trống
|
||||
## Function 4: SearchRoom – Find available rooms
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng tìm phòng trống theo ngày và loại phòng.
|
||||
### Objective
|
||||
Allow users to find available rooms by date and room type.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
1. Form tìm kiếm (ở HomePage hoặc RoomListPage):
|
||||
- Input: ngày đến (from), ngày đi (to), loại phòng.
|
||||
#### Detailed Tasks
|
||||
1. Search form (on HomePage or RoomListPage):
|
||||
- Input: arrival date (from), departure date (to), room type.
|
||||
2. API:
|
||||
```
|
||||
GET /api/rooms/available?from=&to=&type=
|
||||
```
|
||||
3. Validate:
|
||||
3. Validation:
|
||||
- from < to
|
||||
- from không nhỏ hơn hôm nay.
|
||||
4. Kết quả:
|
||||
- Hiển thị danh sách bằng RoomCard.
|
||||
- Nếu không có kết quả → “Không tìm thấy phòng phù hợp”.
|
||||
5. Dùng react-datepicker hoặc react-day-picker.
|
||||
6. Loading spinner khi đang tìm.
|
||||
- from not less than today.
|
||||
4. Results:
|
||||
- Display list using RoomCard.
|
||||
- If no results → "No matching rooms found".
|
||||
5. Use react-datepicker or react-day-picker.
|
||||
6. Loading spinner while searching.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Form tìm phòng hoạt động, validate chính xác.
|
||||
2. Khi bấm tìm → hiển thị danh sách phòng trống.
|
||||
3. Nếu không có kết quả → thông báo thân thiện.
|
||||
4. Loading hiển thị rõ trong lúc chờ.
|
||||
5. Tìm theo ngày & loại phòng chính xác từ backend.
|
||||
### Expected Results
|
||||
1. Room search form works, validates correctly.
|
||||
2. When clicking search → displays available room list.
|
||||
3. If no results → friendly message.
|
||||
4. Loading displays clearly while waiting.
|
||||
5. Search by date & room type accurately from backend.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 5: Wishlist – Danh sách yêu thích
|
||||
## Function 5: Wishlist – Favorites list
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng thêm, bỏ hoặc xem danh sách phòng yêu thích.
|
||||
### Objective
|
||||
Allow users to add, remove or view favorite rooms list.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. API:
|
||||
```
|
||||
POST /api/favorites/:roomId # Thêm
|
||||
DELETE /api/favorites/:roomId # Xóa
|
||||
GET /api/favorites # Lấy danh sách yêu thích
|
||||
POST /api/favorites/:roomId # Add
|
||||
DELETE /api/favorites/:roomId # Remove
|
||||
GET /api/favorites # Get favorites list
|
||||
```
|
||||
2. UI:
|
||||
- FavoriteButton (icon ❤️):
|
||||
+ Nếu yêu thích → tô đỏ
|
||||
+ Nếu chưa → viền xám
|
||||
- Tooltip: “Thêm vào yêu thích” / “Bỏ yêu thích”
|
||||
3. Nếu chưa đăng nhập:
|
||||
- Lưu tạm trong localStorage (guestFavorites)
|
||||
- Khi đăng nhập → đồng bộ với server.
|
||||
4. Toast thông báo khi thêm/bỏ yêu thích.
|
||||
+ If favorited → filled red
|
||||
+ If not → gray border
|
||||
- Tooltip: "Add to favorites" / "Remove from favorites"
|
||||
3. If not logged in:
|
||||
- Save temporarily in localStorage (guestFavorites)
|
||||
- When logged in → sync with server.
|
||||
4. Toast notification when adding/removing favorites.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Nút ❤️ hoạt động đúng trạng thái (đỏ / xám).
|
||||
2. Người chưa đăng nhập vẫn có thể lưu tạm yêu thích.
|
||||
3. Khi đăng nhập → danh sách đồng bộ với backend.
|
||||
4. Toast hiển thị “Đã thêm vào yêu thích” / “Đã bỏ yêu thích”.
|
||||
5. API hoạt động đúng, không lỗi 401 khi đăng nhập hợp lệ.
|
||||
### Expected Results
|
||||
1. ❤️ button works correctly (red / gray).
|
||||
2. Unauthenticated users can still save favorites temporarily.
|
||||
3. When logged in → list syncs with backend.
|
||||
4. Toast displays "Added to favorites" / "Removed from favorites".
|
||||
5. API works correctly, no 401 error when logged in validly.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 6: Tối ưu UI/UX & Performance
|
||||
## Function 6: UI/UX & Performance Optimization
|
||||
|
||||
### Mục tiêu
|
||||
Cải thiện trải nghiệm người dùng, tối ưu tốc độ tải và khả năng hiển thị responsive.
|
||||
### Objective
|
||||
Improve user experience, optimize loading speed and responsive display capability.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
1. Loading skeleton khi fetch phòng hoặc review.
|
||||
2. Debounce khi nhập giá để tránh gọi API liên tục.
|
||||
3. Infinite scroll (tùy chọn) thay cho pagination.
|
||||
#### Detailed Tasks
|
||||
1. Loading skeleton when fetching rooms or reviews.
|
||||
2. Debounce when entering price to avoid continuous API calls.
|
||||
3. Infinite scroll (optional) instead of pagination.
|
||||
4. Responsive layout:
|
||||
- Desktop: 3–4 cột
|
||||
- Tablet: 2 cột
|
||||
- Mobile: 1 cột
|
||||
- Desktop: 3–4 columns
|
||||
- Tablet: 2 columns
|
||||
- Mobile: 1 column
|
||||
5. Empty states:
|
||||
- Không có phòng → hiển thị ảnh minh họa + dòng “Không tìm thấy phòng phù hợp”.
|
||||
- Không có review → “Hãy là người đầu tiên đánh giá!”.
|
||||
6. Toast thông báo khi thêm yêu thích, gửi review, lỗi mạng.
|
||||
- No rooms → display illustration + "No matching rooms found" message.
|
||||
- No reviews → "Be the first to review!".
|
||||
6. Toast notifications when adding favorites, submitting reviews, network errors.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Trang hoạt động mượt, có skeleton khi chờ dữ liệu.
|
||||
2. Tốc độ phản hồi nhanh (debounce hoạt động).
|
||||
3. Responsive trên mọi kích thước màn hình.
|
||||
4. Các empty state hiển thị thân thiện.
|
||||
5. Toast thông báo rõ ràng, UX thân thiện.
|
||||
### Expected Results
|
||||
1. Page works smoothly, has skeleton when waiting for data.
|
||||
2. Fast response speed (debounce works).
|
||||
3. Responsive on all screen sizes.
|
||||
4. Empty states display friendly.
|
||||
5. Toast notifications clear, friendly UX.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,144 +1,144 @@
|
||||
# Booking & Payment
|
||||
|
||||
## Chức năng 1: BookingPage – Form Đặt phòng
|
||||
## Function 1: BookingPage – Booking Form
|
||||
|
||||
### Mục tiêu
|
||||
Xây dựng form đặt phòng đầy đủ thông tin, xác thực dữ liệu, tính tổng tiền theo số ngày, và gửi yêu cầu đặt.
|
||||
### Objective
|
||||
Build a complete booking form with information, data validation, calculate total by number of days, and send booking request.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route:
|
||||
```
|
||||
/booking/:roomId
|
||||
```
|
||||
2. Khi user click “Đặt ngay” ở RoomDetailPage → chuyển sang BookingPage.
|
||||
3. Hiển thị:
|
||||
- Ảnh phòng, tên phòng, giá/đêm
|
||||
- Thông tin người dùng (tự động điền nếu đã login)
|
||||
2. When user clicks "Book Now" on RoomDetailPage → navigate to BookingPage.
|
||||
3. Display:
|
||||
- Room image, room name, price/night
|
||||
- User information (auto-fill if logged in)
|
||||
- Form:
|
||||
+ Ngày check-in / check-out (DateRangePicker)
|
||||
+ Số người
|
||||
+ Ghi chú
|
||||
+ Phương thức thanh toán:
|
||||
1. Thanh toán tại chỗ
|
||||
2. Chuyển khoản (hiển thị QR + hướng dẫn)
|
||||
4. Validate bằng Yup + React Hook Form:
|
||||
+ Check-in / check-out date (DateRangePicker)
|
||||
+ Number of guests
|
||||
+ Notes
|
||||
+ Payment method:
|
||||
1. Pay at hotel
|
||||
2. Bank transfer (display QR + instructions)
|
||||
4. Validate using Yup + React Hook Form:
|
||||
- Check-in < Check-out
|
||||
- Không bỏ trống ngày
|
||||
- Có chọn phương thức thanh toán
|
||||
5. Tính tổng tiền:
|
||||
- Dates not empty
|
||||
- Payment method selected
|
||||
5. Calculate total:
|
||||
```
|
||||
total = room.price * (số ngày ở)
|
||||
total = room.price * (number of nights)
|
||||
```
|
||||
6. Nút “Đặt phòng”:
|
||||
6. "Book" button:
|
||||
- Loading spinner
|
||||
- Disable khi đang submit
|
||||
- Disable when submitting
|
||||
|
||||
7. Nếu chưa đăng nhập → redirect /login.
|
||||
7. If not logged in → redirect to /login.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 2: Booking API (Giao tiếp backend)
|
||||
## Function 2: Booking API (Backend communication)
|
||||
|
||||
### Mục tiêu
|
||||
Kết nối và xử lý API liên quan đến đặt phòng.
|
||||
### Objective
|
||||
Connect and handle APIs related to booking.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
🔧 Endpoints:
|
||||
```
|
||||
POST /api/bookings → Tạo booking
|
||||
GET /api/bookings/me → Lấy danh sách booking của user
|
||||
PATCH /api/bookings/:id/cancel → Hủy booking
|
||||
GET /api/bookings/:id → Chi tiết booking
|
||||
GET /api/bookings/check/:bookingNumber → Tra cứu booking
|
||||
POST /api/bookings → Create booking
|
||||
GET /api/bookings/me → Get user's booking list
|
||||
PATCH /api/bookings/:id/cancel → Cancel booking
|
||||
GET /api/bookings/:id → Booking details
|
||||
GET /api/bookings/check/:bookingNumber → Look up booking
|
||||
```
|
||||
🔄 Luồng xử lý:
|
||||
1. Frontend gọi POST /api/bookings
|
||||
2. Backend kiểm tra phòng trống:
|
||||
🔄 Processing flow:
|
||||
1. Frontend calls POST /api/bookings
|
||||
2. Backend checks room availability:
|
||||
```
|
||||
GET /api/rooms/available?roomId=...&from=...&to=...
|
||||
```
|
||||
3. Nếu trống → tạo booking
|
||||
- Nếu trùng lịch → trả 409 “Phòng đã được đặt trong thời gian này”
|
||||
4. Gửi email xác nhận booking (nếu cần)
|
||||
5. Trả về dữ liệu booking để hiển thị /booking-success/:id.
|
||||
3. If available → create booking
|
||||
- If schedule conflict → return 409 "Room already booked during this time"
|
||||
4. Send booking confirmation email (if needed)
|
||||
5. Return booking data to display /booking-success/:id.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 3: BookingSuccess – Trang kết quả sau đặt phòng
|
||||
## Function 3: BookingSuccess – Page after booking
|
||||
|
||||
### Mục tiêu
|
||||
Hiển thị kết quả đặt phòng thành công và các hành động tiếp theo.
|
||||
### Objective
|
||||
Display successful booking result and next actions.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /booking-success/:id
|
||||
2. Gọi GET /api/bookings/:id → hiển thị chi tiết
|
||||
3. Nút:
|
||||
- “Xem đơn của tôi” → /my-bookings
|
||||
- “Về trang chủ” → /
|
||||
4. Nếu phương thức là Chuyển khoản:
|
||||
+ Hiển thị QR code ngân hàng
|
||||
+ Cho phép upload ảnh xác nhận
|
||||
+ Gọi POST /api/notify/payment khi người dùng xác nhận đã chuyển khoản.
|
||||
2. Call GET /api/bookings/:id → display details
|
||||
3. Buttons:
|
||||
- "View my bookings" → /my-bookings
|
||||
- "Go to home" → /
|
||||
4. If payment method is Bank transfer:
|
||||
+ Display bank QR code
|
||||
+ Allow upload confirmation image
|
||||
+ Call POST /api/notify/payment when user confirms transfer.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 4: MyBookingsPage – Danh sách đơn đặt của người
|
||||
## Function 4: MyBookingsPage – User's booking list
|
||||
|
||||
### Mục tiêu
|
||||
Hiển thị toàn bộ các đơn đặt của user + cho phép hủy đơn.
|
||||
### Objective
|
||||
Display all user's bookings + allow canceling bookings.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /my-bookings
|
||||
2. API: GET /api/bookings/me
|
||||
3. Hiển thị danh sách booking:
|
||||
- Phòng, ngày nhận/trả, tổng tiền
|
||||
- Trạng thái:
|
||||
3. Display booking list:
|
||||
- Room, check-in/check-out dates, total amount
|
||||
- Status:
|
||||
🟡 pending
|
||||
🟢 confirmed
|
||||
🔴 cancelled
|
||||
4. Nút “Hủy đặt phòng”:
|
||||
1. window.confirm("Bạn có chắc muốn hủy không?")
|
||||
2. Gọi PATCH /api/bookings/:id/cancel (hoặc DELETE /api/bookings/:id tùy implement)
|
||||
3. Logic hủy:
|
||||
- Giữ 20% giá trị đơn
|
||||
- Hoàn 80% còn lại cho user
|
||||
- Cập nhật trạng thái phòng về available
|
||||
4. Hiển thị toast “Đơn đã được hủy thành công”
|
||||
5. Cho phép xem chi tiết booking:
|
||||
4. "Cancel booking" button:
|
||||
1. window.confirm("Are you sure you want to cancel?")
|
||||
2. Call PATCH /api/bookings/:id/cancel (or DELETE /api/bookings/:id depending on implementation)
|
||||
3. Cancel logic:
|
||||
- Keep 20% of order value
|
||||
- Refund remaining 80% to user
|
||||
- Update room status to available
|
||||
4. Display toast "Booking cancelled successfully"
|
||||
5. Allow viewing booking details:
|
||||
- Route: /bookings/:id
|
||||
- Gọi GET /api/bookings/:id
|
||||
- Hiển thị chi tiết phòng, thông tin user, tổng tiền, status.
|
||||
- Call GET /api/bookings/:id
|
||||
- Display room details, user information, total amount, status.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 5: Thanh toán (Giả lập Payment)
|
||||
## Function 5: Payment (Simulated Payment)
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng chọn phương thức thanh toán và xác nhận thanh toán.
|
||||
### Objective
|
||||
Allow users to select payment method and confirm payment.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
- Phương thức:
|
||||
1. Thanh toán tại chỗ
|
||||
- Booking được tạo với status = "pending"
|
||||
2. Chuyển khoản
|
||||
- Hiển thị mã QR ngân hàng (tĩnh hoặc từ API)
|
||||
- Upload ảnh biên lai (image upload)
|
||||
- Sau khi upload → gọi POST /api/notify/payment gửi email xác nhận
|
||||
- Cập nhật status = "confirmed"
|
||||
#### Detailed Tasks
|
||||
- Payment methods:
|
||||
1. Pay at hotel
|
||||
- Booking created with status = "pending"
|
||||
2. Bank transfer
|
||||
- Display bank QR code (static or from API)
|
||||
- Upload receipt image (image upload)
|
||||
- After upload → call POST /api/notify/payment send confirmation email
|
||||
- Update status = "confirmed"
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 6: UX & Hiệu năng
|
||||
## Function 6: UX & Performance
|
||||
|
||||
### Mục tiêu
|
||||
Cải thiện trải nghiệm người dùng và tính trực quan.
|
||||
### Objective
|
||||
Improve user experience and intuitiveness.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
1. Toasts (react-hot-toast hoặc sonner)
|
||||
2. Loading spinner rõ ràng
|
||||
3. DateRangePicker cho chọn ngày
|
||||
4. Form được validate đầy đủ (và báo lỗi chi tiết)
|
||||
5. Focus input đầu tiên
|
||||
6. Tự động redirect khi đặt thành công / hủy đơn
|
||||
#### Detailed Tasks
|
||||
1. Toasts (react-hot-toast or sonner)
|
||||
2. Clear loading spinner
|
||||
3. DateRangePicker for date selection
|
||||
4. Form fully validated (and detailed error messages)
|
||||
5. Focus first input
|
||||
6. Auto redirect when booking succeeds / canceling booking
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
# Review System
|
||||
|
||||
## Chức năng 1: ReviewPage – Trang người dùng đánh giá phòng
|
||||
## Function 1: ReviewPage – User Room Review Page
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép người dùng viết đánh giá cho những phòng họ đã đặt thành công.
|
||||
### Objective
|
||||
Allow users to write reviews for rooms they have successfully booked.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /reviews
|
||||
2. Gọi API:
|
||||
2. API Calls:
|
||||
```
|
||||
GET /api/bookings/me → Lấy danh sách phòng người dùng đã đặt.
|
||||
POST /api/reviews → Gửi đánh giá.
|
||||
GET /api/bookings/me → Get list of rooms user has booked.
|
||||
POST /api/reviews → Submit review.
|
||||
```
|
||||
3. Giao diện:
|
||||
- Hiển thị danh sách phòng đã đặt (tên, ngày ở, trạng thái)
|
||||
- Nút “Đánh giá” (hiện nếu chưa đánh giá phòng đó)
|
||||
4. Khi nhấn “Đánh giá” → mở Modal:
|
||||
- Input chọn số sao (⭐ 1–5)
|
||||
- Textarea nhập nội dung bình luận
|
||||
- Nút “Gửi đánh giá”
|
||||
5. Validate:
|
||||
- Rating bắt buộc (1–5)
|
||||
- Comment không để trống
|
||||
6. Sau khi gửi thành công → toast thông báo “Đánh giá của bạn đang chờ duyệt”.
|
||||
3. Interface:
|
||||
- Display list of booked rooms (name, stay dates, status)
|
||||
- "Review" button (shown if room not yet reviewed)
|
||||
4. When clicking "Review" → open Modal:
|
||||
- Input to select star rating (⭐ 1–5)
|
||||
- Textarea to enter comment content
|
||||
- "Submit Review" button
|
||||
5. Validation:
|
||||
- Rating required (1–5)
|
||||
- Comment cannot be empty
|
||||
6. After successful submission → toast notification "Your review is pending approval".
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Người dùng chỉ thấy nút “Đánh giá” với phòng đã từng đặt.
|
||||
2. Modal mở ra và validate chính xác.
|
||||
3. Gửi thành công → review có trạng thái "pending".
|
||||
4. Toast hiển thị thông báo hợp lý.
|
||||
5. Giao diện gọn, trực quan, không lỗi khi chưa có phòng nào đặt.
|
||||
### Expected Results
|
||||
1. Users only see "Review" button for rooms they have booked.
|
||||
2. Modal opens and validates correctly.
|
||||
3. Successful submission → review has status "pending".
|
||||
4. Toast displays appropriate notification.
|
||||
5. Clean, intuitive interface, no errors when no rooms booked.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 2: RoomDetailPage – Hiển thị danh sách đánh giá
|
||||
## Function 2: RoomDetailPage – Display Review List
|
||||
|
||||
### Mục tiêu
|
||||
Hiển thị danh sách các đánh giá đã được admin duyệt cho từng phòng.
|
||||
### Objective
|
||||
Display list of reviews that have been approved by admin for each room.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /rooms/:id
|
||||
2. API:
|
||||
```
|
||||
GET /api/reviews?roomId={id}&status=approved
|
||||
```
|
||||
3. Hiển thị danh sách review:
|
||||
- Avatar + tên người dùng
|
||||
- Số sao (⭐)
|
||||
- Nội dung bình luận
|
||||
- Ngày đăng (createdAt)
|
||||
4. Tính và hiển thị điểm trung bình rating (VD: ⭐ 4.2 / 5)
|
||||
5. Nếu chưa có review → hiển thị: “Chưa có đánh giá nào.”
|
||||
3. Display review list:
|
||||
- Avatar + user name
|
||||
- Star rating (⭐)
|
||||
- Comment content
|
||||
- Post date (createdAt)
|
||||
4. Calculate and display average rating (e.g.: ⭐ 4.2 / 5)
|
||||
5. If no reviews → display: "No reviews yet."
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Danh sách review hiển thị đúng theo phòng.
|
||||
2. Chỉ review có status = approved được render.
|
||||
3. Tính điểm trung bình chính xác (làm tròn 1 chữ số thập phân).
|
||||
4. Hiển thị avatar, tên, sao, và ngày đầy đủ.
|
||||
5. Có thông báo “Chưa có đánh giá” khi danh sách trống.
|
||||
### Expected Results
|
||||
1. Review list displays correctly by room.
|
||||
2. Only reviews with status = approved are rendered.
|
||||
3. Calculate average rating accurately (rounded to 1 decimal place).
|
||||
4. Display avatar, name, stars, and date completely.
|
||||
5. Show "No reviews yet" message when list is empty.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 3: AdminReviewPage – Trang quản trị đánh giá
|
||||
## Function 3: AdminReviewPage – Review Management Page
|
||||
|
||||
### Mục tiêu
|
||||
Cho phép Admin xem, duyệt hoặc từ chối các đánh giá người dùng gửi lên.
|
||||
### Objective
|
||||
Allow Admin to view, approve or reject reviews submitted by users.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
#### Detailed Tasks
|
||||
1. Route: /admin/reviews
|
||||
2. API:
|
||||
```
|
||||
@@ -74,67 +74,67 @@
|
||||
PATCH /api/reviews/:id/approve
|
||||
PATCH /api/reviews/:id/reject
|
||||
```
|
||||
3. Hành động:
|
||||
✅ Duyệt → review chuyển sang approved
|
||||
❌ Từ chối → review chuyển sang rejected
|
||||
4. Sau khi duyệt → cập nhật giao diện và hiển thị toast thông báo.
|
||||
5. Có filter theo trạng thái (pending, approved, rejected).
|
||||
3. Actions:
|
||||
✅ Approve → review changes to approved
|
||||
❌ Reject → review changes to rejected
|
||||
4. After approval → update interface and display toast notification.
|
||||
5. Filter by status (pending, approved, rejected).
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Admin thấy đầy đủ danh sách review.
|
||||
2. Duyệt hoặc từ chối hoạt động đúng API.
|
||||
3. Bảng tự cập nhật khi thay đổi trạng thái.
|
||||
4. Toast hiển thị rõ “Đã duyệt” hoặc “Đã từ chối”.
|
||||
5. Chỉ review approved mới hiển thị công khai cho người dùng.
|
||||
### Expected Results
|
||||
1. Admin sees complete review list.
|
||||
2. Approve or reject works correctly with API.
|
||||
3. Table automatically updates when status changes.
|
||||
4. Toast clearly displays "Approved" or "Rejected".
|
||||
5. Only approved reviews are displayed publicly to users.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 4: Bảo mật & Logic hiển thị
|
||||
## Function 4: Security & Display Logic
|
||||
|
||||
### Mục tiêu
|
||||
Đảm bảo chỉ người hợp lệ mới có thể gửi đánh giá và hệ thống hiển thị đúng dữ liệu.
|
||||
### Objective
|
||||
Ensure only valid users can submit reviews and system displays correct data.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
1. Kiểm tra quyền:
|
||||
- Người dùng chưa đăng nhập → redirect /login
|
||||
- Người dùng chưa từng đặt phòng → không hiển thị nút “Đánh giá”
|
||||
2. Kiểm tra logic:
|
||||
- Mỗi người chỉ được đánh giá 1 lần / phòng
|
||||
- Review mặc định status = pending
|
||||
3. Phân quyền:
|
||||
- User: chỉ gửi review
|
||||
- Admin: duyệt / từ chối
|
||||
- Staff: chỉ xem
|
||||
#### Detailed Tasks
|
||||
1. Permission check:
|
||||
- User not logged in → redirect /login
|
||||
- User has never booked room → don't display "Review" button
|
||||
2. Logic check:
|
||||
- Each person can only review once per room
|
||||
- Review default status = pending
|
||||
3. Authorization:
|
||||
- User: can only submit review
|
||||
- Admin: approve / reject
|
||||
- Staff: view only
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. Người chưa đăng nhập không thể gửi review.
|
||||
2. Mỗi phòng chỉ được review 1 lần bởi 1 user.
|
||||
3. Dữ liệu hiển thị chính xác theo phân quyền.
|
||||
4. Review chỉ xuất hiện công khai khi được duyệt.
|
||||
5. Không có lỗi logic hoặc hiển thị sai trạng thái.
|
||||
### Expected Results
|
||||
1. Users not logged in cannot submit reviews.
|
||||
2. Each room can only be reviewed once by one user.
|
||||
3. Data displays correctly according to permissions.
|
||||
4. Reviews only appear publicly when approved.
|
||||
5. No logic errors or incorrect status display.
|
||||
|
||||
---
|
||||
|
||||
## Chức năng 5: UX & Hiển thị tổng quan
|
||||
## Function 5: UX & Overall Display
|
||||
|
||||
### Mục tiêu
|
||||
Cải thiện trải nghiệm người dùng và giao diện hiển thị đánh giá.
|
||||
### Objective
|
||||
Improve user experience and review display interface.
|
||||
|
||||
#### Nhiệm vụ chi tiết
|
||||
1. Dùng component đánh giá sao trực quan (ví dụ react-rating-stars-component).
|
||||
2. Format ngày tạo bằng:
|
||||
#### Detailed Tasks
|
||||
1. Use intuitive star rating component (e.g., react-rating-stars-component).
|
||||
2. Format creation date using:
|
||||
```
|
||||
new Date(createdAt).toLocaleDateString('vi-VN')
|
||||
new Date(createdAt).toLocaleDateString('en-US')
|
||||
```
|
||||
3. Thêm hiệu ứng hover nhẹ khi hiển thị danh sách review.
|
||||
4. Dùng toast (react-hot-toast) cho thông báo gửi / duyệt / từ chối.
|
||||
5. Loading spinner khi chờ API.
|
||||
3. Add light hover effect when displaying review list.
|
||||
4. Use toast (react-hot-toast) for submit / approve / reject notifications.
|
||||
5. Loading spinner when waiting for API.
|
||||
|
||||
### Kết quả mong đợi
|
||||
1. UI mượt mà, dễ đọc và thân thiện.
|
||||
2. Loading / toast hiển thị đúng trạng thái.
|
||||
3. Ngày tháng, sao và bình luận được format đẹp.
|
||||
4. Giao diện quản trị và người dùng thống nhất phong cách.
|
||||
5. Trải nghiệm người dùng mượt, không giật lag.
|
||||
### Expected Results
|
||||
1. Smooth, readable and user-friendly UI.
|
||||
2. Loading / toast displays correct status.
|
||||
3. Dates, stars and comments are formatted nicely.
|
||||
4. Admin and user interfaces have consistent styling.
|
||||
5. Smooth user experience, no lag or stuttering.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user