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 -1
View File
@@ -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 -3
View File
@@ -1,7 +1,5 @@
{ {
"permissions": { "permissions": {
"allow": [ "allow": ["Bash(npx next build)"]
"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 ### 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
+7
View File
@@ -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
View File
@@ -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 chọn quán yêu thích của bạn Tìm 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
View File
@@ -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>
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
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 ===== // ===== 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",
}, },
]; ];
+24 -15
View File
@@ -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: {}