- Xin lỗi, đã có lỗi xảy ra. Vui lòng thử lại
- hoặc liên hệ hỗ trợ nếu vấn đề vẫn tiếp diễn.
+ Sorry, an error has occurred. Please try again
+ or contact support if the problem persists.
{process.env.NODE_ENV === 'development' &&
@@ -96,7 +96,7 @@ class ErrorBoundary extends Component {
text-red-700 cursor-pointer
hover:text-red-800"
>
- Chi tiết lỗi
+ Error Details
- Tải lại trang
+ Reload Page
window.location.href = '/'}
@@ -128,7 +128,7 @@ class ErrorBoundary extends Component {
hover:bg-gray-300 transition-colors
font-semibold"
>
- Về trang chủ
+ Go to Home
diff --git a/client/src/components/common/Pagination.tsx b/client/src/components/common/Pagination.tsx
index 7c94547d..e9d5bba0 100644
--- a/client/src/components/common/Pagination.tsx
+++ b/client/src/components/common/Pagination.tsx
@@ -69,7 +69,7 @@ const Pagination: React.FC = ({
: 'text-gray-700 hover:bg-gray-50'
}`}
>
- Trước
+ Previous
onPageChange(currentPage + 1)}
@@ -80,7 +80,7 @@ const Pagination: React.FC = ({
: 'text-gray-700 hover:bg-gray-50'
}`}
>
- Sau
+ Next
@@ -88,10 +88,10 @@ const Pagination: React.FC = ({
- Hiển thị{' '}
- {startItem} đến{' '}
- {endItem} trong tổng số{' '}
- {totalItems || 0} kết quả
+ Showing{' '}
+ {startItem} to{' '}
+ {endItem} of{' '}
+ {totalItems || 0} results
diff --git a/client/src/components/layout/SidebarAdmin.tsx b/client/src/components/layout/SidebarAdmin.tsx
index 1bdb4254..e3784be2 100644
--- a/client/src/components/layout/SidebarAdmin.tsx
+++ b/client/src/components/layout/SidebarAdmin.tsx
@@ -53,32 +53,32 @@ const SidebarAdmin: React.FC
= ({
{
path: '/admin/users',
icon: Users,
- label: 'Người dùng'
+ label: 'Users'
},
{
path: '/admin/rooms',
icon: Hotel,
- label: 'Phòng'
+ label: 'Rooms'
},
{
path: '/admin/bookings',
icon: Calendar,
- label: 'Đặt phòng'
+ label: 'Bookings'
},
{
path: '/admin/payments',
icon: CreditCard,
- label: 'Thanh toán'
+ label: 'Payments'
},
{
path: '/admin/services',
icon: Settings,
- label: 'Dịch vụ'
+ label: 'Services'
},
{
path: '/admin/promotions',
icon: Tag,
- label: 'Khuyến mãi'
+ label: 'Promotions'
},
{
path: '/admin/check-in',
@@ -93,22 +93,22 @@ const SidebarAdmin: React.FC = ({
{
path: '/admin/reviews',
icon: Star,
- label: 'Đánh giá'
+ label: 'Reviews'
},
{
path: '/admin/banners',
icon: Image,
- label: 'Banner'
+ label: 'Banners'
},
{
path: '/admin/reports',
icon: BarChart3,
- label: 'Báo cáo'
+ label: 'Reports'
},
{
path: '/admin/settings',
icon: FileText,
- label: 'Cài đặt'
+ label: 'Settings'
},
];
diff --git a/client/src/components/rooms/BannerCarousel.tsx b/client/src/components/rooms/BannerCarousel.tsx
index 41f40198..34a4e26b 100644
--- a/client/src/components/rooms/BannerCarousel.tsx
+++ b/client/src/components/rooms/BannerCarousel.tsx
@@ -43,7 +43,7 @@ const BannerCarousel: React.FC = ({
// Default fallback banner if no banners provided
const defaultBanner = {
id: 0,
- title: 'Chào mừng đến với Hotel Booking',
+ title: 'Welcome to Hotel Booking',
image_url: '/images/default-banner.jpg',
position: 'home',
display_order: 0,
diff --git a/client/src/components/rooms/FavoriteButton.tsx b/client/src/components/rooms/FavoriteButton.tsx
index 2eb0a9b2..599e26e4 100644
--- a/client/src/components/rooms/FavoriteButton.tsx
+++ b/client/src/components/rooms/FavoriteButton.tsx
@@ -61,8 +61,8 @@ const FavoriteButton: React.FC = ({
};
const tooltipText = favorited
- ? 'Bỏ yêu thích'
- : 'Thêm vào yêu thích';
+ ? 'Remove from favorites'
+ : 'Add to favorites';
return (
diff --git a/client/src/examples/useAuthStoreExamples.tsx b/client/src/examples/useAuthStoreExamples.tsx
index 5864e545..bdab79c0 100644
--- a/client/src/examples/useAuthStoreExamples.tsx
+++ b/client/src/examples/useAuthStoreExamples.tsx
@@ -1,8 +1,8 @@
/**
- * Example: Cách sử dụng useAuthStore trong components
+ * Example: How to use useAuthStore in components
*
- * File này chỉ để tham khảo, không được sử dụng
- * trong production
+ * This file is for reference only, should not be used
+ * in production
*/
import { useNavigate } from 'react-router-dom';
@@ -23,7 +23,7 @@ export const LoginExample = () => {
await login({ email, password });
navigate('/dashboard');
} catch (error) {
- // Error đã được xử lý trong store
+ // Error has been handled in store
console.error('Login failed:', error);
}
};
@@ -38,7 +38,7 @@ export const LoginExample = () => {
)}
disabled={isLoading}
>
- {isLoading ? 'Đang xử lý...' : 'Đăng nhập'}
+ {isLoading ? 'Processing...' : 'Login'}
);
@@ -70,7 +70,7 @@ export const RegisterExample = () => {
onClick={handleRegister}
disabled={isLoading}
>
- {isLoading ? 'Đang xử lý...' : 'Đăng ký'}
+ {isLoading ? 'Processing...' : 'Register'}
);
};
@@ -82,13 +82,13 @@ export const UserProfileExample = () => {
const { userInfo, isAuthenticated } = useAuthStore();
if (!isAuthenticated) {
- return Vui lòng đăng nhập
;
+ return Please login
;
}
return (
-
Thông tin người dùng
-
Tên: {userInfo?.name}
+
User Information
+
Name: {userInfo?.name}
Email: {userInfo?.email}
Role: {userInfo?.role}
{userInfo?.avatar && (
@@ -118,7 +118,7 @@ export const LogoutButtonExample = () => {
onClick={handleLogout}
disabled={isLoading}
>
- {isLoading ? 'Đang xử lý...' : 'Đăng xuất'}
+ {isLoading ? 'Processing...' : 'Logout'}
);
};
@@ -134,7 +134,7 @@ export const ForgotPasswordExample = () => {
) => {
try {
await forgotPassword({ email });
- // Toast sẽ hiển thị thông báo thành công
+ // Toast will display success message
} catch (error) {
console.error('Forgot password failed:', error);
}
@@ -147,7 +147,7 @@ export const ForgotPasswordExample = () => {
}
disabled={isLoading}
>
- Gửi email đặt lại mật khẩu
+ Send password reset email
);
};
@@ -185,7 +185,7 @@ export const ResetPasswordExample = () => {
}
disabled={isLoading}
>
- Đặt lại mật khẩu
+ Reset Password
);
};
@@ -222,14 +222,14 @@ export const AuthStateCheckExample = () => {
} = useAuthStore();
if (isLoading) {
- return
Đang tải...
;
+ return
Loading...
;
}
if (!isAuthenticated || !token) {
- return
Bạn chưa đăng nhập
;
+ return
You are not logged in
;
}
- return
Bạn đã đăng nhập
;
+ return
You are logged in
;
};
// ============================================
@@ -250,7 +250,7 @@ export const UpdateUserInfoExample = () => {
return (
- Cập nhật thông tin
+ Update Information
);
};
@@ -270,7 +270,7 @@ export const ErrorHandlingExample = () => {
onClick={clearError}
className="mt-2 text-sm text-red-600"
>
- Đóng
+ Close
);
diff --git a/client/src/pages/admin/BookingManagementPage.tsx b/client/src/pages/admin/BookingManagementPage.tsx
index d13b5a34..275c49aa 100644
--- a/client/src/pages/admin/BookingManagementPage.tsx
+++ b/client/src/pages/admin/BookingManagementPage.tsx
@@ -301,7 +301,7 @@ const BookingManagementPage: React.FC = () => {
onClick={() => setShowDetailModal(false)}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300"
>
- Đóng
+ Close
diff --git a/client/src/pages/admin/CheckInPage.tsx b/client/src/pages/admin/CheckInPage.tsx
index 95a7babf..1210ee30 100644
--- a/client/src/pages/admin/CheckInPage.tsx
+++ b/client/src/pages/admin/CheckInPage.tsx
@@ -229,7 +229,7 @@ const CheckInPage: React.FC = () => {
type="text"
value={actualRoomNumber}
onChange={(e) => setActualRoomNumber(e.target.value)}
- placeholder="VD: 101, 202, 305"
+ placeholder="e.g: 101, 202, 305"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
/>
@@ -271,7 +271,7 @@ const CheckInPage: React.FC = () => {
value={guest.name}
onChange={(e) => handleGuestChange(index, 'name', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
- placeholder="Nguyễn Văn A"
+ placeholder="John Doe"
/>
diff --git a/client/src/pages/admin/DashboardPage.tsx b/client/src/pages/admin/DashboardPage.tsx
index e5fdb892..61eecccc 100644
--- a/client/src/pages/admin/DashboardPage.tsx
+++ b/client/src/pages/admin/DashboardPage.tsx
@@ -191,7 +191,7 @@ const DashboardPage: React.FC = () => {
))}
) : (
- Không có dữ liệu
+ No data available
)}
@@ -227,7 +227,7 @@ const DashboardPage: React.FC = () => {
})}
) : (
- Không có dữ liệu
+ No data available
)}
@@ -236,7 +236,7 @@ const DashboardPage: React.FC = () => {
{/* Top Rooms */}
-
Top phòng được đặt
+
Top Booked Rooms
{stats?.top_rooms && stats.top_rooms.length > 0 ? (
{stats.top_rooms.map((room, index) => (
@@ -246,8 +246,8 @@ const DashboardPage: React.FC = () => {
{index + 1}
-
Phòng {room.room_number}
-
{room.bookings} lượt đặt
+
Room {room.room_number}
+
{room.bookings} bookings
@@ -257,20 +257,20 @@ const DashboardPage: React.FC = () => {
))}
) : (
-
Không có dữ liệu
+
No data available
)}
{/* Service Usage */}
-
Dịch vụ được sử dụng
+
Services Used
{stats?.service_usage && stats.service_usage.length > 0 ? (
{stats.service_usage.map((service) => (
{service.service_name}
-
{service.usage_count} lần sử dụng
+
{service.usage_count} times used
{formatCurrency(service.total_revenue)}
@@ -279,7 +279,7 @@ const DashboardPage: React.FC = () => {
))}
) : (
-
Không có dữ liệu
+
No data available
)}
diff --git a/client/src/pages/admin/PromotionManagementPage.tsx b/client/src/pages/admin/PromotionManagementPage.tsx
index c93e0817..0d81cd31 100644
--- a/client/src/pages/admin/PromotionManagementPage.tsx
+++ b/client/src/pages/admin/PromotionManagementPage.tsx
@@ -152,8 +152,8 @@ const PromotionManagementPage: React.FC = () => {
-
Quản lý khuyến mãi
-
Quản lý mã giảm giá và chương trình khuyến mãi
+
Promotion Management
+
Manage discount codes and promotion programs
{
@@ -163,7 +163,7 @@ const PromotionManagementPage: React.FC = () => {
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center gap-2"
>
- Thêm khuyến mãi
+ Add Promotion
@@ -175,7 +175,7 @@ const PromotionManagementPage: React.FC = () => {
setFilters({ ...filters, search: e.target.value })}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
@@ -187,18 +187,18 @@ const PromotionManagementPage: React.FC = () => {
onChange={(e) => setFilters({ ...filters, type: e.target.value })}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
-
Tất cả loại
-
Phần trăm
-
Số tiền cố định
+
All Types
+
Percentage
+
Fixed Amount
setFilters({ ...filters, status: e.target.value })}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
- Tất cả trạng thái
- Hoạt động
- Ngừng
+ All Statuses
+ Active
+ Inactive
@@ -209,25 +209,25 @@ const PromotionManagementPage: React.FC = () => {
- Mã code
+ Code
- Tên chương trình
+ Program Name
- Giá trị
+ Value
- Thời gian
+ Period
- Đã dùng
+ Used
- Trạng thái
+ Status
- Thao tác
+ Actions
@@ -301,7 +301,7 @@ const PromotionManagementPage: React.FC = () => {
- {editingPromotion ? 'Cập nhật khuyến mãi' : 'Thêm khuyến mãi mới'}
+ {editingPromotion ? 'Update Promotion' : 'Add New Promotion'}
setShowModal(false)}>
@@ -311,27 +311,27 @@ const PromotionManagementPage: React.FC = () => {
- Mã code *
+ Code *
setFormData({ ...formData, code: e.target.value.toUpperCase() })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
- placeholder="VD: SUMMER2024"
+ placeholder="e.g: SUMMER2024"
required
/>
- Tên chương trình *
+ Program Name *
setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
- placeholder="VD: Giảm giá mùa hè"
+ placeholder="e.g: Summer Sale"
required
/>
@@ -339,34 +339,34 @@ const PromotionManagementPage: React.FC = () => {
- Mô tả
+ Description
- Loại giảm giá *
+ Discount Type *
setFormData({ ...formData, discount_type: e.target.value as 'percentage' | 'fixed' })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
- Phần trăm (%)
- Số tiền cố định (VND)
+ Percentage (%)
+ Fixed Amount (VND)
- Giá trị giảm *
+ Discount Value *
{
- Giá trị đơn tối thiểu (VND)
+ Minimum Order Value (VND)
{
- Giảm tối đa (VND)
+ Maximum Discount (VND)
{
- Ngày bắt đầu *
+ Start Date *
{
diff --git a/client/src/pages/admin/ReviewManagementPage.tsx b/client/src/pages/admin/ReviewManagementPage.tsx
index ce53858a..da8f99cd 100644
--- a/client/src/pages/admin/ReviewManagementPage.tsx
+++ b/client/src/pages/admin/ReviewManagementPage.tsx
@@ -150,7 +150,7 @@ const ReviewManagementPage: React.FC = () => {
- Phòng {review.room?.room_number} - {review.room?.room_type?.name}
+ Room {review.room?.room_number} - {review.room?.room_type?.name}
@@ -173,14 +173,14 @@ const ReviewManagementPage: React.FC = () => {
handleApprove(review.id)}
className="text-green-600 hover:text-green-900 mr-3"
- title="Phê duyệt"
+ title="Approve"
>
✓
handleReject(review.id)}
className="text-red-600 hover:text-red-900"
- title="Từ chối"
+ title="Reject"
>
✕
diff --git a/client/src/pages/admin/RoomManagementPage.tsx b/client/src/pages/admin/RoomManagementPage.tsx
index b766ac98..217d6d0c 100644
--- a/client/src/pages/admin/RoomManagementPage.tsx
+++ b/client/src/pages/admin/RoomManagementPage.tsx
@@ -155,29 +155,29 @@ const RoomManagementPage: React.FC = () => {
const handleDeleteImage = async (imageUrl: string) => {
if (!editingRoom) return;
- if (!window.confirm('Bạn có chắc muốn xóa ảnh này?')) return;
+ if (!window.confirm('Are you sure you want to delete this image?')) return;
try {
await apiClient.delete(`/rooms/${editingRoom.id}/images`, {
data: { imageUrl },
});
- toast.success('Xóa ảnh thành công');
+ toast.success('Image deleted successfully');
fetchRooms();
// Refresh editing room data
const response = await roomService.getRoomById(editingRoom.id);
setEditingRoom(response.data.room);
} catch (error: any) {
- toast.error(error.response?.data?.message || 'Không thể xóa ảnh');
+ toast.error(error.response?.data?.message || 'Unable to delete image');
}
};
const getStatusBadge = (status: string) => {
const badges: Record = {
- available: { bg: 'bg-green-100', text: 'text-green-800', label: 'Trống' },
- occupied: { bg: 'bg-blue-100', text: 'text-blue-800', label: 'Đang sử dụng' },
- maintenance: { bg: 'bg-yellow-100', text: 'text-yellow-800', label: 'Bảo trì' },
+ available: { bg: 'bg-green-100', text: 'text-green-800', label: 'Available' },
+ occupied: { bg: 'bg-blue-100', text: 'text-blue-800', label: 'Occupied' },
+ maintenance: { bg: 'bg-yellow-100', text: 'text-yellow-800', label: 'Maintenance' },
};
const badge = badges[status] || badges.available;
return (
@@ -196,8 +196,8 @@ const RoomManagementPage: React.FC = () => {
{/* Header */}
-
Quản lý phòng
-
Quản lý thông tin phòng khách sạn
+
Room Management
+
Manage hotel room information
{
@@ -207,7 +207,7 @@ const RoomManagementPage: React.FC = () => {
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
- Thêm phòng
+ Add Room
@@ -218,7 +218,7 @@ const RoomManagementPage: React.FC = () => {
setFilters({ ...filters, search: e.target.value })}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
@@ -229,17 +229,17 @@ const RoomManagementPage: React.FC = () => {
onChange={(e) => setFilters({ ...filters, status: e.target.value })}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
- Tất cả trạng thái
- Trống
- Đang sử dụng
- Bảo trì
+ All Statuses
+ Available
+ Occupied
+ Maintenance
setFilters({ ...filters, type: e.target.value })}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
- Tất cả loại phòng
+ All Room Types
Standard
Deluxe
Suite
@@ -253,25 +253,25 @@ const RoomManagementPage: React.FC = () => {
- Số phòng
+ Room Number
- Loại phòng
+ Room Type
- Tầng
+ Floor
- Giá
+ Price
- Trạng thái
+ Status
- Nổi bật
+ Featured
- Thao tác
+ Actions
@@ -285,7 +285,7 @@ const RoomManagementPage: React.FC = () => {
{room.room_type?.name || 'N/A'}
- Tầng {room.floor}
+ Floor {room.floor}
@@ -340,7 +340,7 @@ const RoomManagementPage: React.FC = () => {
- {editingRoom ? 'Cập nhật phòng' : 'Thêm phòng mới'}
+ {editingRoom ? 'Update Room' : 'Add New Room'}
setShowModal(false)}>
@@ -351,7 +351,7 @@ const RoomManagementPage: React.FC = () => {
- Số phòng
+ Room Number
{
- Tầng
+ Floor
{
- Loại phòng
+ Room Type
{
- Trạng thái
+ Status
{
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
required
>
- Trống
- Đang sử dụng
- Bảo trì
+ Available
+ Occupied
+ Maintenance
@@ -417,7 +417,7 @@ const RoomManagementPage: React.FC = () => {
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
- Phòng nổi bật
+ Featured Room
@@ -427,13 +427,13 @@ const RoomManagementPage: React.FC = () => {
onClick={() => setShowModal(false)}
className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
>
- Hủy
+ Cancel
- {editingRoom ? 'Cập nhật' : 'Thêm'}
+ {editingRoom ? 'Update' : 'Add'}
@@ -443,13 +443,13 @@ const RoomManagementPage: React.FC = () => {
- Hình ảnh phòng
+ Room Images
{/* Current Images */}
{editingRoom.room_type?.images && editingRoom.room_type.images.length > 0 && (
-
Ảnh hiện tại:
+
Current Images:
{editingRoom.room_type.images.map((img, index) => (
@@ -474,7 +474,7 @@ const RoomManagementPage: React.FC = () => {
{/* Upload New Images */}
- Thêm ảnh mới (tối đa 5 ảnh):
+ Add New Images (max 5 images):
{
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2"
>
- {uploadingImages ? 'Đang tải...' : 'Upload'}
+ {uploadingImages ? 'Uploading...' : 'Upload'}
{selectedFiles.length > 0 && (
- {selectedFiles.length} file đã chọn
+ {selectedFiles.length} file(s) selected
)}
diff --git a/client/src/pages/admin/ServiceManagementPage.tsx b/client/src/pages/admin/ServiceManagementPage.tsx
index 47fc2d1c..a8f4ea8c 100644
--- a/client/src/pages/admin/ServiceManagementPage.tsx
+++ b/client/src/pages/admin/ServiceManagementPage.tsx
@@ -23,7 +23,7 @@ const ServiceManagementPage: React.FC = () => {
name: '',
description: '',
price: 0,
- unit: 'lần',
+ unit: 'time',
status: 'active' as 'active' | 'inactive',
});
@@ -155,9 +155,9 @@ const ServiceManagementPage: React.FC = () => {
onChange={(e) => setFilters({ ...filters, status: e.target.value })}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
-
Tất cả trạng thái
-
Hoạt động
-
Tạm dừng
+
All Statuses
+
Active
+
Inactive
@@ -167,22 +167,22 @@ const ServiceManagementPage: React.FC = () => {
- Tên dịch vụ
+ Service Name
- Mô tả
+ Description
- Giá
+ Price
- Đơn vị
+ Unit
- Trạng thái
+ Status
- Thao tác
+ Actions
@@ -207,7 +207,7 @@ const ServiceManagementPage: React.FC = () => {
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}>
- {service.status === 'active' ? 'Hoạt động' : 'Tạm dừng'}
+ {service.status === 'active' ? 'Active' : 'Inactive'}
@@ -242,7 +242,7 @@ const ServiceManagementPage: React.FC = () => {
- {editingService ? 'Cập nhật dịch vụ' : 'Thêm dịch vụ mới'}
+ {editingService ? 'Update Service' : 'Add New Service'}
setShowModal(false)}>
@@ -251,7 +251,7 @@ const ServiceManagementPage: React.FC = () => {
diff --git a/client/src/pages/auth/ForgotPasswordPage.tsx b/client/src/pages/auth/ForgotPasswordPage.tsx
index 3d7f3ecb..a93958f4 100644
--- a/client/src/pages/auth/ForgotPasswordPage.tsx
+++ b/client/src/pages/auth/ForgotPasswordPage.tsx
@@ -45,7 +45,7 @@ const ForgotPasswordPage: React.FC = () => {
// Show success state
setIsSuccess(true);
} catch (error) {
- // Error đã được xử lý trong store
+ // Error has been handled in store
console.error('Forgot password error:', error);
}
};
diff --git a/client/src/pages/auth/LoginPage.tsx b/client/src/pages/auth/LoginPage.tsx
index dbefe48c..cbb3c33a 100644
--- a/client/src/pages/auth/LoginPage.tsx
+++ b/client/src/pages/auth/LoginPage.tsx
@@ -49,12 +49,12 @@ const LoginPage: React.FC = () => {
rememberMe: data.rememberMe,
});
- // 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 });
} catch (error) {
- // Error đã được xử lý trong store
+ // Error has been handled in store
console.error('Login error:', error);
}
};
diff --git a/client/src/pages/auth/RegisterPage.tsx b/client/src/pages/auth/RegisterPage.tsx
index 3dc36348..63da6c1b 100644
--- a/client/src/pages/auth/RegisterPage.tsx
+++ b/client/src/pages/auth/RegisterPage.tsx
@@ -47,7 +47,7 @@ const RegisterPage: React.FC = () => {
},
});
- // Watch password để hiển thị password strength
+ // Watch password to display password strength
const password = watch('password');
// Password strength checker
@@ -88,7 +88,7 @@ const RegisterPage: React.FC = () => {
// Redirect to login page
navigate('/login', { replace: true });
} catch (error) {
- // Error đã được xử lý trong store
+ // Error has been handled in store
console.error('Register error:', error);
}
};
diff --git a/client/src/pages/auth/ResetPasswordPage.tsx b/client/src/pages/auth/ResetPasswordPage.tsx
index fa02287e..92ed7b87 100644
--- a/client/src/pages/auth/ResetPasswordPage.tsx
+++ b/client/src/pages/auth/ResetPasswordPage.tsx
@@ -44,7 +44,7 @@ const ResetPasswordPage: React.FC = () => {
},
});
- // Watch password để hiển thị password strength
+ // Watch password to display password strength
const password = watch('password');
// Check if token exists
@@ -66,11 +66,11 @@ const ResetPasswordPage: React.FC = () => {
if (/[@$!%*?&]/.test(pwd)) strength++;
const labels = [
- { label: 'Rất yếu', color: 'bg-red-500' },
- { label: 'Yếu', color: 'bg-orange-500' },
- { label: 'Trung bình', color: 'bg-yellow-500' },
- { label: 'Mạnh', color: 'bg-blue-500' },
- { label: 'Rất mạnh', color: 'bg-green-500' },
+ { label: 'Very Weak', color: 'bg-red-500' },
+ { label: 'Weak', color: 'bg-orange-500' },
+ { label: 'Medium', color: 'bg-yellow-500' },
+ { label: 'Strong', color: 'bg-blue-500' },
+ { label: 'Very Strong', color: 'bg-green-500' },
];
return { strength, ...labels[strength] };
@@ -100,7 +100,7 @@ const ResetPasswordPage: React.FC = () => {
navigate('/login', { replace: true });
}, 3000);
} catch (error) {
- // Error đã được xử lý trong store
+ // Error has been handled in store
console.error('Reset password error:', error);
}
};
@@ -129,12 +129,12 @@ const ResetPasswordPage: React.FC = () => {
- {isSuccess ? 'Hoàn tất!' : 'Đặt lại mật khẩu'}
+ {isSuccess ? 'Complete!' : 'Reset Password'}
{isSuccess
- ? 'Mật khẩu đã được đặt lại thành công'
- : 'Nhập mật khẩu mới cho tài khoản của bạn'}
+ ? 'Password has been reset successfully'
+ : 'Enter a new password for your account'}
@@ -160,13 +160,13 @@ const ResetPasswordPage: React.FC = () => {
className="text-xl font-semibold
text-gray-900"
>
- Đặt lại mật khẩu thành công!
+ Password reset successful!
- Mật khẩu của bạn đã được cập nhật.
+ Your password has been updated.
- Bạn có thể đăng nhập bằng mật khẩu mới.
+ You can now login with your new password.
@@ -175,7 +175,7 @@ const ResetPasswordPage: React.FC = () => {
rounded-lg p-4"
>
- Đang chuyển hướng đến trang đăng nhập...
+ Redirecting to login page...
{
transition-colors"
>
- Đăng nhập ngay
+ Login Now
) : (
@@ -224,7 +224,7 @@ const ResetPasswordPage: React.FC = () => {
{isReuseError
- ? 'Mật khẩu mới phải khác mật khẩu cũ'
+ ? 'New password must be different from old password'
: error}
{isTokenError && (
@@ -234,7 +234,7 @@ const ResetPasswordPage: React.FC = () => {
font-medium underline
hover:text-yellow-900"
>
- Yêu cầu link mới
+ Request new link
)}
@@ -248,7 +248,7 @@ const ResetPasswordPage: React.FC = () => {
className="block text-sm font-medium
text-gray-700 mb-2"
>
- Mật khẩu mới
+ New Password
{
= 8}
- text="Ít nhất 8 ký tự"
+ text="At least 8 characters"
/>
@@ -368,7 +368,7 @@ const ResetPasswordPage: React.FC = () => {
className="block text-sm font-medium
text-gray-700 mb-2"
>
- Xác nhận mật khẩu
+ Confirm Password
{
className="animate-spin -ml-1 mr-2
h-5 w-5"
/>
- Đang xử lý...
+ Processing...
>
) : (
<>
- Đặt lại mật khẩu
+ Reset Password
>
)}
@@ -473,7 +473,7 @@ const ResetPasswordPage: React.FC = () => {
text-indigo-600 hover:text-indigo-500
transition-colors"
>
- Quay lại đăng nhập
+ Back to Login
@@ -492,16 +492,16 @@ const ResetPasswordPage: React.FC = () => {
gap-2"
>
- Bảo mật
+ Security
- Link đặt lại chỉ có hiệu lực trong 1 giờ
- Mật khẩu được mã hóa an toàn
+ Reset link is valid for 1 hour only
+ Password is securely encrypted
- Nếu link hết hạn, hãy yêu cầu link mới
+ If the link expires, please request a new link
diff --git a/client/src/pages/customer/BookingDetailPage.tsx b/client/src/pages/customer/BookingDetailPage.tsx
index 22f649e7..0838c542 100644
--- a/client/src/pages/customer/BookingDetailPage.tsx
+++ b/client/src/pages/customer/BookingDetailPage.tsx
@@ -50,7 +50,7 @@ const BookingDetailPage: React.FC = () => {
useEffect(() => {
if (!isAuthenticated) {
toast.error(
- 'Vui lòng đăng nhập để xem chi tiết đặt phòng'
+ 'Please login to view booking details'
);
navigate('/login', {
state: { from: `/bookings/${id}` }
@@ -79,14 +79,14 @@ const BookingDetailPage: React.FC = () => {
setBooking(response.data.booking);
} else {
throw new Error(
- 'Không thể tải thông tin đặt phòng'
+ 'Unable to load booking information'
);
}
} catch (err: any) {
console.error('Error fetching booking:', err);
const message =
err.response?.data?.message ||
- 'Không thể tải thông tin đặt phòng';
+ 'Unable to load booking information';
setError(message);
toast.error(message);
} finally {
@@ -98,12 +98,12 @@ const BookingDetailPage: React.FC = () => {
if (!booking) return;
const confirmed = window.confirm(
- `Bạn có chắc muốn hủy đặt phòng ` +
+ `Are you sure you want to cancel booking ` +
`${booking.booking_number}?\n\n` +
- `⚠️ Lưu ý:\n` +
- `- Bạn sẽ bị giữ 20% giá trị đơn\n` +
- `- 80% còn lại sẽ được hoàn trả\n` +
- `- Trạng thái phòng sẽ được cập nhật về "available"`
+ `⚠️ Note:\n` +
+ `- You will be charged 20% of the order value\n` +
+ `- The remaining 80% will be refunded\n` +
+ `- Room status will be updated to "available"`
);
if (!confirmed) return;
@@ -115,8 +115,7 @@ const BookingDetailPage: React.FC = () => {
if (response.success) {
toast.success(
- `✅ Đã hủy đặt phòng ${booking.booking_number} ` +
- `thành công!`
+ `✅ Booking ${booking.booking_number} cancelled successfully!`
);
// Update local state
@@ -128,14 +127,14 @@ const BookingDetailPage: React.FC = () => {
} else {
throw new Error(
response.message ||
- 'Không thể hủy đặt phòng'
+ 'Unable to cancel booking'
);
}
} catch (err: any) {
console.error('Error cancelling booking:', err);
const message =
err.response?.data?.message ||
- 'Không thể hủy đặt phòng. Vui lòng thử lại.';
+ 'Unable to cancel booking. Please try again.';
toast.error(message);
} finally {
setCancelling(false);
@@ -164,31 +163,31 @@ const BookingDetailPage: React.FC = () => {
return {
icon: Clock,
color: 'bg-yellow-100 text-yellow-800',
- text: 'Chờ xác nhận',
+ text: 'Pending confirmation',
};
case 'confirmed':
return {
icon: CheckCircle,
color: 'bg-green-100 text-green-800',
- text: 'Đã xác nhận',
+ text: 'Confirmed',
};
case 'cancelled':
return {
icon: XCircle,
color: 'bg-red-100 text-red-800',
- text: 'Đã hủy',
+ text: 'Cancelled',
};
case 'checked_in':
return {
icon: DoorOpen,
color: 'bg-blue-100 text-blue-800',
- text: 'Đã nhận phòng',
+ text: 'Checked in',
};
case 'checked_out':
return {
icon: DoorClosed,
color: 'bg-gray-100 text-gray-800',
- text: 'Đã trả phòng',
+ text: 'Checked out',
};
default:
return {
@@ -207,7 +206,7 @@ const BookingDetailPage: React.FC = () => {
};
if (loading) {
- return
;
+ return
;
}
if (error || !booking) {
@@ -223,7 +222,7 @@ const BookingDetailPage: React.FC = () => {
mx-auto mb-3"
/>
- {error || 'Không tìm thấy đặt phòng'}
+ {error || 'Booking not found'}
navigate('/bookings')}
@@ -231,7 +230,7 @@ const BookingDetailPage: React.FC = () => {
text-white rounded-lg
hover:bg-red-700 transition-colors"
>
- Quay lại danh sách
+ Back to list
@@ -255,7 +254,7 @@ const BookingDetailPage: React.FC = () => {
mb-6 transition-colors"
>
- Quay lại danh sách
+ Back to list
{/* Page Title */}
@@ -263,7 +262,7 @@ const BookingDetailPage: React.FC = () => {
mb-6"
>
- Chi tiết đặt phòng
+ Booking Details
{/* Status Badge */}
@@ -284,7 +283,7 @@ const BookingDetailPage: React.FC = () => {
- Mã đặt phòng
+ Booking Number
{
- Thông tin phòng
+ Room Information
{roomType && (
@@ -328,25 +327,25 @@ const BookingDetailPage: React.FC = () => {
- Phòng {room?.room_number} -
- Tầng {room?.floor}
+ Room {room?.room_number} -
+ Floor {room?.floor}
- Sức chứa
+ Capacity
- Tối đa {roomType.capacity} người
+ Max {roomType.capacity} guests
- Giá phòng
+ Room Price
- {formatPrice(roomType.base_price)}/đêm
+ {formatPrice(roomType.base_price)}/night
@@ -362,7 +361,7 @@ const BookingDetailPage: React.FC = () => {
- Chi tiết đặt phòng
+ Booking Details
@@ -373,7 +372,7 @@ const BookingDetailPage: React.FC = () => {
- Ngày nhận phòng
+ Check-in Date
{formatDate(booking.check_in_date)}
@@ -382,7 +381,7 @@ const BookingDetailPage: React.FC = () => {
- Ngày trả phòng
+ Check-out Date
{formatDate(booking.check_out_date)}
@@ -394,10 +393,10 @@ const BookingDetailPage: React.FC = () => {
- Số người
+ Number of Guests
- {booking.guest_count} người
+ {booking.guest_count} guest(s)
@@ -406,7 +405,7 @@ const BookingDetailPage: React.FC = () => {
- Ghi chú
+ Notes
{booking.notes}
@@ -418,16 +417,16 @@ const BookingDetailPage: React.FC = () => {
- Phương thức thanh toán
+ Payment Method
{booking.payment_method === 'cash'
- ? '💵 Thanh toán tại chỗ'
- : '🏦 Chuyển khoản ngân hàng'}
+ ? '💵 Pay at hotel'
+ : '🏦 Bank transfer'}
- Trạng thái:
+ Status:
{
- Tổng thanh toán
+ Total Payment
{
- Thông tin khách hàng
+ Customer Information
- Họ và tên
+ Full Name
{booking.guest_info.full_name}
@@ -488,7 +487,7 @@ const BookingDetailPage: React.FC = () => {
- Số điện thoại
+ Phone Number
{booking.guest_info.phone}
@@ -512,25 +511,25 @@ const BookingDetailPage: React.FC = () => {
/>
- Thông tin chuyển khoản
+ Bank Transfer Information
- Ngân hàng:
+ Bank:
Vietcombank (VCB)
- Số tài khoản:
+ Account Number:
0123456789
- Chủ tài khoản:
+ Account Holder:
KHACH SAN ABC
- Số tiền: {' '}
+ Amount: {' '}
@@ -538,7 +537,7 @@ const BookingDetailPage: React.FC = () => {
- Nội dung: {' '}
+ Content: {' '}
@@ -594,7 +593,7 @@ const BookingDetailPage: React.FC = () => {
font-semibold"
>
- Xác nhận thanh toán
+ Confirm Payment
)}
@@ -614,12 +613,12 @@ const BookingDetailPage: React.FC = () => {
- Đang hủy...
+ Cancelling...
>
) : (
<>
- Hủy đặt phòng
+ Cancel Booking
>
)}
@@ -633,7 +632,7 @@ const BookingDetailPage: React.FC = () => {
hover:bg-gray-700 transition-colors
font-semibold"
>
- Quay lại danh sách
+ Back to list
diff --git a/client/src/pages/customer/BookingListPage.tsx b/client/src/pages/customer/BookingListPage.tsx
index fe5c9f64..1bf24266 100644
--- a/client/src/pages/customer/BookingListPage.tsx
+++ b/client/src/pages/customer/BookingListPage.tsx
@@ -6,10 +6,10 @@ const BookingListPage: React.FC = () => {
- Lịch sử đặt phòng
+ Booking History
- Quản lý và theo dõi các đặt phòng của bạn
+ Manage and track your bookings
@@ -30,13 +30,13 @@ const BookingListPage: React.FC = () => {
- Phòng {booking}01 - Deluxe
+ Room {booking}01 - Deluxe
- Đã xác nhận
+ Confirmed
@@ -51,7 +51,7 @@ const BookingListPage: React.FC = () => {
text-blue-500"
/>
- Nhận phòng: 15/11/2025
+ Check-in: 15/11/2025
- Trả phòng: 18/11/2025
+ Check-out: 18/11/2025
-
3 đêm
+
3 nights
@@ -96,7 +96,7 @@ const BookingListPage: React.FC = () => {
hover:bg-blue-700 transition-colors
text-sm"
>
- Xem chi tiết
+ View Details
@@ -105,16 +105,16 @@ const BookingListPage: React.FC = () => {
{/* Empty State */}
- {/* Uncomment khi không có booking
+ {/* Uncomment when there are no bookings
- Bạn chưa có đặt phòng nào
+ You don't have any bookings yet
- Đặt phòng ngay
+ Book Now
*/}
diff --git a/client/src/pages/customer/BookingPage.tsx b/client/src/pages/customer/BookingPage.tsx
index fadad5c5..45a39827 100644
--- a/client/src/pages/customer/BookingPage.tsx
+++ b/client/src/pages/customer/BookingPage.tsx
@@ -320,7 +320,7 @@ const BookingPage: React.FC = () => {
border-gray-300 rounded-lg
focus:ring-2 focus:ring-indigo-500
focus:border-indigo-500"
- placeholder="Nguyễn Văn A"
+ placeholder="John Doe"
/>
{errors.fullName && (
diff --git a/client/src/pages/customer/BookingSuccessPage.tsx b/client/src/pages/customer/BookingSuccessPage.tsx
index 12311bef..b198993f 100644
--- a/client/src/pages/customer/BookingSuccessPage.tsx
+++ b/client/src/pages/customer/BookingSuccessPage.tsx
@@ -82,14 +82,14 @@ const BookingSuccessPage: React.FC = () => {
}
} else {
throw new Error(
- 'Không thể tải thông tin đặt phòng'
+ 'Unable to load booking information'
);
}
} catch (err: any) {
console.error('Error fetching booking:', err);
const message =
err.response?.data?.message ||
- 'Không thể tải thông tin đặt phòng';
+ 'Unable to load booking information';
setError(message);
toast.error(message);
} finally {
@@ -133,15 +133,15 @@ const BookingSuccessPage: React.FC = () => {
const getStatusText = (status: string) => {
switch (status) {
case 'confirmed':
- return 'Đã xác nhận';
+ return 'Confirmed';
case 'pending':
- return 'Chờ xác nhận';
+ return 'Pending confirmation';
case 'cancelled':
- return 'Đã hủy';
+ return 'Cancelled';
case 'checked_in':
- return 'Đã nhận phòng';
+ return 'Checked in';
case 'checked_out':
- return 'Đã trả phòng';
+ return 'Checked out';
default:
return status;
}
@@ -155,10 +155,10 @@ const BookingSuccessPage: React.FC = () => {
booking.booking_number
);
setCopiedBookingNumber(true);
- toast.success('Đã sao chép mã đặt phòng');
+ toast.success('Booking number copied');
setTimeout(() => setCopiedBookingNumber(false), 2000);
} catch (err) {
- toast.error('Không thể sao chép');
+ toast.error('Unable to copy');
}
};
@@ -170,13 +170,13 @@ const BookingSuccessPage: React.FC = () => {
// Validate file type
if (!file.type.startsWith('image/')) {
- toast.error('Vui lòng chọn file ảnh');
+ toast.error('Please select an image file');
return;
}
// Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
- toast.error('Kích thước ảnh không được vượt quá 5MB');
+ toast.error('Image size must not exceed 5MB');
return;
}
@@ -208,8 +208,8 @@ const BookingSuccessPage: React.FC = () => {
if (response.success) {
toast.success(
- '✅ Đã gửi xác nhận thanh toán thành công! ' +
- 'Chúng tôi sẽ xác nhận trong thời gian sớm nhất.'
+ '✅ Payment confirmation sent successfully! ' +
+ 'We will confirm as soon as possible.'
);
setReceiptUploaded(true);
@@ -228,15 +228,15 @@ const BookingSuccessPage: React.FC = () => {
} else {
throw new Error(
response.message ||
- 'Không thể xác nhận thanh toán'
+ 'Unable to confirm payment'
);
}
} catch (err: any) {
console.error('Error uploading receipt:', err);
const message =
err.response?.data?.message ||
- 'Không thể gửi xác nhận thanh toán. ' +
- 'Vui lòng thử lại.';
+ 'Unable to send payment confirmation. ' +
+ 'Please try again.';
toast.error(message);
} finally {
setUploadingReceipt(false);
@@ -251,7 +251,7 @@ const BookingSuccessPage: React.FC = () => {
: null;
if (loading) {
- return ;
+ return ;
}
if (error || !booking) {
@@ -267,7 +267,7 @@ const BookingSuccessPage: React.FC = () => {
mx-auto mb-3"
/>
- {error || 'Không tìm thấy đặt phòng'}
+ {error || 'Booking not found'}
navigate('/rooms')}
@@ -275,7 +275,7 @@ const BookingSuccessPage: React.FC = () => {
text-white px-3 py-2 rounded-md hover:bg-indigo-700
disabled:bg-gray-400 mb-6 transition-colors"
>
- Quay lại danh sách phòng
+ Back to room list
@@ -307,11 +307,10 @@ const BookingSuccessPage: React.FC = () => {
className="text-3xl font-bold text-gray-900
mb-2"
>
- Đặt phòng thành công!
+ Booking Successful!
- Cảm ơn bạn đã đặt phòng tại khách sạn của chúng
- tôi
+ Thank you for booking with our hotel
{/* Booking Number */}
@@ -322,7 +321,7 @@ const BookingSuccessPage: React.FC = () => {
- Mã đặt phòng:
+ Booking Number:
{
onClick={copyBookingNumber}
className="ml-2 p-1 hover:bg-indigo-100
rounded transition-colors"
- title="Sao chép mã"
+ title="Copy booking number"
>
{copiedBookingNumber ? (
@@ -362,7 +361,7 @@ const BookingSuccessPage: React.FC = () => {
- Chi tiết đặt phòng
+ Booking Details
@@ -389,14 +388,14 @@ const BookingSuccessPage: React.FC = () => {
- Phòng {room.room_number} -
- Tầng {room.floor}
+ Room {room.room_number} -
+ Floor {room.floor}
)}
- {formatPrice(roomType.base_price)}/đêm
+ {formatPrice(roomType.base_price)}/night
@@ -410,7 +409,7 @@ const BookingSuccessPage: React.FC = () => {
- Ngày nhận phòng
+ Check-in Date
{formatDate(booking.check_in_date)}
@@ -419,7 +418,7 @@ const BookingSuccessPage: React.FC = () => {
- Ngày trả phòng
+ Check-out Date
{formatDate(booking.check_out_date)}
@@ -431,10 +430,10 @@ const BookingSuccessPage: React.FC = () => {
- Số người
+ Number of Guests
- {booking.guest_count} người
+ {booking.guest_count} guest(s)
@@ -443,7 +442,7 @@ const BookingSuccessPage: React.FC = () => {
- Ghi chú
+ Notes
{booking.notes}
@@ -455,12 +454,12 @@ const BookingSuccessPage: React.FC = () => {
- Phương thức thanh toán
+ Payment Method
{booking.payment_method === 'cash'
- ? '💵 Thanh toán tại chỗ'
- : '🏦 Chuyển khoản ngân hàng'}
+ ? '💵 Pay at hotel'
+ : '🏦 Bank transfer'}
@@ -472,7 +471,7 @@ const BookingSuccessPage: React.FC = () => {
- Tổng thanh toán
+ Total Payment
{
- Thông tin khách hàng
+ Customer Information
- Họ và tên
+ Full Name
{booking.guest_info.full_name}
@@ -516,7 +515,7 @@ const BookingSuccessPage: React.FC = () => {
- Số điện thoại
+ Phone Number
{booking.guest_info.phone}
@@ -539,13 +538,13 @@ const BookingSuccessPage: React.FC = () => {
/>
- Hướng dẫn chuyển khoản
+ Bank Transfer Instructions
- Vui lòng chuyển khoản theo thông tin sau:
+ Please transfer according to the following information:
- Ngân hàng:
+ Bank:
Vietcombank (VCB)
- Số tài khoản:
+ Account Number:
0123456789
- Chủ tài khoản:
+ Account Holder:
KHACH SAN ABC
- Số tiền: {' '}
+ Amount: {' '}
@@ -576,7 +575,7 @@ const BookingSuccessPage: React.FC = () => {
- Nội dung: {' '}
+ Content: {' '}
@@ -594,7 +593,7 @@ const BookingSuccessPage: React.FC = () => {
- Quét mã QR để chuyển khoản
+ Scan QR code to transfer
{
- Mã QR đã bao gồm đầy đủ thông tin
+ QR code includes all information
)}
- 💡 Lưu ý: Vui lòng ghi đúng mã đặt phòng
- vào nội dung chuyển khoản để chúng tôi
- có thể xác nhận thanh toán của bạn.
+ 💡 Note: Please enter the correct booking number
+ in the transfer content so we can confirm your payment.
@@ -628,12 +626,11 @@ const BookingSuccessPage: React.FC = () => {
- 📎 Xác nhận thanh toán
+ 📎 Payment Confirmation
- Sau khi chuyển khoản, vui lòng tải lên
- ảnh biên lai để chúng tôi xác nhận nhanh
- hơn.
+ After transferring, please upload
+ the receipt image so we can confirm faster.
@@ -675,7 +672,7 @@ const BookingSuccessPage: React.FC = () => {
- Click để chọn ảnh khác
+ Click to select another image
>
) : (
@@ -687,12 +684,12 @@ const BookingSuccessPage: React.FC = () => {
- Chọn ảnh biên lai
+ Select receipt image
- PNG, JPG, JPEG (Tối đa 5MB)
+ PNG, JPG, JPEG (Max 5MB)
>
)}
@@ -720,14 +717,14 @@ const BookingSuccessPage: React.FC = () => {
className="w-5 h-5
animate-spin"
/>
- Đang gửi...
+ Sending...
>
) : (
<>
- Xác nhận đã thanh toán
+ Confirm payment completed
>
)}
@@ -804,7 +801,7 @@ const BookingSuccessPage: React.FC = () => {
font-semibold"
>
- Xem đơn của tôi
+ View My Bookings
{
font-semibold"
>
- Về trang chủ
+ Go to Home
diff --git a/client/src/pages/customer/DashboardPage.tsx b/client/src/pages/customer/DashboardPage.tsx
index 377b4d63..6889d428 100644
--- a/client/src/pages/customer/DashboardPage.tsx
+++ b/client/src/pages/customer/DashboardPage.tsx
@@ -15,7 +15,7 @@ const DashboardPage: React.FC = () => {
Dashboard
- Tổng quan hoạt động của bạn
+ Overview of your activity
@@ -43,7 +43,7 @@ const DashboardPage: React.FC = () => {
- Tổng đặt phòng
+ Total Bookings
45
@@ -70,7 +70,7 @@ const DashboardPage: React.FC = () => {
- Tổng chi tiêu
+ Total Spending
$12,450
@@ -95,7 +95,7 @@ const DashboardPage: React.FC = () => {
- Đang ở
+ Currently Staying
2
@@ -122,7 +122,7 @@ const DashboardPage: React.FC = () => {
- Điểm thưởng
+ Reward Points
1,250
@@ -140,24 +140,24 @@ const DashboardPage: React.FC = () => {
- Hoạt động gần đây
+ Recent Activity
{[
{
- action: 'Đặt phòng',
- room: 'Phòng 201',
- time: '2 giờ trước'
+ action: 'Booking',
+ room: 'Room 201',
+ time: '2 hours ago'
},
{
action: 'Check-in',
- room: 'Phòng 105',
- time: '1 ngày trước'
+ room: 'Room 105',
+ time: '1 day ago'
},
{
action: 'Check-out',
- room: 'Phòng 302',
- time: '3 ngày trước'
+ room: 'Room 302',
+ time: '3 days ago'
},
].map((activity, index) => (
{
- Đặt phòng sắp tới
+ Upcoming Bookings
{[
{
- room: 'Phòng 401',
+ room: 'Room 401',
date: '20/11/2025',
- status: 'Đã xác nhận'
+ status: 'Confirmed'
},
{
- room: 'Phòng 203',
+ room: 'Room 203',
date: '25/11/2025',
- status: 'Chờ xác nhận'
+ status: 'Pending confirmation'
},
].map((booking, index) => (
{
{
// Fetch booking details
const bookingResponse = await getBookingById(id);
if (!bookingResponse.success || !bookingResponse.data?.booking) {
- throw new Error('Không tìm thấy booking');
+ throw new Error('Booking not found');
}
const bookingData = bookingResponse.data.booking;
@@ -60,7 +60,7 @@ const DepositPaymentPage: React.FC = () => {
// Check if booking requires deposit
if (!bookingData.requires_deposit) {
- toast.info('Booking này không yêu cầu đặt cọc');
+ toast.info('This booking does not require a deposit');
navigate(`/bookings/${id}`);
return;
}
@@ -86,7 +86,7 @@ const DepositPaymentPage: React.FC = () => {
} catch (err: any) {
console.error('Error fetching data:', err);
const message =
- err.response?.data?.message || 'Không thể tải thông tin thanh toán';
+ err.response?.data?.message || 'Unable to load payment information';
setError(message);
toast.error(message);
} finally {
@@ -160,7 +160,7 @@ const DepositPaymentPage: React.FC = () => {
>
- {error || 'Không tìm thấy thông tin thanh toán'}
+ {error || 'Payment information not found'}
{
transition-colors"
>
- Quay lại danh sách booking
+ Back to booking list
@@ -191,7 +191,7 @@ const DepositPaymentPage: React.FC = () => {
hover:text-gray-900 mb-6 transition-colors"
>
-
Quay lại chi tiết booking
+
Back to booking details
{/* Success Header (if paid) */}
@@ -209,11 +209,11 @@ const DepositPaymentPage: React.FC = () => {
- Đã thanh toán đặt cọc thành công!
+ Deposit payment successful!
- Booking của bạn đã được xác nhận.
- Phần còn lại thanh toán khi nhận phòng.
+ Your booking has been confirmed.
+ Remaining amount to be paid at check-in.
@@ -235,11 +235,11 @@ const DepositPaymentPage: React.FC = () => {
- Thanh toán tiền đặt cọc
+ Deposit Payment
- Vui lòng thanh toán 20% tiền cọc để
- xác nhận đặt phòng
+ Please pay 20% deposit to
+ confirm your booking
@@ -252,12 +252,12 @@ const DepositPaymentPage: React.FC = () => {
{/* Payment Summary */}
- Thông tin thanh toán
+ Payment Information
- Tổng tiền phòng
+ Total Room Price
{formatPrice(booking.total_price)}
@@ -268,7 +268,7 @@ const DepositPaymentPage: React.FC = () => {
text-orange-600"
>
- Tiền cọc cần thanh toán (20%)
+ Deposit Amount to Pay (20%)
{formatPrice(depositAmount)}
@@ -276,7 +276,7 @@ const DepositPaymentPage: React.FC = () => {
- Phần còn lại thanh toán khi nhận phòng
+ Remaining amount to be paid at check-in
{formatPrice(remainingAmount)}
@@ -284,14 +284,14 @@ const DepositPaymentPage: React.FC = () => {
{isDepositPaid && (
- ✓ Đã thanh toán tiền cọc vào:{' '}
+ ✓ Deposit paid on:{' '}
{depositPayment.payment_date
? new Date(depositPayment.payment_date).toLocaleString('en-US')
: 'N/A'}
{depositPayment.transaction_id && (
- Mã giao dịch: {depositPayment.transaction_id}
+ Transaction ID: {depositPayment.transaction_id}
)}
@@ -302,7 +302,7 @@ const DepositPaymentPage: React.FC = () => {
{!isDepositPaid && (
- Chọn phương thức thanh toán
+ Select Payment Method
{/* Payment Method Buttons */}
@@ -331,10 +331,10 @@ const DepositPaymentPage: React.FC = () => {
: 'text-gray-700'
}`}
>
- Chuyển khoản
+ Bank Transfer
- Chuyển khoản ngân hàng
+ Bank transfer
@@ -351,7 +351,7 @@ const DepositPaymentPage: React.FC = () => {
- Thông tin chuyển khoản
+ Bank Transfer Information
@@ -359,16 +359,16 @@ const DepositPaymentPage: React.FC = () => {
-
Ngân hàng
+
Bank
{bankInfo.bank_name}
- copyToClipboard(bankInfo.bank_name, 'tên ngân hàng')
+ copyToClipboard(bankInfo.bank_name, 'bank name')
}
className="p-2 hover:bg-gray-200 rounded transition-colors"
>
- {copiedText === 'tên ngân hàng' ? (
+ {copiedText === 'bank name' ? (
) : (
@@ -378,18 +378,18 @@ const DepositPaymentPage: React.FC = () => {
-
Số tài khoản
+
Account Number
{bankInfo.account_number}
- copyToClipboard(bankInfo.account_number, 'số tài khoản')
+ copyToClipboard(bankInfo.account_number, 'account number')
}
className="p-2 hover:bg-gray-200 rounded transition-colors"
>
- {copiedText === 'số tài khoản' ? (
+ {copiedText === 'account number' ? (
) : (
@@ -399,16 +399,16 @@ const DepositPaymentPage: React.FC = () => {
-
Chủ tài khoản
+
Account Holder
{bankInfo.account_name}
- copyToClipboard(bankInfo.account_name, 'chủ tài khoản')
+ copyToClipboard(bankInfo.account_name, 'account holder')
}
className="p-2 hover:bg-gray-200 rounded transition-colors"
>
- {copiedText === 'chủ tài khoản' ? (
+ {copiedText === 'account holder' ? (
) : (
@@ -418,18 +418,18 @@ const DepositPaymentPage: React.FC = () => {
-
Số tiền
+
Amount
{formatPrice(bankInfo.amount)}
- copyToClipboard(bankInfo.amount.toString(), 'số tiền')
+ copyToClipboard(bankInfo.amount.toString(), 'amount')
}
className="p-2 hover:bg-orange-100 rounded transition-colors"
>
- {copiedText === 'số tiền' ? (
+ {copiedText === 'amount' ? (
) : (
@@ -439,18 +439,18 @@ const DepositPaymentPage: React.FC = () => {
-
Nội dung chuyển khoản
+
Transfer Content
{bankInfo.content}
- copyToClipboard(bankInfo.content, 'nội dung')
+ copyToClipboard(bankInfo.content, 'content')
}
className="p-2 hover:bg-gray-200 rounded transition-colors"
>
- {copiedText === 'nội dung' ? (
+ {copiedText === 'content' ? (
) : (
@@ -461,8 +461,8 @@ const DepositPaymentPage: React.FC = () => {
- ⚠️ Lưu ý: Vui lòng nhập đúng nội dung chuyển khoản để
- hệ thống tự động xác nhận thanh toán.
+ ⚠️ Note: Please enter the correct transfer content so
+ the system can automatically confirm the payment.
@@ -478,17 +478,17 @@ const DepositPaymentPage: React.FC = () => {
{notifying ? (
<>
- Đang gửi...
+ Sending...
>
) : (
<>
- Tôi đã chuyển khoản
+ I have transferred
>
)}
- Sau khi chuyển khoản, nhấn nút trên để thông báo cho chúng tôi
+ After transferring, click the button above to notify us
@@ -504,7 +504,7 @@ const DepositPaymentPage: React.FC = () => {
- Quét mã QR để thanh toán
+ Scan QR Code to Pay
@@ -517,10 +517,10 @@ const DepositPaymentPage: React.FC = () => {
- Quét mã QR bằng app ngân hàng
+ Scan QR code with your bank app
- Thông tin chuyển khoản đã được điền tự động
+ Transfer information has been automatically filled
@@ -532,7 +532,7 @@ const DepositPaymentPage: React.FC = () => {
text-gray-700 rounded-lg transition-colors"
>
- Tải mã QR
+ Download QR Code
diff --git a/client/src/pages/customer/FavoritesPage.tsx b/client/src/pages/customer/FavoritesPage.tsx
index 331235e5..3c1414e7 100644
--- a/client/src/pages/customer/FavoritesPage.tsx
+++ b/client/src/pages/customer/FavoritesPage.tsx
@@ -39,10 +39,10 @@ const FavoritesPage: React.FC = () => {
className="text-xl font-bold
text-gray-900 mb-2"
>
- Vui lòng đăng nhập
+ Please Login
- Bạn cần đăng nhập để xem danh sách yêu thích
+ You need to login to view your favorites list
{
hover:bg-indigo-700 transition-colors
font-semibold"
>
- Đăng nhập
+ Login
@@ -71,7 +71,7 @@ const FavoritesPage: React.FC = () => {
mb-4 transition-colors"
>
- Quay lại trang chủ
+ Back to home
@@ -84,12 +84,12 @@ const FavoritesPage: React.FC = () => {
className="text-3xl font-bold
text-gray-900"
>
- Danh sách yêu thích
+ Favorites List
{favorites.length > 0
- ? `${favorites.length} phòng`
- : 'Chưa có phòng yêu thích'}
+ ? `${favorites.length} room${favorites.length !== 1 ? 's' : ''}`
+ : 'No favorite rooms yet'}
@@ -126,7 +126,7 @@ const FavoritesPage: React.FC = () => {
text-white rounded-lg
hover:bg-red-700 transition-colors"
>
- Thử lại
+ Try again
)}
@@ -153,16 +153,14 @@ const FavoritesPage: React.FC = () => {
className="text-2xl font-bold
text-gray-900 mb-3"
>
- Chưa có phòng yêu thích
+ No favorite rooms yet
- Bạn chưa thêm phòng nào vào danh sách
- yêu thích. Hãy khám phá và lưu những
- phòng bạn thích!
+ You haven't added any rooms to your favorites list yet. Explore and save the rooms you like!
{
hover:bg-indigo-700 transition-colors
font-semibold"
>
- Khám phá phòng
+ Explore rooms
)}
diff --git a/client/src/pages/customer/MyBookingsPage.tsx b/client/src/pages/customer/MyBookingsPage.tsx
index 6cb8de50..85201eae 100644
--- a/client/src/pages/customer/MyBookingsPage.tsx
+++ b/client/src/pages/customer/MyBookingsPage.tsx
@@ -108,7 +108,7 @@ const MyBookingsPage: React.FC = () => {
console.error('Error fetching bookings:', err);
const message =
err.response?.data?.message ||
- 'Không thể tải danh sách đặt phòng';
+ 'Unable to load bookings list';
setError(message);
toast.error(message);
} finally {
diff --git a/client/src/pages/customer/PaymentConfirmationPage.tsx b/client/src/pages/customer/PaymentConfirmationPage.tsx
index ae62d05d..bc745a03 100644
--- a/client/src/pages/customer/PaymentConfirmationPage.tsx
+++ b/client/src/pages/customer/PaymentConfirmationPage.tsx
@@ -101,7 +101,7 @@ const PaymentConfirmationPage: React.FC = () => {
console.error('Error fetching booking:', err);
const message =
err.response?.data?.message ||
- 'Không thể tải thông tin đặt phòng';
+ 'Unable to load booking information';
setError(message);
toast.error(message);
} finally {
diff --git a/client/src/pages/customer/PaymentResultPage.tsx b/client/src/pages/customer/PaymentResultPage.tsx
index 7dc8326b..ca7cdb7d 100644
--- a/client/src/pages/customer/PaymentResultPage.tsx
+++ b/client/src/pages/customer/PaymentResultPage.tsx
@@ -218,7 +218,7 @@ const PaymentResultPage: React.FC = () => {
font-medium"
>
- Về trang chủ
+ Go to home
)}
diff --git a/client/src/pages/customer/RoomDetailPage.tsx b/client/src/pages/customer/RoomDetailPage.tsx
index e40528f8..fc27dff7 100644
--- a/client/src/pages/customer/RoomDetailPage.tsx
+++ b/client/src/pages/customer/RoomDetailPage.tsx
@@ -46,7 +46,7 @@ const RoomDetailPage: React.FC = () => {
console.error('Error fetching room:', err);
const message =
err.response?.data?.message ||
- 'Không thể tải thông tin phòng';
+ 'Unable to load room information';
setError(message);
} finally {
setLoading(false);
@@ -109,7 +109,7 @@ const RoomDetailPage: React.FC = () => {
disabled:bg-gray-400 mb-6 transition-colors"
>
-
Quay lại danh sách phòng
+
Back to room list
{/* Image Gallery */}
@@ -138,14 +138,14 @@ const RoomDetailPage: React.FC = () => {
- Phòng {room.room_number} - Tầng {room.floor}
+ Room {room.room_number} - Floor {room.floor}
- {roomType?.capacity || 0} người
+ {roomType?.capacity || 0} guests
@@ -176,10 +176,10 @@ const RoomDetailPage: React.FC = () => {
}`}
>
{room.status === 'available'
- ? 'Còn phòng'
+ ? 'Available'
: room.status === 'occupied'
- ? 'Đã đặt'
- : 'Bảo trì'}
+ ? 'Booked'
+ : 'Maintenance'}
@@ -189,7 +189,7 @@ const RoomDetailPage: React.FC = () => {
- Mô tả phòng
+ Room Description
{roomType.description}
@@ -202,7 +202,7 @@ const RoomDetailPage: React.FC = () => {
- Tiện ích
+ Amenities
{
{formattedPrice}
- / đêm
+ / night
@@ -239,13 +239,13 @@ const RoomDetailPage: React.FC = () => {
if (room.status !== 'available') e.preventDefault();
}}
>
- {room.status === 'available' ? 'Đặt ngay' : 'Không khả dụng'}
+ {room.status === 'available' ? 'Book Now' : 'Not Available'}
{room.status === 'available' && (
- Không bị tính phí ngay — thanh toán tại khách sạn
+ No immediate charge — pay at the hotel
)}
@@ -253,15 +253,15 @@ const RoomDetailPage: React.FC = () => {
- Loại phòng
+ Room Type
{roomType?.name}
- Số khách
- {roomType?.capacity} người
+ Guests
+ {roomType?.capacity} guests
- Số phòng
+ Rooms
1
diff --git a/client/src/pages/customer/RoomListPage.tsx b/client/src/pages/customer/RoomListPage.tsx
index d27ab22d..5297c767 100644
--- a/client/src/pages/customer/RoomListPage.tsx
+++ b/client/src/pages/customer/RoomListPage.tsx
@@ -56,7 +56,7 @@ const RoomListPage: React.FC = () => {
}
} catch (err) {
console.error('Error fetching rooms:', err);
- setError('Không thể tải danh sách phòng. Vui lòng thử lại.');
+ setError('Unable to load room list. Please try again.');
} finally {
setLoading(false);
}
@@ -76,12 +76,12 @@ const RoomListPage: React.FC = () => {
disabled:bg-gray-400 mb-6 transition-colors"
>
- Quay lại trang chủ
+ Back to home
- Danh sách phòng
+ Room List
@@ -126,7 +126,7 @@ const RoomListPage: React.FC = () => {
text-white rounded-lg hover:bg-red-700
transition-colors"
>
- Thử lại
+ Try Again
)}
@@ -153,17 +153,17 @@ const RoomListPage: React.FC = () => {
- Không tìm thấy phòng phù hợp
+ No matching rooms found
- Vui lòng thử điều chỉnh bộ lọc hoặc tìm kiếm khác
+ Please try adjusting the filters or search differently
window.location.href = '/rooms'}
className="px-6 py-2 bg-blue-600 text-white
rounded-lg hover:bg-blue-700 transition-colors"
>
- Xóa bộ lọc
+ Clear Filters
)}
diff --git a/client/src/services/api/apiClient.ts b/client/src/services/api/apiClient.ts
index deb4673b..30c7a379 100644
--- a/client/src/services/api/apiClient.ts
+++ b/client/src/services/api/apiClient.ts
@@ -1,6 +1,6 @@
import axios from 'axios';
-// Base URL từ environment hoặc mặc định. Ensure it points to the
+// Base URL from environment or default. Ensure it points to the
// server API root (append '/api' if not provided) so frontend calls
// like '/bookings/me' resolve to e.g. 'http://localhost:3000/api/bookings/me'.
const rawBase = import.meta.env.VITE_API_URL || 'http://localhost:3000';
@@ -12,7 +12,7 @@ const API_BASE_URL = /\/api(\/?$)/i.test(normalized)
? normalized
: normalized + '/api';
-// Tạo axios instance
+// Create axios instance
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: {
@@ -22,7 +22,7 @@ const apiClient = axios.create({
withCredentials: true, // Enable sending cookies
});
-// Request interceptor - Thêm token vào header
+// Request interceptor - Add token to header
apiClient.interceptors.request.use(
(config) => {
// Normalize request URL: if a request path accidentally begins
diff --git a/client/src/services/api/authService.ts b/client/src/services/api/authService.ts
index c0815845..0c9b27c1 100644
--- a/client/src/services/api/authService.ts
+++ b/client/src/services/api/authService.ts
@@ -45,12 +45,12 @@ export interface ResetPasswordData {
}
/**
- * Auth Service - Xử lý các API calls liên quan
- * đến authentication
+ * Auth Service - Handles API calls related
+ * to authentication
*/
const authService = {
/**
- * Đăng nhập
+ * Login
*/
login: async (
credentials: LoginCredentials
@@ -63,7 +63,7 @@ const authService = {
},
/**
- * Đăng ký tài khoản mới
+ * Register new account
*/
register: async (
data: RegisterData
@@ -76,7 +76,7 @@ const authService = {
},
/**
- * Đăng xuất
+ * Logout
*/
logout: async (): Promise => {
try {
@@ -87,7 +87,7 @@ const authService = {
},
/**
- * Lấy thông tin user hiện tại
+ * Get current user information
*/
getProfile: async (): Promise => {
const response = await apiClient.get(
@@ -108,7 +108,7 @@ const authService = {
},
/**
- * Quên mật khẩu - Gửi email reset
+ * Forgot password - Send reset email
*/
forgotPassword: async (
data: ForgotPasswordData
@@ -121,7 +121,7 @@ const authService = {
},
/**
- * Đặt lại mật khẩu
+ * Reset password
*/
resetPassword: async (
data: ResetPasswordData
diff --git a/client/src/services/api/bookingService.ts b/client/src/services/api/bookingService.ts
index 814375bf..2a659a0e 100644
--- a/client/src/services/api/bookingService.ts
+++ b/client/src/services/api/bookingService.ts
@@ -239,7 +239,7 @@ export const checkRoomAvailability = async (
available: false,
message:
error.response.data.message ||
- 'Phòng đã được đặt trong thời gian này',
+ 'Room already booked during this time',
};
}
throw error;
diff --git a/client/src/validators/bookingValidator.ts b/client/src/validators/bookingValidator.ts
index 0f97bf43..64391558 100644
--- a/client/src/validators/bookingValidator.ts
+++ b/client/src/validators/bookingValidator.ts
@@ -3,60 +3,60 @@ import * as yup from 'yup';
export const bookingValidationSchema = yup.object().shape({
checkInDate: yup
.date()
- .required('Vui lòng chọn ngày nhận phòng')
+ .required('Please select check-in date')
.min(
new Date(new Date().setHours(0, 0, 0, 0)),
- 'Ngày nhận phòng không thể là ngày trong quá khứ'
+ 'Check-in date cannot be in the past'
)
- .typeError('Ngày nhận phòng không hợp lệ'),
+ .typeError('Invalid check-in date'),
checkOutDate: yup
.date()
- .required('Vui lòng chọn ngày trả phòng')
+ .required('Please select check-out date')
.min(
yup.ref('checkInDate'),
- 'Ngày trả phòng phải sau ngày nhận phòng'
+ 'Check-out date must be after check-in date'
)
- .typeError('Ngày trả phòng không hợp lệ'),
+ .typeError('Invalid check-out date'),
guestCount: yup
.number()
- .required('Vui lòng nhập số người')
- .min(1, 'Số người tối thiểu là 1')
- .max(10, 'Số người tối đa là 10')
- .integer('Số người phải là số nguyên')
- .typeError('Số người phải là số'),
+ .required('Please enter number of guests')
+ .min(1, 'Minimum number of guests is 1')
+ .max(10, 'Maximum number of guests is 10')
+ .integer('Number of guests must be an integer')
+ .typeError('Number of guests must be a number'),
notes: yup
.string()
- .max(500, 'Ghi chú không được quá 500 ký tự')
+ .max(500, 'Notes cannot exceed 500 characters')
.optional(),
paymentMethod: yup
.mixed<'cash' | 'bank_transfer'>()
- .required('Vui lòng chọn phương thức thanh toán')
+ .required('Please select payment method')
.oneOf(
['cash', 'bank_transfer'],
- 'Phương thức thanh toán không hợp lệ'
+ 'Invalid payment method'
),
fullName: yup
.string()
- .required('Vui lòng nhập họ tên')
- .min(2, 'Họ tên phải có ít nhất 2 ký tự')
- .max(100, 'Họ tên không được quá 100 ký tự'),
+ .required('Please enter full name')
+ .min(2, 'Full name must be at least 2 characters')
+ .max(100, 'Full name cannot exceed 100 characters'),
email: yup
.string()
- .required('Vui lòng nhập email')
- .email('Email không hợp lệ'),
+ .required('Please enter email')
+ .email('Invalid email'),
phone: yup
.string()
- .required('Vui lòng nhập số điện thoại')
+ .required('Please enter phone number')
.matches(
/^[0-9]{10,11}$/,
- 'Số điện thoại phải có 10-11 chữ số'
+ 'Phone number must have 10-11 digits'
),
});
diff --git a/docs/FORGOT_PASSWORD_COMPLETE.md b/docs/FORGOT_PASSWORD_COMPLETE.md
index d5b03fbb..2f4b6b10 100644
--- a/docs/FORGOT_PASSWORD_COMPLETE.md
+++ b/docs/FORGOT_PASSWORD_COMPLETE.md
@@ -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 ? (
<>
- Đang xử lý...
+ Processing...
>
) : (
<>
- 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
-
Cần trợ giúp?
+
Need help?
- Liên hệ: support@hotel.com hoặc 1900-xxxx
+ Contact: support@hotel.com or 1900-xxxx
```
@@ -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
diff --git a/docs/LAYOUT_IMPLEMENTATION.md b/docs/LAYOUT_IMPLEMENTATION.md
index 15f1ea3b..d3fd4a19 100644
--- a/docs/LAYOUT_IMPLEMENTATION.md
+++ b/docs/LAYOUT_IMPLEMENTATION.md
@@ -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 ` ` để render nội dung động
+- Integrates Header, Navbar, Footer
+- Uses ` ` 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
} />
- {/* Các route con khác */}
+ {/* Other child routes */}
```
-#### 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 ` `
-- [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 ` `
+- [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)
diff --git a/docs/LOGIN_FORM_GUIDE.md b/docs/LOGIN_FORM_GUIDE.md
index 206d861c..612de40c 100644
--- a/docs/LOGIN_FORM_GUIDE.md
+++ b/docs/LOGIN_FORM_GUIDE.md
@@ -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:
} />
```
-## 🎨 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);
```
-### 2. Remember Me (7 ngày)
+### 2. Remember Me (7 days)
```typescript
// 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
{isLoading ? (
<>
- Đang xử lý...
+ Processing...
>
) : (
<>
- Đăng nhập
+ Login
>
)}
@@ -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 && (
{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
diff --git a/docs/REGISTER_FORM_COMPLETE.md b/docs/REGISTER_FORM_COMPLETE.md
index 04b0393c..ea05f158 100644
--- a/docs/REGISTER_FORM_COMPLETE.md
+++ b/docs/REGISTER_FORM_COMPLETE.md
@@ -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 ? (
<>
- Đang xử lý...
+ Processing...
>
) : (
<>
- Đă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
diff --git a/docs/ROUTE_PROTECTION.md b/docs/ROUTE_PROTECTION.md
index 9cfb8643..3769aecd 100644
--- a/docs/ROUTE_PROTECTION.md
+++ b/docs/ROUTE_PROTECTION.md
@@ -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
= ({
const location = useLocation();
const { isAuthenticated, isLoading } = useAuthStore();
- // 1. Nếu đang loading → hiển thị spinner
+ // 1. If loading → display spinner
if (isLoading) {
return ;
}
- // 2. Nếu chưa đăng nhập → redirect /login
+ // 2. If not logged in → redirect /login
if (!isAuthenticated) {
return (
);
}
- // 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
```
-### 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 = ({
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 ;
}
- // 2. Nếu chưa đăng nhập → redirect /login
+ // 2. If not logged in → redirect /login
if (!isAuthenticated) {
return (
= ({
);
}
- // 3. Nếu không phải admin → redirect /
+ // 3. If not admin → redirect /
const isAdmin = userInfo?.role === 'admin';
if (!isAdmin) {
return ;
}
- // 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
```
-### 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 (
- {/* Public Routes - Không cần đăng nhập */}
+ {/* Public Routes - No login required */}
}>
} />
} />
} />
- {/* Auth Routes - Không cần layout */}
+ {/* Auth Routes - No layout */}
} />
} />
} />
} />
- {/* Protected Routes - Yêu cầu đăng nhập */}
+ {/* Protected Routes - Login required */}
}>
- {/* Admin Routes - Chỉ Admin */}
+ {/* Admin Routes - Admin only */}
((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) {
- Đang xác thực...
+ Authenticating...
);
@@ -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! ✅**
diff --git a/docs/SERVER_SETUP_COMPLETE.md b/docs/SERVER_SETUP_COMPLETE.md
index 079a70f0..785cac6f 100644
--- a/docs/SERVER_SETUP_COMPLETE.md
+++ b/docs/SERVER_SETUP_COMPLETE.md
@@ -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
diff --git a/docs/TEST_ROUTE_PROTECTION.md b/docs/TEST_ROUTE_PROTECTION.md
index 0281ff3b..350af6be 100644
--- a/docs/TEST_ROUTE_PROTECTION.md
+++ b/docs/TEST_ROUTE_PROTECTION.md
@@ -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
diff --git a/docs/tasks-admin.md b/docs/tasks-admin.md
index 7be488ea..482703d4 100644
--- a/docs/tasks-admin.md
+++ b/docs/tasks-admin.md
@@ -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.
diff --git a/docs/tasks_1_Authentication.md b/docs/tasks_1_Authentication.md
index c501d784..28f22695 100644
--- a/docs/tasks_1_Authentication.md
+++ b/docs/tasks_1_Authentication.md
@@ -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 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 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:
```
}>
} />
@@ -51,25 +51,25 @@
} />
} />
```
-- 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 : ;
};
```
-### 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.
diff --git a/docs/tasks_2_Home_&_RoomSearch.md b/docs/tasks_2_Home_&_RoomSearch.md
index 7d78cc64..a46db583 100644
--- a/docs/tasks_2_Home_&_RoomSearch.md
+++ b/docs/tasks_2_Home_&_RoomSearch.md
@@ -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.
---
diff --git a/docs/tasks_3_Booking_&_Payment.md b/docs/tasks_3_Booking_&_Payment.md
index 0cec9412..b0a46576 100644
--- a/docs/tasks_3_Booking_&_Payment.md
+++ b/docs/tasks_3_Booking_&_Payment.md
@@ -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
----
\ No newline at end of file
+---
diff --git a/docs/tasks_5_Review_System.md b/docs/tasks_5_Review_System.md
index 85fe416e..dcea13c4 100644
--- a/docs/tasks_5_Review_System.md
+++ b/docs/tasks_5_Review_System.md
@@ -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.
---
diff --git a/server/QUICK_START.md b/server/QUICK_START.md
index 28a65994..6a63af1a 100644
--- a/server/QUICK_START.md
+++ b/server/QUICK_START.md
@@ -1,31 +1,31 @@
# 🚀 QUICK START - Server Setup
-## Bước 1: Copy file .env
+## Step 1: Copy .env file
```bash
cd d:/hotel-booking/server
cp .env.example .env
```
-> File .env đã được tạo sẵn với cấu hình mặc định
+> The .env file has been pre-created with default configuration
-## Bước 2: Tạo Database (nếu chưa có)
+## Step 2: Create Database (if not exists)
```bash
-# Mở MySQL command line
+# Open MySQL command line
mysql -u root -p
-# Tạo database
+# Create database
CREATE DATABASE hotel_db;
-# Thoát
+# Exit
exit;
```
-## Bước 3: Chạy Migrations
+## Step 3: Run Migrations
```bash
cd d:/hotel-booking/server
npm run migrate
```
-Lệnh này sẽ tạo các bảng:
+This command will create the following tables:
- roles
- users
- refresh_tokens
@@ -41,23 +41,23 @@ Lệnh này sẽ tạo các bảng:
- password_reset_tokens
- reviews
-## Bước 4: (Optional) Seed Data
+## Step 4: (Optional) Seed Data
```bash
npm run seed
```
-Lệnh này sẽ tạo:
+This command will create:
- 3 roles: admin, staff, customer
- Demo users
- Demo rooms & room types
- Demo bookings
-## Bước 5: Start Server
+## Step 5: Start Server
```bash
npm run dev
```
-Bạn sẽ thấy:
+You will see:
```
✅ Database connection established successfully
📊 Database models synced
@@ -67,12 +67,12 @@ Bạn sẽ thấy:
🏥 Health: http://localhost:3000/health
```
-## Bước 6: Test API
+## Step 6: Test API
### Health Check
-Mở browser: http://localhost:3000/health
+Open browser: http://localhost:3000/health
-### Test Login (sau khi seed data)
+### Test Login (after seeding data)
```bash
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
@@ -81,34 +81,34 @@ curl -X POST http://localhost:3000/api/auth/login \
## ⚠️ Troubleshooting
-### Lỗi: "Access denied for user 'root'"
-**Giải pháp:** Sửa DB_PASS trong file `.env`
+### Error: "Access denied for user 'root'"
+**Solution:** Update DB_PASS in `.env` file
```bash
DB_PASS=your_mysql_password
```
-### Lỗi: "Unknown database 'hotel_db'"
-**Giải pháp:** Tạo database thủ công (Bước 2)
+### Error: "Unknown database 'hotel_db'"
+**Solution:** Create database manually (Step 2)
-### Lỗi: "Port 3000 already in use"
-**Giải pháp:** Đổi PORT trong `.env`
+### Error: "Port 3000 already in use"
+**Solution:** Change PORT in `.env`
```bash
PORT=3001
```
-### Lỗi: "Cannot find module"
-**Giải pháp:** Cài lại dependencies
+### Error: "Cannot find module"
+**Solution:** Reinstall dependencies
```bash
npm install
```
## 📝 Next Steps
-1. ✅ Server đang chạy
-2. ✅ Database đã setup
-3. ✅ API endpoints sẵn sàng
-4. 🔜 Test với frontend login form
-5. 🔜 Implement các API còn lại
+1. ✅ Server is running
+2. ✅ Database is set up
+3. ✅ API endpoints are ready
+4. 🔜 Test with frontend login form
+5. 🔜 Implement remaining APIs
## 🧪 Test với Postman
@@ -146,14 +146,14 @@ Authorization: Bearer YOUR_ACCESS_TOKEN
## ✅ Checklist
-- [ ] MySQL đang chạy
-- [ ] File .env đã tạo và cấu hình đúng
-- [ ] Database hotel_db đã tạo
-- [ ] Migrations đã chạy thành công
-- [ ] Server đang chạy (port 3000)
-- [ ] Health check trả về 200 OK
-- [ ] Frontend .env đã có VITE_API_URL=http://localhost:3000
-- [ ] Frontend đang chạy (port 5173)
+- [ ] MySQL is running
+- [ ] .env file has been created and configured correctly
+- [ ] Database hotel_db has been created
+- [ ] Migrations have run successfully
+- [ ] Server is running (port 3000)
+- [ ] Health check returns 200 OK
+- [ ] Frontend .env has VITE_API_URL=http://localhost:3000
+- [ ] Frontend is running (port 5173)
## 🎯 Ready to Test Login!
@@ -162,4 +162,4 @@ Authorization: Bearer YOUR_ACCESS_TOKEN
3. Login page: http://localhost:5173/login ✅
4. API endpoint: http://localhost:3000/api/auth/login ✅
-**Tất cả sẵn sàng!** Giờ có thể test login form từ frontend! 🚀
+**Everything is ready!** You can now test the login form from the frontend! 🚀
diff --git a/server/src/controllers/authController.js b/server/src/controllers/authController.js
index 4b42e378..f758f3a6 100644
--- a/server/src/controllers/authController.js
+++ b/server/src/controllers/authController.js
@@ -222,7 +222,7 @@ const resetPassword = async (req, res, next) => {
}
if (
error.message.includes('must be different') ||
- error.message.includes('Mật khẩu mới')
+ error.message.includes('New password')
) {
return res.status(400).json({
status: 'error',
diff --git a/server/src/controllers/favoriteController.js b/server/src/controllers/favoriteController.js
index c123b377..0e6d15d6 100644
--- a/server/src/controllers/favoriteController.js
+++ b/server/src/controllers/favoriteController.js
@@ -19,7 +19,7 @@ const addFavorite = async (req, res, next) => {
if (!room) {
return res.status(404).json({
status: 'error',
- message: 'Không tìm thấy phòng',
+ message: 'Room not found',
});
}
@@ -34,7 +34,7 @@ const addFavorite = async (req, res, next) => {
if (existingFavorite) {
return res.status(400).json({
status: 'error',
- message: 'Phòng đã có trong danh sách yêu thích',
+ message: 'Room already in favorites list',
});
}
@@ -46,7 +46,7 @@ const addFavorite = async (req, res, next) => {
res.status(201).json({
status: 'success',
- message: 'Đã thêm vào danh sách yêu thích',
+ message: 'Added to favorites list',
data: {
favorite,
},
@@ -75,7 +75,7 @@ const removeFavorite = async (req, res, next) => {
if (!favorite) {
return res.status(404).json({
status: 'error',
- message: 'Không tìm thấy phòng trong danh sách yêu thích',
+ message: 'Room not found in favorites list',
});
}
@@ -83,7 +83,7 @@ const removeFavorite = async (req, res, next) => {
res.status(200).json({
status: 'success',
- message: 'Đã xóa khỏi danh sách yêu thích',
+ message: 'Removed from favorites list',
});
} catch (error) {
next(error);
diff --git a/server/src/databases/seeders/20250101000010-seed-service-usages.js b/server/src/databases/seeders/20250101000010-seed-service-usages.js
index 7ee24f89..69913f2e 100644
--- a/server/src/databases/seeders/20250101000010-seed-service-usages.js
+++ b/server/src/databases/seeders/20250101000010-seed-service-usages.js
@@ -9,7 +9,7 @@ module.exports = {
// booking_number, service_name, rest of fields
{
booking_number: 'BK2025010001',
- service_name: 'Dịch vụ phòng - Bữa sáng',
+ service_name: 'Room Service - Breakfast',
quantity: 2,
unit_price: 150000,
total_price: 300000,
@@ -20,7 +20,7 @@ module.exports = {
},
{
booking_number: 'BK2025010001',
- service_name: 'Dịch vụ giặt ủi - Thông thường',
+ service_name: 'Laundry Service - Regular',
quantity: 3,
unit_price: 60000,
total_price: 180000,
@@ -31,7 +31,7 @@ module.exports = {
},
{
booking_number: 'BK2025010002',
- service_name: 'Dịch vụ phòng - Bữa sáng',
+ service_name: 'Room Service - Breakfast',
quantity: 1,
unit_price: 150000,
total_price: 150000,
@@ -42,7 +42,7 @@ module.exports = {
},
{
booking_number: 'BK2025010002',
- service_name: 'Spa - Massage truyền thống',
+ service_name: 'Spa - Traditional Massage',
quantity: 1,
unit_price: 500000,
total_price: 500000,
@@ -53,7 +53,7 @@ module.exports = {
},
{
booking_number: 'BK2025010002',
- service_name: 'Trả phòng muộn',
+ service_name: 'Late Check-out',
quantity: 1,
unit_price: 500000,
total_price: 500000,
@@ -64,7 +64,7 @@ module.exports = {
},
{
booking_number: 'BK2025010003',
- service_name: 'Đón sân bay',
+ service_name: 'Airport Pickup',
quantity: 1,
unit_price: 400000,
total_price: 400000,
@@ -75,7 +75,7 @@ module.exports = {
},
{
booking_number: 'BK2025010005',
- service_name: 'Đón sân bay',
+ service_name: 'Airport Pickup',
quantity: 1,
unit_price: 400000,
total_price: 400000,
@@ -86,7 +86,7 @@ module.exports = {
},
{
booking_number: 'BK2025010005',
- service_name: 'Spa - Liệu pháp hương thơm',
+ service_name: 'Spa - Aromatherapy',
quantity: 1,
unit_price: 700000,
total_price: 700000,