From 7fb21e0604ec028f5297c57048ade2f7cfabd2bf Mon Sep 17 00:00:00 2001 From: Le Minh Tri Date: Sat, 14 Mar 2026 06:59:02 +0700 Subject: [PATCH 1/4] Delete: Remove unnecessary components. --- .dockerignore | 12 ---------- .env.local.example | 12 ---------- DOCKER.md | 56 ---------------------------------------------- Dockerfile | 45 ------------------------------------- Makefile | 20 ----------------- docker-compose.yml | 10 --------- 6 files changed, 155 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .env.local.example delete mode 100644 DOCKER.md delete mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 7be03b5..0000000 --- a/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -node_modules -.next -.git -.gitignore -README.md -PROJECT_STRUCTURE.md -.env*.local -.vscode -.DS_Store -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/.env.local.example b/.env.local.example deleted file mode 100644 index 916b316..0000000 --- a/.env.local.example +++ /dev/null @@ -1,12 +0,0 @@ -# Environment Variables Template -# Copy this file to .env.local and fill in your values - -# App -NEXT_PUBLIC_APP_URL=http://localhost:3000 - -# API -# API_URL= -# API_KEY= - -# Database -# DATABASE_URL= diff --git a/DOCKER.md b/DOCKER.md deleted file mode 100644 index 0e5ec99..0000000 --- a/DOCKER.md +++ /dev/null @@ -1,56 +0,0 @@ -# Docker Setup - -## 🐳 Quick Start - -### Build và chạy -```bash -# Build image -make build - -# Start container -make up - -# Xem logs -make logs - -# Stop container -make down - -# Restart -make restart - -# Clean up -make clean -``` - -### Hoặc dùng docker compose trực tiếp -```bash -# Build -docker compose build - -# Start -docker compose up -d - -# Stop -docker compose down -``` - -## 📝 Files - -- `Dockerfile` - Multi-stage build cho production -- `docker-compose.yml` - Orchestration config -- `.dockerignore` - Exclude files khỏi build context -- `Makefile` - Shortcuts cho Docker commands - -## 🚀 Production - -App chạy trên port **3000** - -Access: http://localhost:3000 - -## 💡 Notes - -- Image size tối ưu với multi-stage build -- Standalone output cho Next.js -- Non-root user (nextjs:nodejs) -- Alpine Linux base image diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index d6d4aa3..0000000 --- a/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM node:20-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -RUN apk add --no-cache libc6-compat -WORKDIR /app - -COPY package.json package-lock.json* ./ -RUN npm ci - -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -RUN npm run build - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV=production - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public - -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -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 -ENV HOSTNAME="0.0.0.0" - -CMD ["node", "server.js"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 13e3308..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -.PHONY: build up down logs restart clean - -build: - docker compose build - -up: - docker compose up -d - -down: - docker compose down - -logs: - docker compose logs -f - -restart: - docker compose restart - -clean: - docker compose down -v - docker system prune -f diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 631bf4b..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - app: - build: - context: . - dockerfile: Dockerfile - ports: - - "3000:3000" - environment: - - NODE_ENV=production - restart: unless-stopped From c9e23d9f6710cbfd6896147e86c4a8e687ad7543 Mon Sep 17 00:00:00 2001 From: Le Minh Tri Date: Sat, 14 Mar 2026 07:15:15 +0700 Subject: [PATCH 2/4] fix: align project scripts and lint errors for CI compatibility - Rename typecheck to type-check with --noEmit to match CI workflow - Add placeholder test script for CI test job - Fix conditional React.useId() call in Checkbox (rules-of-hooks) - Replace useState+useEffect with useSyncExternalStore in useMounted - Add CI workflow, .npmrc, .nvmrc, and PR template --- .github/PULL_REQUEST_TEMPLATE.md | 22 ++++++++ .github/workflows/ci.yml | 81 +++++++++++++++++++++++++++ .npmrc | 2 + .nvmrc | 1 + package.json | 3 +- src/components/common/ui/checkbox.tsx | 3 +- src/hooks/use-mounted.ts | 16 +++--- 7 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 .npmrc create mode 100644 .nvmrc diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b7fa6fe --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## 📝 Description + + +## 🔗 Related Issue + +Closes # + +## 🎯 Type of Change +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] 🔨 Refactoring +- [ ] 📦 library update +- [ ] 📝 Documentation +- [ ] 🎨 UI/UX + +## ✅ Checklist +- [ ] Code đã được test locally +- [ ] Không có lỗi lint +- [ ] Đã cập nhật documentation (nếu cần) + +## 📸 Screenshots (nếu có) + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9baefbb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + lint-and-typecheck: + name: Lint & Type Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Type check + run: npm run type-check + + test: + name: Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + build: + name: Build + runs-on: ubuntu-latest + needs: [lint-and-typecheck, test] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + env: + NODE_ENV: production + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-output + path: .next + retention-days: 7 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2dd5681 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +fund=false +audit=false \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..b087cc9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.13.0 \ No newline at end of file diff --git a/package.json b/package.json index 78e9dbd..91e0f1c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "next build", "start": "next start", "lint": "eslint", - "typecheck": "tsc" + "type-check": "tsc --noEmit", + "test": "echo \"No tests configured yet\" && exit 0" }, "dependencies": { "@hookform/resolvers": "^5.2.2", diff --git a/src/components/common/ui/checkbox.tsx b/src/components/common/ui/checkbox.tsx index 3f3c4bf..c2ab284 100644 --- a/src/components/common/ui/checkbox.tsx +++ b/src/components/common/ui/checkbox.tsx @@ -12,7 +12,8 @@ interface CheckboxProps extends Omit< const Checkbox = React.forwardRef( ({ className, label, id, onCheckedChange, ...props }, ref) => { - const checkboxId = id || React.useId(); + const generatedId = React.useId(); + const checkboxId = id || generatedId; const handleChange = (e: React.ChangeEvent) => { onCheckedChange?.(e.target.checked); diff --git a/src/hooks/use-mounted.ts b/src/hooks/use-mounted.ts index 246085a..4442f8d 100644 --- a/src/hooks/use-mounted.ts +++ b/src/hooks/use-mounted.ts @@ -1,17 +1,17 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useSyncExternalStore } from 'react'; + +const emptySubscribe = () => () => {}; /** * Hook to check if component is mounted * Useful for preventing hydration mismatches */ export function useMounted(): boolean { - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - - return mounted; + return useSyncExternalStore( + emptySubscribe, + () => true, + () => false, + ); } From 7c364ef85b3ad01dbe71d33a12f4343d623c5a2f Mon Sep 17 00:00:00 2001 From: Le Minh Tri Date: Sat, 14 Mar 2026 07:18:45 +0700 Subject: [PATCH 3/4] fix(security): enable npm audit and fix high severity vulnerabilities - Remove audit=false from .npmrc to re-enable security checks - Add npm audit step to CI pipeline (audit-level=high) - Update next 16.1.1 -> 16.1.6 (fixes 3 high severity CVEs) - Update flatted to fix unbounded recursion DoS --- .github/workflows/ci.yml | 3 ++ .npmrc | 3 +- package-lock.json | 86 ++++++++++++++++++++-------------------- package.json | 4 +- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9baefbb..368f3df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Security audit + run: npm audit --audit-level=high + - name: Run ESLint run: npm run lint diff --git a/.npmrc b/.npmrc index 2dd5681..7257db9 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ -fund=false -audit=false \ No newline at end of file +fund=false \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 425516b..563fd4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "isbot": "^5.1.31", "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", - "next": "16.1.1", + "next": "^16.1.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", @@ -1132,9 +1132,9 @@ } }, "node_modules/@next/env": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz", - "integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1148,9 +1148,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz", - "integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -1164,9 +1164,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz", - "integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -1180,9 +1180,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz", - "integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -1196,9 +1196,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz", - "integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -1212,9 +1212,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz", - "integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -1228,9 +1228,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz", - "integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -1244,9 +1244,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz", - "integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -1260,9 +1260,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz", - "integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -5633,9 +5633,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -7183,12 +7183,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz", - "integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "16.1.1", + "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", @@ -7202,14 +7202,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.1", - "@next/swc-darwin-x64": "16.1.1", - "@next/swc-linux-arm64-gnu": "16.1.1", - "@next/swc-linux-arm64-musl": "16.1.1", - "@next/swc-linux-x64-gnu": "16.1.1", - "@next/swc-linux-x64-musl": "16.1.1", - "@next/swc-win32-arm64-msvc": "16.1.1", - "@next/swc-win32-x64-msvc": "16.1.1", + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { diff --git a/package.json b/package.json index 91e0f1c..7465e27 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "isbot": "^5.1.31", "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", - "next": "16.1.1", + "next": "^16.1.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", @@ -72,4 +72,4 @@ "Safari >= 13", "iOS >= 13" ] -} \ No newline at end of file +} From 355abb86c52c0f99020027b1515eff1d44b0cc38 Mon Sep 17 00:00:00 2001 From: ANDEV09 Date: Sun, 15 Mar 2026 03:57:09 +0700 Subject: [PATCH 4/4] feat: add dark mode theme system with smooth transition --- package-lock.json | 11 ++ package.json | 1 + src/app/page.tsx | 72 ++++++----- src/components/common/theme-provider.tsx | 8 ++ src/components/common/theme-toggle.tsx | 23 ++++ src/components/common/ui/dropdown-menu.tsx | 138 +++++++++++++++++++++ src/components/common/ui/index.ts | 15 ++- src/provider/index.tsx | 20 ++- 8 files changed, 250 insertions(+), 38 deletions(-) create mode 100644 src/components/common/theme-provider.tsx create mode 100644 src/components/common/theme-toggle.tsx create mode 100644 src/components/common/ui/dropdown-menu.tsx diff --git a/package-lock.json b/package-lock.json index 563fd4d..d4d33c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", "next": "^16.1.6", + "next-themes": "^0.4.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", @@ -7235,6 +7236,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", diff --git a/package.json b/package.json index 7465e27..763404e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", "next": "^16.1.6", + "next-themes": "^0.4.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", diff --git a/src/app/page.tsx b/src/app/page.tsx index ec89aa0..5a9ee4a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,45 +1,49 @@ import Link from "next/link"; import Image from "next/image"; import { Button } from "@/components/common/ui/button"; +import { ThemeToggle } from "@/components/common/theme-toggle"; export default function HomePage(): React.ReactElement { return ( -
+
{/* Header */} -
+
N
- + NextApp
- +
+ + +
@@ -48,21 +52,21 @@ export default function HomePage(): React.ReactElement {
-
+
Mới ra mắt
-

+

Xây dựng sản phẩm
nhanh hơn, tốt hơn

-

+

Nền tảng giúp bạn tập trung vào những gì quan trọng nhất. Đơn giản, hiệu quả và dễ sử dụng cho mọi dự án.

@@ -73,15 +77,15 @@ export default function HomePage(): React.ReactElement {
-
-
+
+
Dashboard preview {/* Features */} -
+
-

+

Tính năng nổi bật

-

Mọi thứ bạn cần để bắt đầu

+

+ Mọi thứ bạn cần để bắt đầu +

{[ @@ -127,17 +133,19 @@ export default function HomePage(): React.ReactElement { ].map((feature, i) => (
{feature.icon}
-

+

{feature.title}

-

{feature.desc}

+

+ {feature.desc} +

))}
@@ -155,13 +163,15 @@ export default function HomePage(): React.ReactElement { ].map((stat, i) => (
{stat.icon}
-
+
{stat.value}
-
{stat.label}
+
+ {stat.label} +
))}
@@ -169,7 +179,7 @@ export default function HomePage(): React.ReactElement {
{/* CTA */} -
+

Sẵn sàng bắt đầu? @@ -185,7 +195,7 @@ export default function HomePage(): React.ReactElement {

{/* Footer */} -