Compare commits
24 Commits
v1.0.1-ci.1
...
v1.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
| 99de2362a0 | |||
| 9b72ad873e | |||
| 893d34e01c | |||
| c1912e5b90 | |||
| 93b6221ff6 | |||
| deae7bcf64 | |||
| 55ff138100 | |||
| 5c771b8c9c | |||
| 609aaf42df | |||
| 184635caf9 | |||
| f5941b2e3b | |||
| aa7331d728 | |||
| df8a6fc96f | |||
| 8d0247024d | |||
| 9ad66301de | |||
| 5593c4fccc | |||
| 801b804d2d | |||
| 7534f41c6e | |||
| 2873952f10 | |||
| 284183098e | |||
| e2aeebd0e1 | |||
| e448d5ebaf | |||
| 1c9b076316 | |||
| 25c44755b9 |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"remote-code": {
|
||||
"httpUrl": "https://cloud.blackbox.ai/api/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer bb_61d6eb03c989586759785863d1ba0efab59885edacd9cd65fc252f79c644bb75"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Bash(npx next build)"]
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,25 @@ jobs:
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
new_version: ${{ steps.set_output.outputs.version }}
|
||||
steps:
|
||||
- name: Calculate and Export Target IP
|
||||
run: |
|
||||
# 1. Lấy IP hiện tại và tính toán IP đích (đổi octet cuối thành .1)
|
||||
RUNNER_IP=$(hostname -I | awk '{print $1}')
|
||||
CALCULATED_IP=$(echo $RUNNER_IP | cut -d'.' -f1-3).1
|
||||
|
||||
# 2. Đưa vào biến môi trường toàn cục của Job
|
||||
echo "TARGET_IP=${CALCULATED_IP}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup SSH Key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
ssh-keyscan -p 222 -t ed25519 $TARGET_IP >> ~/.ssh/known_hosts
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -49,6 +65,9 @@ jobs:
|
||||
- name: Format code
|
||||
run: pnpm format
|
||||
|
||||
- name: Build application
|
||||
run: pnpm build
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITEA_USER: ${{ github.actor }}
|
||||
@@ -69,21 +88,19 @@ jobs:
|
||||
git config --global user.name "gitea-actions"
|
||||
git config --global user.email "actions-user@noreply.git.demonkernel.io.vn"
|
||||
|
||||
REPO_NAME="${{ gitea.repository }}"
|
||||
|
||||
git remote set-url origin ssh://git@$TARGET_IP:222/${REPO_NAME}.git
|
||||
|
||||
git rm -r --cached .
|
||||
git add .
|
||||
git commit -m "chore: release [ci skip]"
|
||||
|
||||
git push
|
||||
fi
|
||||
|
||||
build-and-push:
|
||||
needs: release
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.demonkernel.io.vn
|
||||
@@ -91,19 +108,20 @@ jobs:
|
||||
password: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Prepare Docker Metadata
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
id: meta
|
||||
run: |
|
||||
# Lowercase Repository
|
||||
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Lấy version từ job release và lowercase nó (cho các bản prerelease)
|
||||
RAW_VERSION="${{ needs.release.outputs.new_version }}"
|
||||
RAW_VERSION="${{ steps.set_output.outputs.version }}"
|
||||
VERSION_LOWER=$(echo "$RAW_VERSION" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "repo=$REPO_LOWER" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION_LOWER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
@@ -112,11 +130,3 @@ jobs:
|
||||
git.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:latest
|
||||
git.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
deploy-via-portainer:
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Portainer Webhook
|
||||
run: |
|
||||
curl -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"
|
||||
|
||||
@@ -9,21 +9,53 @@ React 19, TypeScript và Tailwind CSS v4.
|
||||
|
||||
Giao diện người dùng (frontend) cho hệ thống đặt và bán đồ uống trực tuyến.
|
||||
|
||||
### Trang Người Dùng (User Page - /)
|
||||
### Tính Năng Hiện Tại
|
||||
|
||||
#### 1. **Trang Đăng Nhập & Đăng Ký** (app/(main)/login, app/(main)/register)
|
||||
|
||||
- Form đăng nhập với validation (username, password)
|
||||
- Form đăng ký với xác thực OTP
|
||||
- Lưu trữ thông tin người dùng trong localStorage
|
||||
- Hỗ trợ 3 loại tài khoản: Manager, Staff, Customer
|
||||
- Hiển thị thông tin shop trong form
|
||||
|
||||
#### 2. **Trang Khám Phá Quán Nước** (app/(feed)/feed)
|
||||
|
||||
- Danh sách các quán cà phê
|
||||
- Tìm kiếm theo tên quán và địa chỉ
|
||||
- Thẻ quán với ảnh, địa chỉ, nút "Xem menu"
|
||||
- Responsive layout (grid 1/2/3 cột)
|
||||
- Empty state khi không tìm thấy quán
|
||||
|
||||
#### 3. **Trang Chính - Duyệt Thực Đơn** (app/(main))
|
||||
|
||||
Dành cho khách hàng:
|
||||
|
||||
- Duyệt thực đơn theo danh mục (sidebar collapsible)
|
||||
- Sidebar collapsible (64px/240px) - danh mục sản phẩm
|
||||
- Grid sản phẩm responsive (1-5 cột tuỳ thiết bị)
|
||||
- Tìm kiếm món theo tên / mô tả
|
||||
- Xem card sản phẩm với giá và nút Mua
|
||||
- Xem card sản phẩm với giá và nút "Mua"
|
||||
- Lọc tự động theo trạng thái available
|
||||
- Mobile menu: scrollable category tabs (< md)
|
||||
|
||||
### Trang Quản Lý (Manager Page - chưa triển khai)
|
||||
#### 4. **Hệ Thống Giỏ Hàng** (lib/cart-context.tsx)
|
||||
|
||||
Dành cho chủ quán / nhân viên:
|
||||
- Lưu trữ trạng thái giỏ trong localStorage
|
||||
- Thêm/xóa/tăng/giảm số lượng sản phẩm
|
||||
- Tính tổng giá và số mặt hàng
|
||||
- Persist dữ liệu giữa các session
|
||||
|
||||
- Quản lý thực đơn (thêm, sửa, xóa món)
|
||||
- Theo dõi và xử lý đơn hàng
|
||||
#### 5. **Hệ Thống Xác Thực** (lib/auth-context.tsx)
|
||||
|
||||
- Quản lý trạng thái người dùng (login/logout/register)
|
||||
- Lưu thông tin user trong localStorage
|
||||
- Mock auth database với 3 loại tài khoản
|
||||
- Hỗ trợ hoàn tất đăng ký qua OTP
|
||||
|
||||
#### 6. **Hệ Thống Danh Mục** (lib/menu-context.tsx)
|
||||
|
||||
- Chia sẻ trạng thái category giữa Header mobile và Sidebar
|
||||
- Tự động clear search khi thay đổi category
|
||||
|
||||
---
|
||||
|
||||
@@ -67,23 +99,45 @@ pnpm lint
|
||||
```
|
||||
frondend/
|
||||
+-- app/ # Next.js App Router
|
||||
| +-- layout.tsx # Root layout
|
||||
| +-- page.tsx # Trang chủ - sidebar + product grid
|
||||
| +-- layout.tsx # Root layout + Header + Footer
|
||||
| +-- globals.css # CSS design tokens + Tailwind import
|
||||
| +-- providers.tsx # Context providers (Auth, Menu, Cart)
|
||||
| +-- APP.md # Tài liệu chi tiết về routes
|
||||
| +-- (main)/ # Main route group
|
||||
| | +-- layout.tsx # Main layout
|
||||
| | +-- page.tsx # Trang chính - duyệt thực đơn
|
||||
| | +-- login/page.tsx # Trang đăng nhập
|
||||
| | +-- register/page.tsx # Trang đăng ký
|
||||
| | +-- payment/page.tsx # Trang thanh toán
|
||||
| +-- (feed)/ # Feed route group
|
||||
| +-- layout.tsx # Feed layout
|
||||
| +-- feed/page.tsx # Trang khám phá quán nước
|
||||
+-- components/ # Shared UI components
|
||||
| +-- Navbar.tsx # Sidebar danh mục (collapsible)
|
||||
| +-- CartProduct.tsx # Card sản phẩm
|
||||
| +-- COMPONENTS.md # Tài liệu component
|
||||
| +-- CartFab.tsx # FAB - nút giỏ hàng floating
|
||||
| +-- COMPONENTS.md # Tài liệu chi tiết components & contexts
|
||||
+-- layouts/ # Layout-level components
|
||||
| +-- header.tsx # Sticky top header
|
||||
| +-- footer.tsx # Footer
|
||||
| +-- header.tsx # Sticky top header + auth demo
|
||||
| +-- footer.tsx # Footer + shop info
|
||||
| +-- LAYOUTS.md # Tài liệu chi tiết layouts
|
||||
+-- lib/ # Shared logic & data
|
||||
| +-- constants.ts # Mock data
|
||||
| +-- constants.ts # Mock data (products, shops, users, shop info)
|
||||
| +-- types.ts # TypeScript interfaces
|
||||
| +-- auth-context.tsx # Authentication context & provider
|
||||
| +-- cart-context.tsx # Shopping cart context & provider
|
||||
| +-- menu-context.tsx # Menu/Category context & provider
|
||||
| +-- LIB.md # Tài liệu chi tiết về lib
|
||||
+-- types/ # Global TypeScript declarations
|
||||
| +-- css.d.ts # CSS module type shim
|
||||
+-- public/ # Static assets
|
||||
+-- WORKFLOW.md # Tài liệu kiến trúc & quy trình
|
||||
| +-- imgs/
|
||||
| | +-- logo.png
|
||||
| | +-- products/ # Ảnh sản phẩm
|
||||
| +-- favicon/
|
||||
+-- WORKFLOW.md # Tài liệu kiến trúc tổng thể & quy trình
|
||||
+-- README.md # (file này) - Mô tả dự án
|
||||
+-- TODO.md # Danh sách tính năng hoàn thành & chưa làm
|
||||
+-- next.config.ts
|
||||
+-- tsconfig.json
|
||||
+-- postcss.config.mjs
|
||||
@@ -111,10 +165,32 @@ frondend/
|
||||
|
||||
## Ghi Chú Phát Triển
|
||||
|
||||
- Trang chủ (app/page.tsx) là điểm vào chính của User Page
|
||||
- Design tokens định nghĩa trong app/globals.css dưới dạng CSS custom properties
|
||||
- Mock data nằm trong lib/constants.ts - thay bằng API calls khi backend sẵn
|
||||
sàng
|
||||
- Dark mode: biến CSS đã chuẩn bị sẵn trong globals.css nhưng chưa kích hoạt
|
||||
- Ảnh sản phẩm: thêm ảnh thực vào public/imgs/products/
|
||||
- Xem WORKFLOW.md để hiểu kiến trúc tổng thể và quy trình mở rộng dự án
|
||||
### Điểm Vào Chính
|
||||
|
||||
- **Trang Chủ (User):** `app/(main)/page.tsx` - Duyệt thực đơn
|
||||
- **Trang Khám Phá:** `app/(feed)/feed/page.tsx` - Khám phá quán
|
||||
- **Đăng Nhập:** `app/(main)/login/page.tsx`
|
||||
- **Đăng Ký:** `app/(main)/register/page.tsx`
|
||||
|
||||
### Design & Styling
|
||||
|
||||
- Design tokens (CSS variables) định nghĩa trong `app/globals.css`
|
||||
- Tailwind CSS v4 + custom properties cho consistent color/spacing
|
||||
- Dark mode: biến CSS đã chuẩn bị sẵn nhưng chưa kích hoạt
|
||||
- FontAwesome icons từ CDN
|
||||
|
||||
### Data & Integration
|
||||
|
||||
- Mock data nằm trong `lib/constants.ts`
|
||||
- Context providers trong `app/providers.tsx` - Sử dụng: AuthProvider,
|
||||
MenuProvider, CartProvider
|
||||
- Thay bằng API calls khi backend sẵn sàng
|
||||
- Ảnh sản phẩm: thêm vào `public/imgs/products/`
|
||||
|
||||
### Tài Liệu Chi Tiết
|
||||
|
||||
- **WORKFLOW.md** - Kiến trúc tổng thể, data flow, quy trình phát triển
|
||||
- **APP.md** - Chi tiết các routes, layouts, pages
|
||||
- **COMPONENTS.md** - Tài liệu từng component + contexts
|
||||
- **LAYOUTS.md** - Header, Footer, responsive behavior
|
||||
- **LIB.md** - Constants, Types, Contexts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Coffee Shop Frontend - TODO
|
||||
|
||||
## Completed Optimizations
|
||||
## Completed Features & Implementations
|
||||
|
||||
### A. Dead Code Removed
|
||||
|
||||
@@ -43,27 +43,61 @@
|
||||
|
||||
### Cart & Ordering
|
||||
|
||||
- [ ] Implement add-to-cart logic (onBuy callback in CartProduct)
|
||||
- [ ] Cart sidebar or modal with item list and total
|
||||
- [ ] Order submission flow
|
||||
- [ ] Payment page
|
||||
- [ ] Implement cart checkout flow (app/(main)/cart or modal)
|
||||
- [ ] Cart sidebar/modal with item list and total
|
||||
- [ ] Order submission API integration
|
||||
- [ ] Payment page implementation (app/(main)/payment)
|
||||
- [ ] Order history/tracking page
|
||||
- [ ] Toast notifications for cart actions
|
||||
|
||||
### Authentication & User Management
|
||||
|
||||
- [ ] Real backend authentication (replace MOCK_AUTH_DB)
|
||||
- [ ] Real OTP delivery service (SMS integration)
|
||||
- [ ] User profile page with edit capability
|
||||
- [ ] Password reset/recovery flow
|
||||
- [ ] Session management and token refresh
|
||||
|
||||
### Manager Features
|
||||
|
||||
- [ ] Manager dashboard page (app/(manager)/page.tsx)
|
||||
- [ ] Product management (add/edit/delete)
|
||||
- [ ] Category management
|
||||
- [ ] Order management & tracking
|
||||
- [ ] Sales analytics/dashboard
|
||||
- [ ] Inventory management
|
||||
|
||||
### Backend Integration
|
||||
|
||||
- [ ] Replace MOCK_PRODUCTS with real API calls (lib/api.ts)
|
||||
- [ ] Replace MOCK_USERS with real authentication
|
||||
- [ ] Product images: replace placeholder with real images in
|
||||
public/imgs/products/
|
||||
|
||||
### Manager Page
|
||||
|
||||
- [ ] Create app/manager/page.tsx
|
||||
- [ ] Menu management (add/edit/delete products)
|
||||
- [ ] Order tracking dashboard
|
||||
- [ ] Replace MOCK_PRODUCTS with API calls (GET /api/products)
|
||||
- [ ] Replace MOCK_SHOPS with API calls (GET /api/shops)
|
||||
- [ ] Replace MOCK_USERS with real authentication (POST /api/auth/login)
|
||||
- [ ] Real product images (replace placeholder.jpg)
|
||||
- [ ] Image upload for products
|
||||
|
||||
### UX Improvements
|
||||
|
||||
- [ ] Dark mode toggle (CSS variables already prepared in globals.css)
|
||||
- [ ] Loading skeleton for product grid
|
||||
- [ ] Toast notifications for cart actions
|
||||
- [ ] Product detail modal/page
|
||||
- [ ] Dark mode toggle (CSS variables prepared, toggle UI needed)
|
||||
- [ ] Loading skeletons for product grid
|
||||
- [ ] Product detail modal/page with full description
|
||||
- [ ] Wishlist/favorites feature
|
||||
- [ ] Sort products (price, rating, etc.)
|
||||
- [ ] Filter by price range
|
||||
- [ ] Quantity selector in product card
|
||||
- [ ] Related products suggestions
|
||||
|
||||
### Performance & SEO
|
||||
|
||||
- [ ] Dynamic route generation for products (app/(main)/product/[id]/page.tsx)
|
||||
- [ ] Dynamic route generation for shops (app/(feed)/shop/[id]/page.tsx)
|
||||
- [ ] Meta tags and Open Graph for SEO
|
||||
- [ ] Image optimization and lazy loading
|
||||
- [ ] Code splitting and dynamic imports
|
||||
|
||||
### Accessibility & Testing
|
||||
|
||||
- [ ] Keyboard navigation testing
|
||||
- [ ] ARIA labels audit
|
||||
- [ ] Unit tests for contexts
|
||||
- [ ] E2E tests for user flows
|
||||
- [ ] Accessibility audit (WCAG 2.1 AA)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
"use client";
|
||||
|
||||
import { MOCK_SHOPS } from "@/lib/constants";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function FeedPage() {
|
||||
const [searchName, setSearchName] = useState("");
|
||||
const [searchAddress, setSearchAddress] = useState("");
|
||||
|
||||
const filteredShops = MOCK_SHOPS.filter((shop) => {
|
||||
const matchesName =
|
||||
searchName.trim() === "" ||
|
||||
shop.name.toLowerCase().includes(searchName.toLowerCase());
|
||||
const matchesAddress =
|
||||
searchAddress.trim() === "" ||
|
||||
shop.address.toLowerCase().includes(searchAddress.toLowerCase());
|
||||
return matchesName && matchesAddress;
|
||||
});
|
||||
|
||||
return (
|
||||
<main className="bg-background min-h-[calc(100vh-var(--spacing-header-height))]">
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 md:px-6 lg:px-8">
|
||||
{/* Page title */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-foreground text-2xl font-bold md:text-3xl">
|
||||
Khám phá quán nước
|
||||
</h1>
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Shop cards grid */}
|
||||
{filteredShops.length > 0 ? (
|
||||
<div className="mb-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredShops.map((shop) => (
|
||||
<div
|
||||
key={shop.id}
|
||||
className="overflow-hidden rounded-2xl border border-(--color-border) bg-(--color-bg-card) shadow-[0_2px_12px_var(--color-shadow-sm)] transition-all duration-250 hover:-translate-y-1 hover:shadow-[0_4px_20px_var(--color-shadow-md)]"
|
||||
>
|
||||
{/* Shop image */}
|
||||
<div className="relative h-48 w-full sm:h-52">
|
||||
<Image
|
||||
src={shop.image}
|
||||
alt={shop.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Card body */}
|
||||
<div className="p-4">
|
||||
{/* Name + View menu button */}
|
||||
<div className="mb-2 flex items-center justify-between gap-3">
|
||||
<h3 className="text-foreground truncate text-base font-bold">
|
||||
{shop.name}
|
||||
</h3>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex shrink-0 items-center gap-1.5 rounded-xl bg-(--color-primary) px-3.5 py-2 text-xs font-semibold text-white no-underline transition-all duration-150 hover:bg-(--color-primary-dark) active:scale-95"
|
||||
>
|
||||
<i className="fa-solid fa-book-open text-[10px]"></i>
|
||||
Xem menu
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
<div className="flex items-start gap-2 text-sm text-(--color-text-muted)">
|
||||
<i className="fa-solid fa-location-dot mt-0.5 shrink-0 text-(--color-accent)"></i>
|
||||
<span>{shop.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
/* Empty state */
|
||||
<div className="mb-10 flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
|
||||
<i className="fa-solid fa-store text-5xl opacity-30"></i>
|
||||
<p className="text-base font-medium">
|
||||
Không tìm thấy quán nào phù hợp
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchName("");
|
||||
setSearchAddress("");
|
||||
}}
|
||||
className="cursor-pointer border-none bg-transparent text-sm text-(--color-primary) hover:underline"
|
||||
>
|
||||
Xóa bộ lọc
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filter / Search bar */}
|
||||
<div className="sticky bottom-0 rounded-2xl border border-(--color-border) bg-(--color-bg-card) p-4 shadow-[0_-2px_16px_var(--color-shadow-sm)] md:p-5">
|
||||
<div className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
|
||||
{/* Label */}
|
||||
<div className="flex shrink-0 items-center gap-2 text-sm font-semibold text-(--color-text-secondary)">
|
||||
<i className="fa-solid fa-filter text-(--color-primary)"></i>
|
||||
<span>Lọc quán</span>
|
||||
</div>
|
||||
|
||||
{/* Search by name */}
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<i className="fa-solid fa-store pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-xs text-(--color-text-muted)"></i>
|
||||
<input
|
||||
type="text"
|
||||
value={searchName}
|
||||
onChange={(e) => setSearchName(e.target.value)}
|
||||
placeholder="Tìm theo tên quán..."
|
||||
className="bg-background text-foreground focus:ring-opacity-20 w-full rounded-xl border border-(--color-border) py-2.5 pr-9 pl-9 text-sm transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)"
|
||||
/>
|
||||
{searchName && (
|
||||
<button
|
||||
onClick={() => setSearchName("")}
|
||||
aria-label="Xóa tìm kiếm tên"
|
||||
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent p-0 text-(--color-text-muted) transition-colors duration-150 hover:text-(--color-primary)"
|
||||
>
|
||||
<i className="fa-solid fa-xmark text-sm"></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search by address */}
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<i className="fa-solid fa-location-dot pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-xs text-(--color-text-muted)"></i>
|
||||
<input
|
||||
type="text"
|
||||
value={searchAddress}
|
||||
onChange={(e) => setSearchAddress(e.target.value)}
|
||||
placeholder="Tìm theo địa chỉ..."
|
||||
className="bg-background text-foreground focus:ring-opacity-20 w-full rounded-xl border border-(--color-border) py-2.5 pr-9 pl-9 text-sm transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)"
|
||||
/>
|
||||
{searchAddress && (
|
||||
<button
|
||||
onClick={() => setSearchAddress("")}
|
||||
aria-label="Xóa tìm kiếm địa chỉ"
|
||||
className="absolute top-1/2 right-3 -translate-y-1/2 cursor-pointer border-none bg-transparent p-0 text-(--color-text-muted) transition-colors duration-150 hover:text-(--color-primary)"
|
||||
>
|
||||
<i className="fa-solid fa-xmark text-sm"></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function FeedLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Custom Drinkool header — no login button */}
|
||||
<header className="sticky top-0 z-50 h-(--spacing-header-height) w-full border-b border-(--color-border) bg-(--color-bg-header) shadow-[0_1px_8px_var(--color-shadow-sm)]">
|
||||
<div className="mx-auto flex h-full max-w-screen-2xl items-center gap-4 px-6 md:px-8 lg:px-12">
|
||||
<Link
|
||||
href="/feed"
|
||||
className="group flex shrink-0 items-center gap-3 no-underline"
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="relative h-10 w-10 shrink-0 md:h-11 md:w-11">
|
||||
<Image
|
||||
src="/imgs/logo.png"
|
||||
alt="Logo Drinkool"
|
||||
fill
|
||||
className="object-contain transition-transform duration-200 group-hover:scale-105"
|
||||
sizes="44px"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Brand name */}
|
||||
<span className="text-lg font-bold text-(--color-primary-dark) transition-colors duration-150 group-hover:text-(--color-primary) md:text-xl">
|
||||
Drinkool
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<div className="flex-1">{children}</div>
|
||||
|
||||
{/* No footer */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import CartFab from "@/components/CartFab";
|
||||
import Footer from "@/layouts/footer";
|
||||
import Header from "@/layouts/header";
|
||||
|
||||
export default function MainLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Sticky top header */}
|
||||
<Header />
|
||||
|
||||
{/* Page content (grows to fill remaining height) */}
|
||||
<div className="flex-1">{children}</div>
|
||||
|
||||
{/* Footer always at bottom */}
|
||||
<Footer />
|
||||
|
||||
{/* Global floating cart button */}
|
||||
<CartFab />
|
||||
</>
|
||||
);
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
# App Routes & Pages Documentation
|
||||
|
||||
> Tài liệu chi tiết về Next.js App Router structure, routes, và layouts
|
||||
|
||||
---
|
||||
|
||||
## Route Structure Overview
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx # Root layout - Header + Footer wrapper
|
||||
├── globals.css # CSS tokens + Tailwind imports
|
||||
├── providers.tsx # Context providers (Auth, Menu, Cart)
|
||||
├── (main)/
|
||||
│ ├── layout.tsx # Main layout for authenticated routes
|
||||
│ ├── page.tsx # Main page - Duyệt thực đơn (/)
|
||||
│ ├── login/
|
||||
│ │ └── page.tsx # Trang đăng nhập (/login)
|
||||
│ ├── register/
|
||||
│ │ └── page.tsx # Trang đăng ký (/register)
|
||||
│ └── payment/
|
||||
│ └── page.tsx # Trang thanh toán (/payment)
|
||||
└── (feed)/
|
||||
├── layout.tsx # Feed layout
|
||||
└── feed/
|
||||
└── page.tsx # Trang khám phá quán (/feed)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root Layout (app/layout.tsx)
|
||||
|
||||
**Description:** Root layout wrapping entire application. Loads providers,
|
||||
fonts, and common layouts (header/footer).
|
||||
|
||||
### Structure
|
||||
|
||||
- Imports providers (AuthProvider, MenuProvider, CartProvider)
|
||||
- Renders Header component (sticky top)
|
||||
- Renders page content via {children}
|
||||
- Renders Footer component
|
||||
|
||||
### Metadata
|
||||
|
||||
- Title: "Coffee Shop"
|
||||
- Charset: UTF-8
|
||||
- Viewport: responsive
|
||||
|
||||
### Key Features
|
||||
|
||||
- Global CSS variables loaded here
|
||||
- Header remains sticky across all pages
|
||||
- Footer always present
|
||||
|
||||
---
|
||||
|
||||
## Providers (app/providers.tsx)
|
||||
|
||||
**Description:** Client component that wraps all context providers for the
|
||||
application.
|
||||
|
||||
### Providers Included
|
||||
|
||||
1. **AuthProvider** (lib/auth-context.tsx)
|
||||
- Manages user authentication state
|
||||
- Persists user in localStorage
|
||||
- Enables login/logout/register flows
|
||||
|
||||
2. **MenuProvider** (lib/menu-context.tsx)
|
||||
- Shares active category state
|
||||
- Syncs Header mobile menu and Navbar sidebar
|
||||
|
||||
3. **CartProvider** (lib/cart-context.tsx)
|
||||
- Manages shopping cart state
|
||||
- Persists cart items in localStorage
|
||||
- Provides add/remove/update operations
|
||||
|
||||
### Usage in Root Layout
|
||||
|
||||
```tsx
|
||||
<Providers>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</Providers>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## (main) Route Group
|
||||
|
||||
### Layout (app/(main)/layout.tsx)
|
||||
|
||||
**Description:** Layout for authenticated/main routes. Can include
|
||||
route-specific UI (e.g., sidebar navigation).
|
||||
|
||||
### Usage
|
||||
|
||||
- Wraps all pages under `(main)/` route group
|
||||
- Inherits root layout above it
|
||||
- Can add additional styling or structure specific to main pages
|
||||
|
||||
---
|
||||
|
||||
## Pages
|
||||
|
||||
### 1. Main Page - Duyệt Thực Đơn (app/(main)/page.tsx)
|
||||
|
||||
**Route:** `/` (default) or `/(main)` **Type:** Client component
|
||||
**Description:** Main shopping interface. Shows product grid with sidebar
|
||||
category filter.
|
||||
|
||||
#### Key Features
|
||||
|
||||
- **Sidebar (Navbar):**
|
||||
- Collapsible (64px collapsed / 240px expanded)
|
||||
- Category list with icons
|
||||
- Sticky, full viewport height minus header
|
||||
- Responsive: desktop (expanded) vs mobile (collapsed)
|
||||
|
||||
- **Product Grid:**
|
||||
- Responsive columns (1/2/3/4/5 depending on sidebar state and screen size)
|
||||
- Filter by: availability, active category, search query
|
||||
- Search bar with clear button
|
||||
- Empty state when no products match
|
||||
|
||||
- **Mobile Category Menu:**
|
||||
- Visible only on screens < md (768px)
|
||||
- Sticky scrollable tabs below search
|
||||
- Allows category selection on mobile
|
||||
|
||||
#### Context Usage
|
||||
|
||||
- **useMenu()** - Get/set active category
|
||||
- **useCart()** - Add products to cart
|
||||
- **MenuContext** - Shares category state with Header
|
||||
|
||||
#### State Management
|
||||
|
||||
```tsx
|
||||
isSidebarOpen: boolean; // Sidebar expanded/collapsed
|
||||
searchQuery: string; // Search input value
|
||||
activeCategory: string; // From MenuContext
|
||||
```
|
||||
|
||||
#### Responsive Behavior
|
||||
|
||||
- **Mobile (<480px):** 1 col, sidebar collapsed
|
||||
- **Small phone (480px):** 2 cols, sidebar collapsed
|
||||
- **Tablet (768px):** Mobile menu tabs appear
|
||||
- **Desktop (1024px):** Sidebar expands, 2-4 cols depending on sidebar
|
||||
- **Large screen (1280px+):** 3-5 cols
|
||||
|
||||
---
|
||||
|
||||
### 2. Login Page (app/(main)/login/page.tsx)
|
||||
|
||||
**Route:** `/login` **Type:** Client component **Description:** User
|
||||
authentication form. Supports login for Manager, Staff, and Customer roles.
|
||||
|
||||
#### Key Features
|
||||
|
||||
- **Form Fields:**
|
||||
- Username input (with icon)
|
||||
- Password input (with show/hide toggle)
|
||||
- "Dang nhap" button
|
||||
- "Dang ky tai khoan" link to register
|
||||
|
||||
- **Validation:**
|
||||
- Username: required, non-empty
|
||||
- Password: required, min 4 characters
|
||||
- Error messages displayed inline
|
||||
|
||||
- **Demo Credentials:**
|
||||
- Shows example accounts for testing:
|
||||
- Manager: `admin / admin`
|
||||
- Staff: `Nguyễn Văn An / Nguyễn Văn An`
|
||||
- Customer: `0987654321 / user1`
|
||||
|
||||
- **UI:**
|
||||
- Centered card (max-width: 448px)
|
||||
- Shop logo and name at top
|
||||
- Error messages with icons
|
||||
- Responsive design
|
||||
|
||||
#### Context Usage
|
||||
|
||||
- **useAuth()** - Login function
|
||||
|
||||
#### Navigation
|
||||
|
||||
- Success: redirect to `/` (main page)
|
||||
- Register link: go to `/register`
|
||||
|
||||
---
|
||||
|
||||
### 3. Register Page (app/(main)/register/page.tsx)
|
||||
|
||||
**Route:** `/register` **Type:** Client component **Description:** User
|
||||
registration flow. Two-step process: phone verification → account creation.
|
||||
|
||||
#### Key Features
|
||||
|
||||
- **Step 1: Phone Verification**
|
||||
- Input phone number
|
||||
- "Request OTP" button
|
||||
- Mock OTP validation (no real SMS)
|
||||
|
||||
- **Step 2: Account Creation** (after OTP)
|
||||
- Confirm phone number
|
||||
- "Create Account" button
|
||||
- Auto-login after registration
|
||||
|
||||
- **Validation:**
|
||||
- Phone format validation
|
||||
- OTP code validation
|
||||
|
||||
- **UI:**
|
||||
- Centered card layout
|
||||
- Step indicator
|
||||
- Progress feedback
|
||||
|
||||
#### Context Usage
|
||||
|
||||
- **useAuth()** - completeRegistration()
|
||||
|
||||
#### Navigation
|
||||
|
||||
- Success: redirect to `/` (main page)
|
||||
- Login link: go to `/login`
|
||||
|
||||
---
|
||||
|
||||
### 4. Payment Page (app/(main)/payment/page.tsx)
|
||||
|
||||
**Route:** `/payment` **Type:** Client component (TBD) **Description:**
|
||||
Payment/checkout page. Currently a placeholder; implement when cart and order
|
||||
system are ready.
|
||||
|
||||
---
|
||||
|
||||
## (feed) Route Group
|
||||
|
||||
### Layout (app/(feed)/layout.tsx)
|
||||
|
||||
**Description:** Layout for feed routes. Can have specific styling for
|
||||
discovery/exploration pages.
|
||||
|
||||
---
|
||||
|
||||
### Feed Page - Khám Phá Quán (app/(feed)/feed/page.tsx)
|
||||
|
||||
**Route:** `/feed` **Type:** Client component **Description:** Discover coffee
|
||||
shops. Browse and search available shops with their location and details.
|
||||
|
||||
#### Key Features
|
||||
|
||||
- **Shop Cards:**
|
||||
- Shop image (responsive height)
|
||||
- Name and location
|
||||
- "Xem menu" button (links to `/`)
|
||||
- Hover effects with shadow/lift animation
|
||||
|
||||
- **Search & Filter:**
|
||||
- Sticky bottom filter bar
|
||||
- Search by shop name
|
||||
- Search by address
|
||||
- Clear buttons for each field
|
||||
|
||||
- **Grid Layout:**
|
||||
- Responsive (1/2/3 columns)
|
||||
- Adjusts to screen size
|
||||
|
||||
- **Empty State:**
|
||||
- Icon and message when no shops match
|
||||
- Clear filter button
|
||||
|
||||
#### Context Usage
|
||||
|
||||
- **useAuth()** - Optional: check user role
|
||||
|
||||
#### State Management
|
||||
|
||||
```tsx
|
||||
searchName: string // Shop name search
|
||||
searchAddress: string // Shop address search
|
||||
filteredShops: Shop[] // Filtered results
|
||||
```
|
||||
|
||||
#### Data Source
|
||||
|
||||
- MOCK_SHOPS from lib/constants.ts
|
||||
|
||||
---
|
||||
|
||||
## CSS & Styling
|
||||
|
||||
### Global Styles (app/globals.css)
|
||||
|
||||
**Description:** Root CSS file. Imports Tailwind CSS, defines design tokens (CSS
|
||||
variables), and global styles.
|
||||
|
||||
### Design Tokens (CSS Variables)
|
||||
|
||||
Defined at `:root` for light mode, with dark mode variants:
|
||||
|
||||
#### Colors
|
||||
|
||||
- `--color-primary` - Main brand color (brown)
|
||||
- `--color-primary-dark` - Darker shade for hover
|
||||
- `--color-accent` - Secondary accent color
|
||||
- `--color-bg-*` - Background colors (main, card, sidebar)
|
||||
- `--color-border-*` - Border colors
|
||||
- `--color-text-*` - Text colors (primary, secondary, muted)
|
||||
- `--color-shadow-*` - Shadow colors (sm, md)
|
||||
|
||||
#### Spacing
|
||||
|
||||
- `--spacing-header-height` - Header component height
|
||||
- Standard Tailwind spacing (via Tailwind)
|
||||
|
||||
#### Typography
|
||||
|
||||
- Font stack defined via Google Fonts (Geist)
|
||||
|
||||
### Tailwind Config Integration
|
||||
|
||||
- v4 with custom properties support
|
||||
- Extends with CSS variables
|
||||
- Dark mode: CSS variable override
|
||||
|
||||
---
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Route Groups
|
||||
|
||||
- `(main)` and `(feed)` are route groups (don't appear in URL)
|
||||
- Allow different layouts/providers per route group
|
||||
- Used for organization and flexibility
|
||||
|
||||
### SSR vs Client Components
|
||||
|
||||
- Most pages are "use client" (need interactivity)
|
||||
- Context providers must be client components
|
||||
- Layout.tsx can be server component
|
||||
|
||||
### Responsive Design
|
||||
|
||||
- Breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px)
|
||||
- Sidebar width affects grid columns
|
||||
- Mobile: vertical layout, collapsed sidebar
|
||||
- Desktop: horizontal layout, expanded sidebar
|
||||
|
||||
### Navigation
|
||||
|
||||
- Internal links use `next/link`
|
||||
- useRouter() for programmatic navigation
|
||||
- Auth redirects after login/register
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Adding a New Page
|
||||
|
||||
1. Create file under appropriate route group: `app/(main)/new-feature/page.tsx`
|
||||
2. Make it a client component if it needs interactivity: `"use client"`
|
||||
3. Use context hooks as needed (useAuth, useCart, useMenu)
|
||||
4. Export default component
|
||||
5. Add to documentation
|
||||
|
||||
### Adding a New Route Group
|
||||
|
||||
1. Create directory: `app/(group-name)/`
|
||||
2. Create `layout.tsx` if custom layout needed
|
||||
3. Add pages under this group
|
||||
4. Update codebase understanding
|
||||
|
||||
### Styling Pages
|
||||
|
||||
- Use Tailwind classes + CSS variables
|
||||
- Reference design tokens for consistency
|
||||
- Follow responsive patterns from existing pages
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Payment page implementation
|
||||
- [ ] Order history/tracking page
|
||||
- [ ] Manager dashboard (menu management, order tracking)
|
||||
- [ ] User profile page
|
||||
- [ ] Cart checkout flow
|
||||
- [ ] Real backend API integration
|
||||
@@ -100,6 +100,7 @@ body {
|
||||
color: var(--color-text-primary);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
scroll-behavior: smooth;
|
||||
text-rendering: optimizeSpeed;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
+1
-13
@@ -46,19 +46,7 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} flex min-h-screen flex-col antialiased`}
|
||||
>
|
||||
<Providers>
|
||||
{/* Sticky top header */}
|
||||
<Header />
|
||||
|
||||
{/* Page content (grows to fill remaining height) */}
|
||||
<div className="flex-1">{children}</div>
|
||||
|
||||
{/* Footer always at bottom */}
|
||||
<Footer />
|
||||
|
||||
{/* Global floating cart button */}
|
||||
<CartFab />
|
||||
</Providers>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -161,3 +161,158 @@ None - reads SHOP_INFO and SOCIAL_LINKS from lib/constants directly.
|
||||
- next/image, next/link
|
||||
- lib/constants: SHOP_INFO, SOCIAL_LINKS
|
||||
- FontAwesome icons
|
||||
|
||||
---
|
||||
|
||||
## CartFab
|
||||
|
||||
**File:** components/CartFab.tsx **Description:** Floating Action Button
|
||||
displaying cart item count. Shows badge with number of items and total price on
|
||||
hover.
|
||||
|
||||
### Props
|
||||
|
||||
| Prop | Type | Required | Default | Description |
|
||||
| ------- | ---------- | -------- | --------- | ---------------------------- |
|
||||
| onClick | () => void | no | undefined | Callback when FAB is clicked |
|
||||
|
||||
### Features
|
||||
|
||||
- Displays cart icon with item count badge
|
||||
- Shows total price on hover in tooltip
|
||||
- Sticky position (bottom-right)
|
||||
- Uses cart context to get items and total
|
||||
|
||||
### Styling
|
||||
|
||||
| Element | Key classes |
|
||||
| ---------- | ----------------------------------------------- |
|
||||
| FAB button | fixed bottom-6 right-6, rounded-full, shadow-lg |
|
||||
| Badge | absolute top-0 right-0, red bg, small font |
|
||||
| Tooltip | appears on hover, shows total price |
|
||||
|
||||
### Dependencies
|
||||
|
||||
- lib/cart-context: useCart()
|
||||
- FontAwesome icons
|
||||
|
||||
---
|
||||
|
||||
# Contexts Documentation
|
||||
|
||||
## AuthContext (lib/auth-context.tsx)
|
||||
|
||||
**File:** lib/auth-context.tsx **Description:** Manages user authentication
|
||||
state including login, logout, and registration. Uses localStorage for
|
||||
persistence.
|
||||
|
||||
### Provider Props
|
||||
|
||||
| Prop | Type | Required | Description |
|
||||
| -------- | --------------- | -------- | ---------------- |
|
||||
| children | React.ReactNode | yes | Child components |
|
||||
|
||||
### Hook: useAuth()
|
||||
|
||||
Returns `AuthContextType` with:
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------------- | ----------------------------------------------- | ------------------------------------------------- |
|
||||
| user | User \| null | Current logged-in user or null |
|
||||
| login | (username: string, password: string) => boolean | Login function; returns success status |
|
||||
| logout | () => void | Logout function; clears user and localStorage |
|
||||
| registerPhone | string \| null | Phone number during registration flow |
|
||||
| setRegisterPhone | (phone: string \| null) => void | Update registerPhone state |
|
||||
| completeRegistration | (phone: string) => void | Complete registration and create customer account |
|
||||
|
||||
### Mock Database
|
||||
|
||||
Pre-configured accounts:
|
||||
|
||||
- Manager: `admin / admin`
|
||||
- Staff: `Nguyễn Văn An / Nguyễn Văn An`, `Trần Thị Bình / Trần Thị Bình`, etc.
|
||||
- Customer: Phone number as username, `user1` as password
|
||||
|
||||
### Storage
|
||||
|
||||
- Key: `coffee-shop-user`
|
||||
- Format: JSON serialized User object
|
||||
|
||||
---
|
||||
|
||||
## CartContext (lib/cart-context.tsx)
|
||||
|
||||
**File:** lib/cart-context.tsx **Description:** Manages shopping cart state with
|
||||
localStorage persistence. Tracks items, quantities, and totals.
|
||||
|
||||
### Provider Props
|
||||
|
||||
| Prop | Type | Required | Description |
|
||||
| -------- | --------------- | -------- | ---------------- |
|
||||
| children | React.ReactNode | yes | Child components |
|
||||
|
||||
### Hook: useCart()
|
||||
|
||||
Returns `CartContextValue` with:
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | -------------------------------------- | --------------------------------------------------------- |
|
||||
| items | CartItem[] | Array of items in cart |
|
||||
| totalItems | number | Total quantity of items |
|
||||
| totalPrice | number | Total price in VND |
|
||||
| addToCart | (product: Product) => void | Add or increase product quantity |
|
||||
| increaseQty | (id: number) => void | Increase product quantity by 1 |
|
||||
| decreaseQty | (id: number) => void | Decrease product quantity by 1 (removes if qty reaches 0) |
|
||||
| removeFromCart | (id: number) => void | Remove product from cart |
|
||||
| setQuantity | (id: number, quantity: number) => void | Set exact quantity (removes if 0) |
|
||||
|
||||
### CartItem Interface
|
||||
|
||||
```typescript
|
||||
interface CartItem {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
- Key: `coffee-shop-cart`
|
||||
- Format: JSON serialized CartItem[]
|
||||
- Auto-loads on mount and auto-saves on change
|
||||
|
||||
---
|
||||
|
||||
## MenuContext (lib/menu-context.tsx)
|
||||
|
||||
**File:** lib/menu-context.tsx **Description:** Provides shared category/menu
|
||||
state across components. Synchronizes Header mobile menu and Navbar sidebar
|
||||
selection.
|
||||
|
||||
### Provider Props
|
||||
|
||||
| Prop | Type | Required | Description |
|
||||
| -------- | --------------- | -------- | ---------------- |
|
||||
| children | React.ReactNode | yes | Child components |
|
||||
|
||||
### Hook: useMenu()
|
||||
|
||||
Returns `MenuContextType` with:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ----------------- | -------------------- | ----------------------------------------------- |
|
||||
| activeCategory | string | Currently selected category id (default: "all") |
|
||||
| setActiveCategory | (id: string) => void | Update active category |
|
||||
|
||||
### Use Cases
|
||||
|
||||
- Sync Navbar sidebar and Header mobile menu category selection
|
||||
- Clear search query when category changes (implemented in main page)
|
||||
- Pass selected category to product filter logic
|
||||
|
||||
### Default Value
|
||||
|
||||
- `activeCategory: "all"` - Show all products by default
|
||||
|
||||
+2
-4
@@ -15,10 +15,8 @@ spec:
|
||||
app: web
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx-container
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: frontend-container
|
||||
image: git.demonkernel.io.vn/foodsurf/frontend:latest
|
||||
resources:
|
||||
limits:
|
||||
cpu: "50m"
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
# Layouts Documentation
|
||||
|
||||
> Tài liệu chi tiết về layout components (Header, Footer) và responsive behavior
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
```
|
||||
layouts/
|
||||
├── header.tsx # Sticky top header with shop branding and auth
|
||||
├── footer.tsx # Footer with shop info, social links, WiFi details
|
||||
└── LAYOUTS.md # (this file)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Header (layouts/header.tsx)
|
||||
|
||||
**File:** layouts/header.tsx **Type:** Client component **Description:** Sticky
|
||||
top navigation bar with brand info and authentication status display.
|
||||
|
||||
### Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ [Logo] [Shop Name] [Tagline] │ [Auth Status] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
height: 72px (var(--spacing-header-height))
|
||||
```
|
||||
|
||||
**2-Column Layout:**
|
||||
|
||||
- **Left:** Shop branding (logo + name + tagline)
|
||||
- **Right:** Auth status indicator
|
||||
|
||||
### Features
|
||||
|
||||
#### Logo & Branding
|
||||
|
||||
- Shop logo (40x40px)
|
||||
- Shop name (bold text)
|
||||
- Tagline (hidden on mobile < md, visible md+)
|
||||
|
||||
**Responsive:** | Breakpoint | Logo | Name | Tagline |
|
||||
|-----------|------|------|---------| | < sm (640px) | Yes | Yes | Hidden | |
|
||||
sm-md | Yes | Yes | Hidden | | md+ (768px) | Yes | Yes | Visible |
|
||||
|
||||
#### Authentication Status
|
||||
|
||||
The header cycles through 3 auth states (for UI demo):
|
||||
|
||||
**State 1: Guest (Not logged in)**
|
||||
|
||||
```
|
||||
[brown button] "Dang nhap"
|
||||
```
|
||||
|
||||
- Guest state (user = null)
|
||||
- Brown primary button
|
||||
- Click: toggles to next state
|
||||
|
||||
**State 2: Manager**
|
||||
|
||||
```
|
||||
[caramel/gold badge] "👔 Quản lý"
|
||||
```
|
||||
|
||||
- Shows manager role
|
||||
- Gold/caramel colored badge
|
||||
- User tie icon
|
||||
- Name: Nguyễn Văn An
|
||||
|
||||
**State 3: Staff**
|
||||
|
||||
```
|
||||
[border button] "👤 [Name]"
|
||||
```
|
||||
|
||||
- Shows staff role with avatar placeholder
|
||||
- Bordered button style
|
||||
- Name: Trần Thị Bình
|
||||
- Click: toggles back to Guest state
|
||||
|
||||
### Responsive Behavior
|
||||
|
||||
| Breakpoint | Layout |
|
||||
| ------------- | ------------------------------- |
|
||||
| < sm | Stacked vertically |
|
||||
| sm-md (640px) | Side-by-side, reduced spacing |
|
||||
| md+ (768px) | Full width, comfortable spacing |
|
||||
|
||||
**Mobile (<640px):**
|
||||
|
||||
- Logo smaller
|
||||
- Name only (no tagline)
|
||||
- Auth button text hidden, icon shown
|
||||
- Touch-friendly padding
|
||||
|
||||
**Desktop (≥640px):**
|
||||
|
||||
- Normal logo size
|
||||
- Full layout with tagline
|
||||
- Button with text and icon
|
||||
- Comfortable spacing
|
||||
|
||||
### Styling
|
||||
|
||||
| Element | Classes |
|
||||
| -------------------- | --------------------------------------------------------- |
|
||||
| Header wrapper | `sticky top-0 z-50 bg-white shadow-md` |
|
||||
| Container | `max-w-7xl mx-auto px-4 md:px-6 h-72px flex items-center` |
|
||||
| Left section (brand) | `flex items-center gap-3 flex-1` |
|
||||
| Right section (auth) | `flex items-center gap-3 shrink-0` |
|
||||
| Logo image | `w-10 h-10 md:w-12 md:h-12` |
|
||||
| Brand text | `font-bold text-sm md:text-base` |
|
||||
| Tagline | `hidden md:block text-xs text-muted-foreground` |
|
||||
| Auth button | `px-4 py-2 rounded-lg text-sm font-medium` |
|
||||
|
||||
### CSS Variables Used
|
||||
|
||||
```css
|
||||
--spacing-header-height: 72px;
|
||||
--color-primary: brown;
|
||||
--color-primary-dark: darker brown;
|
||||
--color-text-primary: text color;
|
||||
--color-text-muted: gray text;
|
||||
--color-shadow-sm: subtle shadow;
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **next/image** - Logo image rendering
|
||||
- **next/link** - Navigation links
|
||||
- **lib/constants:** SHOP_INFO, MOCK_USERS
|
||||
- **lib/types:** User
|
||||
- **FontAwesome icons:** fa-sign-in-alt, fa-sign-out-alt, fa-user-tie,
|
||||
fa-user-circle
|
||||
|
||||
### Key Code Snippets
|
||||
|
||||
**Auth Demo State Machine:**
|
||||
|
||||
```typescript
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
const handleAuthClick = () => {
|
||||
if (!user) {
|
||||
setUser(MOCK_USERS.manager);
|
||||
} else if (user.role === "manager") {
|
||||
setUser(MOCK_USERS.staff);
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Responsive Brand Text:**
|
||||
|
||||
```typescript
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-sm md:text-base">{SHOP_INFO.name}</span>
|
||||
<span className="hidden md:block text-xs text-muted-foreground">
|
||||
{SHOP_INFO.tagline}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Footer (layouts/footer.tsx)
|
||||
|
||||
**File:** layouts/footer.tsx **Type:** Client component (uses constants)
|
||||
**Description:** Site footer with shop information, social links, and WiFi
|
||||
details.
|
||||
|
||||
### Layout
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ [Brand Info] │ [Social + WiFi] │
|
||||
├────────────────────────────────────────────┤
|
||||
│ © 2024 Coffee Shop. Made with ❤️ in VN │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**12-Column Grid Layout:**
|
||||
|
||||
| Breakpoint | Brand Info | Social + WiFi |
|
||||
| ------------ | ----------- | ------------- |
|
||||
| Mobile | col-span-12 | col-span-12 |
|
||||
| md (768px) | col-span-6 | col-span-6 |
|
||||
| lg+ (1024px) | col-span-6 | col-span-6 |
|
||||
|
||||
### Sections
|
||||
|
||||
#### 1. Brand Info (Left Section)
|
||||
|
||||
**Content:**
|
||||
|
||||
- Shop logo (40x40px)
|
||||
- Shop name (bold)
|
||||
- Tagline (gray text)
|
||||
- Address with location icon
|
||||
- Phone with phone icon
|
||||
- Email with envelope icon
|
||||
- Open hours with clock icon
|
||||
|
||||
**Responsive:**
|
||||
|
||||
```
|
||||
Mobile:
|
||||
└─ Logo
|
||||
└─ Name & Tagline (stacked)
|
||||
└─ Address
|
||||
└─ Phone
|
||||
└─ Email
|
||||
└─ Open Hours
|
||||
|
||||
Desktop:
|
||||
└─ Logo + Name/Tagline (flexbox, side-by-side)
|
||||
└─ Address, Phone, Email, Hours (stacked below)
|
||||
```
|
||||
|
||||
#### 2. Social Links (Top Right)
|
||||
|
||||
**Links:**
|
||||
|
||||
- Facebook icon → SHOP_INFO.facebook
|
||||
- TikTok icon → SHOP_INFO.tiktok
|
||||
- Website icon → SHOP_INFO.website
|
||||
|
||||
**Styling:**
|
||||
|
||||
- Icons in circle backgrounds
|
||||
- Hover effect (color change)
|
||||
- Flex row layout
|
||||
- Centered alignment
|
||||
|
||||
#### 3. WiFi Card (Bottom Right)
|
||||
|
||||
**Content:**
|
||||
|
||||
- "📶 WiFi miễn phí" label
|
||||
- Network name (monospace)
|
||||
- Password (monospace, can be hidden/shown)
|
||||
|
||||
**Styling:**
|
||||
|
||||
- Light gray background
|
||||
- Rounded borders
|
||||
- Monospace font for credentials
|
||||
- Eye icon toggle for password visibility
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
📶 WiFi miễn phí
|
||||
┌────────────────────────┐
|
||||
│ Network: CoffeeShop │
|
||||
│ Password: •••••••• │
|
||||
│ (eye icon) │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
#### 4. Copyright Bar (Bottom)
|
||||
|
||||
**Content:**
|
||||
|
||||
- Copyright text: "© 2024 Coffee Shop"
|
||||
- "Made with ❤️ in Vietnam"
|
||||
- Centered
|
||||
|
||||
**Styling:**
|
||||
|
||||
- Small text
|
||||
- Gray color
|
||||
- Subtle separator line above
|
||||
|
||||
### Responsive Behavior
|
||||
|
||||
**Mobile (<768px):**
|
||||
|
||||
- Single column layout
|
||||
- Stacked sections
|
||||
- Full width
|
||||
- Padding around edges
|
||||
|
||||
**Tablet (768px):**
|
||||
|
||||
- 2-column layout
|
||||
- Brand info left, Social + WiFi right
|
||||
- Equal width columns
|
||||
|
||||
**Desktop (≥768px):**
|
||||
|
||||
- 2-column layout with more spacing
|
||||
- Brand info slightly smaller
|
||||
- Comfortable padding
|
||||
|
||||
### Styling
|
||||
|
||||
| Element | Classes |
|
||||
| -------------- | --------------------------------------------------------- |
|
||||
| Footer wrapper | `bg-gray-100 border-t` |
|
||||
| Container | `max-w-7xl mx-auto px-4 py-8` |
|
||||
| Grid | `grid grid-cols-12 gap-8` |
|
||||
| Brand section | `col-span-12 md:col-span-6` |
|
||||
| Social section | `col-span-12 md:col-span-6` |
|
||||
| Logo | `w-10 h-10` |
|
||||
| Info text | `text-sm text-gray-600` |
|
||||
| Social icons | `w-10 h-10 rounded-full flex items-center justify-center` |
|
||||
| WiFi card | `bg-white p-4 rounded-lg border` |
|
||||
| Copyright bar | `border-t mt-8 pt-6 text-center text-xs text-gray-500` |
|
||||
|
||||
### CSS Variables Used
|
||||
|
||||
```css
|
||||
--color-primary: brand color;
|
||||
--color-text-secondary: secondary text color;
|
||||
--color-text-muted: muted/gray text color;
|
||||
--color-accent: accent color;
|
||||
--color-border: border color;
|
||||
--color-bg-*: background colors;
|
||||
--color-shadow-sm: subtle shadow;
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **next/image** - Shop logo
|
||||
- **next/link** - Social links
|
||||
- **lib/constants:** SHOP_INFO, SOCIAL_LINKS
|
||||
- **FontAwesome icons:** location-dot, phone, envelope, clock, facebook, tiktok,
|
||||
globe, wifi, eye, eye-slash
|
||||
|
||||
### Key Code Snippets
|
||||
|
||||
**Brand Info Section:**
|
||||
|
||||
```typescript
|
||||
<div className="flex gap-4">
|
||||
<Image src={SHOP_INFO.logo} alt={SHOP_INFO.name} width={40} height={40} />
|
||||
<div>
|
||||
<h3 className="font-bold">{SHOP_INFO.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{SHOP_INFO.tagline}</p>
|
||||
<p className="text-sm mt-2">{SHOP_INFO.address}</p>
|
||||
{/* etc */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Social Links:**
|
||||
|
||||
```typescript
|
||||
<div className="flex gap-3">
|
||||
<Link href={SOCIAL_LINKS.facebook} target="_blank" rel="noopener">
|
||||
<i className="fab fa-facebook" />
|
||||
</Link>
|
||||
{/* etc */}
|
||||
</div>
|
||||
```
|
||||
|
||||
**WiFi Display:**
|
||||
|
||||
```typescript
|
||||
<div className="bg-white p-4 rounded-lg">
|
||||
<p className="font-semibold text-sm">📶 WiFi miễn phí</p>
|
||||
<p className="font-mono text-xs mt-2">{SHOP_INFO.wifi.name}</p>
|
||||
<p className="font-mono text-xs">
|
||||
{showPassword ? SHOP_INFO.wifi.password : "••••••••"}
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration in Root Layout
|
||||
|
||||
**File:** app/layout.tsx
|
||||
|
||||
```typescript
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
- Header: Always at top (sticky)
|
||||
- Content: Takes full width between header/footer
|
||||
- Footer: Always at bottom
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design Patterns
|
||||
|
||||
### Header Responsive Pattern
|
||||
|
||||
```css
|
||||
/* Mobile first */
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Tablet+ */
|
||||
@media (min-width: 640px) {
|
||||
.header {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop+ */
|
||||
@media (min-width: 768px) {
|
||||
.header {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
display: block; /* Show tagline on desktop */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Footer Responsive Pattern
|
||||
|
||||
```css
|
||||
/* Mobile: stacked */
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Tablet: 2 columns */
|
||||
@media (min-width: 768px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode Support
|
||||
|
||||
CSS variables are set up for dark mode support. To enable:
|
||||
|
||||
1. Add dark mode CSS variable overrides:
|
||||
|
||||
```css
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg-main: #1a1a1a;
|
||||
--color-text-primary: #ffffff;
|
||||
/* etc */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Or use Tailwind dark class:
|
||||
|
||||
```html
|
||||
<div className="dark:bg-gray-900 dark:text-white">Content</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Header
|
||||
|
||||
- Logo has alt text
|
||||
- Auth button has aria-label
|
||||
- Icons have semantic meaning
|
||||
- Good contrast ratios
|
||||
|
||||
### Footer
|
||||
|
||||
- Headings use semantic tags
|
||||
- Links have descriptive text
|
||||
- Icons are decorative (aria-hidden)
|
||||
- Monospace font for technical info (WiFi credentials)
|
||||
|
||||
### General
|
||||
|
||||
- Touch targets ≥ 48px on mobile
|
||||
- Sufficient color contrast
|
||||
- Semantic HTML structure
|
||||
- ARIA labels where needed
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep layouts simple:** Avoid complex nested layouts
|
||||
2. **Responsive first:** Use mobile-first CSS approach
|
||||
3. **Reuse components:** Use Header/Footer across all pages
|
||||
4. **Props over hardcoding:** Use lib/constants for data
|
||||
5. **Type safety:** Use TypeScript for component props
|
||||
6. **Performance:** Optimize images with next/image
|
||||
7. **Accessibility:** Add ARIA labels and semantic HTML
|
||||
+487
@@ -0,0 +1,487 @@
|
||||
# Lib Documentation
|
||||
|
||||
> Tài liệu chi tiết về utilities, contexts, types, và constants
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The `lib/` directory contains shared logic, data models, and context providers
|
||||
used throughout the application.
|
||||
|
||||
```
|
||||
lib/
|
||||
├── types.ts # TypeScript interfaces and types
|
||||
├── constants.ts # Mock data (products, shops, users, info)
|
||||
├── auth-context.tsx # Authentication context & provider
|
||||
├── cart-context.tsx # Shopping cart context & provider
|
||||
├── menu-context.tsx # Menu/category context & provider
|
||||
└── LIB.md # (this file)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Types (lib/types.ts)
|
||||
|
||||
**Description:** Central TypeScript definitions used throughout the application.
|
||||
|
||||
### User Type
|
||||
|
||||
```typescript
|
||||
type UserRole = "manager" | "staff" | "customer";
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
role: UserRole;
|
||||
avatar: string | null;
|
||||
phone?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Authentication context, header component, role-based UI
|
||||
|
||||
**Roles:**
|
||||
|
||||
- **manager** - Shop owner, full access to menu management and orders
|
||||
- **staff** - Staff member, can view/process orders
|
||||
- **customer** - Regular customer, can browse and order
|
||||
|
||||
---
|
||||
|
||||
### Menu Types
|
||||
|
||||
```typescript
|
||||
interface MenuCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string; // FontAwesome class e.g. "fa-solid fa-mug-hot"
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Sidebar navigation, category filter, product display
|
||||
|
||||
**Example Categories:**
|
||||
|
||||
- `all` - All products
|
||||
- `cafe` - Coffee
|
||||
- `tra` - Tea
|
||||
- `sua-chua` - Yogurt
|
||||
- `nuoc-ep` - Juice
|
||||
- `latte` - Latte drinks
|
||||
- `giai-khat` - Snacks
|
||||
- `topping` - Toppings
|
||||
|
||||
---
|
||||
|
||||
### Product Type
|
||||
|
||||
```typescript
|
||||
interface Product {
|
||||
id: number;
|
||||
name: string;
|
||||
category: string; // matches MenuCategory.id
|
||||
price: number;
|
||||
image: string;
|
||||
description: string;
|
||||
available?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Product grid display, cart items, filtering
|
||||
|
||||
**Key Fields:**
|
||||
|
||||
- `id` - Unique product identifier
|
||||
- `name` - Display name in Vietnamese
|
||||
- `category` - Category ID for filtering
|
||||
- `price` - Price in VND
|
||||
- `image` - URL or path to product image
|
||||
- `description` - Short 1-2 sentence description
|
||||
- `available` - If `false`, product won't show (defaults to `true`)
|
||||
|
||||
---
|
||||
|
||||
### Shop Info Type
|
||||
|
||||
```typescript
|
||||
interface WifiInfo {
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface ShopInfo {
|
||||
name: string;
|
||||
tagline: string;
|
||||
logo: string;
|
||||
address: string;
|
||||
phone: string;
|
||||
managerPhone: string;
|
||||
email: string;
|
||||
wifi: WifiInfo;
|
||||
openHours: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Header, footer, login page display
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const SHOP_INFO: ShopInfo = {
|
||||
name: "Coffee Shop",
|
||||
tagline: "Hương vị đậm đà – Khoảnh khắc thư giãn",
|
||||
logo: "/imgs/logo.png",
|
||||
address: "123 Đường Nguyễn Huệ, Quận 1, TP. Hồ Chí Minh",
|
||||
phone: "0901 234 567",
|
||||
managerPhone: "0912 345 678",
|
||||
email: "contact@coffeeshop.vn",
|
||||
wifi: { name: "CoffeeShop_Free", password: "coffee2024" },
|
||||
openHours: "07:00 – 22:00 (Thứ 2 – Chủ nhật)",
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Social Links Type
|
||||
|
||||
```typescript
|
||||
interface SocialLinks {
|
||||
facebook: string;
|
||||
tiktok: string;
|
||||
website: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Footer component
|
||||
|
||||
---
|
||||
|
||||
### Shop (Discovery) Type
|
||||
|
||||
```typescript
|
||||
interface Shop {
|
||||
id: number;
|
||||
name: string;
|
||||
address: string;
|
||||
image: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** Feed page - list of coffee shops to discover
|
||||
|
||||
---
|
||||
|
||||
## Constants (lib/constants.ts)
|
||||
|
||||
**Description:** Mock data used throughout the application. Replace with API
|
||||
calls when backend is ready.
|
||||
|
||||
### SHOP_INFO
|
||||
|
||||
Single instance of shop information displayed in header, footer, and login page.
|
||||
|
||||
**Key Info Used:**
|
||||
|
||||
- Header: name, logo, phone
|
||||
- Footer: all fields
|
||||
- Login: logo, name, email
|
||||
|
||||
---
|
||||
|
||||
### SOCIAL_LINKS
|
||||
|
||||
Social media links for footer component.
|
||||
|
||||
```typescript
|
||||
export const SOCIAL_LINKS: SocialLinks = {
|
||||
facebook: "https://facebook.com/coffeeshop",
|
||||
tiktok: "https://tiktok.com/@coffeeshop",
|
||||
website: "/",
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### MENU_CATEGORIES
|
||||
|
||||
Array of 8 product categories. Used in sidebar, mobile menu, and product
|
||||
filtering.
|
||||
|
||||
```typescript
|
||||
export const MENU_CATEGORIES: MenuCategory[] = [
|
||||
{ id: "all", name: "Tất cả", icon: "fa-solid fa-border-all" },
|
||||
{ id: "cafe", name: "Cà Phê", icon: "fa-solid fa-mug-hot" },
|
||||
{ id: "tra", name: "Trà", icon: "fa-solid fa-leaf" },
|
||||
{ id: "sua-chua", name: "Sữa Chua", icon: "fa-solid fa-jar" },
|
||||
{ id: "nuoc-ep", name: "Nước Ép", icon: "fa-solid fa-blender" },
|
||||
{ id: "latte", name: "Latte", icon: "fa-solid fa-mug-saucer" },
|
||||
{
|
||||
id: "giai-khat",
|
||||
name: "Giải Khát / Ăn Vặt",
|
||||
icon: "fa-solid fa-ice-cream",
|
||||
},
|
||||
{ id: "topping", name: "Topping", icon: "fa-solid fa-layer-group" },
|
||||
];
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
- Navbar sidebar category buttons
|
||||
- Header mobile scrollable menu
|
||||
- Product grid category filter
|
||||
|
||||
---
|
||||
|
||||
### MOCK_PRODUCTS
|
||||
|
||||
Array of 18 mock product items across all categories.
|
||||
|
||||
**Example Product:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 1,
|
||||
name: "Cà Phê Đen",
|
||||
category: "cafe",
|
||||
price: 25000,
|
||||
image: "/imgs/products/placeholder.jpg",
|
||||
description: "Cà phê đen truyền thống, đậm đà hương vị Việt Nam, pha phin thủ công.",
|
||||
available: true,
|
||||
}
|
||||
```
|
||||
|
||||
**Product Distribution:**
|
||||
|
||||
- Café: 4 items (Đen, Sữa, Bạc Xỉu, Trứng)
|
||||
- Tea: 3 items (Đào Cam Sả, Matcha, Vải Hoa Nhài)
|
||||
- Yogurt: 2 items (Trân Châu, Dâu)
|
||||
- Juice: 2 items (Cam, Dưa Hấu)
|
||||
- Latte: 2 items (Caramel, Vanilla)
|
||||
- Snacks: 2 items (Bánh Mì, Bánh Flan)
|
||||
- Toppings: 3 items (Trân Châu Đen, Thạch, Trân Châu Trắng)
|
||||
|
||||
**Price Range:** 10,000 - 45,000 VND
|
||||
|
||||
---
|
||||
|
||||
### MOCK_SHOPS
|
||||
|
||||
Array of 5 coffee shops for the feed/discovery page.
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 1,
|
||||
name: "The Coffee House",
|
||||
address: "86 Cao Thắng, Quận 3, TP. Hồ Chí Minh",
|
||||
image: "https://images.unsplash.com/...", // Unsplash URL
|
||||
}
|
||||
```
|
||||
|
||||
**Shops:**
|
||||
|
||||
1. The Coffee House - Quận 3
|
||||
2. Highlands Coffee - Quận 1
|
||||
3. Phúc Long Heritage - Quận 1
|
||||
4. Katinat Saigon Kafe - Quận 1
|
||||
5. Trung Nguyên E-Coffee - Quận 1
|
||||
|
||||
**Usage:** Feed page grid display
|
||||
|
||||
---
|
||||
|
||||
### MOCK_USERS
|
||||
|
||||
Pre-configured user accounts for authentication demo.
|
||||
|
||||
```typescript
|
||||
export const MOCK_USERS: Record<string, User> = {
|
||||
manager: {
|
||||
id: 1,
|
||||
name: "Nguyễn Văn An",
|
||||
role: "manager",
|
||||
avatar: null,
|
||||
},
|
||||
staff: {
|
||||
id: 2,
|
||||
name: "Trần Thị Bình",
|
||||
role: "staff",
|
||||
avatar: null,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Usage:** AuthContext for login demo
|
||||
|
||||
**Note:** More accounts defined in `auth-context.tsx` MOCK_AUTH_DB
|
||||
|
||||
---
|
||||
|
||||
## Auth Context (lib/auth-context.tsx)
|
||||
|
||||
See [COMPONENTS.md - AuthContext](../components/COMPONENTS.md#authcontext) for
|
||||
detailed documentation.
|
||||
|
||||
**Quick Summary:**
|
||||
|
||||
- Manages user login/logout/register
|
||||
- Persists user in localStorage
|
||||
- Provides user info to components via useAuth() hook
|
||||
|
||||
---
|
||||
|
||||
## Cart Context (lib/cart-context.tsx)
|
||||
|
||||
See [COMPONENTS.md - CartContext](../components/COMPONENTS.md#cartcontext) for
|
||||
detailed documentation.
|
||||
|
||||
**Quick Summary:**
|
||||
|
||||
- Manages shopping cart items and quantities
|
||||
- Persists cart in localStorage
|
||||
- Provides cart operations via useCart() hook
|
||||
|
||||
---
|
||||
|
||||
## Menu Context (lib/menu-context.tsx)
|
||||
|
||||
See [COMPONENTS.md - MenuContext](../components/COMPONENTS.md#menucontext) for
|
||||
detailed documentation.
|
||||
|
||||
**Quick Summary:**
|
||||
|
||||
- Shares active category state
|
||||
- Syncs sidebar and mobile menu selection
|
||||
- Provides activeCategory and setActiveCategory via useMenu() hook
|
||||
|
||||
---
|
||||
|
||||
## Integration Guide
|
||||
|
||||
### Using Types
|
||||
|
||||
```typescript
|
||||
import type { MenuCategory, Product, User } from "@/lib/types";
|
||||
```
|
||||
|
||||
### Using Constants
|
||||
|
||||
```typescript
|
||||
import { MENU_CATEGORIES, MOCK_PRODUCTS, SHOP_INFO } from "@/lib/constants";
|
||||
|
||||
// In a component:
|
||||
const products = MOCK_PRODUCTS.filter((p) => p.category === "cafe");
|
||||
const shopName = SHOP_INFO.name;
|
||||
```
|
||||
|
||||
### Using Auth Context
|
||||
|
||||
```typescript
|
||||
import { useAuth } from "@/lib/auth-context";
|
||||
|
||||
export default function MyComponent() {
|
||||
const { user, login, logout } = useAuth();
|
||||
|
||||
if (!user) return <p>Not logged in</p>;
|
||||
return <p>Welcome, {user.name}!</p>;
|
||||
}
|
||||
```
|
||||
|
||||
### Using Cart Context
|
||||
|
||||
```typescript
|
||||
import { useCart } from "@/lib/cart-context";
|
||||
import type { Product } from "@/lib/types";
|
||||
|
||||
export default function ProductCard({ product }: { product: Product }) {
|
||||
const { addToCart } = useCart();
|
||||
|
||||
return (
|
||||
<button onClick={() => addToCart(product)}>
|
||||
Add to Cart
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Using Menu Context
|
||||
|
||||
```typescript
|
||||
import { useMenu } from "@/lib/menu-context";
|
||||
import { MENU_CATEGORIES } from "@/lib/constants";
|
||||
|
||||
export default function CategoryFilter() {
|
||||
const { activeCategory, setActiveCategory } = useMenu();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{MENU_CATEGORIES.map(cat => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setActiveCategory(cat.id)}
|
||||
className={activeCategory === cat.id ? "active" : ""}
|
||||
>
|
||||
{cat.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow & Updates
|
||||
|
||||
### When to Update Constants
|
||||
|
||||
1. **Product List:** When adding/removing products
|
||||
- Update MOCK_PRODUCTS array
|
||||
- Or replace with API call in the future
|
||||
|
||||
2. **Categories:** When adding new product categories
|
||||
- Update MENU_CATEGORIES array
|
||||
- Update product category references
|
||||
|
||||
3. **Shop Info:** When shop details change
|
||||
- Update SHOP_INFO object
|
||||
- Automatically reflects in header/footer
|
||||
|
||||
4. **Social Links:** When social handles change
|
||||
- Update SOCIAL_LINKS object
|
||||
|
||||
### Future API Integration
|
||||
|
||||
When backend is ready:
|
||||
|
||||
```typescript
|
||||
// Replace MOCK_PRODUCTS with:
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
useEffect(() => {
|
||||
fetch("/api/products")
|
||||
.then((r) => r.json())
|
||||
.then(setProducts);
|
||||
}, []);
|
||||
|
||||
// Replace MOCK_USERS with:
|
||||
const login = async (username: string, password: string) => {
|
||||
const res = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
return res.ok;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Import Types:** Always use `import type` for types to avoid runtime bloat
|
||||
2. **Immutability:** Contexts return functions to update state, not direct
|
||||
mutations
|
||||
3. **localStorage:** Keys are centralized (`coffee-shop-user`,
|
||||
`coffee-shop-cart`)
|
||||
4. **Validation:** Contexts validate/sanitize data (e.g., filter invalid cart
|
||||
items)
|
||||
5. **Default Values:** Contexts have sensible defaults (activeCategory: "all")
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
MenuCategory,
|
||||
Product,
|
||||
Shop,
|
||||
ShopInfo,
|
||||
SocialLinks,
|
||||
User,
|
||||
@@ -231,6 +232,45 @@ export const MOCK_PRODUCTS: Product[] = [
|
||||
},
|
||||
];
|
||||
|
||||
// ===== MOCK SHOPS (for Feed page) =====
|
||||
export const MOCK_SHOPS: Shop[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "The Coffee House",
|
||||
address: "86 Cao Thắng, Quận 3, TP. Hồ Chí Minh",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1554118811-1e0d58224f24?w=600&h=400&fit=crop",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Highlands Coffee",
|
||||
address: "123 Nguyễn Huệ, Quận 1, TP. Hồ Chí Minh",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1559305616-3f99cd43e353?w=600&h=400&fit=crop",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Phúc Long Heritage",
|
||||
address: "42 Lê Lợi, Quận 1, TP. Hồ Chí Minh",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=600&h=400&fit=crop",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Katinat Saigon Kafe",
|
||||
address: "26 Lý Tự Trọng, Quận 1, TP. Hồ Chí Minh",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=600&h=400&fit=crop",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Trung Nguyên E-Coffee",
|
||||
address: "15 Hai Bà Trưng, Quận 1, TP. Hồ Chí Minh",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1453614512568-c4024d13c247?w=600&h=400&fit=crop",
|
||||
},
|
||||
];
|
||||
|
||||
// ===== MOCK USERS (for UI demo – replace with real auth) =====
|
||||
export const MOCK_USERS: Record<string, User> = {
|
||||
manager: {
|
||||
|
||||
@@ -51,3 +51,11 @@ export interface SocialLinks {
|
||||
tiktok: string;
|
||||
website: string;
|
||||
}
|
||||
|
||||
// ===== SHOP (QUÁN NƯỚC) TYPES =====
|
||||
export interface Shop {
|
||||
id: number;
|
||||
name: string;
|
||||
address: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
+7
-2
@@ -10,9 +10,14 @@ import type { NextConfig } from "next";
|
||||
* Docs: https://nextjs.org/docs/app/api-reference/next-config-js
|
||||
*/
|
||||
const nextConfig: NextConfig = {
|
||||
output: "export", // Bắt buộc để tạo ra thư mục /out
|
||||
output: "export",
|
||||
images: {
|
||||
unoptimized: true, // Thường cần thiết cho static export
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Generated
+742
-2793
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "temp",
|
||||
"version": "1.0.0-created-login-and-register-page.1",
|
||||
"version": "1.0.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -11,20 +11,20 @@
|
||||
"release": "semantic-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/react": "^19.2.14",
|
||||
"next": "16.1.7",
|
||||
"php": "^1.1.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"tailwind": "^4.0.0",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/react": "^19.2.14",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/exec": "^7.1.0",
|
||||
"@semantic-release/github": "^12.0.6",
|
||||
"@semantic-release/npm": "^13.1.5",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
||||
Generated
+41
-35
@@ -20,6 +20,9 @@ importers:
|
||||
next:
|
||||
specifier: 16.1.7
|
||||
version: 16.1.7(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
php:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
react:
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
@@ -42,9 +45,6 @@ importers:
|
||||
'@semantic-release/exec':
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(semantic-release@25.0.3(typescript@5.9.3))
|
||||
'@semantic-release/github':
|
||||
specifier: ^12.0.6
|
||||
version: 12.0.6(semantic-release@25.0.3(typescript@5.9.3))
|
||||
'@semantic-release/npm':
|
||||
specifier: ^13.1.5
|
||||
version: 13.1.5(semantic-release@25.0.3(typescript@5.9.3))
|
||||
@@ -1095,8 +1095,8 @@ packages:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
baseline-browser-mapping@2.10.11:
|
||||
resolution: {integrity: sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==}
|
||||
baseline-browser-mapping@2.10.12:
|
||||
resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1121,11 +1121,11 @@ packages:
|
||||
bottleneck@2.19.5:
|
||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
brace-expansion@1.1.13:
|
||||
resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
brace-expansion@2.0.3:
|
||||
resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
|
||||
|
||||
brace-expansion@5.0.5:
|
||||
resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
|
||||
@@ -1174,8 +1174,8 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001781:
|
||||
resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==}
|
||||
caniuse-lite@1.0.30001782:
|
||||
resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==}
|
||||
|
||||
chalk@2.4.1:
|
||||
resolution: {integrity: sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==}
|
||||
@@ -1277,8 +1277,8 @@ packages:
|
||||
resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
conventional-changelog-angular@8.3.0:
|
||||
resolution: {integrity: sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==}
|
||||
conventional-changelog-angular@8.3.1:
|
||||
resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
conventional-changelog-writer@8.4.0:
|
||||
@@ -1290,8 +1290,8 @@ packages:
|
||||
resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
conventional-commits-parser@6.3.0:
|
||||
resolution: {integrity: sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==}
|
||||
conventional-commits-parser@6.4.0:
|
||||
resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1466,8 +1466,8 @@ packages:
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
electron-to-chromium@1.5.327:
|
||||
resolution: {integrity: sha512-hLxLdIJDf8zIzKoH2TPCs+Botc+wUmj9sp4jVMwklY/sKleM8xxxOExRX3Gxj73nCXmJe3anhG7SvsDDPDvmuQ==}
|
||||
electron-to-chromium@1.5.328:
|
||||
resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==}
|
||||
|
||||
emoji-regex@10.6.0:
|
||||
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||
@@ -2968,6 +2968,10 @@ packages:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
php@1.1.0:
|
||||
resolution: {integrity: sha512-IUIOrFBooOyTP3SD9zZZ0cX2UwL5fQj3cmuaXGcc1gsSbMdHseNcEmpwXoh+b0G+hv5LfBL0nBggEOS2YRk7sQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -4308,10 +4312,10 @@ snapshots:
|
||||
|
||||
'@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.3(typescript@5.9.3))':
|
||||
dependencies:
|
||||
conventional-changelog-angular: 8.3.0
|
||||
conventional-changelog-angular: 8.3.1
|
||||
conventional-changelog-writer: 8.4.0
|
||||
conventional-commits-filter: 5.0.0
|
||||
conventional-commits-parser: 6.3.0
|
||||
conventional-commits-parser: 6.4.0
|
||||
debug: 4.4.3
|
||||
import-from-esm: 2.0.0
|
||||
lodash-es: 4.17.23
|
||||
@@ -4380,10 +4384,10 @@ snapshots:
|
||||
|
||||
'@semantic-release/release-notes-generator@14.1.0(semantic-release@25.0.3(typescript@5.9.3))':
|
||||
dependencies:
|
||||
conventional-changelog-angular: 8.3.0
|
||||
conventional-changelog-angular: 8.3.1
|
||||
conventional-changelog-writer: 8.4.0
|
||||
conventional-commits-filter: 5.0.0
|
||||
conventional-commits-parser: 6.3.0
|
||||
conventional-commits-parser: 6.4.0
|
||||
debug: 4.4.3
|
||||
get-stream: 7.0.1
|
||||
import-from-esm: 2.0.0
|
||||
@@ -4882,7 +4886,7 @@ snapshots:
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
baseline-browser-mapping@2.10.11: {}
|
||||
baseline-browser-mapping@2.10.12: {}
|
||||
|
||||
basic-auth@2.0.1:
|
||||
dependencies:
|
||||
@@ -4913,12 +4917,12 @@ snapshots:
|
||||
|
||||
bottleneck@2.19.5: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
brace-expansion@1.1.13:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
brace-expansion@2.0.3:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
@@ -4932,9 +4936,9 @@ snapshots:
|
||||
|
||||
browserslist@4.28.1:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.11
|
||||
caniuse-lite: 1.0.30001781
|
||||
electron-to-chromium: 1.5.327
|
||||
baseline-browser-mapping: 2.10.12
|
||||
caniuse-lite: 1.0.30001782
|
||||
electron-to-chromium: 1.5.328
|
||||
node-releases: 2.0.36
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||
|
||||
@@ -4978,7 +4982,7 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001781: {}
|
||||
caniuse-lite@1.0.30001782: {}
|
||||
|
||||
chalk@2.4.1:
|
||||
dependencies:
|
||||
@@ -5096,7 +5100,7 @@ snapshots:
|
||||
|
||||
content-type@1.0.4: {}
|
||||
|
||||
conventional-changelog-angular@8.3.0:
|
||||
conventional-changelog-angular@8.3.1:
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
|
||||
@@ -5110,7 +5114,7 @@ snapshots:
|
||||
|
||||
conventional-commits-filter@5.0.0: {}
|
||||
|
||||
conventional-commits-parser@6.3.0:
|
||||
conventional-commits-parser@6.4.0:
|
||||
dependencies:
|
||||
'@simple-libs/stream-utils': 1.2.0
|
||||
meow: 13.2.0
|
||||
@@ -5263,7 +5267,7 @@ snapshots:
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
electron-to-chromium@1.5.327: {}
|
||||
electron-to-chromium@1.5.328: {}
|
||||
|
||||
emoji-regex@10.6.0: {}
|
||||
|
||||
@@ -6542,11 +6546,11 @@ snapshots:
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
brace-expansion: 1.1.13
|
||||
|
||||
minimatch@9.0.9:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
brace-expansion: 2.0.3
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@@ -6588,8 +6592,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@next/env': 16.1.7
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.10.11
|
||||
caniuse-lite: 1.0.30001781
|
||||
baseline-browser-mapping: 2.10.12
|
||||
caniuse-lite: 1.0.30001782
|
||||
postcss: 8.4.31
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
@@ -6850,6 +6854,8 @@ snapshots:
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
php@1.1.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.2: {}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ const config: GlobalConfig = {
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
prepareCmd:
|
||||
"pnpm build && node scripts/release.ts ${nextRelease.version}",
|
||||
"node scripts/release.ts ${nextRelease.version} && sed -i 's|image: git.demonkernel.io.vn/foodsurf/frontend:.*|image: git.demonkernel.io.vn/foodsurf/frontend:${nextRelease.version}|g' k8s/k8s.yaml",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ const zipPath = "release.zip";
|
||||
|
||||
console.log(`📦 Đang nén file cho version: ${version}...`);
|
||||
|
||||
execSync(`zip -r ${zipPath} out`, {
|
||||
execSync(`cd out && zip -r ../${zipPath} .`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user