Hotel Booking

This commit is contained in:
Iliyan Angelov
2025-11-16 14:19:13 +02:00
commit 824eec6190
203 changed files with 37696 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
# Chức năng 6: Quên Mật Khẩu (Forgot Password) - Hoàn Thành ✅
## 📦 Files Đã Tạo/Cập Nhật
### Frontend
1. **`client/src/pages/auth/ForgotPasswordPage.tsx`** - Component form quên mật khẩu
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
## ✨ Tính Năng Chính
### 1. Form State (Initial)
```
┌─────────────────────────────────────┐
│ 🏨 Hotel Icon (Blue) │
│ Quên mật khẩu? │
│ Nhập email để nhận link... │
├─────────────────────────────────────┤
│ ┌───────────────────────────────┐ │
│ │ Email │ │
│ │ [📧 email@example.com ] │ │
│ ├───────────────────────────────┤ │
│ │ [📤 Gửi link đặt lại MK] │ │
│ ├───────────────────────────────┤ │
│ │ ← Quay lại đăng nhập │ │
│ └───────────────────────────────┘ │
│ Chưa có tài khoản? Đăng ký ngay │
└─────────────────────────────────────┘
```
### 2. Success State (After Submit)
```
┌─────────────────────────────────────┐
│ ✅ Success Icon │
│ │
│ Email đã được gửi! │
│ Chúng tôi đã gửi link đến │
│ 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 │
├─────────────────────────────────────┤
│ [📧 Gửi lại email] │
│ [← Quay lại đăng nhập] │
└─────────────────────────────────────┘
```
### 3. Two-State Design Pattern
**Form State** - Nhập email
**Success State** - Hiển thị xác nhận & hướng dẫn
State management:
```typescript
const [isSuccess, setIsSuccess] = useState(false);
const [submittedEmail, setSubmittedEmail] = useState('');
```
## 🔧 Features Chi Tiết
### 1. Validation (Yup Schema)
```typescript
email:
- Required: "Email là bắt buộc"
- Valid format: "Email không hợp lệ"
- Trim whitespace
```
### 2. Form Field
- **Email input** với Mail icon
- Auto-focus khi load page
- Validation real-time
- Error message inline
### 3. Submit Button States
```tsx
{isLoading ? (
<>
<Loader2 className="animate-spin" />
Đang xử ...
</>
) : (
<>
<Send />
Gửi link đặt lại mật khẩu
</>
)}
```
### 4. Success Features
**Visual Confirmation**
- Green checkmark icon
- Success message
- Display submitted email
**User Instructions**
- Link expires in 1 hour
- Check spam folder
- Can resend email
**Action Buttons**
- "Gửi lại email" - Reset to form state
- "Quay lại đăng nhập" - Navigate to /login
### 5. Help Section
```tsx
<div className="bg-white rounded-lg shadow-sm border">
<h3>Cần trợ giúp?</h3>
<p>
Liên hệ: support@hotel.com hoặc 1900-xxxx
</p>
</div>
```
## 🔗 Backend Integration
### API Endpoint: Forgot Password
```
POST /api/auth/forgot-password
```
**Request Body:**
```json
{
"email": "user@example.com"
}
```
**Response (Success):**
```json
{
"status": "success",
"message": "Password reset link has been sent to your email"
}
```
**Implementation:**
```javascript
const forgotPassword = async (req, res, next) => {
// 1. Find user by email
// 2. Generate crypto reset token (32 bytes)
// 3. Hash token with SHA256
// 4. Save to password_reset_tokens table
// 5. Expires in 1 hour
// 6. TODO: Send email with reset link
// 7. Return success (prevent email enumeration)
};
```
### API Endpoint: Reset Password
```
POST /api/auth/reset-password
```
**Request Body:**
```json
{
"token": "reset_token_from_email",
"password": "NewPassword123@"
}
```
**Response (Success):**
```json
{
"status": "success",
"message": "Password has been reset successfully"
}
```
**Implementation:**
```javascript
const resetPassword = async (req, res, next) => {
// 1. Hash incoming token
// 2. Find valid token in DB (not expired)
// 3. Find user by user_id
// 4. Hash new password with bcrypt
// 5. Update user password
// 6. Delete used token
// 7. TODO: Send confirmation email
// 8. Return success
};
```
## 🔐 Security Features
### 1. Token Generation
```javascript
// Generate random 32-byte token
const resetToken = crypto.randomBytes(32).toString('hex');
// Hash with SHA256 before storing
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
```
### 2. Token Storage
- Stored in `password_reset_tokens` table
- Hashed (SHA256)
- Expires in 1 hour
- One token per user (old tokens deleted)
### 3. Email Enumeration Prevention
```javascript
// Always return success, even if email not found
if (!user) {
return res.status(200).json({
status: 'success',
message: 'If email exists, reset link has been sent'
});
}
```
### 4. Token Validation
```javascript
// Check token exists and not expired
const resetToken = await PasswordResetToken.findOne({
where: {
token: hashedToken,
expires_at: { [Op.gt]: new Date() }
}
});
```
## 📊 Database Schema
### password_reset_tokens Table
```sql
CREATE TABLE password_reset_tokens (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
token VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
```
## 🎨 Design & Styling
**Color Scheme:**
- Primary: blue-600, blue-700
- Background: gradient from-blue-50 to-indigo-100
- Success: green-100, green-600
- Info: blue-50, blue-200
- Text: gray-600, gray-700, gray-900
**Icons:**
- 🏨 Hotel - Main logo
- 📧 Mail - Email input
- 📤 Send - Submit button
- ⏳ Loader2 - Loading spinner
- ✅ CheckCircle - Success state
- ← ArrowLeft - Back navigation
## 🔄 User Flow
```
1. User visits /forgot-password
2. Enter email address
3. Click "Gửi link đặt lại mật khẩu"
4. Frontend validation (Yup)
5. Call useAuthStore.forgotPassword()
6. API POST /api/auth/forgot-password
7. Backend:
- Generate token
- Save to DB
- TODO: Send email
8. Frontend shows success state
9. User receives email (TODO)
10. Click link → /reset-password/:token
11. Enter new password (Chức năng 7)
```
## 🧪 Test Scenarios
### Test Case 1: Valid email
```
Input: email = "user@example.com"
Expected:
- API called successfully
- Success state shown
- Email displayed correctly
- Instructions visible
```
### Test Case 2: Invalid email format
```
Input: email = "notanemail"
Expected:
- Validation error: "Email không hợp lệ"
- Form not submitted
```
### Test Case 3: Empty email
```
Input: email = ""
Expected:
- Validation error: "Email là bắt buộc"
- Form not submitted
```
### Test Case 4: Loading state
```
Action: Submit form
Expected:
- Button disabled
- Spinner shows
- Text: "Đang xử lý..."
```
### Test Case 5: Resend email
```
Action: Click "Gửi lại email" in success state
Expected:
- Return to form state
- Email field cleared
- Error cleared
```
### Test Case 6: Back to login
```
Action: Click "Quay lại đăng nhập"
Expected:
- Navigate to /login
```
### Test Case 7: Email not found (Backend)
```
Input: email = "nonexistent@example.com"
Expected:
- Still return success (security)
- No error shown to user
```
### Test Case 8: Token expiry (Backend)
```
Scenario: Token created 2 hours ago
Expected:
- Reset fails
- Error: "Invalid or expired reset token"
```
## 📝 Code Quality
**TypeScript**: Full type safety
**React Hook Form**: Optimized performance
**Yup Validation**: Schema-based validation
**State Management**: Local state for success toggle
**Error Handling**: Try-catch blocks
**Security**: Token hashing, email enumeration prevention
**UX**: Clear instructions, help section
**Accessibility**: Labels, autocomplete, focus management
**Responsive**: Mobile-friendly
**80 chars/line**: Code formatting
## 🔗 Integration Points
### useAuthStore.forgotPassword()
```typescript
const { forgotPassword, isLoading, error, clearError } =
useAuthStore();
await forgotPassword({ email: data.email });
```
### Success Handling
```typescript
await forgotPassword({ email: data.email });
setIsSuccess(true); // Show success state
```
### Error Handling
```typescript
try {
await forgotPassword({ email });
} catch (error) {
// Error displayed via store.error
console.error('Forgot password error:', error);
}
```
## 🚀 Usage
### Test Frontend
```bash
URL: http://localhost:5173/forgot-password
Test Data:
Email: admin@hotel.com (from seed data)
```
### Test Backend API
```bash
curl -X POST http://localhost:3000/api/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"admin@hotel.com"}'
```
Expected response:
```json
{
"status": "success",
"message": "Password reset link has been sent to your email"
}
```
Ensure SMTP is configured in `server/.env` and that the server
is able to send emails. The application must never log raw reset
tokens or reset URLs in production.
## ⚠️ TODO: Email Service
The project must send real emails via SMTP in production and must
never expose raw reset tokens in logs. To enable email sending:
1. Install `nodemailer` in the `server` package and configure
SMTP credentials in `server/.env` (`MAIL_HOST`, `MAIL_PORT`,
`MAIL_USER`, `MAIL_PASS`, `MAIL_FROM`). Do not commit these
credentials to source control.
2. Implement a mail helper (e.g. `server/src/utils/mailer.js`) that
uses the SMTP settings. Ensure it throws or fails when SMTP
credentials are missing so that emails are not silently dropped.
3. Use the mail helper to send the reset email with a link built as
`${process.env.CLIENT_URL}/reset-password/${resetToken}`.
4. Important: never log the raw `resetToken` or the reset URL. If
email sending fails, log a generic error and surface a safe
message to the user.
## ✅ Checklist
- [x] ✅ Create ForgotPasswordPage.tsx
- [x] ✅ Implement React Hook Form
- [x] ✅ Add Yup validation
- [x] ✅ Two-state design (form + success)
- [x] ✅ Loading state
- [x] ✅ Error display
- [x] ✅ Success state with instructions
- [x] ✅ Resend email button
- [x] ✅ Back to login navigation
- [x] ✅ Help section
- [x] ✅ Integration with useAuthStore
- [x] ✅ Add route to App.tsx
- [x] ✅ Backend: forgotPassword() method
- [x] ✅ Backend: resetPassword() method
- [x] ✅ Backend: Routes added
- [x] ✅ Token generation & hashing
- [x] ✅ Token expiry (1 hour)
- [x] ✅ Security: Email enumeration prevention
- [ ] ⏳ TODO: Send actual email (nodemailer)
- [ ] ⏳ TODO: Email templates
## 📚 Related Files
- `client/src/pages/auth/LoginPage.tsx` - Login form
- `client/src/pages/auth/RegisterPage.tsx` - Register form
- `client/src/utils/validationSchemas.ts` - Validation schemas
- `client/src/store/useAuthStore.ts` - Auth state
- `server/src/controllers/authController.js` - Auth logic
- `server/src/routes/authRoutes.js` - Auth routes
- `server/src/databases/models/PasswordResetToken.js` - Token model
---
**Status:** ✅ Chức năng 6 hoàn thành
**Next:** Chức năng 7 - Reset Password (form to change password with token)
**Test URL:** http://localhost:5173/forgot-password
**API:** POST /api/auth/forgot-password

