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/.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..368f3df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +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: Security audit + run: npm audit --audit-level=high + + - 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..7257db9 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +fund=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/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 diff --git a/package-lock.json b/package-lock.json index 425516b..d4d33c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,8 @@ "isbot": "^5.1.31", "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", - "next": "16.1.1", + "next": "^16.1.6", + "next-themes": "^0.4.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", @@ -1132,9 +1133,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 +1149,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 +1165,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 +1181,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 +1197,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 +1213,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 +1229,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 +1245,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 +1261,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 +5634,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 +7184,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 +7203,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": { @@ -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 78e9dbd..763404e 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", @@ -33,7 +34,8 @@ "isbot": "^5.1.31", "js-cookie": "^3.0.5", "lucide-react": "^0.554.0", - "next": "16.1.1", + "next": "^16.1.6", + "next-themes": "^0.4.6", "react": "^19.2.3", "react-apexcharts": "^1.9.0", "react-day-picker": "^9.13.0", @@ -71,4 +73,4 @@ "Safari >= 13", "iOS >= 13" ] -} \ No newline at end of file +} 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 ( -
+
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 {Mọi thứ bạn cần để bắt đầu
++ Mọi thứ bạn cần để bắt đầu +
{feature.desc}
++ {feature.desc} +