Components Documentation
Whenever you create a new component in the components/ directory,
automatically append a new section following the template below.
CartProduct
File: components/CartProduct.tsx Description: Product card component.
Displays product image, name, description, formatted price, and a Buy button.
Width is controlled by the parent grid (w-full), not the card itself.
Props
| Prop |
Type |
Required |
Default |
Description |
| image |
string |
yes |
- |
URL/path to product image (Next.js Image) |
| imageAlt |
string |
no |
Anh san pham |
Alt text for accessibility |
| productName |
string |
yes |
- |
Product display name |
| price |
number or string |
yes |
- |
If number: auto-formatted to VND. If string: rendered as-is |
| description |
string |
yes |
- |
Short description, clamped to 2 lines |
| onBuy |
() => void |
no |
undefined |
Callback when Buy button is clicked |
Internal Logic
- formattedPrice: number -> toLocaleString(vi-VN, { style: currency, currency:
VND })
- Image fallback: fa-solid fa-mug-hot icon shown behind image; if image fails
onError hides the img element
Styling (CSS variables)
| Element |
Key classes |
| Card wrapper |
flex flex-col w-full rounded-2xl, shadow uses --color-shadow-sm/md |
| Image area |
relative w-full h-36, bg --color-border-light |
| Product name |
font-bold text-sm, color --color-text-primary, line-clamp-1 |
| Description |
text-xs, color --color-text-muted, line-clamp-2 |
| Price |
text-sm font-bold, color --color-primary |
| Buy button |
bg --color-primary, hover --color-primary-dark, active:scale-95 |
Dependencies
- next/image
- Tailwind CSS + CSS custom properties from globals.css
- FontAwesome (fa-solid fa-mug-hot fallback, fa-cart-plus button icon)
Notes
- Card width is w-full - controlled by parent grid in page.tsx
- available field on Product is checked in page.tsx before rendering
- onBuy currently logs to console - TODO: implement cart logic
Navbar
File: components/Navbar.tsx Description: Left sidebar with collapsible
category filter. Sticky below header, full viewport height minus header.
Props
| Prop |
Type |
Required |
Default |
Description |
| isOpen |
boolean |
yes |
- |
true = expanded (240px), false = collapsed (64px) |
| onToggle |
() => void |
yes |
- |
Toggle expand/collapse |
| activeCategory |
string |
no |
all |
Currently selected category id |
| onCategoryChange |
(id: string) => void |
no |
undefined |
Fired when user clicks a category |
Behavior
- Collapsed: 64px wide, icon only (w-16)
- Expanded: 240px wide, icon + label (w-60)
- Width transition: transition-all duration-250ms
- Active category: highlighted with --color-primary background
- Footer shows SHOP_INFO.openHours (icon only when collapsed)
Styling
| Element |
Key classes |
| Aside |
sticky, border-r --color-border, bg --color-bg-sidebar |
| Toggle button |
w-8 h-8 rounded-lg, hover bg --color-border-light |
| Active item |
bg --color-primary text-white shadow-sm |
| Inactive item |
hover bg --color-border-light, color --color-text-secondary |
Dependencies
- next/link
- lib/constants: MENU_CATEGORIES, SHOP_INFO
- lib/types: MenuCategory
- FontAwesome icons
File: layouts/header.tsx Description: Sticky top bar. 2-column layout:
Brand (left) + Auth button (right). Auth cycles Guest -> Manager -> Staff ->
Guest for UI demo.
Props
None - reads SHOP_INFO and MOCK_USERS from lib/constants directly.
Internal State
| State |
Type |
Description |
| user |
User or null |
Current demo user. null = guest |
Auth States
| State |
Appearance |
| Guest (null) |
Brown primary button, Dang nhap label |
| Manager |
Gold/caramel badge with user-tie icon |
| Staff |
Avatar circle + name, bordered button |
Responsive
- Logo + shop name: always visible
- Tagline: hidden < md, shown md+
- Button label: hidden < sm, shown sm+
Dependencies
- next/image, next/link
- lib/constants: SHOP_INFO, MOCK_USERS
- lib/types: User
- FontAwesome icons
File: layouts/footer.tsx Description: Site footer with 12-column grid. 3
sections: Brand info, Social links, WiFi card.
Props
None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly.
Layout
| Section |
Mobile |
md |
lg/xl |
| Brand info |
col-span-12 |
col-span-6 |
col-span-8/6 |
| Social + WiFi |
col-span-12 |
col-span-6 |
col-span-4/6 |
Sections
- Brand: logo, name, tagline, address, phone, email, open hours
- Social: Facebook, TikTok, Website links
- WiFi: network name + password in monospace styled box
- Bottom bar: copyright + Made with heart in Vietnam
Dependencies
- next/image, next/link
- lib/constants: SHOP_INFO, SOCIAL_LINKS
- FontAwesome icons
CartFab
File: components/CartFab.tsx Description: Floating Action Button
displaying cart item count. Shows badge with number of items and total price on
hover.
Props
| Prop |
Type |
Required |
Default |
Description |
| onClick |
() => void |
no |
undefined |
Callback when FAB is clicked |
Features
- Displays cart icon with item count badge
- Shows total price on hover in tooltip
- Sticky position (bottom-right)
- Uses cart context to get items and total
Styling
| Element |
Key classes |
| FAB button |
fixed bottom-6 right-6, rounded-full, shadow-lg |
| Badge |
absolute top-0 right-0, red bg, small font |
| Tooltip |
appears on hover, shows total price |
Dependencies
- lib/cart-context: useCart()
- FontAwesome icons
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
Contexts Documentation
AuthContext (lib/auth-context.tsx)
File: lib/auth-context.tsx Description: Manages user authentication
state including login, logout, and registration. Uses localStorage for
persistence.
Provider Props
| Prop |
Type |
Required |
Description |
| children |
React.ReactNode |
yes |
Child components |
Hook: useAuth()
Returns AuthContextType with:
| Property |
Type |
Description |
| user |
User | null |
Current logged-in user or null |
| login |
(username: string, password: string) => boolean |
Login function; returns success status |
| logout |
() => void |
Logout function; clears user and localStorage |
| registerPhone |
string | null |
Phone number during registration flow |
| setRegisterPhone |
(phone: string | null) => void |
Update registerPhone state |
| completeRegistration |
(phone: string) => void |
Complete registration and create customer account |
Mock Database
Pre-configured accounts:
- Manager:
admin / admin
- Staff:
Nguyễn Văn An / Nguyễn Văn An, Trần Thị Bình / Trần Thị Bình, etc.
- Customer: Phone number as username,
user1 as password
Storage
- Key:
coffee-shop-user
- Format: JSON serialized User object
CartContext (lib/cart-context.tsx)
File: lib/cart-context.tsx Description: Manages shopping cart state with
localStorage persistence. Tracks items, quantities, and totals.
Provider Props
| Prop |
Type |
Required |
Description |
| children |
React.ReactNode |
yes |
Child components |
Hook: useCart()
Returns CartContextValue with:
| Property |
Type |
Description |
| items |
CartItem[] |
Array of items in cart |
| totalItems |
number |
Total quantity of items |
| totalPrice |
number |
Total price in VND |
| addToCart |
(product: Product) => void |
Add or increase product quantity |
| increaseQty |
(id: number) => void |
Increase product quantity by 1 |
| decreaseQty |
(id: number) => void |
Decrease product quantity by 1 (removes if qty reaches 0) |
| removeFromCart |
(id: number) => void |
Remove product from cart |
| setQuantity |
(id: number, quantity: number) => void |
Set exact quantity (removes if 0) |
CartItem Interface
Storage
- Key:
coffee-shop-cart
- Format: JSON serialized CartItem[]
- Auto-loads on mount and auto-saves on change
File: lib/menu-context.tsx Description: Provides shared category/menu
state across components. Synchronizes Header mobile menu and Navbar sidebar
selection.
Provider Props
| Prop |
Type |
Required |
Description |
| children |
React.ReactNode |
yes |
Child components |
Returns MenuContextType with:
| Property |
Type |
Description |
| activeCategory |
string |
Currently selected category id (default: "all") |
| setActiveCategory |
(id: string) => void |
Update active category |
Use Cases
- Sync Navbar sidebar and Header mobile menu category selection
- Clear search query when category changes (implemented in main page)
- Pass selected category to product filter logic
Default Value
activeCategory: "all" - Show all products by default