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