This commit is contained in:
@@ -198,6 +198,75 @@ hover.
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
<button onClick={() => setIsReviewOpen(true)}>Đánh giá</button>
|
||||
|
||||
// Payment buttons (Tiền mặt / QR Code) also open the modal for customers
|
||||
const handlePayment = () => {
|
||||
if (isCustomer) setIsReviewOpen(true);
|
||||
};
|
||||
|
||||
<ReviewModal isOpen={isReviewOpen} onClose={() => setIsReviewOpen(false)} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Contexts Documentation
|
||||
|
||||
## AuthContext (lib/auth-context.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 (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="review-modal-title"
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={handleClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative w-full max-w-md rounded-2xl border border-(--color-border-light) bg-white p-6 shadow-xl sm:p-8">
|
||||
{submitted ? (
|
||||
/* Thank you state */
|
||||
<div className="flex flex-col items-center gap-4 py-4 text-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-(--color-accent-light) text-3xl">
|
||||
<i className="fa-solid fa-heart text-(--color-accent)"></i>
|
||||
</div>
|
||||
<h2
|
||||
id="review-modal-title"
|
||||
className="text-xl font-bold text-foreground"
|
||||
>
|
||||
Cảm ơn quý khách
|
||||
</h2>
|
||||
<p className="text-(--color-text-muted)">
|
||||
Chúng tôi trân trọng đánh giá của bạn!
|
||||
</p>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="mt-2 inline-flex items-center justify-center gap-2 rounded-xl bg-(--color-primary) px-6 py-2.5 text-white transition-colors hover:bg-(--color-primary-dark)"
|
||||
>
|
||||
Đóng
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
/* Review form */
|
||||
<>
|
||||
<h2
|
||||
id="review-modal-title"
|
||||
className="mb-1 text-xl font-bold text-foreground"
|
||||
>
|
||||
Đánh giá của bạn
|
||||
</h2>
|
||||
<p className="mb-5 text-sm text-(--color-text-muted)">
|
||||
Hãy cho chúng tôi biết trải nghiệm của bạn hôm nay
|
||||
</p>
|
||||
|
||||
{/* Star rating */}
|
||||
<div className="mb-5">
|
||||
<p className="mb-2 text-sm font-medium text-(--color-text-secondary)">
|
||||
Mức độ hài lòng
|
||||
</p>
|
||||
<div
|
||||
className="flex gap-2"
|
||||
role="radiogroup"
|
||||
aria-label="Xếp hạng sao"
|
||||
>
|
||||
{[1, 2, 3, 4, 5].map((star) => {
|
||||
const isActive = star <= (hovered || rating);
|
||||
return (
|
||||
<button
|
||||
key={star}
|
||||
type="button"
|
||||
onClick={() => setRating(star)}
|
||||
onMouseEnter={() => setHovered(star)}
|
||||
onMouseLeave={() => setHovered(0)}
|
||||
aria-label={`${star} sao`}
|
||||
aria-pressed={rating === star}
|
||||
className="text-3xl transition-transform hover:scale-110 active:scale-95 sm:text-4xl"
|
||||
>
|
||||
<i
|
||||
className={
|
||||
isActive
|
||||
? "fa-solid fa-star text-yellow-400"
|
||||
: "fa-regular fa-star text-(--color-border)"
|
||||
}
|
||||
></i>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{rating > 0 && (
|
||||
<p className="mt-1.5 text-xs text-(--color-text-muted)">
|
||||
{
|
||||
["", "Rất tệ", "Tệ", "Bình thường", "Tốt", "Xuất sắc"][
|
||||
rating
|
||||
]
|
||||
}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Review textarea */}
|
||||
<div className="mb-6">
|
||||
<label
|
||||
htmlFor="review-text"
|
||||
className="mb-2 block text-sm font-medium text-(--color-text-secondary)"
|
||||
>
|
||||
Nhận xét (tùy chọn)
|
||||
</label>
|
||||
<textarea
|
||||
id="review-text"
|
||||
value={review}
|
||||
onChange={(e) => setReview(e.target.value)}
|
||||
placeholder="Chia sẻ cảm nhận của bạn về đồ uống, dịch vụ..."
|
||||
rows={4}
|
||||
className="w-full resize-none rounded-xl border border-(--color-border) bg-transparent px-3 py-2.5 text-sm text-foreground placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:outline-none focus:ring-2 focus:ring-(--color-primary)/20 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer buttons */}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClose}
|
||||
className="flex-1 inline-flex items-center justify-center gap-2 rounded-xl border border-(--color-border) px-4 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-(--color-border-light)"
|
||||
>
|
||||
<i className="fa-solid fa-arrow-left"></i>
|
||||
Quay lại
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={rating === 0}
|
||||
className="flex-1 inline-flex items-center justify-center gap-2 rounded-xl bg-(--color-primary) px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-(--color-primary-dark) disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<i className="fa-solid fa-check"></i>
|
||||
Xác nhận
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user