feat(ci): add CI/CD pipeline configuration and tests
This commit is contained in:
65
.gitea/workflows/ci.yml
Normal file
65
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,65 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
name: Lint, Test & Build
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run TypeScript type check
|
||||
run: bun run type-check
|
||||
|
||||
- name: Run ESLint
|
||||
run: bun run lint
|
||||
|
||||
- name: Check Prettier formatting
|
||||
run: bun run format:check
|
||||
|
||||
- name: Run tests
|
||||
run: bun run test:ci
|
||||
|
||||
- name: Build application
|
||||
run: bun run build
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-files
|
||||
path: .next/
|
||||
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run security audit
|
||||
run: bun audit
|
||||
|
||||
- name: Run dependency check
|
||||
run: bunx audit-ci --moderate
|
43
.gitignore
vendored
43
.gitignore
vendored
@ -39,3 +39,46 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# IDEs and editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
storybook-static/
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
lerna-debug.log*
|
||||
|
31
.prettierignore
Normal file
31
.prettierignore
Normal file
@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Build outputs
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Environment variables
|
||||
.env*
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
bun.lock
|
21
.prettierrc.json
Normal file
21
.prettierrc.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss", "@trivago/prettier-plugin-sort-imports"],
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"importOrder": ["^react", "^next", "^@/(.*)$", "^[./]"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true
|
||||
}
|
228
README.md
228
README.md
@ -1,36 +1,220 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# Next.js Template with App Router
|
||||
|
||||
## Getting Started
|
||||
A comprehensive, production-ready Next.js template featuring modern development tools, strict linting, testing setup, and CI/CD pipeline.
|
||||
|
||||
First, run the development server:
|
||||
## 🚀 Features
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
- **Next.js 15** with App Router
|
||||
- **TypeScript** with strict configuration
|
||||
- **Tailwind CSS** for styling
|
||||
- **ESLint** with comprehensive rules
|
||||
- **Prettier** for code formatting
|
||||
- **Husky** for Git hooks
|
||||
- **lint-staged** for pre-commit checks
|
||||
- **Jest** and **React Testing Library** for testing
|
||||
- **Commitizen** for conventional commits
|
||||
- **Gitea Actions** CI/CD pipeline
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```treeview
|
||||
├── .gitea/
|
||||
│ └── workflows/ # Gitea Actions CI/CD
|
||||
├── src/
|
||||
│ ├── app/ # Next.js App Router pages
|
||||
│ ├── components/ # Reusable React components
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── lib/ # Utility libraries
|
||||
│ ├── types/ # TypeScript type definitions
|
||||
│ └── utils/ # Utility functions
|
||||
├── public/ # Static assets
|
||||
├── __tests__/ # Test files
|
||||
├── .eslintrc.js # ESLint configuration
|
||||
├── .prettierrc.json # Prettier configuration
|
||||
├── jest.config.js # Jest configuration
|
||||
└── tsconfig.json # TypeScript configuration
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
## 🛠️ Getting Started
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
### Prerequisites
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
- Bun (latest version recommended)
|
||||
- Node.js 18.x or higher (for compatibility)
|
||||
|
||||
## Learn More
|
||||
### Installation
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
1. **Clone the repository:**
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
```bash
|
||||
git clone <your-repo-url>
|
||||
cd next-template-app-router
|
||||
```
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
2. **Install dependencies:**
|
||||
|
||||
## Deploy on Vercel
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
3. **Set up Git hooks:**
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
```bash
|
||||
bun run prepare
|
||||
```
|
||||
|
||||
4. **Start the development server:**
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) to view the application.
|
||||
|
||||
## 📝 Available Scripts
|
||||
|
||||
| Script | Description |
|
||||
| ---------------------- | --------------------------------------- |
|
||||
| `bun run dev` | Start development server with Turbopack |
|
||||
| `bun run build` | Build the application for production |
|
||||
| `bun run start` | Start the production server |
|
||||
| `bun run lint` | Run ESLint |
|
||||
| `bun run lint:fix` | Run ESLint and fix issues |
|
||||
| `bun run format` | Format code with Prettier |
|
||||
| `bun run format:check` | Check code formatting |
|
||||
| `bun run type-check` | Run TypeScript type checking |
|
||||
| `bun run test` | Run tests |
|
||||
| `bun run test:watch` | Run tests in watch mode |
|
||||
| `bun run test:ci` | Run tests for CI |
|
||||
| `bun run commit` | Make a conventional commit |
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### ESLint
|
||||
|
||||
The project uses a comprehensive ESLint configuration with:
|
||||
|
||||
- Next.js specific rules
|
||||
- TypeScript support
|
||||
- React and React Hooks rules
|
||||
- Import sorting and organization
|
||||
- Accessibility rules
|
||||
- Testing library rules
|
||||
|
||||
### Prettier
|
||||
|
||||
Code formatting is handled by Prettier with:
|
||||
|
||||
- Consistent code style
|
||||
- Tailwind CSS class sorting
|
||||
- TypeScript support
|
||||
|
||||
### TypeScript
|
||||
|
||||
Strict TypeScript configuration with:
|
||||
|
||||
- Strict mode enabled
|
||||
- No unchecked indexed access
|
||||
- Exact optional property types
|
||||
- Path mapping for clean imports
|
||||
|
||||
### Testing
|
||||
|
||||
Testing setup includes:
|
||||
|
||||
- Jest for unit testing
|
||||
- React Testing Library for component testing
|
||||
- Coverage reporting
|
||||
- Mock utilities for common browser APIs
|
||||
|
||||
## 🔄 CI/CD Pipeline
|
||||
|
||||
The Gitea Actions workflow includes:
|
||||
|
||||
1. **Lint and Test Job:**
|
||||
|
||||
- Type checking
|
||||
- ESLint validation
|
||||
- Prettier formatting check
|
||||
- Unit tests with coverage
|
||||
- Build verification
|
||||
|
||||
2. **Security Audit:**
|
||||
|
||||
- Dependency vulnerability scanning
|
||||
- Security audit reporting
|
||||
|
||||
## 📊 Code Quality
|
||||
|
||||
### Pre-commit Hooks
|
||||
|
||||
- **ESLint**: Catches code issues
|
||||
- **Prettier**: Ensures consistent formatting
|
||||
- **Type checking**: Validates TypeScript
|
||||
|
||||
### Conventional Commits
|
||||
|
||||
Use `bun run commit` to create conventional commits:
|
||||
|
||||
- `feat`: New features
|
||||
- `fix`: Bug fixes
|
||||
- `docs`: Documentation changes
|
||||
- `style`: Code style changes
|
||||
- `refactor`: Code refactoring
|
||||
- `test`: Test changes
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
### Coverage Requirements
|
||||
|
||||
- Minimum 70% coverage for branches, functions, lines, and statements
|
||||
- Coverage reports are generated in CI/CD
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
### Path Aliases
|
||||
|
||||
The project includes TypeScript path aliases:
|
||||
|
||||
- `@/*` → `./src/*`
|
||||
- `@/components/*` → `./src/components/*`
|
||||
- `@/lib/*` → `./src/lib/*`
|
||||
- `@/utils/*` → `./src/utils/*`
|
||||
- `@/hooks/*` → `./src/hooks/*`
|
||||
- `@/types/*` → `./src/types/*`
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env.local` file for local environment variables:
|
||||
|
||||
```bash
|
||||
# Example environment variables
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
DATABASE_URL=your-database-url
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/new-feature`
|
||||
3. Make your changes
|
||||
4. Run tests: `bun test`
|
||||
5. Commit changes: `bun run commit`
|
||||
6. Push to the branch: `git push origin feature/new-feature`
|
||||
7. Submit a pull request
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs)
|
||||
- [Bun Documentation](https://bun.sh/docs)
|
||||
- [TypeScript Documentation](https://www.typescriptlang.org/docs)
|
||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
||||
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
|
||||
- [Gitea Documentation](https://docs.gitea.io/)
|
||||
|
||||
---
|
||||
|
||||
**Happy coding! 🎉**
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
import js from '@eslint/js';
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||
import typescriptParser from '@typescript-eslint/parser';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import jestDom from 'eslint-plugin-jest-dom';
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
import react from 'eslint-plugin-react';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import testingLibrary from 'eslint-plugin-testing-library';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@ -10,7 +20,113 @@ const compat = new FlatCompat({
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
js.configs.recommended,
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'jsx-a11y': jsxA11y,
|
||||
import: importPlugin,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// TypeScript specific rules
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/prefer-as-const': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
|
||||
// React specific rules
|
||||
'react/react-in-jsx-scope': 'off', // Not needed in Next.js
|
||||
'react/prop-types': 'off', // Using TypeScript
|
||||
'react/jsx-uses-react': 'off', // Not needed in Next.js
|
||||
'react/jsx-uses-vars': 'error',
|
||||
'react/jsx-key': 'error',
|
||||
'react/jsx-no-duplicate-props': 'error',
|
||||
'react/jsx-no-undef': 'error',
|
||||
'react/no-children-prop': 'error',
|
||||
'react/no-danger-with-children': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
'react/no-direct-mutation-state': 'error',
|
||||
'react/no-find-dom-node': 'error',
|
||||
'react/no-is-mounted': 'error',
|
||||
'react/no-render-return-value': 'error',
|
||||
'react/no-string-refs': 'error',
|
||||
'react/no-unescaped-entities': 'error',
|
||||
'react/no-unknown-property': 'error',
|
||||
'react/require-render-return': 'error',
|
||||
'react/self-closing-comp': 'error',
|
||||
|
||||
// React Hooks rules
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
|
||||
// Import rules
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index'],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-unresolved': 'error',
|
||||
'import/no-cycle': 'error',
|
||||
|
||||
// General rules
|
||||
'no-console': 'warn',
|
||||
'no-debugger': 'error',
|
||||
'no-alert': 'error',
|
||||
'no-var': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/__tests__/**/*', '**/*.{test,spec}.{js,jsx,ts,tsx}'],
|
||||
plugins: {
|
||||
'testing-library': testingLibrary,
|
||||
'jest-dom': jestDom,
|
||||
},
|
||||
rules: {
|
||||
...testingLibrary.configs.react.rules,
|
||||
...jestDom.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
prettier, // Must be last to override other configs
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
|
38
jest.config.js
Normal file
38
jest.config.js
Normal file
@ -0,0 +1,38 @@
|
||||
const nextJest = require('next/jest');
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
// Provide the path to your Next.js app to load next.config.js and .env files
|
||||
dir: './',
|
||||
});
|
||||
|
||||
// Add any custom config to be passed to Jest
|
||||
const customJestConfig = {
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
testEnvironment: 'jsdom',
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@/components/(.*)$': '<rootDir>/src/components/$1',
|
||||
'^@/lib/(.*)$': '<rootDir>/src/lib/$1',
|
||||
'^@/utils/(.*)$': '<rootDir>/src/utils/$1',
|
||||
'^@/hooks/(.*)$': '<rootDir>/src/hooks/$1',
|
||||
'^@/types/(.*)$': '<rootDir>/src/types/$1',
|
||||
},
|
||||
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts', '!src/**/index.ts'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 70,
|
||||
lines: 70,
|
||||
statements: 70,
|
||||
},
|
||||
},
|
||||
testMatch: ['**/__tests__/**/*.(js|jsx|ts|tsx)', '**/*.(test|spec).(js|jsx|ts|tsx)'],
|
||||
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/', '<rootDir>/e2e/'],
|
||||
transform: {
|
||||
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
|
||||
},
|
||||
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
|
||||
};
|
||||
|
||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
||||
module.exports = createJestConfig(customJestConfig);
|
33
jest.setup.js
Normal file
33
jest.setup.js
Normal file
@ -0,0 +1,33 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
// Optional: configure or set up a testing framework before each test
|
||||
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
|
||||
|
||||
// Global test utilities can be added here
|
||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock IntersectionObserver
|
||||
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import type { NextConfig } from "next";
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
|
59
package.json
59
package.json
@ -6,7 +6,19 @@
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:ci": "jest --ci --coverage --watchAll=false",
|
||||
"prepare": "husky",
|
||||
"commit": "cz",
|
||||
"install": "bun install",
|
||||
"add": "bun add",
|
||||
"remove": "bun remove"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
@ -14,14 +26,51 @@
|
||||
"next": "15.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"audit-ci": "^7.1.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest-dom": "^5.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-testing-library": "^6.2.2",
|
||||
"husky": "^9.1.6",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"**/*.{json,css,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
plugins: ['@tailwindcss/postcss'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
25
src/app/__tests__/page.test.tsx
Normal file
25
src/app/__tests__/page.test.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import Home from '../page';
|
||||
|
||||
describe('Home Page', () => {
|
||||
it('renders the Next.js logo', () => {
|
||||
render(<Home />);
|
||||
|
||||
const logo = screen.getByAltText('Next.js logo');
|
||||
expect(logo).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("contains the 'Get started' text", () => {
|
||||
render(<Home />);
|
||||
|
||||
expect(screen.getByText(/get started/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has the Vercel logomark', () => {
|
||||
render(<Home />);
|
||||
|
||||
const vercelLogo = screen.getByAltText('Vercel logomark');
|
||||
expect(vercelLogo).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
|
@ -1,20 +1,21 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
variable: '--font-geist-sans',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
variable: '--font-geist-mono',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@ -23,12 +24,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
<html lang='en'>
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
@ -1,100 +1,80 @@
|
||||
import Image from "next/image";
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<div className='grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]'>
|
||||
<main className='flex flex-col gap-[32px] row-start-2 items-center sm:items-start'>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
className='dark:invert'
|
||||
src='/next.svg'
|
||||
alt='Next.js logo'
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
<ol className='list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]'>
|
||||
<li className='mb-2 tracking-[-.01em]'>
|
||||
Get started by editing{' '}
|
||||
<code className='bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold'>
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
<li className='tracking-[-.01em]'>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<div className='flex gap-4 items-center flex-col sm:flex-row'>
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto'
|
||||
href='https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
className='dark:invert'
|
||||
src='/vercel.svg'
|
||||
alt='Vercel logomark'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]'
|
||||
href='https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<footer className='row-start-3 flex gap-[24px] flex-wrap items-center justify-center'>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='flex items-center gap-2 hover:underline hover:underline-offset-4'
|
||||
href='https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<Image aria-hidden src='/file.svg' alt='File icon' width={16} height={16} />
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='flex items-center gap-2 hover:underline hover:underline-offset-4'
|
||||
href='https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<Image aria-hidden src='/window.svg' alt='Window icon' width={16} height={16} />
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='flex items-center gap-2 hover:underline hover:underline-offset-4'
|
||||
href='https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<Image aria-hidden src='/globe.svg' alt='Globe icon' width={16} height={16} />
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
|
9
src/types/jest.d.ts
vendored
Normal file
9
src/types/jest.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
declare global {
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toBeInTheDocument(): R;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,13 +13,23 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
"@/components/*": ["./src/components/*"],
|
||||
"@/lib/*": ["./src/lib/*"],
|
||||
"@/utils/*": ["./src/utils/*"],
|
||||
"@/hooks/*": ["./src/hooks/*"],
|
||||
"@/types/*": ["./src/types/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
Reference in New Issue
Block a user