This commit is contained in:
+26
-15
@@ -239,9 +239,9 @@ registration flow. Two-step process: phone verification → account creation.
|
||||
|
||||
### 4. Payment Page (app/(main)/payment/page.tsx)
|
||||
|
||||
**Route:** `/payment`
|
||||
**Type:** Client component
|
||||
**Description:** Payment/checkout page. Shows cart items in a table with quantity controls and a summary aside with payment actions.
|
||||
**Route:** `/payment` **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
|
||||
|
||||
@@ -254,32 +254,43 @@ registration flow. Two-step process: phone verification → account creation.
|
||||
- **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
|
||||
- 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
|
||||
- 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)
|
||||
- **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"
|
||||
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.
|
||||
- **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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+28
-20
@@ -200,52 +200,60 @@ 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.
|
||||
**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 |
|
||||
| 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 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.
|
||||
- **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.
|
||||
- **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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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
|
||||
- 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)
|
||||
- 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
|
||||
@@ -255,14 +263,14 @@ hover.
|
||||
const [isReviewOpen, setIsReviewOpen] = useState(false);
|
||||
|
||||
// Button in payment aside (only visible to customers)
|
||||
<button onClick={() => setIsReviewOpen(true)}>Đánh giá</button>
|
||||
<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)} />
|
||||
<ReviewModal isOpen={isReviewOpen} onClose={() => setIsReviewOpen(false)} />;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) {
|
||||
</div>
|
||||
<h2
|
||||
id="review-modal-title"
|
||||
className="text-xl font-bold text-foreground"
|
||||
className="text-foreground text-xl font-bold"
|
||||
>
|
||||
Cảm ơn quý khách
|
||||
</h2>
|
||||
@@ -71,7 +71,7 @@ export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) {
|
||||
<>
|
||||
<h2
|
||||
id="review-modal-title"
|
||||
className="mb-1 text-xl font-bold text-foreground"
|
||||
className="text-foreground mb-1 text-xl font-bold"
|
||||
>
|
||||
Đánh giá của bạn
|
||||
</h2>
|
||||
@@ -138,7 +138,7 @@ export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) {
|
||||
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"
|
||||
className="text-foreground w-full resize-none rounded-xl border border-(--color-border) bg-transparent px-3 py-2.5 text-sm transition-colors placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) {
|
||||
<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)"
|
||||
className="text-foreground inline-flex flex-1 items-center justify-center gap-2 rounded-xl border border-(--color-border) px-4 py-2.5 text-sm font-medium transition-colors hover:bg-(--color-border-light)"
|
||||
>
|
||||
<i className="fa-solid fa-arrow-left"></i>
|
||||
Quay lại
|
||||
@@ -156,7 +156,7 @@ export default function ReviewModal({ isOpen, onClose }: ReviewModalProps) {
|
||||
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"
|
||||
className="inline-flex flex-1 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
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend-container
|
||||
image: git.demonkernel.io.vn/foodsurf/frontend:latest
|
||||
image: git.demonkernel.io.vn/foodsurf/frontend:1.0.10
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
resources:
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "temp",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "temp",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/node": "^20.19.37",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "temp",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Generated
+11
-20
@@ -20,9 +20,6 @@ importers:
|
||||
next:
|
||||
specifier: 16.1.7
|
||||
version: 16.1.7(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
php:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
react:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
@@ -1077,8 +1074,8 @@ packages:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
axe-core@4.11.1:
|
||||
resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==}
|
||||
axe-core@4.11.2:
|
||||
resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
axobject-query@4.1.0:
|
||||
@@ -1135,8 +1132,8 @@ packages:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
browserslist@4.28.1:
|
||||
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
|
||||
browserslist@4.28.2:
|
||||
resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
@@ -2968,10 +2965,6 @@ packages:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
php@1.1.0:
|
||||
resolution: {integrity: sha512-IUIOrFBooOyTP3SD9zZZ0cX2UwL5fQj3cmuaXGcc1gsSbMdHseNcEmpwXoh+b0G+hv5LfBL0nBggEOS2YRk7sQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -3903,7 +3896,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.29.0
|
||||
'@babel/helper-validator-option': 7.27.1
|
||||
browserslist: 4.28.1
|
||||
browserslist: 4.28.2
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
@@ -4873,7 +4866,7 @@ snapshots:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
|
||||
axe-core@4.11.1: {}
|
||||
axe-core@4.11.2: {}
|
||||
|
||||
axobject-query@4.1.0: {}
|
||||
|
||||
@@ -4934,13 +4927,13 @@ snapshots:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
browserslist@4.28.1:
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.12
|
||||
caniuse-lite: 1.0.30001782
|
||||
electron-to-chromium: 1.5.329
|
||||
node-releases: 2.0.36
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
@@ -5504,7 +5497,7 @@ snapshots:
|
||||
array-includes: 3.1.9
|
||||
array.prototype.flatmap: 1.3.3
|
||||
ast-types-flow: 0.0.8
|
||||
axe-core: 4.11.1
|
||||
axe-core: 4.11.2
|
||||
axobject-query: 4.1.0
|
||||
damerau-levenshtein: 1.0.8
|
||||
emoji-regex: 9.2.2
|
||||
@@ -6854,8 +6847,6 @@ snapshots:
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
php@1.1.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.2: {}
|
||||
@@ -7688,9 +7679,9 @@ snapshots:
|
||||
|
||||
untildify@3.0.3: {}
|
||||
|
||||
update-browserslist-db@1.2.3(browserslist@4.28.1):
|
||||
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
||||
dependencies:
|
||||
browserslist: 4.28.1
|
||||
browserslist: 4.28.2
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ const config: GlobalConfig = {
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
prepareCmd:
|
||||
"node scripts/release.ts ${nextRelease.version} && sed -i 's|image: git.demonkernel.io.vn/foodsurf/frontend:.*|image: git.demonkernel.io.vn/foodsurf/frontend:${nextRelease.version}|g' k8s/k8s.yaml",
|
||||
"node scripts/release.ts ${nextRelease.version} && sed -i 's|image: git.demonkernel.io.vn/foodsurf/frontend:.*|image: git.demonkernel.io.vn/foodsurf/frontend:${nextRelease.version}|g' k8s.yaml",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user