Compare commits

...

24 Commits

Author SHA1 Message Date
TakahashiNguyen 2913fa0d73 fix: better ci and docker build (#20)
Release package / release (push) Successful in 28m17s
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #20
2026-03-31 07:52:42 +00:00
gitea-actions 423d4e3436 chore: release [ci skip] 2026-03-31 07:21:05 +00:00
TakahashiNguyen ddaa1ad873 fix: change from static execute to dynamic execute (#19)
Release package / release (push) Failing after 24m2s
Reviewed-on: #19
2026-03-31 07:16:53 +00:00
gitea-actions ee137f66f0 chore: release [ci skip] 2026-03-31 05:22:38 +00:00
TakahashiNguyen 9a488df385 fix: better release config (#18)
Release package / release (push) Successful in 10m28s
Reviewed-on: #18
2026-03-31 05:19:37 +00:00
TakahashiNguyen 14d5f570bd fix: Update k8s.yaml (#17)
Release package / release (push) Has started running
Reviewed-on: #17
2026-03-31 05:03:34 +00:00
gitea-actions e68cb026f1 chore: release [ci skip] 2026-03-30 08:31:15 +00:00
TakahashiNguyen 99de2362a0 fix: better release config (#15)
Release package / release (push) Successful in 16m28s
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #15
2026-03-30 08:24:28 +00:00
gitea-actions 9b72ad873e chore: release [ci skip] 2026-03-30 07:17:13 +00:00
TakahashiNguyen 893d34e01c fix: ci issue (#14)
Release package / release (push) Successful in 16m52s
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #14
2026-03-30 07:12:34 +00:00
gitea-actions c1912e5b90 chore: release [ci skip] 2026-03-30 06:46:06 +00:00
TakahashiNguyen 93b6221ff6 ci: better script (#13)
Release package / release (push) Failing after 5m27s
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #13
2026-03-30 06:42:11 +00:00
TakahashiNguyen deae7bcf64 fix(k8s): better yaml file (#12)
Release package / release (push) Failing after 4m48s
Release package / build-and-push (push) Has been skipped
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #12
2026-03-30 02:00:59 +00:00
gitea-actions 55ff138100 chore: release [ci skip] 2026-03-30 01:24:59 +00:00
TaNguyenThanhQuy 5c771b8c9c Merge pull request 'Đã merge toàn bộ features vào nhánh develop và không có conflic' (#11) from develop into main
Release package / release (push) Successful in 4m48s
Release package / build-and-push (push) Failing after 2m6s
Reviewed-on: #11
2026-03-30 01:21:40 +00:00
TakahashiNg 609aaf42df chore(k8s): removed unnecessary
Release package / release (push) Successful in 7m51s
Release package / build-and-push (push) Has been skipped
2026-03-30 01:13:32 +00:00
Thanh Quy - wolf 184635caf9 resolve conflict 2026-03-29 22:29:44 +07:00
Thanh Quy - wolf f5941b2e3b Update markdown files 2026-03-29 22:12:30 +07:00
gitea-actions aa7331d728 chore: release [ci skip] 2026-03-29 14:32:04 +00:00
TakahashiNguyen df8a6fc96f fix: better ci (#9)
Release package / release (push) Successful in 5m10s
Release package / build-and-push (push) Successful in 3m50s
Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #9
2026-03-29 14:28:13 +00:00
TakahashiNguyen 8d0247024d Merge pull request 'fix: better ci script' (#7) from dev-cicd into main
Release package / release (push) Failing after 4m38s
Release package / build-and-push (push) Has been skipped
Reviewed-on: #7
2026-03-29 14:15:26 +00:00
TakahashiNg 9ad66301de ci: better ci
Release package / release (push) Successful in 2m23s
Release package / build-and-push (push) Has been skipped
2026-03-29 14:12:07 +00:00
Thanh Quy - wolf 284183098e docs: update and create comprehensive markdown documentation
- Updated README.md with current features (Auth, Cart, Feed pages)
- Updated COMPONENTS.md with CartFab and all context providers (Auth, Cart, Menu)
- Created APP.md: detailed documentation of all routes and pages
- Created LIB.md: documentation of types, constants, and contexts
- Created LAYOUTS.md: header and footer component documentation
- Updated TODO.md: reflects completed implementations and future work

This provides agents with comprehensive documentation to understand the project structure and implementation details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 20:17:55 +07:00
Thanh Quy - wolf e2aeebd0e1 Created Page Feed 2026-03-29 20:05:53 +07:00
30 changed files with 2992 additions and 3041 deletions
+11
View File
@@ -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"
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"permissions": {
"allow": ["Bash(npx next build)"]
}
}
+29 -29
View File
@@ -4,7 +4,10 @@ on:
push:
branches:
- main
- dev-*
pull_request:
branches:
- main
jobs:
release:
@@ -14,8 +17,6 @@ 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: |
@@ -67,6 +68,9 @@ jobs:
- name: Format code
run: pnpm format
- name: Build application
run: pnpm build
- name: Release
env:
GITEA_USER: ${{ github.actor }}
@@ -80,31 +84,8 @@ jobs:
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Commit & Push Changes
if: gitea.ref == 'refs/heads/main'
run: |
if [ -n "$(git status --porcelain)" ]; then
git config --global user.name "gitea-actions"
git config --global user.email "actions-user@noreply.git.demonkernel.io.vn"
git remote set-url origin ssh://git@$TARGET_IP:222/FoodSurf/backend.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
@@ -112,19 +93,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: .
@@ -133,3 +115,21 @@ jobs:
git.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:latest
git.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.version }}
- name: Commit & Push Changes
if: gitea.ref == 'refs/heads/main'
run: |
if [ -n "$(git status --porcelain)" ]; then
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
+97 -21
View File
@@ -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
+53 -19
View File
@@ -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)
+153
View File
@@ -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 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>
);
}
+44
View File
@@ -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 */}
</>
);
}
+25
View File
@@ -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
View File
@@ -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
+1
View File
@@ -100,6 +100,7 @@ body {
color: var(--color-text-primary);
font-family: Arial, Helvetica, sans-serif;
scroll-behavior: smooth;
text-rendering: optimizeSpeed;
}
/* ============================================================
+1 -13
View File
@@ -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>
);
+155
View File
@@ -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
+34 -19
View File
@@ -1,32 +1,47 @@
# --- Giai đoạn 1: Build ---
FROM node:25-alpine AS builder
# --- Giai đoạn 1: Dependencies ---
FROM node:25-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Cài đặt pnpm
RUN npm install -g pnpm
WORKDIR /app
# Copy file định nghĩa package
# Copy file định nghĩa package để tận dụng cache của Docker
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Cài đặt dependencies (sử dụng --frozen-lockfile để đảm bảo đúng phiên bản)
RUN pnpm install --prod --frozen-lockfile
# Copy toàn bộ code
# --- Giai đoạn 2: Builder ---
FROM node:25-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build Next.js (Yêu cầu next.config.js có output: 'export')
RUN pnpm run build
# Tắt dữ liệu thu thập của Next.js trong quá trình build
ENV NEXT_TELEMETRY_DISABLED 1
# --- Giai đoạn 2: Run (Sản phẩm cuối) ---
FROM nginx:alpine
RUN npm run build
# Xóa file mặc định của nginx (tùy chọn nhưng nên làm)
RUN rm -rf /usr/share/nginx/html/*
# --- Giai đoạn 3: Runner (Sản phẩm cuối) ---
FROM node:25-alpine AS runner
WORKDIR /app
# Copy folder out từ giai đoạn builder
COPY --from=builder /app/out /usr/share/nginx/html
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
EXPOSE 80
# Tạo user để chạy app (bảo mật hơn chạy root)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
CMD ["nginx", "-g", "daemon off;"]
# Copy các file cần thiết từ builder
# Standalone chỉ copy code cần chạy, bỏ qua devDependencies và file thừa
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# Chạy bằng server.js được sinh ra bởi output: 'standalone'
CMD ["node", "server.js"]
+13 -10
View File
@@ -14,29 +14,32 @@ spec:
labels:
app: web
spec:
imagePullSecrets:
- name: gitea-registry-secret
containers:
- name: frontend-container
image: git.demonkernel.io.vn/foodsurf/frontend:latest
ports:
- containerPort: 3000
resources:
limits:
cpu: "50m"
memory: "32Mi"
cpu: "200m"
memory: "256Mi"
requests:
cpu: "10m"
memory: "16Mi"
cpu: "50m"
memory: "128Mi"
env:
- name: NODE_ENV
value: "production"
---
apiVersion: v1
kind: Service
metadata:
name: my-web-service
name: drinkool-frontend-svc
spec:
type: NodePort
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
port: 80
targetPort: 3000
nodePort: 30080
+515
View File
@@ -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
View File
@@ -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")
+40
View File
@@ -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: {
+8
View File
@@ -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
View File
@@ -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: "standalone",
images: {
unoptimized: true, // Thường cần thiết cho static export
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
},
],
},
};
+742 -2793
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "temp",
"version": "1.0.0-created-login-and-register-page.1",
"version": "1.0.8",
"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",
+126 -120
View File
@@ -20,6 +20,9 @@ importers:
next:
specifier: 16.1.7
version: 16.1.7(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
php:
specifier: ^1.1.0
version: 1.1.0
react:
specifier: 19.2.3
version: 19.2.3
@@ -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))
@@ -59,7 +59,7 @@ importers:
version: 9.39.4(jiti@2.6.1)
eslint-config-next:
specifier: 16.1.7
version: 16.1.7(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
version: 16.1.7(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
prettier:
specifier: ^3.8.1
version: 3.8.1
@@ -769,63 +769,63 @@ packages:
'@types/responselike@1.0.3':
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
'@typescript-eslint/eslint-plugin@8.57.2':
resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==}
'@typescript-eslint/eslint-plugin@8.58.0':
resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.57.2
'@typescript-eslint/parser': ^8.58.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/parser@8.57.2':
resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==}
'@typescript-eslint/parser@8.58.0':
resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/project-service@8.57.2':
resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==}
'@typescript-eslint/project-service@8.58.0':
resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/scope-manager@8.57.2':
resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==}
'@typescript-eslint/scope-manager@8.58.0':
resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.57.2':
resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==}
'@typescript-eslint/tsconfig-utils@8.58.0':
resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/type-utils@8.57.2':
resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==}
'@typescript-eslint/type-utils@8.58.0':
resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/types@8.57.2':
resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==}
'@typescript-eslint/types@8.58.0':
resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.57.2':
resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==}
'@typescript-eslint/typescript-estree@8.58.0':
resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/utils@8.57.2':
resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==}
'@typescript-eslint/utils@8.58.0':
resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/visitor-keys@8.57.2':
resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==}
'@typescript-eslint/visitor-keys@8.58.0':
resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -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.329:
resolution: {integrity: sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -2578,8 +2578,8 @@ packages:
resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==}
engines: {node: '>=8'}
minimatch@10.2.4:
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
minimatch@3.1.5:
@@ -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==}
@@ -3618,12 +3622,12 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
typescript-eslint@8.57.2:
resolution: {integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==}
typescript-eslint@8.58.0:
resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
typescript: '>=4.8.4 <6.1.0'
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
@@ -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
@@ -4524,7 +4528,7 @@ snapshots:
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.2.4
minimatch: 10.2.5
'@types/node@20.19.37':
dependencies:
@@ -4544,14 +4548,14 @@ snapshots:
dependencies:
'@types/node': 20.19.37
'@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.57.2
'@typescript-eslint/type-utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.57.2
'@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.58.0
'@typescript-eslint/type-utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.58.0
eslint: 9.39.4(jiti@2.6.1)
ignore: 7.0.5
natural-compare: 1.4.0
@@ -4560,41 +4564,41 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.57.2
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.57.2
'@typescript-eslint/scope-manager': 8.58.0
'@typescript-eslint/types': 8.58.0
'@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.58.0
debug: 4.4.3
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.57.2(typescript@5.9.3)':
'@typescript-eslint/project-service@8.58.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3)
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3)
'@typescript-eslint/types': 8.58.0
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.57.2':
'@typescript-eslint/scope-manager@8.58.0':
dependencies:
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/visitor-keys': 8.57.2
'@typescript-eslint/types': 8.58.0
'@typescript-eslint/visitor-keys': 8.58.0
'@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)':
'@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.58.0
'@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
debug: 4.4.3
eslint: 9.39.4(jiti@2.6.1)
ts-api-utils: 2.5.0(typescript@5.9.3)
@@ -4602,16 +4606,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.57.2': {}
'@typescript-eslint/types@8.58.0': {}
'@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.57.2(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3)
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/visitor-keys': 8.57.2
'@typescript-eslint/project-service': 8.58.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3)
'@typescript-eslint/types': 8.58.0
'@typescript-eslint/visitor-keys': 8.58.0
debug: 4.4.3
minimatch: 10.2.4
minimatch: 10.2.5
semver: 7.7.4
tinyglobby: 0.2.15
ts-api-utils: 2.5.0(typescript@5.9.3)
@@ -4619,20 +4623,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
'@typescript-eslint/utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.57.2
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.58.0
'@typescript-eslint/types': 8.58.0
'@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.57.2':
'@typescript-eslint/visitor-keys@8.58.0':
dependencies:
'@typescript-eslint/types': 8.57.2
'@typescript-eslint/types': 8.58.0
eslint-visitor-keys: 5.0.1
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -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.329
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.329: {}
emoji-regex@10.6.0: {}
@@ -5411,18 +5415,18 @@ snapshots:
escape-string-regexp@5.0.0: {}
eslint-config-next@16.1.7(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3):
eslint-config-next@16.1.7(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@next/eslint-plugin-next': 16.1.7
eslint: 9.39.4(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.6.1))
globals: 16.4.0
typescript-eslint: 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
typescript-eslint: 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
@@ -5450,22 +5454,22 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -5476,7 +5480,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.4(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -5488,7 +5492,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -6536,17 +6540,17 @@ snapshots:
mimic-response@2.1.0: {}
minimatch@10.2.4:
minimatch@10.2.5:
dependencies:
brace-expansion: 5.0.5
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: {}
@@ -7607,12 +7613,12 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
typescript-eslint@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3):
typescript-eslint@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
Binary file not shown.
+1 -2
View File
@@ -18,8 +18,7 @@ const config: GlobalConfig = {
[
"@semantic-release/exec",
{
prepareCmd:
"pnpm build && node scripts/release.ts ${nextRelease.version}",
prepareCmd: "node scripts/release.ts ${nextRelease.version}",
},
],
[
+44 -7
View File
@@ -1,4 +1,5 @@
import { execSync } from "node:child_process";
import fs from "node:fs";
const version = process.argv[2];
if (!version) {
@@ -6,14 +7,50 @@ if (!version) {
process.exit(1);
}
const zipPath = "release.zip";
const zipPath = `release-v${version}.zip`; // Thêm version vào tên file cho dễ quản lý
console.log(`📦 Đang nén file cho version: ${version}...`);
console.log(
`📦 Đang chuẩn bị đóng gói App Router Standalone cho version: ${version}...`,
);
execSync(`zip -r ${zipPath} out`, {
stdio: "inherit",
});
// Kiểm tra xem đã build chưa
if (!fs.existsSync(".next/standalone")) {
console.error(
"❌ Thư mục .next/standalone không tồn tại. Hãy chạy 'pnpm build' trước!",
);
process.exit(1);
}
console.log(`✅ Đã tạo file zip: ${zipPath}`);
/**
* Với Next.js Standalone, ta cần:
* 1. Nội dung trong .next/standalone (copy ra ngoài hoặc nén trực tiếp)
* 2. .next/static (phải nằm trong .next/static của gói release)
* 3. public (phải nằm trong public của gói release)
*/
execSync("pnpm format");
try {
// Tạo thư mục tạm để cấu trúc lại file trước khi nén
execSync("rm -rf temp_release && mkdir -p temp_release");
console.log("🚚 Đang sao chép các file standalone...");
execSync("cp -r .next/standalone/. temp_release/");
console.log("🚚 Đang sao chép static và public...");
execSync("mkdir -p temp_release/.next/static");
execSync("cp -r .next/static/. temp_release/.next/static/");
execSync("cp -r public temp_release/ 2>/dev/null || : "); // Bỏ qua nếu không có thư mục public
console.log("🗜️ Đang nén thành file zip...");
// Di chuyển vào temp_release để nén các file bên trong nó
execSync(`cd temp_release && zip -r ../${zipPath} .`, { stdio: "inherit" });
// Xóa thư mục tạm
execSync("rm -rf temp_release");
console.log(`✅ Đã tạo file thành công: ${zipPath}`);
execSync("pnpm format");
} catch (error) {
console.error("❌ Có lỗi xảy ra trong quá trình nén:", error.message);
process.exit(1);
}
+1 -1
View File
@@ -31,5 +31,5 @@
".next/dev/types/**/*.ts",
"types/**/*.d.ts"
],
"exclude": ["node_modules", "script", "*.ts"]
"exclude": ["node_modules", "scripts", "*.ts"]
}