diff --git a/app/(main)/payment/page.tsx b/app/(main)/payment/page.tsx index 5316216..8d0229b 100644 --- a/app/(main)/payment/page.tsx +++ b/app/(main)/payment/page.tsx @@ -1,7 +1,10 @@ "use client"; +import ReviewModal from "@/components/ReviewModal"; +import { useAuth } from "@/lib/auth-context"; import { useCart } from "@/lib/cart-context"; import Link from "next/link"; +import { useState } from "react"; const formatPrice = (value: number) => value.toLocaleString("vi-VN", { style: "currency", currency: "VND" }); @@ -15,140 +18,173 @@ export default function PaymentPage() { removeFromCart, setQuantity, } = useCart(); + const { user } = useAuth(); + + const [isReviewOpen, setIsReviewOpen] = useState(false); + const isCustomer = user?.role === "customer"; + + const handlePayment = () => { + // UI-only: open review modal after "payment" + if (isCustomer) { + setIsReviewOpen(true); + } + }; return ( -
-
-
-
-
-

- Trang thanh toán -

-
- - {items.length === 0 ? ( -
- Chưa có sản phẩm nào trong giỏ hàng. + <> +
+
+
+
+
+

+ Trang thanh toán +

- ) : ( -
- - - - - - - - - - - - {items.map((item) => ( - - - - - - + + {items.length === 0 ? ( +
+ Chưa có sản phẩm nào trong giỏ hàng. +
+ ) : ( +
+
Tên sản phẩmGiá tiềnMô tảSố lượng - Xóa -
- {item.name} - - {formatPrice(item.price)} - -

{item.description}

-
-
- - - setQuantity(item.id, Number(e.target.value)) - } - className="h-8 w-16 rounded-lg border border-(--color-border) bg-transparent text-center" - title="Nhập số lượng" - /> - -
-
- -
+ + + + + + + - ))} - -
+ Tên sản phẩm + Giá tiềnMô tảSố lượng + Xóa +
-
- )} -
-
- -
-
- +
- + +
- + + setIsReviewOpen(false)} + /> + ); } diff --git a/app/APP.md b/app/APP.md index e3a4ad3..597b53b 100644 --- a/app/APP.md +++ b/app/APP.md @@ -217,8 +217,46 @@ activeCategory: string // From MenuContext ### 4. Payment Page (app/(main)/payment/page.tsx) **Route:** `/payment` -**Type:** Client component (TBD) -**Description:** Payment/checkout page. Currently a placeholder; implement when cart and order system are ready. +**Type:** Client component +**Description:** Payment/checkout page. Shows cart items in a table with quantity controls and a summary aside with payment actions. + +#### Key Features + +- **Cart Table:** + - Lists all cart items (name, price, description, quantity controls, delete) + - Quantity input with +/- buttons and direct number input + - Empty state message when cart is empty + - Horizontal scroll on small screens (min-w-190) + +- **Invoice Aside:** + - Sticky on desktop (top offset = header height + 1rem) + - Shows total price + - Payment buttons: Tiền mặt, QR Code (UI-only; trigger review modal for customers) + - **"Đánh giá" button** — only visible when `user.role === "customer"`; opens ReviewModal + - **"Quay về" button** — links back to `/`; spans full width when review button is absent + +- **Review Modal (ReviewModal):** + - Opened when a customer clicks "Đánh giá" OR clicks a payment button (Tiền mặt/QR) + - Closed via "Quay lại" button, backdrop click, or after submitting and closing + - See `components/ReviewModal.tsx` for full documentation + +#### Context Usage + +- **useCart()** — items, totalPrice, increaseQty, decreaseQty, removeFromCart, setQuantity +- **useAuth()** — user (to check `user.role === "customer"` for review button visibility) + +#### State Management + +```tsx +isReviewOpen: boolean // Controls ReviewModal visibility +isCustomer: boolean // Derived from user.role === "customer" +``` + +#### Responsive Behavior + +- **Mobile:** Single column layout, table scrolls horizontally, button labels hidden (icon only) +- **Desktop (lg+):** Button labels visible, aside becomes sticky sidebar (xl: w-85) +- **Review button grid:** When customer is logged in, 3 buttons in 2-column grid; "Quay về" occupies remaining space. Otherwise 2-column grid with "Quay về" spanning full width. --- @@ -353,7 +391,8 @@ Defined at `:root` for light mode, with dark mode variants: ## Future Enhancements -- [ ] Payment page implementation +- [x] Payment page implementation +- [x] Customer review modal (ReviewModal) with 5-star rating + textarea - [ ] Order history/tracking page - [ ] Manager dashboard (menu management, order tracking) - [ ] User profile page diff --git a/components/COMPONENTS.md b/components/COMPONENTS.md index e64a4ff..8549041 100644 --- a/components/COMPONENTS.md +++ b/components/COMPONENTS.md @@ -197,6 +197,75 @@ None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly. --- +## ReviewModal + +**File:** components/ReviewModal.tsx +**Description:** Modal for customer reviews. Shows a 5-star rating selector and a textarea for written feedback. After submission, displays a thank-you message. Appears on the payment page for customers (via a dedicated "Đánh giá" button) and also opens automatically after a successful payment action. + +### Props + +| Prop | Type | Required | Default | Description | +| ------- | ---------- | -------- | ------- | ------------------------------------------ | +| isOpen | boolean | yes | - | Controls modal visibility | +| onClose | () => void | yes | - | Callback to close the modal & reset state | + +### Behavior + +- **Star rating:** 5 interactive stars; hover highlights up to hovered star, click locks selection. Disabled submit button until at least 1 star selected. +- **Star labels:** 1 = Rất tệ, 2 = Tệ, 3 = Bình thường, 4 = Tốt, 5 = Xuất sắc +- **Textarea:** Optional free-text review (placeholder in Vietnamese). +- **Footer buttons:** + - "Quay lại" — closes modal without submitting; resets all state. + - "Xác nhận" — submits (UI-only); transitions to thank-you state. +- **Thank-you state:** Replaces form with "Cảm ơn quý khách" message + close button. +- **Backdrop click:** Also closes the modal (same as "Quay lại"). +- **State reset:** All state (rating, hover, review text, submitted) resets on close. + +### Styling + +| Element | Key classes | +| --------------- | ----------------------------------------------------------------------- | +| Backdrop | fixed inset-0, bg-black/50 backdrop-blur-sm, z-50 | +| Modal panel | max-w-md, rounded-2xl, border --color-border-light, white bg, shadow-xl | +| Stars | text-3xl sm:text-4xl, hover:scale-110, active:scale-95 | +| Active star | fa-solid fa-star text-yellow-400 | +| Inactive star | fa-regular fa-star text-(--color-border) | +| Textarea | rounded-xl, focus:ring with --color-primary/20 | +| Quay lại button | border --color-border, hover --color-border-light | +| Xác nhận button | bg --color-primary, disabled:opacity-50 | +| Thank-you icon | fa-solid fa-heart text-(--color-accent), --color-accent-light bg | + +### Responsive + +- Padding: `p-6 sm:p-8` — larger on sm+ screens +- Stars: `text-3xl sm:text-4xl` +- Modal width: `w-full max-w-md` with `p-4` page padding — works on all screen sizes + +### Dependencies + +- React useState +- FontAwesome icons (fa-star, fa-regular fa-star, fa-heart, fa-arrow-left, fa-check) +- Tailwind CSS + CSS custom properties from globals.css + +### Usage in Payment Page + +```tsx +// Only shown for customers (user.role === "customer") +const [isReviewOpen, setIsReviewOpen] = useState(false); + +// Button in payment aside (only visible to customers) + + +// Payment buttons (Tiền mặt / QR Code) also open the modal for customers +const handlePayment = () => { + if (isCustomer) setIsReviewOpen(true); +}; + + setIsReviewOpen(false)} /> +``` + +--- + # Contexts Documentation ## AuthContext (lib/auth-context.tsx) diff --git a/components/ReviewModal.tsx b/components/ReviewModal.tsx new file mode 100644 index 0000000..738fee7 --- /dev/null +++ b/components/ReviewModal.tsx @@ -0,0 +1,170 @@ +"use client"; + +import { useState } from "react"; + +interface ReviewModalProps { + isOpen: boolean; + onClose: () => void; +} + +export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) { + const [rating, setRating] = useState(0); + const [hovered, setHovered] = useState(0); + const [review, setReview] = useState(""); + const [submitted, setSubmitted] = useState(false); + + if (!isOpen) return null; + + const handleSubmit = () => { + setSubmitted(true); + }; + + const handleClose = () => { + // Reset state when closing + setRating(0); + setHovered(0); + setReview(""); + setSubmitted(false); + onClose(); + }; + + return ( +
+ {/* Backdrop */} +