View File

@@ -0,0 +1,175 @@
# Layout Components - Chức năng 1
## Tổng quan
Đã triển khai thành công **Chức năng 1: Layout cơ bản** bao gồm:
### Components đã tạo
#### 1. **Header** (`src/components/layout/Header.tsx`)
- Logo và tên ứng dụng
- Sticky header với shadow
- Responsive design
- Links cơ bản (Trang chủ, Phòng, Đặt phòng)
#### 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ệ)
- Social media icons
- Copyright info
- 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
#### 4. **SidebarAdmin** (`src/components/layout/SidebarAdmin.tsx`)
- Chỉ hiển thị cho role = "admin"
- Collapsible sidebar (mở/đóng)
- 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
- Props: `isAuthenticated`, `userInfo`, `onLogout`
- Min-height 100vh với flex layout
### Cấu trúc thư mục
```
src/
├── components/
│ └── layout/
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── Navbar.tsx
│ ├── SidebarAdmin.tsx
│ ├── LayoutMain.tsx
│ └── index.ts
├── pages/
│ ├── HomePage.tsx
│ └── AdminLayout.tsx
├── styles/
│ └── index.css
├── App.tsx
└── main.tsx
```
### Cách sử dụng
#### 1. Import Layout vào App
```tsx
import LayoutMain from './components/layout/LayoutMain';
// Trong Routes
<Route
path="/"
element={
<LayoutMain
isAuthenticated={isAuthenticated}
userInfo={userInfo}
onLogout={handleLogout}
/>
}
>
<Route index element={<HomePage />} />
{/* Các route con khác */}
</Route>
```
#### 2. Sử dụng SidebarAdmin cho trang Admin
```tsx
import SidebarAdmin from '../components/layout/SidebarAdmin';
const AdminLayout = () => (
<div className="flex h-screen">
<SidebarAdmin />
<div className="flex-1 overflow-auto">
<Outlet />
</div>
</div>
);
```
### Tính năng đã hoàn thành ✅
- [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
### Demo Routes đã tạo
**Public Routes** (với LayoutMain):
- `/` - Trang chủ
- `/rooms` - Danh sách phòng
- `/bookings` - Đặt phòng
- `/about` - Giới thiệu
**Auth Routes** (không có layout):
- `/login` - Đăng nhập
- `/register` - Đăng ký
- `/forgot-password` - Quên mật khẩu
**Admin Routes** (với 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
### Chạy ứng dụng
```bash
# Di chuyển vào thư mục client
cd client
# Cài đặt dependencies (nếu chưa cài)
npm install
# Chạy development server
npm run dev
# Mở trình duyệt tại: http://localhost:5173
```
### Các bước tiếp theo
**Chức năng 2**: Cấu hình Routing (react-router-dom)
- ProtectedRoute component
- AdminRoute component
- Redirect logic
**Chức năng 3**: useAuthStore (Zustand Store)
- Quản lý authentication state
- Login/Logout functions
- Persist state trong localStorage
**Chức năng 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)

432
docs/LOGIN_FORM_GUIDE.md Normal file
View File

@@ -0,0 +1,432 @@
# Chức năng 4: Form Đăng Nhập - Hướng Dẫn Sử Dụng
## 📋 Tổng Quan
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
- ✅ Responsive design
## 🗂️ Các File Đã Tạo/Cập Nhật
### 1. **LoginPage.tsx** - Component form đăng nhập
**Đường dẫn:** `client/src/pages/auth/LoginPage.tsx`
```typescript
// Các tính năng chính:
- React Hook Form với Yup validation
- Show/hide password toggle
- Remember me checkbox
- Loading state với spinner
- Error handling
- Redirect với location state
```
### 2. **index.ts** - Export module
**Đường dẫn:** `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`
```typescript
// Đã thêm:
import { LoginPage } from './pages/auth';
// Route:
<Route path="/login" element={<LoginPage />} />
```
## 🎨 Cấu Trúc UI
### Layout
```
┌─────────────────────────────────────┐
│ 🏨 Hotel Icon │
│ Đăng nhập │
│ Chào mừng bạn trở lại... │
├─────────────────────────────────────┤
│ ┌───────────────────────────────┐ │
│ │ [Error message if any] │ │
│ ├───────────────────────────────┤ │
│ │ Email │ │
│ │ [📧 email@example.com ] │ │
│ ├───────────────────────────────┤ │
│ │ Mật khẩu │ │
│ │ [🔒 •••••••• 👁️] │ │
│ ├───────────────────────────────┤ │
│ │ ☑️ Nhớ đăng nhập │ │
│ │ Quên mật khẩu? → │ │
│ ├───────────────────────────────┤ │
│ │ [🔐 Đăng nhập] │ │
│ └───────────────────────────────┘ │
│ Chưa có tài khoản? Đăng ký ngay │
│ │
│ Điều khoản & Chính sách bảo mật │
└─────────────────────────────────────┘
```
## 🔧 Cách Sử Dụng
### 1. Truy Cập Form
```bash
# URL
http://localhost:5173/login
```
### 2. Các Trường Trong Form
| Trường | Type | Bắt buộc | Validation |
|--------|------|----------|------------|
| Email | text | ✅ | Email hợp lệ |
| Password | password | ✅ | Min 8 ký tự |
| 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ệ"
- 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ự"
```
### 4. Luồng Đăng Nhập
```
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()
- API POST /api/auth/login
5. Nếu thành công:
- Lưu token vào localStorage
- Update Zustand state
- Redirect đến /dashboard
6. Nếu lỗi:
- Hiển thị error message
- Button enabled lại
```
## 🎯 Tính Năng Chính
### 1. Show/Hide Password
```typescript
const [showPassword, setShowPassword] = useState(false);
// Toggle button
<button onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <EyeOff /> : <Eye />}
</button>
// Input type
<input type={showPassword ? 'text' : 'password'} />
```
### 2. Remember Me (7 ngày)
```typescript
// Checkbox
<input {...register('rememberMe')} type="checkbox" />
// Logic trong authService.login()
if (rememberMe) {
// Token sẽ được lưu trong localStorage
// và không bị xóa khi đóng trình duyệt
}
```
### 3. Loading State
```typescript
// Button disabled khi đang loading
<button disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="animate-spin" />
Đang xử ...
</>
) : (
<>
<LogIn />
Đăng nhập
</>
)}
</button>
```
### 4. Error Handling
```typescript
// Error từ Zustand store
const { error } = useAuthStore();
// Hiển thị error message
{error && (
<div className="bg-red-50 border border-red-200">
{error}
</div>
)}
```
### 5. Redirect Logic
```typescript
// Lấy location state từ ProtectedRoute
const location = useLocation();
// Redirect về trang trước đó hoặc dashboard
const from = location.state?.from?.pathname || '/dashboard';
navigate(from, { replace: true });
```
## 🔗 Integration với Zustand Store
```typescript
// Hook usage
const {
login, // Function để login
isLoading, // Loading state
error, // Error message
clearError // Clear error
} = useAuthStore();
// Login call
await login({
email: 'user@example.com',
password: 'password123',
rememberMe: true
});
```
## 🎨 Styling với Tailwind
### Color Scheme
```
- Primary: blue-600, blue-700
- Background: gradient from-blue-50 to-indigo-100
- Error: red-50, red-200, red-600, red-700
- Text: gray-600, gray-700, gray-900
```
### Responsive Design
```typescript
// Container
className="max-w-md w-full" // Max width 28rem
// Grid (nếu có)
className="grid grid-cols-1 md:grid-cols-2"
```
## 🧪 Testing Scenarios
### 1. Validation Testing
**Test Case 1: Empty form**
```
- Input: Submit form trống
- Expected: Hiển thị lỗi "Email là bắt buộc"
```
**Test Case 2: Invalid email**
```
- Input: Email = "notanemail"
- Expected: "Email không hợp lệ"
```
**Test Case 3: Short password**
```
- Input: Password = "123"
- Expected: "Mật khẩu phải có ít nhất 8 ký tự"
```
### 2. Authentication Testing
**Test Case 4: Valid credentials**
```
- Input: Valid email + password
- Expected: Redirect to /dashboard
```
**Test Case 5: Invalid credentials**
```
- Input: Wrong password
- Expected: Error message từ server
```
**Test Case 6: Network error**
```
- Input: Server offline
- Expected: Error message "Có lỗi xảy ra"
```
### 3. UX Testing
**Test Case 7: Show/hide password**
```
- Action: Click eye icon
- Expected: Password text visible/hidden
```
**Test Case 8: Remember me**
```
- Action: Check "Nhớ đăng nhập"
- Expected: Token persist sau khi reload
```
**Test Case 9: Loading state**
```
- Action: Submit form
- Expected: Button disabled, spinner hiển thị
```
## 🔐 Security Features
### 1. Password Visibility
```typescript
// Default: password hidden
type="password"
// Toggle: user control
onClick={() => setShowPassword(!showPassword)}
```
### 2. HTTPS Only (Production)
```typescript
// Trong .env
VITE_API_URL=https://api.yourdomain.com
```
### 3. Token Storage
```typescript
// LocalStorage cho remember me
if (rememberMe) {
localStorage.setItem('token', token);
}
// SessionStorage cho session only
else {
sessionStorage.setItem('token', token);
}
```
## 📝 Best Practices
### 1. Form Validation
```typescript
Client-side validation (Yup)
Server-side validation (Express validator)
Immediate feedback
Clear error messages
```
### 2. Error Handling
```typescript
Try-catch blocks
User-friendly messages
Clear error state
Console logging for debugging
```
### 3. UX
```typescript
Loading indicators
Disabled states
Auto-focus first field
Enter key submit
Remember form state
```
## 🚀 Next Steps (Chức năng 5-7)
1. **Chức năng 5: Form Register**
- Copy structure từ LoginPage
- Thêm 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
- Send reset link
- Success message
3. **Chức năng 7: Reset Password**
- Form với password + confirmPassword
- Token từ URL params
- Redirect to /login after success
## 🐛 Troubleshooting
### Issue 1: "Error: Token expired"
```typescript
Solution: Check token expiry time
- Access token: 1 hour
- Refresh token: 7 days
```
### Issue 2: Form không submit
```typescript
Solution: Check console for validation errors
- Open DevTools > Console
- Look for Yup validation errors
```
### Issue 3: Redirect không hoạt động
```typescript
Solution: Check location state
console.log(location.state?.from);
```
### Issue 4: Remember me không work
```typescript
Solution: Check localStorage
- Open DevTools > Application > Local Storage
- Check "token" "refreshToken" keys
```
## 📚 Resources
- [React Hook Form Docs](https://react-hook-form.com/)
- [Yup Validation](https://github.com/jquense/yup)
- [Zustand Guide](https://github.com/pmndrs/zustand)
- [Tailwind CSS](https://tailwindcss.com/)
- [Lucide Icons](https://lucide.dev/)
## ✅ Checklist
- [x] ✅ Create validationSchemas.ts
- [x] ✅ Create LoginPage.tsx
- [x] ✅ Add route to App.tsx
- [x] ✅ Email validation
- [x] ✅ Password validation
- [x] ✅ Show/hide password
- [x] ✅ Remember me checkbox
- [x] ✅ Loading state
- [x] ✅ Error display
- [x] ✅ Redirect after login
- [x] ✅ Responsive design
- [x] ✅ Icons integration
- [ ] ⏳ E2E testing
---
**Status:** ✅ Chức năng 4 hoàn thành
**Next:** Chức năng 5 - Form Register

View File

@@ -0,0 +1,486 @@
# Chức năng 5: Form Đăng Ký - Hoàn Thành ✅
## 📦 Files Đã Tạo/Cập Nhật
### 1. **RegisterPage.tsx** - Component form đăng ký
**Đường dẫn:** `client/src/pages/auth/RegisterPage.tsx`
### 2. **index.ts** - Export module
**Đường dẫn:** `client/src/pages/auth/index.ts`
- Đã thêm export RegisterPage
### 3. **App.tsx** - Cập nhật routing
**Đường dẫn:** `client/src/App.tsx`
- Đã thêm route `/register`
## ✨ Tính Năng Chính
### 1. Form Fields (5 fields)
**Họ và tên** (name)
- Required, 2-50 ký tự
- Icon: User
- Placeholder: "Nguyễn Văn A"
**Email**
- Required, valid email format
- Icon: Mail
- Placeholder: "email@example.com"
**Số điện thoại** (phone) - Optional
- 10-11 chữ số
- Icon: Phone
- Placeholder: "0123456789"
**Mật khẩu** (password)
- Required, min 8 chars
- Must contain: uppercase, lowercase, number, special char
- Show/hide toggle với Eye icon
- Icon: Lock
**Xác nhận mật khẩu** (confirmPassword)
- Must match password
- Show/hide toggle với 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)
**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 (@$!%*?&)
### 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ự"
- Trim whitespace
email:
- Required: "Email là bắt buộc"
- Valid format: "Email không hợp lệ"
- Trim whitespace
phone (optional):
- Pattern /^[0-9]{10,11}$/
- Error: "Số điện thoại không hợp lệ"
password:
- Required: "Mật khẩu là bắt buộc"
- 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"
confirmPassword:
- Required: "Vui lòng xác nhận mật khẩu"
- Must match password: "Mật khẩu không khớp"
```
### 4. UX Features
**Loading State**
```tsx
{isLoading ? (
<>
<Loader2 className="animate-spin" />
Đang xử ...
</>
) : (
<>
<UserPlus />
Đăng
</>
)}
```
**Show/Hide Password** (2 toggles)
- Eye/EyeOff icons
- Separate toggle cho password và confirmPassword
- Visual feedback khi 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
**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."
5. Navigate to /login
```
### 5. Design & Styling
**Color Scheme:**
- Primary: purple-600, purple-700
- Background: gradient from-purple-50 to-pink-100
- Success: green-500, green-600
- Error: red-50, red-200, red-600
- Text: gray-600, gray-700, gray-900
**Layout:**
```
┌─────────────────────────────────────┐
│ 🏨 Hotel Icon (Purple) │
│ Đăng ký tài khoản │
│ Tạo tài khoản mới để đặt phòng... │
├─────────────────────────────────────┤
│ ┌───────────────────────────────┐ │
│ │ [Error message if any] │ │
│ ├───────────────────────────────┤ │
│ │ Họ và tên │ │
│ │ [👤 Nguyễn Văn A ] │ │
│ ├───────────────────────────────┤ │
│ │ Email │ │
│ │ [📧 email@example.com ] │ │
│ ├───────────────────────────────┤ │
│ │ Số điện thoại (Tùy chọn) │ │
│ │ [📱 0123456789 ] │ │
│ ├───────────────────────────────┤ │
│ │ Mật khẩu │ │
│ │ [🔒 •••••••• 👁️] │ │
│ │ ▓▓▓▓▓░░░░░ 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 │ │
│ ├───────────────────────────────┤ │
│ │ Xác nhận mật khẩu │ │
│ │ [🔒 •••••••• 👁️] │ │
│ ├───────────────────────────────┤ │
│ │ [👤 Đăng ký] │ │
│ └───────────────────────────────┘ │
│ Đã có tài khoản? Đăng nhập ngay │
│ │
│ Điều khoản & Chính sách bảo mật │
└─────────────────────────────────────┘
```
## 🔗 Integration
### API Endpoint
```
POST /api/auth/register
```
### Request Body
```typescript
{
name: string; // "Nguyễn Văn A"
email: string; // "user@example.com"
password: string; // "Password123@"
phone?: string; // "0123456789" (optional)
}
```
### Response (Success)
```json
{
"status": "success",
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"name": "Nguyễn Văn A",
"email": "user@example.com",
"phone": "0123456789",
"role": "customer"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
### useAuthStore Integration
```typescript
const { register: registerUser, isLoading, error, clearError } =
useAuthStore();
await registerUser({
name: data.name,
email: data.email,
password: data.password,
phone: data.phone,
});
// After success:
navigate('/login', { replace: true });
```
## 🧪 Test Scenarios
### 1. Validation Tests
**Test Case 1: Empty form**
```
Action: Submit empty form
Expected: Show validation errors for name, email, password
```
**Test Case 2: Invalid email**
```
Input: email = "notanemail"
Expected: "Email không hợp lệ"
```
**Test Case 3: Short name**
```
Input: name = "A"
Expected: "Họ tên phải có ít nhất 2 ký tự"
```
**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
```
**Test Case 5: Password mismatch**
```
Input:
password = "Password123@"
confirmPassword = "Password456@"
Expected: "Mật khẩu không khớp"
```
**Test Case 6: Invalid phone**
```
Input: phone = "123"
Expected: "Số điện thoại không hợp lệ"
```
### 2. UX Tests
**Test Case 7: Password strength indicator**
```
Input: Type password character by character
Expected:
- Progress bar animates
- Color changes: red → orange → yellow → blue → green
- Checkmarks appear as requirements met
```
**Test Case 8: Show/hide password**
```
Action: Click eye icon on password field
Expected: Password text becomes visible/hidden
Action: Click eye icon on confirmPassword field
Expected: Confirm password text becomes visible/hidden
```
**Test Case 9: Loading state**
```
Action: Submit valid form
Expected:
- Button disabled
- Spinner shows
- Text changes to "Đang xử lý..."
```
### 3. Integration Tests
**Test Case 10: Successful registration**
```
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."
3. Redirect to /login
```
**Test Case 11: Email already exists**
```
Input: email = "existing@example.com"
Expected:
- Error message: "Email already registered"
- Toast error displayed
- Form remains on page
```
**Test Case 12: Network error**
```
Scenario: Server offline
Expected:
- Error message: "Đăng ký thất bại. Vui lòng thử lại."
- Toast error displayed
```
## 📊 Password Strength Algorithm
```typescript
function getPasswordStrength(pwd: string) {
let strength = 0;
if (pwd.length >= 8) strength++; // +1
if (/[a-z]/.test(pwd)) strength++; // +1
if (/[A-Z]/.test(pwd)) strength++; // +1
if (/\d/.test(pwd)) strength++; // +1
if (/[@$!%*?&]/.test(pwd)) strength++; // +1
return {
strength: 0-5,
label: ['Rất yếu', 'Yếu', 'Trung bình', 'Mạnh', 'Rất mạnh'][strength],
color: ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-blue-500', 'bg-green-500'][strength]
};
}
```
## 🎨 Component Structure
```tsx
RegisterPage/
├── Header Section
├── Hotel Icon (purple)
├── Title: "Đăng ký tài khoản"
└── Subtitle
├── Form Container (white card)
├── Error Alert (conditional)
├── Name Input
├── Email Input
├── Phone Input (optional)
├── Password Input
├── Show/Hide Toggle
├── Strength Indicator
└── Requirements Checklist
├── Confirm Password Input
└── Show/Hide Toggle
└── Submit Button (with loading)
├── Login Link
└── "Đã có tài khoản? Đăng nhập ngay"
└── Footer Links
├── Terms of Service
└── Privacy Policy
```
## 🔐 Security Features
### 1. Password Validation
- Min 8 characters
- Requires: uppercase, lowercase, number, special char
- Visual feedback for strength
### 2. Confirm Password
- Must match original password
- Prevents typos
### 3. Client-side Validation
- Immediate feedback
- Prevents invalid API calls
- Better UX
### 4. Server-side Validation
- Backend also validates all fields
- Checks email uniqueness
- Password hashed with bcrypt
## 📝 Code Quality
**TypeScript**: Full type safety
**React Hook Form**: Optimized re-renders
**Yup Validation**: Schema-based validation
**Component Composition**: Reusable PasswordRequirement component
**Accessibility**: Proper labels, IDs, autocomplete
**Error Handling**: Try-catch, user-friendly messages
**Loading States**: Visual feedback during async operations
**Responsive Design**: Works on mobile and desktop
**80 chars/line**: Code formatting standard
## 🚀 Usage
### Navigate to Register Page
```bash
http://localhost:5173/register
```
### Example Registration
```typescript
Name: "Nguyễn Văn A"
Email: "nguyenvana@example.com"
Phone: "0123456789"
Password: "Password123@"
Confirm: "Password123@"
Submit Success Redirect to /login
```
## 🔄 Flow Diagram
```
User visits /register
Fill in form fields
Real-time validation (Yup)
Password strength updates live
Submit button clicked
Frontend validation passes
Call useAuthStore.register()
API POST /api/auth/register
┌───────┴───────┐
↓ ↓
Success Failure
↓ ↓
Toast success Toast error
↓ ↓
Navigate Stay on page
to /login Show errors
```
## ✅ Checklist
- [x] ✅ Create RegisterPage.tsx component
- [x] ✅ Implement React Hook Form
- [x] ✅ Add Yup validation schema
- [x] ✅ Add 5 form fields (name, email, phone, password, confirmPassword)
- [x] ✅ Show/hide password toggle (2 fields)
- [x] ✅ Password strength indicator
- [x] ✅ Real-time requirements checker
- [x] ✅ Loading state
- [x] ✅ Error display (inline + global)
- [x] ✅ Integration with useAuthStore
- [x] ✅ Redirect to /login after success
- [x] ✅ Toast notifications
- [x] ✅ Add route to App.tsx
- [x] ✅ Responsive design
- [x] ✅ Purple color scheme
- [x] ✅ Icons integration (Lucide React)
- [x] ✅ Terms & Privacy links
## 📚 Related Files
- `client/src/pages/auth/LoginPage.tsx` - Login form (same design pattern)
- `client/src/utils/validationSchemas.ts` - Validation schemas
- `client/src/store/useAuthStore.ts` - Auth state management
- `client/src/services/api/authService.ts` - API calls
- `client/src/App.tsx` - Route configuration
---
**Status:** ✅ Chức năng 5 hoàn thành
**Next:** Chức năng 6 - Forgot Password
**Test URL:** http://localhost:5173/register

484
docs/ROUTE_PROTECTION.md Normal file
View File

@@ -0,0 +1,484 @@
# Route Protection Documentation
## Chức năng 8: Phân quyền & Bảo vệ Route
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
---
## 1. ProtectedRoute
### Mục đích
Bảo vệ các route yêu cầu authentication (đăng nhập).
### Cách hoạt động
```tsx
// File: client/src/components/auth/ProtectedRoute.tsx
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children
}) => {
const location = useLocation();
const { isAuthenticated, isLoading } = useAuthStore();
// 1. Nếu đang loading → hiển thị spinner
if (isLoading) {
return <LoadingScreen />;
}
// 2. Nếu chưa đăng nhập → redirect /login
if (!isAuthenticated) {
return (
<Navigate
to="/login"
state={{ from: location }} // Lưu location để quay lại
replace
/>
);
}
// 3. Đã đăng nhập → cho phép truy cập
return <>{children}</>;
};
```
### Sử dụng trong App.tsx
```tsx
import { ProtectedRoute } from './components/auth';
// Route yêu cầu đăng nhập
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/bookings"
element={
<ProtectedRoute>
<BookingListPage />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
```
### 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`
---
## 2. AdminRoute
### Mục đích
Bảo vệ các route chỉ dành cho Admin (role-based access).
### Cách hoạt động
```tsx
// File: client/src/components/auth/AdminRoute.tsx
const AdminRoute: React.FC<AdminRouteProps> = ({
children
}) => {
const location = useLocation();
const { isAuthenticated, userInfo, isLoading } = useAuthStore();
// 1. Nếu đang loading → hiển thị spinner
if (isLoading) {
return <LoadingScreen />;
}
// 2. Nếu chưa đăng nhập → redirect /login
if (!isAuthenticated) {
return (
<Navigate
to="/login"
state={{ from: location }}
replace
/>
);
}
// 3. Nếu không phải admin → redirect /
const isAdmin = userInfo?.role === 'admin';
if (!isAdmin) {
return <Navigate to="/" replace />;
}
// 4. Là admin → cho phép truy cập
return <>{children}</>;
};
```
### Sử dụng trong App.tsx
```tsx
import { AdminRoute } from './components/auth';
// Route chỉ dành cho Admin
<Route
path="/admin"
element={
<AdminRoute>
<AdminLayout />
</AdminRoute>
}
>
<Route path="dashboard" element={<AdminDashboard />} />
<Route path="users" element={<UserManagement />} />
<Route path="rooms" element={<RoomManagement />} />
<Route path="bookings" element={<BookingManagement />} />
<Route path="settings" element={<Settings />} />
</Route>
```
### Luồng hoạt động
#### 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 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 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
---
## 3. Cấu trúc Route trong App.tsx
```tsx
function App() {
return (
<BrowserRouter>
<Routes>
{/* Public Routes - Không cần đăng nhập */}
<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 */}
<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 */}
<Route path="/" element={<LayoutMain />}>
<Route
path="dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="bookings"
element={
<ProtectedRoute>
<BookingListPage />
</ProtectedRoute>
}
/>
<Route
path="profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
</Route>
{/* Admin Routes - Chỉ Admin */}
<Route
path="/admin"
element={
<AdminRoute>
<AdminLayout />
</AdminRoute>
}
>
<Route index element={<Navigate to="dashboard" replace />} />
<Route path="dashboard" element={<AdminDashboard />} />
<Route path="users" element={<UserManagement />} />
<Route path="rooms" element={<RoomManagement />} />
<Route path="bookings" element={<BookingManagement />} />
<Route path="payments" element={<PaymentManagement />} />
<Route path="services" element={<ServiceManagement />} />
<Route path="promotions" element={<PromotionManagement />} />
<Route path="banners" element={<BannerManagement />} />
<Route path="reports" element={<Reports />} />
<Route path="settings" element={<Settings />} />
</Route>
{/* 404 Route */}
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
```
---
## 4. Tích hợp với Zustand Store
### useAuthStore State
```tsx
// File: client/src/store/useAuthStore.ts
const useAuthStore = create<AuthStore>((set) => ({
// State
token: localStorage.getItem('token') || null,
refreshToken: localStorage.getItem('refreshToken') || null,
userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null'),
isAuthenticated: !!localStorage.getItem('token'),
isLoading: false,
error: null,
// Actions
login: async (credentials) => { ... },
logout: () => {
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
localStorage.removeItem('userInfo');
set({
token: null,
refreshToken: null,
userInfo: null,
isAuthenticated: false,
error: null
});
},
// ... other actions
}));
```
### 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)
---
## 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)
```tsx
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<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>
</div>
</div>
);
}
```
---
## 6. Redirect After Login
### LoginPage implementation
```tsx
const LoginPage: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const { login, isLoading } = useAuthStore();
const from = location.state?.from?.pathname || '/dashboard';
const onSubmit = async (data: LoginFormData) => {
try {
await login(data);
// Redirect về page ban đầu hoặc /dashboard
navigate(from, { replace: true });
toast.success('Đăng nhập thành công!');
} catch (error) {
toast.error('Đăng nhập thất bại!');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* ... form fields */}
</form>
);
};
```
### Flow
1. User truy cập `/bookings` (protected)
2. Redirect `/login?from=/bookings`
3. Login thành công
4. Redirect về `/bookings` (page ban đầu)
---
## 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
### Test Case 2: ProtectedRoute - Authenticated
**Given**: User đã đăng nhập
**When**: Truy cập `/dashboard`
**Then**: Hiển thị DashboardPage thành công
### Test Case 3: AdminRoute - Not Admin
**Given**: User có role='customer'
**When**: Truy cập `/admin`
**Then**: Redirect về `/` (trang chủ)
### 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
### Test Case 5: Loading State
**Given**: Auth đang initialize
**When**: isLoading === true
**Then**: Hiển thị loading spinner
**And**: Không redirect
---
## 8. Security Best Practices
### ✅ Đã Implement
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
### ⚠️ 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`
- Never trust client-side role → Always verify on server
### Backend Middleware Example
```javascript
// server/src/middlewares/auth.js
const auth = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({
message: 'Unauthorized'
});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findByPk(decoded.userId);
next();
} catch (error) {
res.status(401).json({ message: 'Invalid token' });
}
};
const adminOnly = (req, res, next) => {
if (req.user.role !== 'admin') {
return res.status(403).json({
message: 'Forbidden: Admin only'
});
}
next();
};
// Usage
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
### 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
### 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
### 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
---
## 10. Summary
### ProtectedRoute
- ✅ Kiểm tra `isAuthenticated`
- ✅ Redirect `/login` nếu chưa đăng nhập
- ✅ Lưu location state để quay lại
- ✅ 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
- ✅ 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)
---
**Chức năng 8 hoàn thành! ✅**

View File

@@ -0,0 +1,363 @@
# ✅ Server Backend - Setup Complete
## 📦 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
### Controllers
4. **`src/controllers/authController.js`** - Authentication logic
- register()
- login()
- refreshAccessToken()
- logout()
- getProfile()
### Routes
5. **`src/routes/authRoutes.js`** - Auth endpoints
- POST /api/auth/register
- POST /api/auth/login
- POST /api/auth/refresh-token
- POST /api/auth/logout
- GET /api/auth/profile
6. **`src/routes/userRoutes.js`** - User endpoints (placeholder)
7. **`src/routes/roomRoutes.js`** - Room endpoints (placeholder)
8. **`src/routes/bookingRoutes.js`** - Booking endpoints (placeholder)
### Middleware
9. **`src/middlewares/auth.js`** - JWT authentication
- authenticateToken()
- authorizeRoles()
10. **`src/middlewares/errorHandler.js`** - Global error handling
11. **`src/middlewares/validate.js`** - Validation middleware
### Validators
12. **`src/validators/authValidator.js`** - Validation rules
- registerValidation
- loginValidation
- refreshTokenValidation
### Documentation
13. **`README.md`** - Server documentation
14. **`QUICK_START.md`** - Quick setup guide
## 🎯 Features Implemented
### Security
- ✅ JWT authentication (access + refresh tokens)
- ✅ Password hashing with bcrypt (10 rounds)
- ✅ Helmet security headers
- ✅ CORS configuration
- ✅ Rate limiting (100 req/15min)
- ✅ Input validation with express-validator
### Authentication Flow
- ✅ Register with email/password validation
- ✅ Login with email/password
- ✅ Remember me (7 days vs 1 day token expiry)
- ✅ Refresh token mechanism
- ✅ Logout with token cleanup
- ✅ Protected routes with JWT
### Error Handling
- ✅ Global error handler
- ✅ Sequelize error handling
- ✅ JWT error handling
- ✅ Validation error formatting
- ✅ Development vs production error responses
### Validation Rules
**Register:**
```javascript
- name: 2-50 chars, required
- email: valid email format, required, unique
- password: min 8 chars, uppercase + lowercase + number
- phone: 10-11 digits, optional
```
**Login:**
```javascript
- email: valid email format, required
- password: required
- rememberMe: boolean, optional
```
## 🗄️ Database Integration
### Models Used
- ✅ User model (full_name, email, password, phone, role_id)
- ✅ Role model (for role-based access)
- ✅ RefreshToken model (token storage)
### Associations
- User belongsTo Role
- User hasMany RefreshToken
- User hasMany Booking
## 🔐 JWT Configuration
```javascript
Access Token:
- Secret: JWT_SECRET
- Expiry: 1h
- Payload: { userId }
Refresh Token:
- Secret: JWT_REFRESH_SECRET
- Expiry: 7d (remember me) or 1d (normal)
- Payload: { userId }
- Stored in database (refresh_tokens table)
```
## 📡 API Endpoints Ready
### Public Endpoints
```
✅ GET /health - Health check
✅ POST /api/auth/register - User registration
✅ POST /api/auth/login - User login
✅ POST /api/auth/refresh-token - Refresh access token
✅ POST /api/auth/logout - User logout
```
### Protected Endpoints
```
✅ GET /api/auth/profile - Get user profile (JWT required)
🔜 GET /api/users - Get all users (Admin only)
🔜 GET /api/rooms - Get rooms (Public)
🔜 GET /api/bookings - Get bookings (User's own)
```
## 🧪 Request/Response Examples
### Login Request
```json
POST /api/auth/login
{
"email": "user@example.com",
"password": "Password123",
"rememberMe": true
}
```
### Login Response (Success)
```json
{
"status": "success",
"message": "Login successful",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "user@example.com",
"phone": "0123456789",
"role": "customer"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
### Login Response (Error)
```json
{
"status": "error",
"message": "Invalid email or password"
}
```
### Validation Error
```json
{
"status": "error",
"message": "Validation failed",
"errors": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
}
```
## 🔧 Middleware Stack
```javascript
1. helmet() - Security headers
2. compression() - Response compression
3. cors() - CORS handling
4. express.json() - JSON body parser
5. morgan() - Request logging
6. rateLimit() - Rate limiting
7. Routes - API routes
8. errorHandler() - Global error handler
```
## ⚙️ Environment Variables
```bash
# Server
PORT=3000
NODE_ENV=development
# Database
DB_HOST=localhost
DB_USER=root
DB_PASS=
DB_NAME=hotel_db
# JWT
JWT_SECRET=your-super-secret-jwt-key
JWT_EXPIRES_IN=1h
JWT_REFRESH_SECRET=your-refresh-token-secret
JWT_REFRESH_EXPIRES_IN=7d
# Client
CLIENT_URL=http://localhost:5173
```
## 📋 Next Steps (Manual)
### 1. Database Setup
```bash
# Tạo database
mysql -u root -p
CREATE DATABASE hotel_db;
# Chạy migrations
cd d:/hotel-booking/server
npm run migrate
# (Optional) Seed data
npm run seed
```
### 2. Start Server
```bash
cd d:/hotel-booking/server
npm run dev
```
Expected output:
```
✅ Database connection established successfully
📊 Database models synced
🚀 Server running on port 3000
🌐 Environment: development
🔗 API: http://localhost:3000/api
🏥 Health: http://localhost:3000/health
```
### 3. Test API
**Health Check:**
```bash
curl http://localhost:3000/health
```
**Register:**
```bash
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "Test User",
"email": "test@example.com",
"password": "Test1234",
"phone": "0123456789"
}'
```
**Login:**
```bash
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test1234",
"rememberMe": true
}'
```
### 4. Test with Frontend
1. Make sure client .env has:
```
VITE_API_URL=http://localhost:3000
```
2. Start frontend:
```bash
cd d:/hotel-booking/client
npm run dev
```
3. Navigate to: http://localhost:5173/login
4. Try to login with credentials from step 3
## 🎯 Integration Checklist
- [ ] MySQL server running
- [ ] Database `hotel_db` created
- [ ] Migrations executed successfully
- [ ] Server running on port 3000
- [ ] Health endpoint returns 200
- [ ] Frontend .env configured
- [ ] Frontend running on port 5173
- [ ] Login API working with Postman/curl
- [ ] Frontend login form connects to backend
- [ ] JWT tokens stored in localStorage
- [ ] Protected routes work after login
## 🔍 Troubleshooting
### Server won't start
- Check MySQL is running
- Check .env database credentials
- Check port 3000 is not in use
### Login returns 401
- Check email/password are correct
- Check user exists in database
- Check JWT_SECRET in .env
### CORS errors in frontend
- Check CLIENT_URL in server .env
- Check frontend is using correct API URL
- Check server CORS middleware
### Token expired immediately
- Check JWT_EXPIRES_IN in .env
- Check system clock is correct
- Check refresh token mechanism
## 📚 Code Quality
- ✅ Proper error handling with try-catch
- ✅ Async/await pattern
- ✅ Input validation before processing
- ✅ Password never returned in responses
- ✅ Proper HTTP status codes
- ✅ Consistent API response format
- ✅ Environment-based logging
- ✅ Rate limiting for security
- ✅ Token expiry management
- ✅ Database connection pooling
---
**Status:** ✅ Backend Setup Complete
**Next:** Run migrations → Start server → Test login from frontend
**Time to complete:** ~5 minutes manual setup
🎉 **Congratulations!** Backend API is ready for testing!

View File

@@ -0,0 +1,590 @@
# Test Scenarios - Route Protection (Chức năng 8)
## Test Setup
### Test Users
```javascript
// Admin user
{
email: "admin@hotel.com",
password: "Admin@123",
role: "admin"
}
// Customer user
{
email: "customer@hotel.com",
password: "Customer@123",
role: "customer"
}
// Staff user
{
email: "staff@hotel.com",
password: "Staff@123",
role: "staff"
}
```
---
## Test Case 1: ProtectedRoute - Unauthenticated User
### Objective
Verify that unauthenticated users cannot access protected routes.
### Steps
1. Open browser (incognito mode)
2. Navigate to `http://localhost:5173/dashboard`
### Expected Result
- ✅ Redirected to `/login`
- ✅ URL shows `/login`
- ✅ Login form displayed
- ✅ No error in console
- ✅ Location state contains `from: '/dashboard'`
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 2: ProtectedRoute - Authenticated User
### Objective
Verify that authenticated users can access protected routes.
### Steps
1. Login as customer (`customer@hotel.com` / `Customer@123`)
2. Navigate to `http://localhost:5173/dashboard`
### Expected Result
- ✅ Dashboard page displayed successfully
- ✅ URL shows `/dashboard`
- ✅ No redirect to login
- ✅ Navbar shows user info
- ✅ Logout button visible
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 3: ProtectedRoute - Redirect After Login
### Objective
Verify that users are redirected back to original page after login.
### Steps
1. Logout (if logged in)
2. Navigate to `http://localhost:5173/bookings` (protected)
3. Should redirect to `/login`
4. Login with valid credentials
5. Observe redirect behavior
### Expected Result
- ✅ Step 2: Redirected to `/login`
- ✅ Step 4: Login successful
- ✅ Step 5: Redirected back to `/bookings` (original page)
- ✅ URL shows `/bookings`
- ✅ BookingListPage displayed
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 4: AdminRoute - Unauthenticated User
### Objective
Verify that unauthenticated users cannot access admin routes.
### Steps
1. Open browser (incognito mode)
2. Navigate to `http://localhost:5173/admin`
### Expected Result
- ✅ Redirected to `/login`
- ✅ URL shows `/login`
- ✅ Login form displayed
- ✅ Location state contains `from: '/admin'`
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 5: AdminRoute - Customer User
### Objective
Verify that non-admin users (customer) cannot access admin routes.
### Steps
1. Login as customer (`customer@hotel.com` / `Customer@123`)
2. Navigate to `http://localhost:5173/admin`
### Expected Result
- ✅ Redirected to `/` (homepage)
- ✅ URL shows `/`
- ✅ Homepage displayed
- ✅ No admin content visible
- ✅ Toast message: "Access denied" (optional)
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 6: AdminRoute - Staff User
### Objective
Verify that staff users cannot access admin routes.
### Steps
1. Login as staff (`staff@hotel.com` / `Staff@123`)
2. Navigate to `http://localhost:5173/admin`
### Expected Result
- ✅ Redirected to `/` (homepage)
- ✅ URL shows `/`
- ✅ Homepage displayed
- ✅ No admin content visible
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 7: AdminRoute - Admin User
### Objective
Verify that admin users can access admin routes.
### Steps
1. Login as admin (`admin@hotel.com` / `Admin@123`)
2. Navigate to `http://localhost:5173/admin`
### Expected Result
- ✅ Admin dashboard displayed
- ✅ URL shows `/admin` or `/admin/dashboard`
- ✅ Admin sidebar visible
- ✅ Admin navigation menu visible
- ✅ Can access all admin sub-routes:
- `/admin/users`
- `/admin/rooms`
- `/admin/bookings`
- `/admin/payments`
- `/admin/services`
- `/admin/promotions`
- `/admin/banners`
- `/admin/reports`
- `/admin/settings`
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 8: Loading State - Slow Network
### Objective
Verify that loading state is displayed during auth check.
### Steps
1. Open DevTools → Network tab
2. Set throttling to "Slow 3G"
3. Refresh page at protected route
4. Observe loading behavior
### Expected Result
- ✅ Loading spinner displayed
- ✅ Text "Đang tải..." or "Đang xác thực..." visible
- ✅ No flash of redirect
- ✅ Smooth transition after loading
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 9: Token Expiration
### Objective
Verify that expired tokens are handled correctly.
### Steps
1. Login successfully
2. Manually modify token in localStorage to invalid value:
```javascript
localStorage.setItem('token', 'invalid-token')
```
3. Navigate to protected route `/dashboard`
4. Observe behavior
### Expected Result
- ✅ Redirected to `/login`
- ✅ Toast message: "Session expired" (optional)
- ✅ Location state saved
- ✅ Can login again successfully
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 10: Direct URL Access - Admin
### Objective
Verify that direct URL access to admin routes is blocked for non-admin.
### Steps
1. Login as customer
2. Type in address bar: `http://localhost:5173/admin/users`
3. Press Enter
### Expected Result
- ✅ Redirected to `/` (homepage)
- ✅ Cannot access admin/users
- ✅ No admin content visible
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 11: Nested Admin Routes
### Objective
Verify that all nested admin routes are protected.
### Steps
1. Login as admin
2. Navigate to each admin route:
- `/admin/dashboard`
- `/admin/users`
- `/admin/rooms`
- `/admin/bookings`
- `/admin/payments`
- `/admin/services`
- `/admin/promotions`
- `/admin/banners`
- `/admin/reports`
- `/admin/settings`
### Expected Result
- ✅ All routes accessible
- ✅ Each page displays correctly
- ✅ No errors in console
- ✅ Admin layout consistent
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 12: Public Routes Access
### Objective
Verify that public routes are accessible without authentication.
### Steps
1. Logout (if logged in)
2. Navigate to public routes:
- `/` (homepage)
- `/rooms`
- `/about`
- `/login`
- `/register`
- `/forgot-password`
### Expected Result
- ✅ All public routes accessible
- ✅ No redirect to login
- ✅ Pages display correctly
- ✅ No errors in console
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 13: Logout Behavior
### Objective
Verify that logout clears auth and redirects properly.
### Steps
1. Login as any user
2. Navigate to protected route `/dashboard`
3. Click logout button
4. Observe behavior
### Expected Result
- ✅ User logged out
- ✅ Token removed from localStorage
- ✅ userInfo removed from localStorage
- ✅ Redirected to `/` or `/login`
- ✅ Navbar shows "Đăng nhập" button
- ✅ Cannot access protected routes anymore
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 14: Multiple Tabs - Logout Sync
### Objective
Verify that logout in one tab affects other tabs.
### Steps
1. Login in Tab 1
2. Open Tab 2 with same site
3. Logout in Tab 1
4. Switch to Tab 2
5. Try to access protected route
### Expected Result
- ✅ Tab 2 detects logout
- ✅ Redirected to login
- ✅ Auth state synced across tabs
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 15: Browser Refresh - Auth Persistence
### Objective
Verify that auth state persists after browser refresh.
### Steps
1. Login successfully
2. Navigate to protected route `/dashboard`
3. Press F5 (refresh)
4. Observe behavior
### Expected Result
- ✅ User still logged in
- ✅ Dashboard displays correctly
- ✅ No redirect to login
- ✅ userInfo still available
- ✅ Token still in localStorage
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 16: Role Change Detection
### Objective
Verify that role changes are detected and enforced.
### Steps
1. Login as admin
2. Access `/admin/dashboard` successfully
3. Manually change role in localStorage:
```javascript
const user = JSON.parse(localStorage.getItem('userInfo'))
user.role = 'customer'
localStorage.setItem('userInfo', JSON.stringify(user))
```
4. Refresh page
5. Try to access `/admin`
### Expected Result
- ✅ Redirected to `/` (homepage)
- ✅ Cannot access admin routes
- ✅ Role validation working
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 17: 404 Route
### Objective
Verify that non-existent routes show 404 page.
### Steps
1. Navigate to `http://localhost:5173/non-existent-route`
### Expected Result
- ✅ 404 page displayed
- ✅ "404 - Không tìm thấy trang" message
- ✅ URL shows `/non-existent-route`
- ✅ No errors in console
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Test Case 18: Remember Me Feature
### Objective
Verify that "Remember Me" extends token duration.
### Steps
1. Login with "Remember Me" checked
2. Check token expiry in localStorage
3. Close browser
4. Reopen browser after 1 day
5. Navigate to protected route
### Expected Result
- ✅ User still logged in (if < 7 days)
- ✅ No need to login again
- ✅ Token valid
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Performance Tests
### Test Case 19: Route Navigation Speed
### Steps
1. Login as user
2. Navigate between routes:
- `/dashboard` → `/bookings` → `/profile` → `/rooms`
3. Measure navigation time
### Expected Result
- ✅ Navigation < 200ms
- ✅ No flickering
- ✅ Smooth transitions
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Security Tests
### Test Case 20: XSS in Route Params
### Steps
1. Navigate to `/reset-password/<script>alert('xss')</script>`
2. Observe behavior
### Expected Result
- ✅ No alert popup
- ✅ Script not executed
- ✅ Token treated as string
### Actual Result
- [ ] Pass
- [ ] Fail (describe issue):
---
## Summary
| Test Case | Status | Notes |
|-----------|--------|-------|
| TC-01: ProtectedRoute - Unauth | ⏳ | |
| TC-02: ProtectedRoute - Auth | ⏳ | |
| TC-03: Redirect After Login | ⏳ | |
| TC-04: AdminRoute - Unauth | ⏳ | |
| TC-05: AdminRoute - Customer | ⏳ | |
| TC-06: AdminRoute - Staff | ⏳ | |
| TC-07: AdminRoute - Admin | ⏳ | |
| TC-08: Loading State | ⏳ | |
| TC-09: Token Expiration | ⏳ | |
| TC-10: Direct URL - Admin | ⏳ | |
| TC-11: Nested Admin Routes | ⏳ | |
| TC-12: Public Routes | ⏳ | |
| TC-13: Logout | ⏳ | |
| TC-14: Multi-tab Sync | ⏳ | |
| TC-15: Refresh Persistence | ⏳ | |
| TC-16: Role Change | ⏳ | |
| TC-17: 404 Route | ⏳ | |
| TC-18: Remember Me | ⏳ | |
| TC-19: Performance | ⏳ | |
| TC-20: XSS Security | ⏳ | |
**Overall Status**: ⏳ Pending Testing
---
## How to Run Tests
### Manual Testing
```bash
# 1. Start server
cd server
npm run dev
# 2. Start client
cd client
npm run dev
# 3. Open browser
http://localhost:5173
# 4. Follow test cases above
```
### Automated Testing (Optional - Future)
```bash
# Install testing dependencies
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
# Run tests
npm test
```
---
## Bug Report Template
```markdown
### Bug: [Short description]
**Test Case**: TC-XX
**Steps to Reproduce**:
1. Step 1
2. Step 2
3. Step 3
**Expected**: [What should happen]
**Actual**: [What actually happened]
**Environment**:
- Browser: Chrome 120
- OS: Windows 11
- Node: v18.17.0
**Screenshots**: [Attach if applicable]
**Console Errors**: [Copy-paste errors]
**Priority**: High/Medium/Low
```

View File

@@ -0,0 +1,2 @@
VNPay integration docs removed per repository cleanup.
This file has been replaced with a short note because VNPay is no longer used in this project.

0
docs/VNPAY_SETUP.md Normal file
View File

207
docs/tasks-admin.md Normal file
View File

@@ -0,0 +1,207 @@
# 🏨 Hotel Management & Booking System
## Bản Phân Tích Dành Cho Admin (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.
---
# 2. Phân tích chức năng dành cho Admin
---
## 2.1 Setup Module (Thiết lập hệ thống)
### 2.1.1 Setup Rooms (Quản lý phòng)
**Vai trò sử dụng:** 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
**Thông tin phòng gồm:**
- RoomID
- Description
- Type (VIP, DELUX, SUITE, …)
- Size (Single, Double, …)
- 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
---
### 2.1.2 Setup Services (Quản lý dịch vụ)
**Vai trò:** Manager, Admin
**Chức năng:**
- Thêm dịch vụ
- Chỉnh sửa
- Xoá dịch vụ
**Thông tin dịch vụ:**
- Service ID
- Service Name
- Description
- Unit (giờ, suất, lần,…)
- Price
**Quy tắc:**
- Validate tất cả dữ liệu nhập
---
### 2.1.3 Promotion Management (Quản lý khuyến mãi)
**Vai trò:** Manager, Admin
**Chức năng:**
- Add promotion
- Edit promotion
- Delete promotion
- Promotion có thể áp dụng bằng code hoặc tự động trong booking
**Thông tin:**
- ID
- Name
- Description
- Value (phần trăm hoặc số tiền)
---
# 2.2 Operation Module (Vận hành khách sạn)
---
## 2.2.1 Booking Management
**Vai trò:** 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
- Checkout
---
## 2.2.2 Check-in
**Vai trò:** 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
---
## 2.2.3 Use Services (Khách đăng ký sử dụng dịch vụ)
**Vai trò:** 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
---
## 2.2.4 Check-out
**Vai trò:** 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
---
# 2.3 Report Module (Báo cáo)
**Vai trò:** 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:
- Excel
- PDF
**Nội dung báo cáo:**
- Booking ID
- Customer Name
- Room
- Total Amount
- Booking Date
- Status
- Revenue Summary
---
# 2.4 System Administration Module (Quản trị hệ thống)
---
## 2.4.1 User Management
**Vai trò:** Admin
**Chức năng:**
- Add user
- Edit user
- Delete user
- View user detail
- List tất cả user
- Gán role (Admin, Manager, Staff)
---
## 2.4.2 Security
**Chức năng bảo mật của hệ thống:**
### Roles được định nghĩa:
| Role | Quyền |
|------|-------|
| **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 |
### 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
---
# 3. Tóm tắt theo góc nhìn Admin
| Module | Quyền Admin | Nội dung |
|--------|-------------|----------|
| 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 |
---
# 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.

View File

@@ -0,0 +1,228 @@
# Authentication
## Chức năng 1: Layout cơ bản (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.
#### Nhiệm vụ chi tiết
- Tạo thư mục:
```
src/components/layouts/
```
- Bao gồm:
+ 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.
### 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.
---
## Chức năng 2: Cấu hình Routing (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.
#### Nhiệm vụ chi tiết
- Cấu trúc route chính:
```
<Route path="/" element={<LayoutMain />}>
<Route index element={<HomePage />} />
<Route path="rooms" element={<RoomListPage />} />
<Route path="bookings" element={<BookingListPage />} />
</Route>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
<Route path="/admin/*" element={<AdminRoute><AdminModule /></AdminRoute>} />
```
- Dùng ProtectedRoute và AdminRoute để kiểm tra:
+ 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.
---
## Chức năng 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).
#### Nhiệm vụ chi tiết
- Tạo src/stores/useAuthStore.js
- Cấu trúc:
```
const useAuthStore = create((set) => ({
token: localStorage.getItem("token") || null,
userInfo: JSON.parse(localStorage.getItem("userInfo")) || null,
isAuthenticated: !!localStorage.getItem("token"),
login: async (credentials) => { ... },
logout: () => { ... },
setUser: (user) => { ... },
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.
### 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.
---
## Chức năng 4: Form Login
### Mục tiêu
Cho phép người dùng đăng nhập hệ thống.
#### 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ự
- 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.
### 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.
---
## Chức năng 5: Form Register
### Mục tiêu
Cho phép người dùng đăng ký tài khoản mới.
#### 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
- 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.
### 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.
---
## Chức năng 6: Quên mật khẩu (Forgot Password)
### Mục tiêu
Cung cấp chức năng gửi email reset mật khẩu.
#### Nhiệm vụ chi tiết
- Tạo 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:
```
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.
---
## Chức năng 7: Đặt lại mật khẩu (Reset Password)
### Mục tiêu
Cho phép người dùng đổi mật khẩu thông qua link email.
#### 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
- 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.
### 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).
---
## Chức năng 8: Phân quyền & Bảo vệ route (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.
#### Nhiệm vụ chi tiết
- Tạo component ProtectedRoute.jsx:
```
const ProtectedRoute = ({ children }) => {
const { isAuthenticated } = useAuthStore();
const location = useLocation();
return isAuthenticated ? children : <Navigate to="/login" state={{ from: location }} replace />;
};
```
- Tạo 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ị.

View File

@@ -0,0 +1,182 @@
# Review System
## Chức năng 1: HomePage Trang chủ hiển thị phòng nổi bật
### 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.
#### Nhiệm vụ chi tiết
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:
```
GET /api/rooms?featured=true
```
- Hiển thị 46 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.
### 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 46 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.
---
## Chức năng 2: RoomListPage Danh sách & Bộ lọc phòng
### 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.
#### Nhiệm vụ chi tiết
1. Route: /rooms
2. Bộ lọc (component RoomFilter):
- Trường lọc: loại phòng, giá minmax, số người.
- Khi submit → gọi 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.
### 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ỡ.
---
## Chức năng 3: RoomDetailPage Chi tiết phòng & Đánh giá
### 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á.
#### Nhiệm vụ chi tiết
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
3. Review Section:
- Lấy danh sách review đã duyệt:
```
GET /api/rooms/:id/reviews
```
- Nếu người dùng đã từng đặt phòng:
```
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.
### 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.
---
## Chức năng 4: SearchRoom Tìm phòng trống
### 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.
#### 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.
2. API:
```
GET /api/rooms/available?from=&to=&type=
```
3. Validate:
- 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.
### 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.
---
## Chức năng 5: Wishlist Danh sách yêu thích
### 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.
#### Nhiệm vụ chi tiết
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
```
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.
### 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ệ.
---
## Chức năng 6: Tối ưu UI/UX & Performance
### 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.
#### 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.
4. Responsive layout:
- Desktop: 34 cột
- Tablet: 2 cột
- Mobile: 1 cột
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.
### 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.
---

View File

@@ -0,0 +1,144 @@
# Booking & Payment
## Chức năng 1: BookingPage Form Đặt phòng
### 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.
#### Nhiệm vụ chi tiết
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)
- 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
- Không bỏ trống ngày
- Có chọn phương thức thanh toán
5. Tính tổng tiền:
```
total = room.price * (số ngày ở)
```
6. Nút “Đặt phòng”:
- Loading spinner
- Disable khi đang submit
7. Nếu chưa đăng nhập → redirect /login.
---
## Chức năng 2: Booking API (Giao tiếp backend)
### Mục tiêu
Kết nối và xử lý API liên quan đến đặt phòng.
#### Nhiệm vụ chi tiết
🔧 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
```
🔄 Luồng xử lý:
1. Frontend gọi POST /api/bookings
2. Backend kiểm tra phòng trống:
```
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.
---
## Chức năng 3: BookingSuccess Trang kết quả sau đặt phòng
### 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.
#### Nhiệm vụ chi tiết
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.
---
## Chức năng 4: MyBookingsPage Danh sách đơn đặt của người
### Mục tiêu
Hiển thị toàn bộ các đơn đặt của user + cho phép hủy đơn.
#### Nhiệm vụ chi tiết
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:
🟡 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:
- 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.
---
## Chức năng 5: Thanh toán (Giả lập 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.
#### 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"
---
## Chức năng 6: UX & Hiệu năng
### Mục tiêu
Cải thiện trải nghiệm người dùng và tính trực quan.
#### 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
---

View File

@@ -0,0 +1,140 @@
# Review System
## Chức năng 1: ReviewPage Trang người dùng đánh giá phòng
### 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.
#### Nhiệm vụ chi tiết
1. Route: /reviews
2. Gọi API:
```
GET /api/bookings/me → Lấy danh sách phòng người dùng đã đặt.
POST /api/reviews → Gửi đánh giá.
```
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 (⭐ 15)
- Textarea nhập nội dung bình luận
- Nút “Gửi đánh giá”
5. Validate:
- Rating bắt buộc (15)
- 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”.
### 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.
---
## Chức năng 2: RoomDetailPage Hiển thị danh sách đánh giá
### Mục tiêu
Hiển thị danh sách các đánh giá đã được admin duyệt cho từng phòng.
#### Nhiệm vụ chi tiết
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.”
### 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.
---
## Chức năng 3: AdminReviewPage Trang quản trị đánh giá
### 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.
#### Nhiệm vụ chi tiết
1. Route: /admin/reviews
2. API:
```
GET /api/reviews
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).
### 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.
---
## Chức năng 4: Bảo mật & Logic hiển thị
### 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.
#### 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
### 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.
---
## Chức năng 5: UX & Hiển thị tổng quan
### 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á.
#### 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:
```
new Date(createdAt).toLocaleDateString('vi-VN')
```
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.
### 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.
---

View File

@@ -0,0 +1,2 @@
VNPay testing guide removed per repository cleanup.
This document was deleted because the project no longer integrates with VNPay.