chore: release [ci skip]

This commit is contained in:
gitea-actions
2026-03-30 01:24:59 +00:00
parent 5c771b8c9c
commit 55ff138100
14 changed files with 322 additions and 242 deletions
+1 -3
View File
@@ -1,7 +1,5 @@
{
"permissions": {
"allow": [
"Bash(npx next build)"
]
"allow": ["Bash(npx next build)"]
}
}
+13 -1
View File
@@ -12,6 +12,7 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
### Tính Năng Hiện Tại
#### 1. **Trang Đăng Nhập & Đăng Ký** (app/(main)/login, app/(main)/register)
- Form đăng nhập với validation (username, password)
- Form đăng ký với xác thực OTP
- Lưu trữ thông tin người dùng trong localStorage
@@ -19,6 +20,7 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
- Hiển thị thông tin shop trong form
#### 2. **Trang Khám Phá Quán Nước** (app/(feed)/feed)
- Danh sách các quán cà phê
- Tìm kiếm theo tên quán và địa chỉ
- Thẻ quán với ảnh, địa chỉ, nút "Xem menu"
@@ -26,7 +28,9 @@ Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ u
- Empty state khi không tìm thấy quán
#### 3. **Trang Chính - Duyệt Thực Đơn** (app/(main))
Dành cho khách hàng:
- Sidebar collapsible (64px/240px) - danh mục sản phẩm
- Grid sản phẩm responsive (1-5 cột tuỳ thiết bị)
- Tìm kiếm món theo tên / mô tả
@@ -35,18 +39,21 @@ Dành cho khách hàng:
- Mobile menu: scrollable category tabs (< md)
#### 4. **Hệ Thống Giỏ Hàng** (lib/cart-context.tsx)
- Lưu trữ trạng thái giỏ trong localStorage
- Thêm/xóa/tăng/giảm số lượng sản phẩm
- Tính tổng giá và số mặt hàng
- Persist dữ liệu giữa các session
#### 5. **Hệ Thống Xác Thực** (lib/auth-context.tsx)
- Quản lý trạng thái người dùng (login/logout/register)
- Lưu thông tin user trong localStorage
- Mock auth database với 3 loại tài khoản
- Hỗ trợ hoàn tất đăng ký qua OTP
#### 6. **Hệ Thống Danh Mục** (lib/menu-context.tsx)
- Chia sẻ trạng thái category giữa Header mobile và Sidebar
- Tự động clear search khi thay đổi category
@@ -159,24 +166,29 @@ frondend/
## Ghi Chú Phát Triển
### Điểm Vào Chính
- **Trang Chủ (User):** `app/(main)/page.tsx` - Duyệt thực đơn
- **Trang Khám Phá:** `app/(feed)/feed/page.tsx` - Khám phá quán
- **Đăng Nhập:** `app/(main)/login/page.tsx`
- **Đăng Ký:** `app/(main)/register/page.tsx`
### Design & Styling
- Design tokens (CSS variables) định nghĩa trong `app/globals.css`
- Tailwind CSS v4 + custom properties cho consistent color/spacing
- Dark mode: biến CSS đã chuẩn bị sẵn nhưng chưa kích hoạt
- FontAwesome icons từ CDN
### Data & Integration
- Mock data nằm trong `lib/constants.ts`
- Context providers trong `app/providers.tsx` - Sử dụng: AuthProvider, MenuProvider, CartProvider
- Context providers trong `app/providers.tsx` - Sử dụng: AuthProvider,
MenuProvider, CartProvider
- Thay bằng API calls khi backend sẵn sàng
- Ảnh sản phẩm: thêm vào `public/imgs/products/`
### Tài Liệu Chi Tiết
- **WORKFLOW.md** - Kiến trúc tổng thể, data flow, quy trình phát triển
- **APP.md** - Chi tiết các routes, layouts, pages
- **COMPONENTS.md** - Tài liệu từng component + contexts
+7
View File
@@ -42,6 +42,7 @@
## Pending Features (Future Work)
### Cart & Ordering
- [ ] Implement cart checkout flow (app/(main)/cart or modal)
- [ ] Cart sidebar/modal with item list and total
- [ ] Order submission API integration
@@ -50,6 +51,7 @@
- [ ] Toast notifications for cart actions
### Authentication & User Management
- [ ] Real backend authentication (replace MOCK_AUTH_DB)
- [ ] Real OTP delivery service (SMS integration)
- [ ] User profile page with edit capability
@@ -57,6 +59,7 @@
- [ ] Session management and token refresh
### Manager Features
- [ ] Manager dashboard page (app/(manager)/page.tsx)
- [ ] Product management (add/edit/delete)
- [ ] Category management
@@ -65,6 +68,7 @@
- [ ] Inventory management
### Backend Integration
- [ ] Replace MOCK_PRODUCTS with API calls (GET /api/products)
- [ ] Replace MOCK_SHOPS with API calls (GET /api/shops)
- [ ] Replace MOCK_USERS with real authentication (POST /api/auth/login)
@@ -72,6 +76,7 @@
- [ ] Image upload for products
### UX Improvements
- [ ] Dark mode toggle (CSS variables prepared, toggle UI needed)
- [ ] Loading skeletons for product grid
- [ ] Product detail modal/page with full description
@@ -82,6 +87,7 @@
- [ ] Related products suggestions
### Performance & SEO
- [ ] Dynamic route generation for products (app/(main)/product/[id]/page.tsx)
- [ ] Dynamic route generation for shops (app/(feed)/shop/[id]/page.tsx)
- [ ] Meta tags and Open Graph for SEO
@@ -89,6 +95,7 @@
- [ ] Code splitting and dynamic imports
### Accessibility & Testing
- [ ] Keyboard navigation testing
- [ ] ARIA labels audit
- [ ] Unit tests for contexts
+24 -59
View File
@@ -1,9 +1,9 @@
"use client";
import { useState } from "react";
import { MOCK_SHOPS } from "@/lib/constants";
import Image from "next/image";
import Link from "next/link";
import { MOCK_SHOPS } from "@/lib/constants";
import { useState } from "react";
export default function FeedPage() {
const [searchName, setSearchName] = useState("");
@@ -21,32 +21,27 @@ export default function FeedPage() {
return (
<main className="bg-background min-h-[calc(100vh-var(--spacing-header-height))]">
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8 py-8">
<div className="mx-auto max-w-7xl px-4 py-8 md:px-6 lg:px-8">
{/* Page title */}
<div className="mb-8">
<h1 className="text-2xl md:text-3xl font-bold text-foreground">
<h1 className="text-foreground text-2xl font-bold md:text-3xl">
Khám phá quán nước
</h1>
<p className="text-sm text-(--color-text-muted) mt-1">
<p className="mt-1 text-sm text-(--color-text-muted)">
Tìm chọn quán yêu thích của bạn
</p>
</div>
{/* Shop cards grid */}
{filteredShops.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-10">
<div className="mb-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{filteredShops.map((shop) => (
<div
key={shop.id}
className="bg-(--color-bg-card) rounded-2xl border border-(--color-border)
shadow-[0_2px_12px_var(--color-shadow-sm)]
overflow-hidden
hover:shadow-[0_4px_20px_var(--color-shadow-md)]
hover:-translate-y-1
transition-all duration-250"
className="overflow-hidden rounded-2xl border border-(--color-border) bg-(--color-bg-card) shadow-[0_2px_12px_var(--color-shadow-sm)] transition-all duration-250 hover:-translate-y-1 hover:shadow-[0_4px_20px_var(--color-shadow-md)]"
>
{/* Shop image */}
<div className="relative w-full h-48 sm:h-52">
<div className="relative h-48 w-full sm:h-52">
<Image
src={shop.image}
alt={shop.name}
@@ -59,18 +54,13 @@ export default function FeedPage() {
{/* Card body */}
<div className="p-4">
{/* Name + View menu button */}
<div className="flex items-center justify-between gap-3 mb-2">
<h3 className="font-bold text-base text-foreground truncate">
<div className="mb-2 flex items-center justify-between gap-3">
<h3 className="text-foreground truncate text-base font-bold">
{shop.name}
</h3>
<Link
href="/"
className="shrink-0 inline-flex items-center gap-1.5
px-3.5 py-2 rounded-xl text-xs font-semibold
bg-(--color-primary) text-white
hover:bg-(--color-primary-dark)
active:scale-95
transition-all duration-150 no-underline"
className="inline-flex shrink-0 items-center gap-1.5 rounded-xl bg-(--color-primary) px-3.5 py-2 text-xs font-semibold text-white no-underline transition-all duration-150 hover:bg-(--color-primary-dark) active:scale-95"
>
<i className="fa-solid fa-book-open text-[10px]"></i>
Xem menu
@@ -88,7 +78,7 @@ export default function FeedPage() {
</div>
) : (
/* Empty state */
<div className="flex flex-col items-center justify-center py-24 gap-4 text-(--color-text-muted) mb-10">
<div className="mb-10 flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
<i className="fa-solid fa-store text-5xl opacity-30"></i>
<p className="text-base font-medium">
Không tìm thấy quán nào phù hợp
@@ -98,8 +88,7 @@ export default function FeedPage() {
setSearchName("");
setSearchAddress("");
}}
className="text-sm text-(--color-primary) hover:underline cursor-pointer
border-none bg-transparent"
className="cursor-pointer border-none bg-transparent text-sm text-(--color-primary) hover:underline"
>
Xóa bộ lọc
</button>
@@ -107,43 +96,29 @@ export default function FeedPage() {
)}
{/* Filter / Search bar */}
<div
className="sticky bottom-0 bg-(--color-bg-card) border border-(--color-border)
rounded-2xl shadow-[0_-2px_16px_var(--color-shadow-sm)]
p-4 md:p-5"
>
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<div className="sticky bottom-0 rounded-2xl border border-(--color-border) bg-(--color-bg-card) p-4 shadow-[0_-2px_16px_var(--color-shadow-sm)] md:p-5">
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
{/* Label */}
<div className="flex items-center gap-2 shrink-0 text-sm font-semibold text-(--color-text-secondary)">
<div className="flex shrink-0 items-center gap-2 text-sm font-semibold text-(--color-text-secondary)">
<i className="fa-solid fa-filter text-(--color-primary)"></i>
<span>Lọc quán</span>
</div>
{/* Search by name */}
<div className="relative flex-1 min-w-0">
<i
className="fa-solid fa-store absolute left-3 top-1/2 -translate-y-1/2
text-xs text-(--color-text-muted) pointer-events-none"
></i>
<div className="relative min-w-0 flex-1">
<i className="fa-solid fa-store pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-xs text-(--color-text-muted)"></i>
<input
type="text"
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
placeholder="Tìm theo tên quán..."
className="w-full pl-9 pr-9 py-2.5 text-sm rounded-xl border outline-none
bg-background text-foreground
border-(--color-border) placeholder:text-(--color-text-muted)
focus:border-(--color-primary) focus:ring-2
focus:ring-(--color-primary) focus:ring-opacity-20
transition-all duration-150"
className="bg-background text-foreground focus:ring-opacity-20 w-full rounded-xl border border-(--color-border) py-2.5 pr-9 pl-9 text-sm transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)"
/>
{searchName && (
<button
onClick={() => setSearchName("")}
aria-label="Xóa tìm kiếm tên"
className="absolute right-3 top-1/2 -translate-y-1/2 text-(--color-text-muted)
hover:text-(--color-primary) transition-colors duration-150
cursor-pointer border-none bg-transparent p-0"
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent p-0 text-(--color-text-muted) transition-colors duration-150 hover:text-(--color-primary)"
>
<i className="fa-solid fa-xmark text-sm"></i>
</button>
@@ -151,30 +126,20 @@ export default function FeedPage() {
</div>
{/* Search by address */}
<div className="relative flex-1 min-w-0">
<i
className="fa-solid fa-location-dot absolute left-3 top-1/2 -translate-y-1/2
text-xs text-(--color-text-muted) pointer-events-none"
></i>
<div className="relative min-w-0 flex-1">
<i className="fa-solid fa-location-dot pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-xs text-(--color-text-muted)"></i>
<input
type="text"
value={searchAddress}
onChange={(e) => setSearchAddress(e.target.value)}
placeholder="Tìm theo địa chỉ..."
className="w-full pl-9 pr-9 py-2.5 text-sm rounded-xl border outline-none
bg-background text-foreground
border-(--color-border) placeholder:text-(--color-text-muted)
focus:border-(--color-primary) focus:ring-2
focus:ring-(--color-primary) focus:ring-opacity-20
transition-all duration-150"
className="bg-background text-foreground focus:ring-opacity-20 w-full rounded-xl border border-(--color-border) py-2.5 pr-9 pl-9 text-sm transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)"
/>
{searchAddress && (
<button
onClick={() => setSearchAddress("")}
aria-label="Xóa tìm kiếm địa chỉ"
className="absolute right-3 top-1/2 -translate-y-1/2 text-(--color-text-muted)
hover:text-(--color-primary) transition-colors duration-150
cursor-pointer border-none bg-transparent p-0"
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent p-0 text-(--color-text-muted) transition-colors duration-150 hover:text-(--color-primary)"
>
<i className="fa-solid fa-xmark text-sm"></i>
</button>
+5 -19
View File
@@ -9,23 +9,14 @@ export default function FeedLayout({
return (
<>
{/* Custom Drinkool header — no login button */}
<header
className="w-full sticky top-0 z-50
bg-(--color-bg-header) border-b border-(--color-border)
shadow-[0_1px_8px_var(--color-shadow-sm)]
h-(--spacing-header-height)"
>
<div
className="h-full px-6 md:px-8 lg:px-12
flex items-center gap-4
max-w-screen-2xl mx-auto"
>
<header className="sticky top-0 z-50 h-(--spacing-header-height) w-full border-b border-(--color-border) bg-(--color-bg-header) shadow-[0_1px_8px_var(--color-shadow-sm)]">
<div className="mx-auto flex h-full max-w-screen-2xl items-center gap-4 px-6 md:px-8 lg:px-12">
<Link
href="/feed"
className="flex items-center gap-3 shrink-0 no-underline group"
className="group flex shrink-0 items-center gap-3 no-underline"
>
{/* Logo */}
<div className="relative w-10 h-10 md:w-11 md:h-11 shrink-0">
<div className="relative h-10 w-10 shrink-0 md:h-11 md:w-11">
<Image
src="/imgs/logo.png"
alt="Logo Drinkool"
@@ -37,12 +28,7 @@ export default function FeedLayout({
</div>
{/* Brand name */}
<span
className="font-bold text-lg md:text-xl
text-(--color-primary-dark)
group-hover:text-(--color-primary)
transition-colors duration-150"
>
<span className="text-lg font-bold text-(--color-primary-dark) transition-colors duration-150 group-hover:text-(--color-primary) md:text-xl">
Drinkool
</span>
</Link>
+2 -2
View File
@@ -1,6 +1,6 @@
import Header from "@/layouts/header";
import Footer from "@/layouts/footer";
import CartFab from "@/components/CartFab";
import Footer from "@/layouts/footer";
import Header from "@/layouts/header";
export default function MainLayout({
children,
+57 -23
View File
@@ -30,20 +30,24 @@ app/
## Root Layout (app/layout.tsx)
**Description:** Root layout wrapping entire application. Loads providers, fonts, and common layouts (header/footer).
**Description:** Root layout wrapping entire application. Loads providers,
fonts, and common layouts (header/footer).
### Structure
- Imports providers (AuthProvider, MenuProvider, CartProvider)
- Renders Header component (sticky top)
- Renders page content via {children}
- Renders Footer component
### Metadata
- Title: "Coffee Shop"
- Charset: UTF-8
- Viewport: responsive
### Key Features
- Global CSS variables loaded here
- Header remains sticky across all pages
- Footer always present
@@ -52,9 +56,11 @@ app/
## Providers (app/providers.tsx)
**Description:** Client component that wraps all context providers for the application.
**Description:** Client component that wraps all context providers for the
application.
### Providers Included
1. **AuthProvider** (lib/auth-context.tsx)
- Manages user authentication state
- Persists user in localStorage
@@ -70,6 +76,7 @@ app/
- Provides add/remove/update operations
### Usage in Root Layout
```tsx
<Providers>
<Header />
@@ -84,9 +91,11 @@ app/
### Layout (app/(main)/layout.tsx)
**Description:** Layout for authenticated/main routes. Can include route-specific UI (e.g., sidebar navigation).
**Description:** Layout for authenticated/main routes. Can include
route-specific UI (e.g., sidebar navigation).
### Usage
- Wraps all pages under `(main)/` route group
- Inherits root layout above it
- Can add additional styling or structure specific to main pages
@@ -97,11 +106,12 @@ app/
### 1. Main Page - Duyệt Thực Đơn (app/(main)/page.tsx)
**Route:** `/` (default) or `/(main)`
**Type:** Client component
**Description:** Main shopping interface. Shows product grid with sidebar category filter.
**Route:** `/` (default) or `/(main)` **Type:** Client component
**Description:** Main shopping interface. Shows product grid with sidebar
category filter.
#### Key Features
- **Sidebar (Navbar):**
- Collapsible (64px collapsed / 240px expanded)
- Category list with icons
@@ -120,18 +130,21 @@ app/
- Allows category selection on mobile
#### Context Usage
- **useMenu()** - Get/set active category
- **useCart()** - Add products to cart
- **MenuContext** - Shares category state with Header
#### State Management
```tsx
isSidebarOpen: boolean // Sidebar expanded/collapsed
searchQuery: string // Search input value
activeCategory: string // From MenuContext
isSidebarOpen: boolean; // Sidebar expanded/collapsed
searchQuery: string; // Search input value
activeCategory: string; // From MenuContext
```
#### Responsive Behavior
- **Mobile (<480px):** 1 col, sidebar collapsed
- **Small phone (480px):** 2 cols, sidebar collapsed
- **Tablet (768px):** Mobile menu tabs appear
@@ -142,11 +155,11 @@ activeCategory: string // From MenuContext
### 2. Login Page (app/(main)/login/page.tsx)
**Route:** `/login`
**Type:** Client component
**Description:** User authentication form. Supports login for Manager, Staff, and Customer roles.
**Route:** `/login` **Type:** Client component **Description:** User
authentication form. Supports login for Manager, Staff, and Customer roles.
#### Key Features
- **Form Fields:**
- Username input (with icon)
- Password input (with show/hide toggle)
@@ -171,9 +184,11 @@ activeCategory: string // From MenuContext
- Responsive design
#### Context Usage
- **useAuth()** - Login function
#### Navigation
- Success: redirect to `/` (main page)
- Register link: go to `/register`
@@ -181,11 +196,11 @@ activeCategory: string // From MenuContext
### 3. Register Page (app/(main)/register/page.tsx)
**Route:** `/register`
**Type:** Client component
**Description:** User registration flow. Two-step process: phone verification → account creation.
**Route:** `/register` **Type:** Client component **Description:** User
registration flow. Two-step process: phone verification → account creation.
#### Key Features
- **Step 1: Phone Verification**
- Input phone number
- "Request OTP" button
@@ -206,9 +221,11 @@ activeCategory: string // From MenuContext
- Progress feedback
#### Context Usage
- **useAuth()** - completeRegistration()
#### Navigation
- Success: redirect to `/` (main page)
- Login link: go to `/login`
@@ -216,9 +233,9 @@ 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.
**Route:** `/payment` **Type:** Client component (TBD) **Description:**
Payment/checkout page. Currently a placeholder; implement when cart and order
system are ready.
---
@@ -226,17 +243,18 @@ activeCategory: string // From MenuContext
### Layout (app/(feed)/layout.tsx)
**Description:** Layout for feed routes. Can have specific styling for discovery/exploration pages.
**Description:** Layout for feed routes. Can have specific styling for
discovery/exploration pages.
---
### Feed Page - Khám Phá Quán (app/(feed)/feed/page.tsx)
**Route:** `/feed`
**Type:** Client component
**Description:** Discover coffee shops. Browse and search available shops with their location and details.
**Route:** `/feed` **Type:** Client component **Description:** Discover coffee
shops. Browse and search available shops with their location and details.
#### Key Features
- **Shop Cards:**
- Shop image (responsive height)
- Name and location
@@ -258,9 +276,11 @@ activeCategory: string // From MenuContext
- Clear filter button
#### Context Usage
- **useAuth()** - Optional: check user role
#### State Management
```tsx
searchName: string // Shop name search
searchAddress: string // Shop address search
@@ -268,6 +288,7 @@ filteredShops: Shop[] // Filtered results
```
#### Data Source
- MOCK_SHOPS from lib/constants.ts
---
@@ -276,12 +297,15 @@ filteredShops: Shop[] // Filtered results
### Global Styles (app/globals.css)
**Description:** Root CSS file. Imports Tailwind CSS, defines design tokens (CSS variables), and global styles.
**Description:** Root CSS file. Imports Tailwind CSS, defines design tokens (CSS
variables), and global styles.
### Design Tokens (CSS Variables)
Defined at `:root` for light mode, with dark mode variants:
#### Colors
- `--color-primary` - Main brand color (brown)
- `--color-primary-dark` - Darker shade for hover
- `--color-accent` - Secondary accent color
@@ -291,13 +315,16 @@ Defined at `:root` for light mode, with dark mode variants:
- `--color-shadow-*` - Shadow colors (sm, md)
#### Spacing
- `--spacing-header-height` - Header component height
- Standard Tailwind spacing (via Tailwind)
#### Typography
- Font stack defined via Google Fonts (Geist)
### Tailwind Config Integration
- v4 with custom properties support
- Extends with CSS variables
- Dark mode: CSS variable override
@@ -307,22 +334,26 @@ Defined at `:root` for light mode, with dark mode variants:
## Key Concepts
### Route Groups
- `(main)` and `(feed)` are route groups (don't appear in URL)
- Allow different layouts/providers per route group
- Used for organization and flexibility
### SSR vs Client Components
- Most pages are "use client" (need interactivity)
- Context providers must be client components
- Layout.tsx can be server component
### Responsive Design
- Breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px)
- Sidebar width affects grid columns
- Mobile: vertical layout, collapsed sidebar
- Desktop: horizontal layout, expanded sidebar
### Navigation
- Internal links use `next/link`
- useRouter() for programmatic navigation
- Auth redirects after login/register
@@ -332,6 +363,7 @@ Defined at `:root` for light mode, with dark mode variants:
## Development Workflow
### Adding a New Page
1. Create file under appropriate route group: `app/(main)/new-feature/page.tsx`
2. Make it a client component if it needs interactivity: `"use client"`
3. Use context hooks as needed (useAuth, useCart, useMenu)
@@ -339,12 +371,14 @@ Defined at `:root` for light mode, with dark mode variants:
5. Add to documentation
### Adding a New Route Group
1. Create directory: `app/(group-name)/`
2. Create `layout.tsx` if custom layout needed
3. Add pages under this group
4. Update codebase understanding
### Styling Pages
- Use Tailwind classes + CSS variables
- Reference design tokens for consistency
- Follow responsive patterns from existing pages
+1 -3
View File
@@ -46,9 +46,7 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} flex min-h-screen flex-col antialiased`}
>
<Providers>
{children}
</Providers>
<Providers>{children}</Providers>
</body>
</html>
);
+50 -46
View File
@@ -166,14 +166,15 @@ None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly.
## CartFab
**File:** components/CartFab.tsx
**Description:** Floating Action Button displaying cart item count. Shows badge with number of items and total price on hover.
**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 |
| Prop | Type | Required | Default | Description |
| ------- | ---------- | -------- | --------- | ---------------------------- |
| onClick | () => void | no | undefined | Callback when FAB is clicked |
### Features
@@ -184,11 +185,11 @@ None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly.
### Styling
| Element | Key classes |
|---------|-------------|
| 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 |
| Badge | absolute top-0 right-0, red bg, small font |
| Tooltip | appears on hover, shows total price |
### Dependencies
@@ -201,31 +202,33 @@ None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly.
## AuthContext (lib/auth-context.tsx)
**File:** lib/auth-context.tsx
**Description:** Manages user authentication state including login, logout, and registration. Uses localStorage for persistence.
**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 |
| 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 |
| 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
@@ -239,29 +242,29 @@ Pre-configured accounts:
## CartContext (lib/cart-context.tsx)
**File:** lib/cart-context.tsx
**Description:** Manages shopping cart state with localStorage persistence. Tracks items, quantities, and totals.
**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 |
| 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) |
| 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
@@ -285,23 +288,24 @@ interface CartItem {
## MenuContext (lib/menu-context.tsx)
**File:** lib/menu-context.tsx
**Description:** Provides shared category/menu state across components. Synchronizes Header mobile menu and Navbar sidebar selection.
**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 |
| Prop | Type | Required | Description |
| -------- | --------------- | -------- | ---------------- |
| children | React.ReactNode | yes | Child components |
### Hook: useMenu()
Returns `MenuContextType` with:
| Property | Type | Description |
|----------|------|-------------|
| activeCategory | string | Currently selected category id (default: "all") |
| setActiveCategory | (id: string) => void | Update active category |
| Property | Type | Description |
| ----------------- | -------------------- | ----------------------------------------------- |
| activeCategory | string | Currently selected category id (default: "all") |
| setActiveCategory | (id: string) => void | Update active category |
### Use Cases
+79 -50
View File
@@ -6,7 +6,9 @@
## Overview
The `layouts/` directory contains layout-level components used across all pages. These are included in the root layout and provide consistent header/footer across the application.
The `layouts/` directory contains layout-level components used across all pages.
These are included in the root layout and provide consistent header/footer
across the application.
```
layouts/
@@ -19,9 +21,8 @@ layouts/
## Header (layouts/header.tsx)
**File:** layouts/header.tsx
**Type:** Client component
**Description:** Sticky top navigation bar with brand info and authentication status display.
**File:** layouts/header.tsx **Type:** Client component **Description:** Sticky
top navigation bar with brand info and authentication status display.
### Layout
@@ -33,48 +34,53 @@ height: 72px (var(--spacing-header-height))
```
**2-Column Layout:**
- **Left:** Shop branding (logo + name + tagline)
- **Right:** Auth status indicator
### Features
#### Logo & Branding
- Shop logo (40x40px)
- Shop name (bold text)
- Tagline (hidden on mobile < md, visible md+)
**Responsive:**
| Breakpoint | Logo | Name | Tagline |
|-----------|------|------|---------|
| < sm (640px) | Yes | Yes | Hidden |
| sm-md | Yes | Yes | Hidden |
| md+ (768px) | Yes | Yes | Visible |
**Responsive:** | Breakpoint | Logo | Name | Tagline |
|-----------|------|------|---------| | < sm (640px) | Yes | Yes | Hidden | |
sm-md | Yes | Yes | Hidden | | md+ (768px) | Yes | Yes | Visible |
#### Authentication Status
The header cycles through 3 auth states (for UI demo):
**State 1: Guest (Not logged in)**
```
[brown button] "Dang nhap"
```
- Guest state (user = null)
- Brown primary button
- Click: toggles to next state
**State 2: Manager**
```
[caramel/gold badge] "👔 Quản lý"
```
- Shows manager role
- Gold/caramel colored badge
- User tie icon
- Name: Nguyễn Văn An
**State 3: Staff**
```
[border button] "👤 [Name]"
```
- Shows staff role with avatar placeholder
- Bordered button style
- Name: Trần Thị Bình
@@ -82,19 +88,21 @@ The header cycles through 3 auth states (for UI demo):
### Responsive Behavior
| Breakpoint | Layout |
|-----------|--------|
| < sm | Stacked vertically |
| sm-md (640px) | Side-by-side, reduced spacing |
| md+ (768px) | Full width, comfortable spacing |
| Breakpoint | Layout |
| ------------- | ------------------------------- |
| < sm | Stacked vertically |
| sm-md (640px) | Side-by-side, reduced spacing |
| md+ (768px) | Full width, comfortable spacing |
**Mobile (<640px):**
- Logo smaller
- Name only (no tagline)
- Auth button text hidden, icon shown
- Touch-friendly padding
**Desktop (≥640px):**
- Normal logo size
- Full layout with tagline
- Button with text and icon
@@ -102,16 +110,16 @@ The header cycles through 3 auth states (for UI demo):
### Styling
| Element | Classes |
|---------|---------|
| Header wrapper | `sticky top-0 z-50 bg-white shadow-md` |
| Container | `max-w-7xl mx-auto px-4 md:px-6 h-72px flex items-center` |
| Left section (brand) | `flex items-center gap-3 flex-1` |
| Right section (auth) | `flex items-center gap-3 shrink-0` |
| Logo image | `w-10 h-10 md:w-12 md:h-12` |
| Brand text | `font-bold text-sm md:text-base` |
| Tagline | `hidden md:block text-xs text-muted-foreground` |
| Auth button | `px-4 py-2 rounded-lg text-sm font-medium` |
| Element | Classes |
| -------------------- | --------------------------------------------------------- |
| Header wrapper | `sticky top-0 z-50 bg-white shadow-md` |
| Container | `max-w-7xl mx-auto px-4 md:px-6 h-72px flex items-center` |
| Left section (brand) | `flex items-center gap-3 flex-1` |
| Right section (auth) | `flex items-center gap-3 shrink-0` |
| Logo image | `w-10 h-10 md:w-12 md:h-12` |
| Brand text | `font-bold text-sm md:text-base` |
| Tagline | `hidden md:block text-xs text-muted-foreground` |
| Auth button | `px-4 py-2 rounded-lg text-sm font-medium` |
### CSS Variables Used
@@ -130,11 +138,13 @@ The header cycles through 3 auth states (for UI demo):
- **next/link** - Navigation links
- **lib/constants:** SHOP_INFO, MOCK_USERS
- **lib/types:** User
- **FontAwesome icons:** fa-sign-in-alt, fa-sign-out-alt, fa-user-tie, fa-user-circle
- **FontAwesome icons:** fa-sign-in-alt, fa-sign-out-alt, fa-user-tie,
fa-user-circle
### Key Code Snippets
**Auth Demo State Machine:**
```typescript
const [user, setUser] = useState<User | null>(null);
@@ -150,6 +160,7 @@ const handleAuthClick = () => {
```
**Responsive Brand Text:**
```typescript
<div className="flex flex-col">
<span className="font-bold text-sm md:text-base">{SHOP_INFO.name}</span>
@@ -163,9 +174,9 @@ const handleAuthClick = () => {
## Footer (layouts/footer.tsx)
**File:** layouts/footer.tsx
**Type:** Client component (uses constants)
**Description:** Site footer with shop information, social links, and WiFi details.
**File:** layouts/footer.tsx **Type:** Client component (uses constants)
**Description:** Site footer with shop information, social links, and WiFi
details.
### Layout
@@ -179,17 +190,18 @@ const handleAuthClick = () => {
**12-Column Grid Layout:**
| Breakpoint | Brand Info | Social + WiFi |
|-----------|-----------|---------------|
| Mobile | col-span-12 | col-span-12 |
| md (768px) | col-span-6 | col-span-6 |
| lg+ (1024px) | col-span-6 | col-span-6 |
| Breakpoint | Brand Info | Social + WiFi |
| ------------ | ----------- | ------------- |
| Mobile | col-span-12 | col-span-12 |
| md (768px) | col-span-6 | col-span-6 |
| lg+ (1024px) | col-span-6 | col-span-6 |
### Sections
#### 1. Brand Info (Left Section)
**Content:**
- Shop logo (40x40px)
- Shop name (bold)
- Tagline (gray text)
@@ -199,6 +211,7 @@ const handleAuthClick = () => {
- Open hours with clock icon
**Responsive:**
```
Mobile:
└─ Logo
@@ -216,11 +229,13 @@ Desktop:
#### 2. Social Links (Top Right)
**Links:**
- Facebook icon → SHOP_INFO.facebook
- TikTok icon → SHOP_INFO.tiktok
- Website icon → SHOP_INFO.website
**Styling:**
- Icons in circle backgrounds
- Hover effect (color change)
- Flex row layout
@@ -229,17 +244,20 @@ Desktop:
#### 3. WiFi Card (Bottom Right)
**Content:**
- "📶 WiFi miễn phí" label
- Network name (monospace)
- Password (monospace, can be hidden/shown)
**Styling:**
- Light gray background
- Rounded borders
- Monospace font for credentials
- Eye icon toggle for password visibility
**Example:**
```
📶 WiFi miễn phí
┌────────────────────────┐
@@ -252,11 +270,13 @@ Desktop:
#### 4. Copyright Bar (Bottom)
**Content:**
- Copyright text: "© 2024 Coffee Shop"
- "Made with ❤️ in Vietnam"
- Centered
**Styling:**
- Small text
- Gray color
- Subtle separator line above
@@ -264,35 +284,38 @@ Desktop:
### Responsive Behavior
**Mobile (<768px):**
- Single column layout
- Stacked sections
- Full width
- Padding around edges
**Tablet (768px):**
- 2-column layout
- Brand info left, Social + WiFi right
- Equal width columns
**Desktop (≥768px):**
- 2-column layout with more spacing
- Brand info slightly smaller
- Comfortable padding
### Styling
| Element | Classes |
|---------|---------|
| Footer wrapper | `bg-gray-100 border-t` |
| Container | `max-w-7xl mx-auto px-4 py-8` |
| Grid | `grid grid-cols-12 gap-8` |
| Brand section | `col-span-12 md:col-span-6` |
| Social section | `col-span-12 md:col-span-6` |
| Logo | `w-10 h-10` |
| Info text | `text-sm text-gray-600` |
| Social icons | `w-10 h-10 rounded-full flex items-center justify-center` |
| WiFi card | `bg-white p-4 rounded-lg border` |
| Copyright bar | `border-t mt-8 pt-6 text-center text-xs text-gray-500` |
| Element | Classes |
| -------------- | --------------------------------------------------------- |
| Footer wrapper | `bg-gray-100 border-t` |
| Container | `max-w-7xl mx-auto px-4 py-8` |
| Grid | `grid grid-cols-12 gap-8` |
| Brand section | `col-span-12 md:col-span-6` |
| Social section | `col-span-12 md:col-span-6` |
| Logo | `w-10 h-10` |
| Info text | `text-sm text-gray-600` |
| Social icons | `w-10 h-10 rounded-full flex items-center justify-center` |
| WiFi card | `bg-white p-4 rounded-lg border` |
| Copyright bar | `border-t mt-8 pt-6 text-center text-xs text-gray-500` |
### CSS Variables Used
@@ -311,11 +334,13 @@ Desktop:
- **next/image** - Shop logo
- **next/link** - Social links
- **lib/constants:** SHOP_INFO, SOCIAL_LINKS
- **FontAwesome icons:** location-dot, phone, envelope, clock, facebook, tiktok, globe, wifi, eye, eye-slash
- **FontAwesome icons:** location-dot, phone, envelope, clock, facebook, tiktok,
globe, wifi, eye, eye-slash
### Key Code Snippets
**Brand Info Section:**
```typescript
<div className="flex gap-4">
<Image src={SHOP_INFO.logo} alt={SHOP_INFO.name} width={40} height={40} />
@@ -329,6 +354,7 @@ Desktop:
```
**Social Links:**
```typescript
<div className="flex gap-3">
<Link href={SOCIAL_LINKS.facebook} target="_blank" rel="noopener">
@@ -339,6 +365,7 @@ Desktop:
```
**WiFi Display:**
```typescript
<div className="bg-white p-4 rounded-lg">
<p className="font-semibold text-sm">📶 WiFi miễn phí</p>
@@ -370,6 +397,7 @@ export default function RootLayout({ children }) {
```
**Result:**
- Header: Always at top (sticky)
- Content: Takes full width between header/footer
- Footer: Always at bottom
@@ -446,9 +474,7 @@ CSS variables are set up for dark mode support. To enable:
2. Or use Tailwind dark class:
```html
<div className="dark:bg-gray-900 dark:text-white">
Content
</div>
<div className="dark:bg-gray-900 dark:text-white">Content</div>
```
---
@@ -456,18 +482,21 @@ CSS variables are set up for dark mode support. To enable:
## Accessibility
### Header
- Logo has alt text
- Auth button has aria-label
- Icons have semantic meaning
- Good contrast ratios
### Footer
- Headings use semantic tags
- Links have descriptive text
- Icons are decorative (aria-hidden)
- Monospace font for technical info (WiFi credentials)
### General
- Touch targets ≥ 48px on mobile
- Sufficient color contrast
- Semantic HTML structure
+40 -14
View File
@@ -6,7 +6,8 @@
## Overview
The `lib/` directory contains shared logic, data models, and context providers used throughout the application.
The `lib/` directory contains shared logic, data models, and context providers
used throughout the application.
```
lib/
@@ -41,6 +42,7 @@ interface User {
**Usage:** Authentication context, header component, role-based UI
**Roles:**
- **manager** - Shop owner, full access to menu management and orders
- **staff** - Staff member, can view/process orders
- **customer** - Regular customer, can browse and order
@@ -60,6 +62,7 @@ interface MenuCategory {
**Usage:** Sidebar navigation, category filter, product display
**Example Categories:**
- `all` - All products
- `cafe` - Coffee
- `tra` - Tea
@@ -88,6 +91,7 @@ interface Product {
**Usage:** Product grid display, cart items, filtering
**Key Fields:**
- `id` - Unique product identifier
- `name` - Display name in Vietnamese
- `category` - Category ID for filtering
@@ -122,6 +126,7 @@ interface ShopInfo {
**Usage:** Header, footer, login page display
**Example:**
```typescript
const SHOP_INFO: ShopInfo = {
name: "Coffee Shop",
@@ -169,13 +174,15 @@ interface Shop {
## Constants (lib/constants.ts)
**Description:** Mock data used throughout the application. Replace with API calls when backend is ready.
**Description:** Mock data used throughout the application. Replace with API
calls when backend is ready.
### SHOP_INFO
Single instance of shop information displayed in header, footer, and login page.
**Key Info Used:**
- Header: name, logo, phone
- Footer: all fields
- Login: logo, name, email
@@ -198,7 +205,8 @@ export const SOCIAL_LINKS: SocialLinks = {
### MENU_CATEGORIES
Array of 8 product categories. Used in sidebar, mobile menu, and product filtering.
Array of 8 product categories. Used in sidebar, mobile menu, and product
filtering.
```typescript
export const MENU_CATEGORIES: MenuCategory[] = [
@@ -208,12 +216,17 @@ export const MENU_CATEGORIES: MenuCategory[] = [
{ id: "sua-chua", name: "Sữa Chua", icon: "fa-solid fa-jar" },
{ id: "nuoc-ep", name: "Nước Ép", icon: "fa-solid fa-blender" },
{ id: "latte", name: "Latte", icon: "fa-solid fa-mug-saucer" },
{ id: "giai-khat", name: "Giải Khát / Ăn Vặt", icon: "fa-solid fa-ice-cream" },
{
id: "giai-khat",
name: "Giải Khát / Ăn Vặt",
icon: "fa-solid fa-ice-cream",
},
{ id: "topping", name: "Topping", icon: "fa-solid fa-layer-group" },
];
```
**Usage:**
- Navbar sidebar category buttons
- Header mobile scrollable menu
- Product grid category filter
@@ -225,6 +238,7 @@ export const MENU_CATEGORIES: MenuCategory[] = [
Array of 18 mock product items across all categories.
**Example Product:**
```typescript
{
id: 1,
@@ -238,6 +252,7 @@ Array of 18 mock product items across all categories.
```
**Product Distribution:**
- Café: 4 items (Đen, Sữa, Bạc Xỉu, Trứng)
- Tea: 3 items (Đào Cam Sả, Matcha, Vải Hoa Nhài)
- Yogurt: 2 items (Trân Châu, Dâu)
@@ -264,6 +279,7 @@ Array of 5 coffee shops for the feed/discovery page.
```
**Shops:**
1. The Coffee House - Quận 3
2. Highlands Coffee - Quận 1
3. Phúc Long Heritage - Quận 1
@@ -303,9 +319,11 @@ export const MOCK_USERS: Record<string, User> = {
## Auth Context (lib/auth-context.tsx)
See [COMPONENTS.md - AuthContext](../components/COMPONENTS.md#authcontext) for detailed documentation.
See [COMPONENTS.md - AuthContext](../components/COMPONENTS.md#authcontext) for
detailed documentation.
**Quick Summary:**
- Manages user login/logout/register
- Persists user in localStorage
- Provides user info to components via useAuth() hook
@@ -314,9 +332,11 @@ See [COMPONENTS.md - AuthContext](../components/COMPONENTS.md#authcontext) for d
## Cart Context (lib/cart-context.tsx)
See [COMPONENTS.md - CartContext](../components/COMPONENTS.md#cartcontext) for detailed documentation.
See [COMPONENTS.md - CartContext](../components/COMPONENTS.md#cartcontext) for
detailed documentation.
**Quick Summary:**
- Manages shopping cart items and quantities
- Persists cart in localStorage
- Provides cart operations via useCart() hook
@@ -325,9 +345,11 @@ See [COMPONENTS.md - CartContext](../components/COMPONENTS.md#cartcontext) for d
## Menu Context (lib/menu-context.tsx)
See [COMPONENTS.md - MenuContext](../components/COMPONENTS.md#menucontext) for detailed documentation.
See [COMPONENTS.md - MenuContext](../components/COMPONENTS.md#menucontext) for
detailed documentation.
**Quick Summary:**
- Shares active category state
- Syncs sidebar and mobile menu selection
- Provides activeCategory and setActiveCategory via useMenu() hook
@@ -339,7 +361,7 @@ See [COMPONENTS.md - MenuContext](../components/COMPONENTS.md#menucontext) for d
### Using Types
```typescript
import type { User, Product, MenuCategory } from "@/lib/types";
import type { MenuCategory, Product, User } from "@/lib/types";
```
### Using Constants
@@ -348,7 +370,7 @@ import type { User, Product, MenuCategory } from "@/lib/types";
import { MENU_CATEGORIES, MOCK_PRODUCTS, SHOP_INFO } from "@/lib/constants";
// In a component:
const products = MOCK_PRODUCTS.filter(p => p.category === "cafe");
const products = MOCK_PRODUCTS.filter((p) => p.category === "cafe");
const shopName = SHOP_INFO.name;
```
@@ -412,6 +434,7 @@ export default function CategoryFilter() {
## Data Flow & Updates
### When to Update Constants
1. **Product List:** When adding/removing products
- Update MOCK_PRODUCTS array
- Or replace with API call in the future
@@ -436,7 +459,7 @@ When backend is ready:
const [products, setProducts] = useState<Product[]>([]);
useEffect(() => {
fetch("/api/products")
.then(r => r.json())
.then((r) => r.json())
.then(setProducts);
}, []);
@@ -444,7 +467,7 @@ useEffect(() => {
const login = async (username: string, password: string) => {
const res = await fetch("/api/auth/login", {
method: "POST",
body: JSON.stringify({ username, password })
body: JSON.stringify({ username, password }),
});
return res.ok;
};
@@ -455,7 +478,10 @@ const login = async (username: string, password: string) => {
## Best Practices
1. **Import Types:** Always use `import type` for types to avoid runtime bloat
2. **Immutability:** Contexts return functions to update state, not direct mutations
3. **localStorage:** Keys are centralized (`coffee-shop-user`, `coffee-shop-cart`)
4. **Validation:** Contexts validate/sanitize data (e.g., filter invalid cart items)
2. **Immutability:** Contexts return functions to update state, not direct
mutations
3. **localStorage:** Keys are centralized (`coffee-shop-user`,
`coffee-shop-cart`)
4. **Validation:** Contexts validate/sanitize data (e.g., filter invalid cart
items)
5. **Default Values:** Contexts have sensible defaults (activeCategory: "all")
+18 -6
View File
@@ -1,4 +1,11 @@
import type { MenuCategory, Product, Shop, ShopInfo, SocialLinks, User } from "./types";
import type {
MenuCategory,
Product,
Shop,
ShopInfo,
SocialLinks,
User,
} from "./types";
// ===== SHOP INFORMATION =====
export const SHOP_INFO: ShopInfo = {
@@ -231,31 +238,36 @@ export const MOCK_SHOPS: Shop[] = [
id: 1,
name: "The Coffee House",
address: "86 Cao Thắng, Quận 3, TP. Hồ Chí Minh",
image: "https://images.unsplash.com/photo-1554118811-1e0d58224f24?w=600&h=400&fit=crop",
image:
"https://images.unsplash.com/photo-1554118811-1e0d58224f24?w=600&h=400&fit=crop",
},
{
id: 2,
name: "Highlands Coffee",
address: "123 Nguyễn Huệ, Quận 1, TP. Hồ Chí Minh",
image: "https://images.unsplash.com/photo-1559305616-3f99cd43e353?w=600&h=400&fit=crop",
image:
"https://images.unsplash.com/photo-1559305616-3f99cd43e353?w=600&h=400&fit=crop",
},
{
id: 3,
name: "Phúc Long Heritage",
address: "42 Lê Lợi, Quận 1, TP. Hồ Chí Minh",
image: "https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=600&h=400&fit=crop",
image:
"https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=600&h=400&fit=crop",
},
{
id: 4,
name: "Katinat Saigon Kafe",
address: "26 Lý Tự Trọng, Quận 1, TP. Hồ Chí Minh",
image: "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&h=400&fit=crop",
image:
"https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&h=400&fit=crop",
},
{
id: 5,
name: "Trung Nguyên E-Coffee",
address: "15 Hai Bà Trưng, Quận 1, TP. Hồ Chí Minh",
image: "https://images.unsplash.com/photo-1453614512568-c4024d13c247?w=600&h=400&fit=crop",
image:
"https://images.unsplash.com/photo-1453614512568-c4024d13c247?w=600&h=400&fit=crop",
},
];
+24 -15
View File
@@ -20,6 +20,9 @@ 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
@@ -1174,8 +1177,8 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001781:
resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==}
caniuse-lite@1.0.30001782:
resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==}
chalk@2.4.1:
resolution: {integrity: sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==}
@@ -1277,8 +1280,8 @@ packages:
resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
engines: {node: '>= 0.6'}
conventional-changelog-angular@8.3.0:
resolution: {integrity: sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==}
conventional-changelog-angular@8.3.1:
resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==}
engines: {node: '>=18'}
conventional-changelog-writer@8.4.0:
@@ -1290,8 +1293,8 @@ packages:
resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==}
engines: {node: '>=18'}
conventional-commits-parser@6.3.0:
resolution: {integrity: sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==}
conventional-commits-parser@6.4.0:
resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==}
engines: {node: '>=18'}
hasBin: true
@@ -2968,6 +2971,10 @@ 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==}
@@ -4308,10 +4315,10 @@ snapshots:
'@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.3(typescript@5.9.3))':
dependencies:
conventional-changelog-angular: 8.3.0
conventional-changelog-angular: 8.3.1
conventional-changelog-writer: 8.4.0
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.3.0
conventional-commits-parser: 6.4.0
debug: 4.4.3
import-from-esm: 2.0.0
lodash-es: 4.17.23
@@ -4380,10 +4387,10 @@ snapshots:
'@semantic-release/release-notes-generator@14.1.0(semantic-release@25.0.3(typescript@5.9.3))':
dependencies:
conventional-changelog-angular: 8.3.0
conventional-changelog-angular: 8.3.1
conventional-changelog-writer: 8.4.0
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.3.0
conventional-commits-parser: 6.4.0
debug: 4.4.3
get-stream: 7.0.1
import-from-esm: 2.0.0
@@ -4933,7 +4940,7 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.10.12
caniuse-lite: 1.0.30001781
caniuse-lite: 1.0.30001782
electron-to-chromium: 1.5.328
node-releases: 2.0.36
update-browserslist-db: 1.2.3(browserslist@4.28.1)
@@ -4978,7 +4985,7 @@ snapshots:
callsites@3.1.0: {}
caniuse-lite@1.0.30001781: {}
caniuse-lite@1.0.30001782: {}
chalk@2.4.1:
dependencies:
@@ -5096,7 +5103,7 @@ snapshots:
content-type@1.0.4: {}
conventional-changelog-angular@8.3.0:
conventional-changelog-angular@8.3.1:
dependencies:
compare-func: 2.0.0
@@ -5110,7 +5117,7 @@ snapshots:
conventional-commits-filter@5.0.0: {}
conventional-commits-parser@6.3.0:
conventional-commits-parser@6.4.0:
dependencies:
'@simple-libs/stream-utils': 1.2.0
meow: 13.2.0
@@ -6589,7 +6596,7 @@ snapshots:
'@next/env': 16.1.7
'@swc/helpers': 0.5.15
baseline-browser-mapping: 2.10.12
caniuse-lite: 1.0.30001781
caniuse-lite: 1.0.30001782
postcss: 8.4.31
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -6850,6 +6857,8 @@ snapshots:
path-type@4.0.0: {}
php@1.1.0: {}
picocolors@1.1.1: {}
picomatch@2.3.2: {}