Add authentication, testing, CI/CD, and security features - Implement login page with email/password authentication
This commit is contained in:
parent
20b0251259
commit
375d75fe44
16
.dockerignore
Normal file
16
.dockerignore
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
.env.development
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
README.md
|
||||||
11
.env.example
Normal file
11
.env.example
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Backend API Configuration
|
||||||
|
VITE_BACKEND_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
VITE_ENV=development
|
||||||
|
|
||||||
|
# Optional: Analytics
|
||||||
|
# VITE_ANALYTICS_ID=
|
||||||
|
|
||||||
|
# Optional: Sentry Error Tracking
|
||||||
|
# VITE_SENTRY_DSN=
|
||||||
11
.env.production.example
Normal file
11
.env.production.example
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Backend API Configuration
|
||||||
|
VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
VITE_ENV=production
|
||||||
|
|
||||||
|
# Optional: Analytics
|
||||||
|
# VITE_ANALYTICS_ID=your-analytics-id
|
||||||
|
|
||||||
|
# Optional: Sentry Error Tracking
|
||||||
|
# VITE_SENTRY_DSN=your-sentry-dsn
|
||||||
85
.github/workflows/ci.yml
vendored
Normal file
85
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test & Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x, 20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Run type check
|
||||||
|
run: npm run type-check
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test:run
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
run: npm run test:coverage
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
files: ./coverage/coverage-final.json
|
||||||
|
flags: unittests
|
||||||
|
name: codecov-umbrella
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
- name: Build application
|
||||||
|
run: npm run build
|
||||||
|
env:
|
||||||
|
VITE_BACKEND_API_URL: ${{ secrets.VITE_BACKEND_API_URL }}
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ matrix.node-version }}
|
||||||
|
path: dist/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
security:
|
||||||
|
name: Security Audit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
|
||||||
|
- name: Run npm audit
|
||||||
|
run: npm audit --audit-level=moderate
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Run Snyk security scan
|
||||||
|
uses: snyk/actions/node@master
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
67
.github/workflows/deploy.yml
vendored
Normal file
67
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Netlify/Vercel
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test:run
|
||||||
|
|
||||||
|
- name: Build for production
|
||||||
|
run: npm run build:prod
|
||||||
|
env:
|
||||||
|
VITE_BACKEND_API_URL: ${{ secrets.VITE_BACKEND_API_URL_PROD }}
|
||||||
|
VITE_ENV: production
|
||||||
|
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
|
||||||
|
|
||||||
|
# Option 1: Deploy to Netlify
|
||||||
|
- name: Deploy to Netlify
|
||||||
|
uses: nwtgck/actions-netlify@v3.0
|
||||||
|
with:
|
||||||
|
publish-dir: './dist'
|
||||||
|
production-branch: main
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
deploy-message: "Deploy from GitHub Actions"
|
||||||
|
enable-pull-request-comment: true
|
||||||
|
enable-commit-comment: true
|
||||||
|
env:
|
||||||
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
# Option 2: Deploy to Vercel (comment out Netlify if using this)
|
||||||
|
# - name: Deploy to Vercel
|
||||||
|
# uses: amondnet/vercel-action@v25
|
||||||
|
# with:
|
||||||
|
# vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
# vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
# vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
# vercel-args: '--prod'
|
||||||
|
# working-directory: ./
|
||||||
|
|
||||||
|
- name: Notify deployment success
|
||||||
|
if: success()
|
||||||
|
run: echo "Deployment successful!"
|
||||||
|
|
||||||
|
- name: Notify deployment failure
|
||||||
|
if: failure()
|
||||||
|
run: echo "Deployment failed!"
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -12,6 +12,12 @@ dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
.env.development
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
|
||||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Build stage
|
||||||
|
FROM node:18-alpine as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build:prod
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy built assets from build stage
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx configuration
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Expose port 80
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
|
||||||
|
|
||||||
|
# Start nginx
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
296
README.md
296
README.md
|
|
@ -1,73 +1,251 @@
|
||||||
# React + TypeScript + Vite
|
# Yaltopia Ticket Admin
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
Admin dashboard for Yaltopia Ticket management system built with React, TypeScript, and Vite.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
## Features
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
- User Management
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
- Analytics Dashboard
|
||||||
|
- Security Monitoring
|
||||||
|
- System Health Monitoring
|
||||||
|
- Audit Logs
|
||||||
|
- Announcements Management
|
||||||
|
- Maintenance Mode
|
||||||
|
- API Key Management
|
||||||
|
|
||||||
## React Compiler
|
## Tech Stack
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
- React 19
|
||||||
|
- TypeScript
|
||||||
|
- Vite
|
||||||
|
- TanStack Query (React Query)
|
||||||
|
- React Router v7
|
||||||
|
- Tailwind CSS
|
||||||
|
- Radix UI Components
|
||||||
|
- Recharts for data visualization
|
||||||
|
- Axios for API calls
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
## Prerequisites
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
- Node.js 18+
|
||||||
|
- npm or yarn
|
||||||
|
|
||||||
```js
|
## Getting Started
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
### 1. Clone the repository
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
```bash
|
||||||
],
|
git clone <repository-url>
|
||||||
languageOptions: {
|
cd yaltopia-ticket-admin
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
### 2. Install dependencies
|
||||||
|
|
||||||
```js
|
```bash
|
||||||
// eslint.config.js
|
npm install
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3. Environment Configuration
|
||||||
|
|
||||||
|
Copy the example environment file and configure it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env` and set your API URL:
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_BACKEND_API_URL=http://localhost:3000/api/v1
|
||||||
|
VITE_ENV=development
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run development server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at `http://localhost:5173`
|
||||||
|
|
||||||
|
## Building for Production
|
||||||
|
|
||||||
|
### 1. Configure production environment
|
||||||
|
|
||||||
|
Copy the production environment example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env.production` with your production API URL:
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1
|
||||||
|
VITE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Build the application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
The production build will be in the `dist` directory.
|
||||||
|
|
||||||
|
### 3. Preview production build locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Static Hosting (Netlify, Vercel, etc.)
|
||||||
|
|
||||||
|
1. Build the application: `npm run build:prod`
|
||||||
|
2. Deploy the `dist` directory
|
||||||
|
3. Configure environment variables in your hosting platform
|
||||||
|
4. Set up redirects for SPA routing (see below)
|
||||||
|
|
||||||
|
### SPA Routing Configuration
|
||||||
|
|
||||||
|
For proper routing, add a redirect rule:
|
||||||
|
|
||||||
|
**Netlify** (`netlify.toml`):
|
||||||
|
```toml
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vercel** (`vercel.json`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
Create a `Dockerfile`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:18-alpine as build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build:prod
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `nginx.conf`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t yaltopia-admin .
|
||||||
|
docker run -p 80:80 yaltopia-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Required | Default |
|
||||||
|
|----------|-------------|----------|---------|
|
||||||
|
| `VITE_BACKEND_API_URL` | Backend API base URL | Yes | `http://localhost:3000/api/v1` |
|
||||||
|
| `VITE_ENV` | Environment name | No | `development` |
|
||||||
|
| `VITE_ANALYTICS_ID` | Analytics tracking ID | No | - |
|
||||||
|
| `VITE_SENTRY_DSN` | Sentry error tracking DSN | No | - |
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- `npm run dev` - Start development server
|
||||||
|
- `npm run build` - Build for production
|
||||||
|
- `npm run build:prod` - Build with production environment
|
||||||
|
- `npm run preview` - Preview production build
|
||||||
|
- `npm run lint` - Run ESLint
|
||||||
|
- `npm run lint:fix` - Fix ESLint errors
|
||||||
|
- `npm run type-check` - Run TypeScript type checking
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/ # App configuration (query client)
|
||||||
|
├── assets/ # Static assets
|
||||||
|
├── components/ # Reusable UI components
|
||||||
|
│ └── ui/ # Shadcn UI components
|
||||||
|
├── layouts/ # Layout components
|
||||||
|
├── lib/ # Utilities and API client
|
||||||
|
├── pages/ # Page components
|
||||||
|
│ ├── admin/ # Admin pages
|
||||||
|
│ ├── dashboard/ # Dashboard pages
|
||||||
|
│ └── ...
|
||||||
|
├── App.tsx # Main app component
|
||||||
|
├── main.tsx # App entry point
|
||||||
|
└── index.css # Global styles
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
|
||||||
|
- JWT tokens stored in localStorage
|
||||||
|
- Token automatically attached to API requests
|
||||||
|
- Automatic redirect to login on 401 errors
|
||||||
|
- Error handling for common HTTP status codes
|
||||||
|
|
||||||
|
### Production Recommendations
|
||||||
|
|
||||||
|
1. **Use httpOnly cookies** instead of localStorage for tokens
|
||||||
|
2. **Implement HTTPS** - Never deploy without SSL/TLS
|
||||||
|
3. **Add security headers** - CSP, HSTS, X-Frame-Options
|
||||||
|
4. **Enable CORS** properly on your backend
|
||||||
|
5. **Implement rate limiting** on authentication endpoints
|
||||||
|
6. **Add error boundary** for graceful error handling
|
||||||
|
7. **Set up monitoring** (Sentry, LogRocket, etc.)
|
||||||
|
8. **Regular security audits** - Run `npm audit` regularly
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
- Chrome (latest)
|
||||||
|
- Firefox (latest)
|
||||||
|
- Safari (latest)
|
||||||
|
- Edge (latest)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Create a feature branch
|
||||||
|
2. Make your changes
|
||||||
|
3. Run linting and type checking
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Proprietary - All rights reserved
|
||||||
|
|
||||||
|
|
|
||||||
476
dev-docs/API_STANDARDS.md
Normal file
476
dev-docs/API_STANDARDS.md
Normal file
|
|
@ -0,0 +1,476 @@
|
||||||
|
# API Client Standards
|
||||||
|
|
||||||
|
## Industry Best Practices Implemented
|
||||||
|
|
||||||
|
### 1. Separation of Concerns
|
||||||
|
- **Public API Instance**: Used for unauthenticated endpoints (login, register, forgot password)
|
||||||
|
- **Authenticated API Instance**: Used for protected endpoints requiring authentication
|
||||||
|
- This prevents unnecessary token attachment to public endpoints
|
||||||
|
|
||||||
|
### 2. Cookie-Based Authentication (Recommended)
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
```typescript
|
||||||
|
withCredentials: true // Enables sending/receiving cookies
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows the backend to set httpOnly cookies which are:
|
||||||
|
- **Secure**: Not accessible via JavaScript (prevents XSS attacks)
|
||||||
|
- **Automatic**: Browser automatically sends with each request
|
||||||
|
- **Industry Standard**: Used by major platforms (Google, Facebook, etc.)
|
||||||
|
|
||||||
|
#### Backend Requirements for Cookie-Based Auth
|
||||||
|
|
||||||
|
**Login Response:**
|
||||||
|
```http
|
||||||
|
POST /auth/login
|
||||||
|
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
|
||||||
|
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
||||||
|
|
||||||
|
Response Body:
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cookie Attributes Explained:**
|
||||||
|
- `HttpOnly`: Prevents JavaScript access (XSS protection)
|
||||||
|
- `Secure`: Only sent over HTTPS (production)
|
||||||
|
- `SameSite=Strict`: CSRF protection
|
||||||
|
- `Path=/`: Cookie scope
|
||||||
|
- `Max-Age`: Expiration time in seconds
|
||||||
|
|
||||||
|
**Logout:**
|
||||||
|
```http
|
||||||
|
POST /auth/logout
|
||||||
|
Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0
|
||||||
|
Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Token Refresh:**
|
||||||
|
```http
|
||||||
|
POST /auth/refresh
|
||||||
|
Cookie: refresh_token=<jwt>
|
||||||
|
|
||||||
|
Response:
|
||||||
|
Set-Cookie: access_token=<new_jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Fallback: localStorage (Current Implementation)
|
||||||
|
|
||||||
|
For backends that don't support httpOnly cookies, the system falls back to localStorage:
|
||||||
|
- Token stored in `localStorage.access_token`
|
||||||
|
- Automatically added to Authorization header
|
||||||
|
- Less secure than cookies (vulnerable to XSS)
|
||||||
|
|
||||||
|
### 4. Authentication Flow
|
||||||
|
|
||||||
|
#### Login
|
||||||
|
```typescript
|
||||||
|
// Uses publicApi (no token required)
|
||||||
|
adminApiHelpers.login({ email, password })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response Expected (Cookie-based):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// + Set-Cookie headers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response Expected (localStorage fallback):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "jwt-token",
|
||||||
|
"refresh_token": "refresh-token",
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logout
|
||||||
|
```typescript
|
||||||
|
// Centralized logout handling
|
||||||
|
await adminApiHelpers.logout()
|
||||||
|
```
|
||||||
|
- Calls backend `/auth/logout` to clear httpOnly cookies
|
||||||
|
- Clears localStorage (access_token, user)
|
||||||
|
- Prevents duplicate logout logic across components
|
||||||
|
|
||||||
|
#### Token Refresh (Automatic)
|
||||||
|
```typescript
|
||||||
|
// Automatically called on 401 response
|
||||||
|
adminApiHelpers.refreshToken()
|
||||||
|
```
|
||||||
|
- Refreshes expired access token using refresh token
|
||||||
|
- Retries failed request with new token
|
||||||
|
- If refresh fails, logs out user
|
||||||
|
|
||||||
|
#### Get Current User
|
||||||
|
```typescript
|
||||||
|
adminApiHelpers.getCurrentUser()
|
||||||
|
```
|
||||||
|
- Validates token and fetches current user data
|
||||||
|
- Useful for session validation
|
||||||
|
|
||||||
|
### 5. Interceptor Improvements
|
||||||
|
|
||||||
|
#### Request Interceptor
|
||||||
|
- Adds `withCredentials: true` to send cookies
|
||||||
|
- Adds Authorization header if localStorage token exists (fallback)
|
||||||
|
- Bearer token format: `Authorization: Bearer <token>`
|
||||||
|
|
||||||
|
#### Response Interceptor
|
||||||
|
- **401 Unauthorized**:
|
||||||
|
- Attempts automatic token refresh
|
||||||
|
- Retries original request
|
||||||
|
- If refresh fails, auto-logout and redirect
|
||||||
|
- Prevents infinite loops on login page
|
||||||
|
- **403 Forbidden**: Shows permission error toast
|
||||||
|
- **404 Not Found**: Shows resource not found toast
|
||||||
|
- **500 Server Error**: Shows server error toast
|
||||||
|
- **Network Error**: Shows connection error toast
|
||||||
|
|
||||||
|
### 6. Security Best Practices
|
||||||
|
|
||||||
|
✅ **Implemented:**
|
||||||
|
- Separate public/private API instances
|
||||||
|
- Cookie support with `withCredentials: true`
|
||||||
|
- Bearer token authentication (fallback)
|
||||||
|
- Automatic token injection
|
||||||
|
- Automatic token refresh on 401
|
||||||
|
- Centralized logout with backend call
|
||||||
|
- Auto-redirect on 401 (with login page check)
|
||||||
|
- Retry mechanism for failed requests
|
||||||
|
|
||||||
|
✅ **Backend Should Implement:**
|
||||||
|
- httpOnly cookies for tokens
|
||||||
|
- Secure flag (HTTPS only)
|
||||||
|
- SameSite=Strict (CSRF protection)
|
||||||
|
- Short-lived access tokens (15 min)
|
||||||
|
- Long-lived refresh tokens (7 days)
|
||||||
|
- Token rotation on refresh
|
||||||
|
- Logout endpoint to clear cookies
|
||||||
|
|
||||||
|
⚠️ **Additional Production Recommendations:**
|
||||||
|
- Rate limiting on login endpoint
|
||||||
|
- Account lockout after failed attempts
|
||||||
|
- Two-factor authentication (2FA)
|
||||||
|
- IP whitelisting for admin access
|
||||||
|
- Audit logging for all admin actions
|
||||||
|
- Content Security Policy (CSP) headers
|
||||||
|
- CORS configuration
|
||||||
|
- Request/response encryption for sensitive data
|
||||||
|
|
||||||
|
### 7. Security Comparison
|
||||||
|
|
||||||
|
| Feature | localStorage | httpOnly Cookies |
|
||||||
|
|---------|-------------|------------------|
|
||||||
|
| XSS Protection | ❌ Vulnerable | ✅ Protected |
|
||||||
|
| CSRF Protection | ✅ Not vulnerable | ⚠️ Needs SameSite |
|
||||||
|
| Automatic Sending | ❌ Manual | ✅ Automatic |
|
||||||
|
| Cross-domain | ✅ Easy | ⚠️ Complex |
|
||||||
|
| Mobile Apps | ✅ Works | ❌ Limited |
|
||||||
|
| Industry Standard | ⚠️ Common | ✅ Recommended |
|
||||||
|
|
||||||
|
### 8. Error Handling
|
||||||
|
|
||||||
|
All API errors are consistently handled:
|
||||||
|
- User-friendly error messages
|
||||||
|
- Toast notifications for feedback
|
||||||
|
- Proper error propagation for component-level handling
|
||||||
|
- Automatic retry on token expiration
|
||||||
|
|
||||||
|
### 9. Type Safety
|
||||||
|
|
||||||
|
All API methods have TypeScript types for:
|
||||||
|
- Request parameters
|
||||||
|
- Request body
|
||||||
|
- Response data (can be improved with response types)
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Login Flow (Cookie-based)
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const response = await adminApiHelpers.login({ email, password })
|
||||||
|
const { user } = response.data // No access_token in response
|
||||||
|
|
||||||
|
// Verify admin role
|
||||||
|
if (user.role !== 'ADMIN') {
|
||||||
|
throw new Error('Admin access required')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store user data only (token is in httpOnly cookie)
|
||||||
|
localStorage.setItem('user', JSON.stringify(user))
|
||||||
|
|
||||||
|
// Navigate to dashboard
|
||||||
|
navigate('/admin/dashboard')
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Login failed')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authenticated Request
|
||||||
|
```typescript
|
||||||
|
// Token automatically sent via cookie or Authorization header
|
||||||
|
const response = await adminApiHelpers.getUsers({ page: 1, limit: 20 })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logout
|
||||||
|
```typescript
|
||||||
|
// Centralized logout (clears cookies and localStorage)
|
||||||
|
await adminApiHelpers.logout()
|
||||||
|
navigate('/login')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Token Refresh
|
||||||
|
```typescript
|
||||||
|
// Happens automatically on 401 response
|
||||||
|
// No manual intervention needed
|
||||||
|
const response = await adminApiHelpers.getUsers()
|
||||||
|
// If token expired, it's automatically refreshed and request retried
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoint Requirements
|
||||||
|
|
||||||
|
### Authentication Endpoints
|
||||||
|
|
||||||
|
#### POST /auth/login
|
||||||
|
- **Public endpoint** (no authentication required)
|
||||||
|
- Validates credentials
|
||||||
|
- **Cookie-based**: Sets httpOnly cookies in response headers
|
||||||
|
- **localStorage fallback**: Returns access_token in response body
|
||||||
|
- Returns user data
|
||||||
|
- Should verify user role on backend
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "password123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Cookie-based):**
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
|
||||||
|
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
||||||
|
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "123",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (localStorage fallback):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGc...",
|
||||||
|
"refresh_token": "eyJhbGc...",
|
||||||
|
"user": {
|
||||||
|
"id": "123",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"role": "ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /auth/logout
|
||||||
|
- **Protected endpoint**
|
||||||
|
- Clears httpOnly cookies
|
||||||
|
- Invalidates tokens on server
|
||||||
|
- Clears server-side session
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0
|
||||||
|
Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=0
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": "Logged out successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /auth/refresh
|
||||||
|
- **Protected endpoint**
|
||||||
|
- Reads refresh_token from httpOnly cookie
|
||||||
|
- Returns new access token
|
||||||
|
- Implements token rotation (optional)
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
|
Cookie: refresh_token=<jwt>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Set-Cookie: access_token=<new_jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
|
||||||
|
Set-Cookie: refresh_token=<new_jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
||||||
|
|
||||||
|
{
|
||||||
|
"message": "Token refreshed"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GET /auth/me
|
||||||
|
- **Protected endpoint**
|
||||||
|
- Returns current authenticated user
|
||||||
|
- Useful for session validation
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "123",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Implementation Guide
|
||||||
|
|
||||||
|
### Node.js/Express Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Login endpoint
|
||||||
|
app.post('/auth/login', async (req, res) => {
|
||||||
|
const { email, password } = req.body
|
||||||
|
|
||||||
|
// Validate credentials
|
||||||
|
const user = await validateUser(email, password)
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ message: 'Invalid credentials' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate tokens
|
||||||
|
const accessToken = generateAccessToken(user)
|
||||||
|
const refreshToken = generateRefreshToken(user)
|
||||||
|
|
||||||
|
// Set httpOnly cookies
|
||||||
|
res.cookie('access_token', accessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
maxAge: 15 * 60 * 1000 // 15 minutes
|
||||||
|
})
|
||||||
|
|
||||||
|
res.cookie('refresh_token', refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
path: '/auth/refresh',
|
||||||
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return user data (no tokens in body)
|
||||||
|
res.json({ user: sanitizeUser(user) })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Logout endpoint
|
||||||
|
app.post('/auth/logout', (req, res) => {
|
||||||
|
res.clearCookie('access_token')
|
||||||
|
res.clearCookie('refresh_token', { path: '/auth/refresh' })
|
||||||
|
res.json({ message: 'Logged out successfully' })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Refresh endpoint
|
||||||
|
app.post('/auth/refresh', async (req, res) => {
|
||||||
|
const refreshToken = req.cookies.refresh_token
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
return res.status(401).json({ message: 'No refresh token' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = verifyRefreshToken(refreshToken)
|
||||||
|
const newAccessToken = generateAccessToken(decoded)
|
||||||
|
|
||||||
|
res.cookie('access_token', newAccessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
maxAge: 15 * 60 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({ message: 'Token refreshed' })
|
||||||
|
} catch (error) {
|
||||||
|
res.status(401).json({ message: 'Invalid refresh token' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auth middleware
|
||||||
|
const authMiddleware = (req, res, next) => {
|
||||||
|
const token = req.cookies.access_token ||
|
||||||
|
req.headers.authorization?.replace('Bearer ', '')
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({ message: 'No token provided' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = verifyAccessToken(token)
|
||||||
|
req.user = decoded
|
||||||
|
next()
|
||||||
|
} catch (error) {
|
||||||
|
res.status(401).json({ message: 'Invalid token' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORS Configuration
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
app.use(cors({
|
||||||
|
origin: 'http://localhost:5173', // Your frontend URL
|
||||||
|
credentials: true // Important: Allow cookies
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
If migrating from the old implementation:
|
||||||
|
1. Login now uses `publicApi` instead of `adminApi`
|
||||||
|
2. Added `withCredentials: true` for cookie support
|
||||||
|
3. Logout is centralized and calls backend endpoint
|
||||||
|
4. Automatic token refresh on 401 responses
|
||||||
|
5. Backend should set httpOnly cookies instead of returning tokens
|
||||||
|
6. Frontend stores only user data, not tokens (if using cookies)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Cookie-based Auth
|
||||||
|
1. Login and check browser DevTools > Application > Cookies
|
||||||
|
2. Should see `access_token` and `refresh_token` cookies
|
||||||
|
3. Cookies should have HttpOnly, Secure, SameSite flags
|
||||||
|
4. Make authenticated request - cookie sent automatically
|
||||||
|
5. Logout - cookies should be cleared
|
||||||
|
|
||||||
|
### Test localStorage Fallback
|
||||||
|
1. Backend returns `access_token` in response body
|
||||||
|
2. Token stored in localStorage
|
||||||
|
3. Token added to Authorization header automatically
|
||||||
|
4. Works for backends without cookie support
|
||||||
180
dev-docs/AUTHENTICATION.md
Normal file
180
dev-docs/AUTHENTICATION.md
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
# Authentication Setup
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The admin dashboard now requires authentication before accessing any admin routes and follows industry-standard security practices.
|
||||||
|
|
||||||
|
## Security Status
|
||||||
|
|
||||||
|
### ✅ Frontend Security (Implemented)
|
||||||
|
- Protected routes with authentication check
|
||||||
|
- Role-based access control (ADMIN only)
|
||||||
|
- httpOnly cookie support (`withCredentials: true`)
|
||||||
|
- Automatic token refresh on expiration
|
||||||
|
- Centralized logout with backend call
|
||||||
|
- localStorage fallback for compatibility
|
||||||
|
- Secure error handling
|
||||||
|
- CSRF protection ready (via SameSite cookies)
|
||||||
|
|
||||||
|
### ⚠️ Backend Security (Required)
|
||||||
|
The backend MUST implement these critical security measures:
|
||||||
|
1. **httpOnly Cookies**: Store tokens in httpOnly cookies (not response body)
|
||||||
|
2. **Password Hashing**: Use bcrypt with salt rounds >= 12
|
||||||
|
3. **Rate Limiting**: Limit login attempts (5 per 15 minutes)
|
||||||
|
4. **HTTPS**: Enable HTTPS in production
|
||||||
|
5. **Token Refresh**: Implement refresh token endpoint
|
||||||
|
6. **Input Validation**: Sanitize and validate all inputs
|
||||||
|
7. **CORS**: Configure with specific origin and credentials
|
||||||
|
8. **Security Headers**: Use helmet.js for security headers
|
||||||
|
9. **Audit Logging**: Log all admin actions
|
||||||
|
10. **SQL Injection Prevention**: Use parameterized queries
|
||||||
|
|
||||||
|
See `SECURITY_CHECKLIST.md` for complete requirements.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. Protected Routes
|
||||||
|
All admin routes are wrapped with `ProtectedRoute` component that checks for a valid access token.
|
||||||
|
|
||||||
|
### 2. Login Flow
|
||||||
|
- User visits any admin route without authentication → Redirected to `/login`
|
||||||
|
- User enters credentials → API validates and returns user data
|
||||||
|
- Backend sets httpOnly cookies (recommended) OR returns token (fallback)
|
||||||
|
- Token/cookies stored, user redirected to originally requested page
|
||||||
|
|
||||||
|
### 3. Token Management
|
||||||
|
- **Recommended**: Tokens stored in httpOnly cookies (XSS protection)
|
||||||
|
- **Fallback**: Access token in localStorage, automatically added to requests
|
||||||
|
- Token automatically sent with all API requests
|
||||||
|
- On 401 response, automatically attempts token refresh
|
||||||
|
- If refresh fails, user is logged out and redirected to login
|
||||||
|
|
||||||
|
### 4. Logout
|
||||||
|
- Calls backend `/auth/logout` to clear httpOnly cookies
|
||||||
|
- Clears localStorage (access_token, user)
|
||||||
|
- Redirects to `/login` page
|
||||||
|
|
||||||
|
## API Endpoints Required
|
||||||
|
|
||||||
|
### POST /auth/login
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "password123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Cookie-based - Recommended):**
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
|
||||||
|
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
||||||
|
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"firstName": "Admin",
|
||||||
|
"lastName": "User",
|
||||||
|
"role": "ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (localStorage fallback):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"role": "ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /auth/logout
|
||||||
|
Clears httpOnly cookies and invalidates tokens.
|
||||||
|
|
||||||
|
### POST /auth/refresh
|
||||||
|
Refreshes expired access token using refresh token from cookie.
|
||||||
|
|
||||||
|
### GET /auth/me (Optional)
|
||||||
|
Returns current authenticated user for session validation.
|
||||||
|
|
||||||
|
## Files Modified/Created
|
||||||
|
|
||||||
|
### Created:
|
||||||
|
- `src/pages/login/index.tsx` - Login page with show/hide password
|
||||||
|
- `src/components/ProtectedRoute.tsx` - Route protection wrapper
|
||||||
|
- `dev-docs/AUTHENTICATION.md` - This documentation
|
||||||
|
- `dev-docs/API_STANDARDS.md` - Detailed API standards
|
||||||
|
- `dev-docs/SECURITY_CHECKLIST.md` - Complete security checklist
|
||||||
|
|
||||||
|
### Modified:
|
||||||
|
- `src/App.tsx` - Added login route and protected admin routes
|
||||||
|
- `src/layouts/app-shell.tsx` - User state management and logout
|
||||||
|
- `src/lib/api-client.ts` - Cookie support, token refresh, centralized auth
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
1. Start the application
|
||||||
|
2. Navigate to any admin route (e.g., `/admin/dashboard`)
|
||||||
|
3. Should be redirected to `/login`
|
||||||
|
4. Enter valid admin credentials
|
||||||
|
5. Should be redirected back to the dashboard
|
||||||
|
6. Check browser DevTools > Application > Cookies (if backend uses cookies)
|
||||||
|
7. Click logout to clear session
|
||||||
|
|
||||||
|
## Security Comparison
|
||||||
|
|
||||||
|
| Feature | Current (Frontend) | With Backend Implementation |
|
||||||
|
|---------|-------------------|----------------------------|
|
||||||
|
| XSS Protection | ⚠️ Partial (localStorage) | ✅ Full (httpOnly cookies) |
|
||||||
|
| CSRF Protection | ✅ Ready | ✅ Full (SameSite cookies) |
|
||||||
|
| Token Refresh | ✅ Automatic | ✅ Automatic |
|
||||||
|
| Rate Limiting | ❌ None | ✅ Required |
|
||||||
|
| Password Hashing | ❌ Backend only | ✅ Required |
|
||||||
|
| Audit Logging | ❌ Backend only | ✅ Required |
|
||||||
|
| HTTPS | ⚠️ Production | ✅ Required |
|
||||||
|
|
||||||
|
## Production Deployment Checklist
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- ✅ Build with production environment variables
|
||||||
|
- ✅ Enable HTTPS
|
||||||
|
- ✅ Configure CSP headers
|
||||||
|
- ✅ Set secure cookie flags
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- ⚠️ Implement httpOnly cookies
|
||||||
|
- ⚠️ Enable HTTPS with valid SSL certificate
|
||||||
|
- ⚠️ Configure CORS with specific origin
|
||||||
|
- ⚠️ Add rate limiting
|
||||||
|
- ⚠️ Implement password hashing
|
||||||
|
- ⚠️ Add security headers (helmet.js)
|
||||||
|
- ⚠️ Set up audit logging
|
||||||
|
- ⚠️ Configure environment variables
|
||||||
|
- ⚠️ Enable database encryption
|
||||||
|
- ⚠️ Set up monitoring and alerting
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
- Frontend follows industry standards
|
||||||
|
- Supports both cookie-based and localStorage authentication
|
||||||
|
- Automatic token refresh prevents session interruption
|
||||||
|
- Centralized error handling and logout
|
||||||
|
|
||||||
|
### Backend Requirements
|
||||||
|
- **Critical**: Backend must implement security measures in `SECURITY_CHECKLIST.md`
|
||||||
|
- **Recommended**: Use httpOnly cookies instead of localStorage
|
||||||
|
- **Required**: Implement rate limiting, password hashing, HTTPS
|
||||||
|
- **Important**: Regular security audits and updates
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For detailed security requirements, see:
|
||||||
|
- `dev-docs/SECURITY_CHECKLIST.md` - Complete security checklist
|
||||||
|
- `dev-docs/API_STANDARDS.md` - API implementation guide
|
||||||
|
- `dev-docs/DEPLOYMENT.md` - Deployment instructions
|
||||||
209
dev-docs/CI_CD_SETUP.md
Normal file
209
dev-docs/CI_CD_SETUP.md
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
# CI/CD Setup Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This project uses **GitHub Actions** for continuous integration and deployment.
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### 1. CI Workflow (`.github/workflows/ci.yml`)
|
||||||
|
|
||||||
|
Runs on every push and pull request to main/develop branches.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Checkout code
|
||||||
|
2. Setup Node.js (18.x, 20.x matrix)
|
||||||
|
3. Install dependencies
|
||||||
|
4. Run linter
|
||||||
|
5. Run type check
|
||||||
|
6. Run tests
|
||||||
|
7. Generate coverage report
|
||||||
|
8. Upload coverage to Codecov
|
||||||
|
9. Build application
|
||||||
|
10. Upload build artifacts
|
||||||
|
11. Security audit
|
||||||
|
|
||||||
|
### 2. Deploy Workflow (`.github/workflows/deploy.yml`)
|
||||||
|
|
||||||
|
Runs on push to main branch or manual trigger.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Checkout code
|
||||||
|
2. Setup Node.js
|
||||||
|
3. Install dependencies
|
||||||
|
4. Run tests
|
||||||
|
5. Build for production
|
||||||
|
6. Deploy to Netlify/Vercel
|
||||||
|
7. Notify deployment status
|
||||||
|
|
||||||
|
## Required Secrets
|
||||||
|
|
||||||
|
Configure these in GitHub Settings > Secrets and variables > Actions:
|
||||||
|
|
||||||
|
### For CI
|
||||||
|
- `CODECOV_TOKEN` - Codecov upload token (optional)
|
||||||
|
- `SNYK_TOKEN` - Snyk security scanning token (optional)
|
||||||
|
- `VITE_BACKEND_API_URL` - API URL for build
|
||||||
|
|
||||||
|
### For Deployment
|
||||||
|
|
||||||
|
#### Netlify
|
||||||
|
- `NETLIFY_AUTH_TOKEN` - Netlify authentication token
|
||||||
|
- `NETLIFY_SITE_ID` - Netlify site ID
|
||||||
|
- `VITE_BACKEND_API_URL_PROD` - Production API URL
|
||||||
|
- `VITE_SENTRY_DSN` - Sentry DSN for error tracking
|
||||||
|
|
||||||
|
#### Vercel (Alternative)
|
||||||
|
- `VERCEL_TOKEN` - Vercel authentication token
|
||||||
|
- `VERCEL_ORG_ID` - Vercel organization ID
|
||||||
|
- `VERCEL_PROJECT_ID` - Vercel project ID
|
||||||
|
- `VITE_BACKEND_API_URL_PROD` - Production API URL
|
||||||
|
- `VITE_SENTRY_DSN` - Sentry DSN
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Enable GitHub Actions
|
||||||
|
GitHub Actions is enabled by default for all repositories.
|
||||||
|
|
||||||
|
### 2. Configure Secrets
|
||||||
|
|
||||||
|
Go to your repository:
|
||||||
|
```
|
||||||
|
Settings > Secrets and variables > Actions > New repository secret
|
||||||
|
```
|
||||||
|
|
||||||
|
Add all required secrets listed above.
|
||||||
|
|
||||||
|
### 3. Configure Codecov (Optional)
|
||||||
|
|
||||||
|
1. Sign up at [codecov.io](https://codecov.io)
|
||||||
|
2. Add your repository
|
||||||
|
3. Copy the upload token
|
||||||
|
4. Add as `CODECOV_TOKEN` secret
|
||||||
|
|
||||||
|
### 4. Configure Netlify
|
||||||
|
|
||||||
|
1. Sign up at [netlify.com](https://netlify.com)
|
||||||
|
2. Create a new site
|
||||||
|
3. Get your Site ID from Site settings
|
||||||
|
4. Generate a Personal Access Token
|
||||||
|
5. Add both as secrets
|
||||||
|
|
||||||
|
### 5. Configure Vercel (Alternative)
|
||||||
|
|
||||||
|
1. Sign up at [vercel.com](https://vercel.com)
|
||||||
|
2. Install Vercel CLI: `npm i -g vercel`
|
||||||
|
3. Run `vercel login`
|
||||||
|
4. Run `vercel link` in your project
|
||||||
|
5. Get tokens from Vercel dashboard
|
||||||
|
6. Add as secrets
|
||||||
|
|
||||||
|
## Manual Deployment
|
||||||
|
|
||||||
|
### Trigger via GitHub UI
|
||||||
|
1. Go to Actions tab
|
||||||
|
2. Select "Deploy to Production"
|
||||||
|
3. Click "Run workflow"
|
||||||
|
4. Select branch
|
||||||
|
5. Click "Run workflow"
|
||||||
|
|
||||||
|
### Trigger via CLI
|
||||||
|
```bash
|
||||||
|
gh workflow run deploy.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### View Workflow Runs
|
||||||
|
```
|
||||||
|
Repository > Actions tab
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Logs
|
||||||
|
Click on any workflow run to see detailed logs.
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
Configure notifications in:
|
||||||
|
```
|
||||||
|
Settings > Notifications > Actions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Fails
|
||||||
|
1. Check logs in Actions tab
|
||||||
|
2. Verify all secrets are set correctly
|
||||||
|
3. Test build locally: `npm run build`
|
||||||
|
|
||||||
|
### Tests Fail
|
||||||
|
1. Run tests locally: `npm run test:run`
|
||||||
|
2. Check for environment-specific issues
|
||||||
|
3. Verify test setup is correct
|
||||||
|
|
||||||
|
### Deployment Fails
|
||||||
|
1. Check deployment logs
|
||||||
|
2. Verify API URL is correct
|
||||||
|
3. Check Netlify/Vercel dashboard for errors
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always run tests before merging**
|
||||||
|
2. **Use pull requests for code review**
|
||||||
|
3. **Keep secrets secure** - never commit them
|
||||||
|
4. **Monitor build times** - optimize if needed
|
||||||
|
5. **Review security audit results**
|
||||||
|
6. **Keep dependencies updated**
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Branch Protection Rules
|
||||||
|
|
||||||
|
Recommended settings:
|
||||||
|
```
|
||||||
|
Settings > Branches > Add rule
|
||||||
|
|
||||||
|
Branch name pattern: main
|
||||||
|
☑ Require a pull request before merging
|
||||||
|
☑ Require status checks to pass before merging
|
||||||
|
- test
|
||||||
|
- build
|
||||||
|
☑ Require branches to be up to date before merging
|
||||||
|
☑ Do not allow bypassing the above settings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
The workflows use npm caching to speed up builds:
|
||||||
|
```yaml
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Matrix Testing
|
||||||
|
|
||||||
|
Tests run on multiple Node.js versions:
|
||||||
|
```yaml
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x, 20.x]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cost Optimization
|
||||||
|
|
||||||
|
GitHub Actions is free for public repositories and includes:
|
||||||
|
- 2,000 minutes/month for private repos (free tier)
|
||||||
|
- Unlimited for public repos
|
||||||
|
|
||||||
|
Tips to reduce usage:
|
||||||
|
1. Use caching
|
||||||
|
2. Run tests only on changed files
|
||||||
|
3. Skip redundant jobs
|
||||||
|
4. Use self-hosted runners for heavy workloads
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||||
|
- [Netlify Deploy Action](https://github.com/nwtgck/actions-netlify)
|
||||||
|
- [Vercel Deploy Action](https://github.com/amondnet/vercel-action)
|
||||||
|
- [Codecov Action](https://github.com/codecov/codecov-action)
|
||||||
|
|
||||||
279
dev-docs/DEPLOYMENT.md
Normal file
279
dev-docs/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
# Deployment Guide
|
||||||
|
|
||||||
|
## Pre-Deployment Checklist
|
||||||
|
|
||||||
|
### 1. Code Quality
|
||||||
|
- [ ] All TypeScript errors resolved
|
||||||
|
- [ ] ESLint warnings addressed
|
||||||
|
- [ ] Build completes successfully
|
||||||
|
- [ ] No console errors in production build
|
||||||
|
|
||||||
|
### 2. Environment Configuration
|
||||||
|
- [ ] `.env.production` configured with production API URL
|
||||||
|
- [ ] All required environment variables set
|
||||||
|
- [ ] API endpoints tested and accessible
|
||||||
|
- [ ] CORS configured on backend for production domain
|
||||||
|
|
||||||
|
### 3. Security
|
||||||
|
- [ ] HTTPS enabled (SSL/TLS certificate)
|
||||||
|
- [ ] Security headers configured (CSP, HSTS, X-Frame-Options)
|
||||||
|
- [ ] Authentication tokens secured (consider httpOnly cookies)
|
||||||
|
- [ ] API keys and secrets not exposed in client code
|
||||||
|
- [ ] Rate limiting configured on backend
|
||||||
|
- [ ] Input validation on all forms
|
||||||
|
|
||||||
|
### 4. Performance
|
||||||
|
- [ ] Code splitting implemented (check vite.config.ts)
|
||||||
|
- [ ] Images optimized
|
||||||
|
- [ ] Lazy loading for routes (if needed)
|
||||||
|
- [ ] Bundle size analyzed and optimized
|
||||||
|
- [ ] CDN configured for static assets (optional)
|
||||||
|
|
||||||
|
### 5. Monitoring & Error Tracking
|
||||||
|
- [ ] Error boundary implemented ✓
|
||||||
|
- [ ] Error tracking service configured (Sentry, LogRocket, etc.)
|
||||||
|
- [ ] Analytics configured (Google Analytics, Plausible, etc.)
|
||||||
|
- [ ] Logging strategy defined
|
||||||
|
- [ ] Uptime monitoring configured
|
||||||
|
|
||||||
|
### 6. Testing
|
||||||
|
- [ ] Manual testing completed on staging
|
||||||
|
- [ ] Cross-browser testing (Chrome, Firefox, Safari, Edge)
|
||||||
|
- [ ] Mobile responsiveness verified
|
||||||
|
- [ ] Authentication flow tested
|
||||||
|
- [ ] API error handling tested
|
||||||
|
|
||||||
|
### 7. Documentation
|
||||||
|
- [ ] README.md updated ✓
|
||||||
|
- [ ] Environment variables documented ✓
|
||||||
|
- [ ] Deployment instructions clear ✓
|
||||||
|
- [ ] API documentation available
|
||||||
|
|
||||||
|
## Deployment Options
|
||||||
|
|
||||||
|
### Option 1: Vercel (Recommended for Quick Deploy)
|
||||||
|
|
||||||
|
1. Install Vercel CLI:
|
||||||
|
```bash
|
||||||
|
npm i -g vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Login to Vercel:
|
||||||
|
```bash
|
||||||
|
vercel login
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Deploy:
|
||||||
|
```bash
|
||||||
|
vercel --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Set environment variables in Vercel dashboard:
|
||||||
|
- Go to Project Settings → Environment Variables
|
||||||
|
- Add `VITE_API_URL` with your production API URL
|
||||||
|
|
||||||
|
### Option 2: Netlify
|
||||||
|
|
||||||
|
1. Install Netlify CLI:
|
||||||
|
```bash
|
||||||
|
npm i -g netlify-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Login:
|
||||||
|
```bash
|
||||||
|
netlify login
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Deploy:
|
||||||
|
```bash
|
||||||
|
netlify deploy --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Set environment variables in Netlify dashboard
|
||||||
|
|
||||||
|
### Option 3: Docker + Cloud Provider
|
||||||
|
|
||||||
|
1. Build Docker image:
|
||||||
|
```bash
|
||||||
|
docker build -t yaltopia-admin:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Test locally:
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:80 yaltopia-admin:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Push to container registry:
|
||||||
|
```bash
|
||||||
|
# For Docker Hub
|
||||||
|
docker tag yaltopia-admin:latest username/yaltopia-admin:latest
|
||||||
|
docker push username/yaltopia-admin:latest
|
||||||
|
|
||||||
|
# For AWS ECR
|
||||||
|
aws ecr get-login-password --region region | docker login --username AWS --password-stdin account-id.dkr.ecr.region.amazonaws.com
|
||||||
|
docker tag yaltopia-admin:latest account-id.dkr.ecr.region.amazonaws.com/yaltopia-admin:latest
|
||||||
|
docker push account-id.dkr.ecr.region.amazonaws.com/yaltopia-admin:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Deploy to cloud:
|
||||||
|
- AWS ECS/Fargate
|
||||||
|
- Google Cloud Run
|
||||||
|
- Azure Container Instances
|
||||||
|
- DigitalOcean App Platform
|
||||||
|
|
||||||
|
### Option 4: Traditional VPS (Ubuntu/Debian)
|
||||||
|
|
||||||
|
1. SSH into your server
|
||||||
|
|
||||||
|
2. Install Node.js and nginx:
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Clone repository:
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>
|
||||||
|
cd yaltopia-ticket-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Install dependencies and build:
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Configure nginx:
|
||||||
|
```bash
|
||||||
|
sudo cp nginx.conf /etc/nginx/sites-available/yaltopia-admin
|
||||||
|
sudo ln -s /etc/nginx/sites-available/yaltopia-admin /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Copy build files:
|
||||||
|
```bash
|
||||||
|
sudo cp -r dist/* /var/www/html/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Post-Deployment
|
||||||
|
|
||||||
|
### 1. Verification
|
||||||
|
- [ ] Application loads correctly
|
||||||
|
- [ ] All routes work (test deep links)
|
||||||
|
- [ ] API calls successful
|
||||||
|
- [ ] Authentication works
|
||||||
|
- [ ] No console errors
|
||||||
|
- [ ] Performance acceptable (Lighthouse score)
|
||||||
|
|
||||||
|
### 2. Monitoring Setup
|
||||||
|
- [ ] Error tracking active
|
||||||
|
- [ ] Analytics tracking
|
||||||
|
- [ ] Uptime monitoring configured
|
||||||
|
- [ ] Alert notifications set up
|
||||||
|
|
||||||
|
### 3. Backup & Rollback Plan
|
||||||
|
- [ ] Previous version tagged in git
|
||||||
|
- [ ] Rollback procedure documented
|
||||||
|
- [ ] Database backup (if applicable)
|
||||||
|
|
||||||
|
## Continuous Deployment
|
||||||
|
|
||||||
|
### GitHub Actions (Automated)
|
||||||
|
|
||||||
|
The `.github/workflows/ci.yml` file is configured for CI.
|
||||||
|
|
||||||
|
For CD, add deployment step:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Deploy to Vercel
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
npm i -g vercel
|
||||||
|
vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for Netlify:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Deploy to Netlify
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
npm i -g netlify-cli
|
||||||
|
netlify deploy --prod --auth=${{ secrets.NETLIFY_AUTH_TOKEN }} --site=${{ secrets.NETLIFY_SITE_ID }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Fails
|
||||||
|
- Check Node.js version (18+)
|
||||||
|
- Clear node_modules and reinstall: `rm -rf node_modules package-lock.json && npm install`
|
||||||
|
- Check for TypeScript errors: `npm run type-check`
|
||||||
|
|
||||||
|
### Blank Page After Deploy
|
||||||
|
- Check browser console for errors
|
||||||
|
- Verify API URL is correct
|
||||||
|
- Check nginx/server configuration for SPA routing
|
||||||
|
- Verify all environment variables are set
|
||||||
|
|
||||||
|
### API Calls Failing
|
||||||
|
- Check CORS configuration on backend
|
||||||
|
- Verify API URL in environment variables
|
||||||
|
- Check network tab in browser DevTools
|
||||||
|
- Verify authentication token handling
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
- Analyze bundle size: `npm run build -- --mode production`
|
||||||
|
- Check for large dependencies
|
||||||
|
- Implement code splitting
|
||||||
|
- Enable compression (gzip/brotli)
|
||||||
|
- Use CDN for static assets
|
||||||
|
|
||||||
|
## Security Hardening
|
||||||
|
|
||||||
|
### 1. Content Security Policy (CSP)
|
||||||
|
|
||||||
|
Add to nginx.conf or hosting platform headers:
|
||||||
|
|
||||||
|
```
|
||||||
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.yourdomain.com;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Additional Security Headers
|
||||||
|
|
||||||
|
```
|
||||||
|
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
X-Frame-Options: SAMEORIGIN
|
||||||
|
X-XSS-Protection: 1; mode=block
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Rate Limiting
|
||||||
|
|
||||||
|
Implement on backend and consider using Cloudflare or similar CDN with DDoS protection.
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### Regular Tasks
|
||||||
|
- [ ] Update dependencies monthly: `npm update`
|
||||||
|
- [ ] Security audit: `npm audit`
|
||||||
|
- [ ] Review error logs weekly
|
||||||
|
- [ ] Monitor performance metrics
|
||||||
|
- [ ] Backup configuration and data
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
1. Test updates in development
|
||||||
|
2. Deploy to staging
|
||||||
|
3. Run full test suite
|
||||||
|
4. Deploy to production during low-traffic period
|
||||||
|
5. Monitor for issues
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
- Check logs in error tracking service
|
||||||
|
- Review browser console errors
|
||||||
|
- Check server logs
|
||||||
|
- Contact backend team for API issues
|
||||||
393
dev-docs/DEPLOYMENT_OPTIONS.md
Normal file
393
dev-docs/DEPLOYMENT_OPTIONS.md
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
# Deployment Options - Industry Standard
|
||||||
|
|
||||||
|
## ✅ Your Project Has All Major Deployment Configurations!
|
||||||
|
|
||||||
|
Your project includes deployment configs for:
|
||||||
|
1. **Vercel** (vercel.json)
|
||||||
|
2. **Netlify** (netlify.toml)
|
||||||
|
3. **Docker** (Dockerfile + nginx.conf)
|
||||||
|
4. **GitHub Actions** (CI/CD workflows)
|
||||||
|
|
||||||
|
This makes your project **deployment-ready** for any platform!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Vercel Deployment ⚡
|
||||||
|
|
||||||
|
**File:** `vercel.json`
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
- ✅ Production build command
|
||||||
|
- ✅ SPA routing (rewrites)
|
||||||
|
- ✅ Security headers
|
||||||
|
- ✅ Asset caching (1 year)
|
||||||
|
- ✅ XSS protection
|
||||||
|
- ✅ Clickjacking protection
|
||||||
|
|
||||||
|
### Deploy:
|
||||||
|
```bash
|
||||||
|
# Install Vercel CLI
|
||||||
|
npm i -g vercel
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
vercel
|
||||||
|
|
||||||
|
# Deploy to production
|
||||||
|
vercel --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Or via GitHub:
|
||||||
|
1. Connect repository to Vercel
|
||||||
|
2. Auto-deploys on push to main
|
||||||
|
3. Preview deployments for PRs
|
||||||
|
|
||||||
|
### Environment Variables:
|
||||||
|
Set in Vercel dashboard:
|
||||||
|
- `VITE_API_URL` - Your production API URL
|
||||||
|
- `VITE_SENTRY_DSN` - Sentry error tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Netlify Deployment 🌐
|
||||||
|
|
||||||
|
**File:** `netlify.toml`
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
- ✅ Production build command
|
||||||
|
- ✅ SPA routing (redirects)
|
||||||
|
- ✅ Security headers
|
||||||
|
- ✅ Asset caching
|
||||||
|
- ✅ Node.js 18 environment
|
||||||
|
|
||||||
|
### Deploy:
|
||||||
|
```bash
|
||||||
|
# Install Netlify CLI
|
||||||
|
npm i -g netlify-cli
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
netlify deploy
|
||||||
|
|
||||||
|
# Deploy to production
|
||||||
|
netlify deploy --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Or via GitHub:
|
||||||
|
1. Connect repository to Netlify
|
||||||
|
2. Auto-deploys on push to main
|
||||||
|
3. Deploy previews for PRs
|
||||||
|
|
||||||
|
### Environment Variables:
|
||||||
|
Set in Netlify dashboard:
|
||||||
|
- `VITE_API_URL`
|
||||||
|
- `VITE_SENTRY_DSN`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Docker Deployment 🐳
|
||||||
|
|
||||||
|
**Files:** `Dockerfile` + `nginx.conf`
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
- ✅ Multi-stage build (optimized)
|
||||||
|
- ✅ Nginx web server
|
||||||
|
- ✅ Gzip compression
|
||||||
|
- ✅ Security headers
|
||||||
|
- ✅ Health checks
|
||||||
|
- ✅ Asset caching
|
||||||
|
- ✅ Production-ready
|
||||||
|
|
||||||
|
### Build & Run:
|
||||||
|
```bash
|
||||||
|
# Build image
|
||||||
|
docker build -t yaltopia-admin .
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -p 80:80 yaltopia-admin
|
||||||
|
|
||||||
|
# Or with environment variables
|
||||||
|
docker run -p 80:80 \
|
||||||
|
-e VITE_API_URL=https://api.yourdomain.com/api/v1 \
|
||||||
|
yaltopia-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy to Cloud:
|
||||||
|
- **AWS ECS/Fargate**
|
||||||
|
- **Google Cloud Run**
|
||||||
|
- **Azure Container Instances**
|
||||||
|
- **DigitalOcean App Platform**
|
||||||
|
- **Kubernetes**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. GitHub Actions CI/CD 🚀
|
||||||
|
|
||||||
|
**Files:** `.github/workflows/ci.yml` + `.github/workflows/deploy.yml`
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
- ✅ Automated testing
|
||||||
|
- ✅ Linting & type checking
|
||||||
|
- ✅ Security scanning
|
||||||
|
- ✅ Code coverage
|
||||||
|
- ✅ Automated deployment
|
||||||
|
- ✅ Multi-node testing (18.x, 20.x)
|
||||||
|
|
||||||
|
### Triggers:
|
||||||
|
- Push to main/develop
|
||||||
|
- Pull requests
|
||||||
|
- Manual workflow dispatch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Headers Comparison
|
||||||
|
|
||||||
|
All deployment configs include these security headers:
|
||||||
|
|
||||||
|
| Header | Purpose | Included |
|
||||||
|
|--------|---------|----------|
|
||||||
|
| X-Frame-Options | Prevent clickjacking | ✅ |
|
||||||
|
| X-Content-Type-Options | Prevent MIME sniffing | ✅ |
|
||||||
|
| X-XSS-Protection | XSS protection | ✅ |
|
||||||
|
| Referrer-Policy | Control referrer info | ✅ |
|
||||||
|
| Cache-Control | Asset caching | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### All Configs Include:
|
||||||
|
1. **Gzip Compression** - Reduce file sizes
|
||||||
|
2. **Asset Caching** - 1 year cache for static files
|
||||||
|
3. **Production Build** - Minified, optimized code
|
||||||
|
4. **Code Splitting** - Vendor chunks separated
|
||||||
|
5. **Tree Shaking** - Remove unused code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comparison: Which to Use?
|
||||||
|
|
||||||
|
### Vercel ⚡
|
||||||
|
**Best for:**
|
||||||
|
- Fastest deployment
|
||||||
|
- Automatic HTTPS
|
||||||
|
- Edge network (CDN)
|
||||||
|
- Serverless functions
|
||||||
|
- Preview deployments
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Zero config needed
|
||||||
|
- Excellent DX
|
||||||
|
- Fast global CDN
|
||||||
|
- Free tier generous
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Vendor lock-in
|
||||||
|
- Limited customization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Netlify 🌐
|
||||||
|
**Best for:**
|
||||||
|
- Static sites
|
||||||
|
- Form handling
|
||||||
|
- Split testing
|
||||||
|
- Identity/Auth
|
||||||
|
- Functions
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Easy to use
|
||||||
|
- Great free tier
|
||||||
|
- Built-in forms
|
||||||
|
- Deploy previews
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Slower than Vercel
|
||||||
|
- Limited compute
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Docker 🐳
|
||||||
|
**Best for:**
|
||||||
|
- Full control
|
||||||
|
- Any cloud provider
|
||||||
|
- Kubernetes
|
||||||
|
- On-premise
|
||||||
|
- Complex setups
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Complete control
|
||||||
|
- Portable
|
||||||
|
- Scalable
|
||||||
|
- No vendor lock-in
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- More complex
|
||||||
|
- Need to manage infra
|
||||||
|
- Requires DevOps knowledge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Industry Standards Checklist
|
||||||
|
|
||||||
|
Your project has:
|
||||||
|
|
||||||
|
### Deployment ✅
|
||||||
|
- [x] Multiple deployment options
|
||||||
|
- [x] Vercel configuration
|
||||||
|
- [x] Netlify configuration
|
||||||
|
- [x] Docker support
|
||||||
|
- [x] CI/CD pipelines
|
||||||
|
|
||||||
|
### Security ✅
|
||||||
|
- [x] Security headers
|
||||||
|
- [x] XSS protection
|
||||||
|
- [x] Clickjacking protection
|
||||||
|
- [x] MIME sniffing prevention
|
||||||
|
- [x] Referrer policy
|
||||||
|
|
||||||
|
### Performance ✅
|
||||||
|
- [x] Gzip compression
|
||||||
|
- [x] Asset caching
|
||||||
|
- [x] Code splitting
|
||||||
|
- [x] Production builds
|
||||||
|
- [x] Optimized images
|
||||||
|
|
||||||
|
### DevOps ✅
|
||||||
|
- [x] Automated testing
|
||||||
|
- [x] Automated deployment
|
||||||
|
- [x] Environment variables
|
||||||
|
- [x] Health checks (Docker)
|
||||||
|
- [x] Multi-stage builds
|
||||||
|
|
||||||
|
### Documentation ✅
|
||||||
|
- [x] Deployment guides
|
||||||
|
- [x] Environment setup
|
||||||
|
- [x] API documentation
|
||||||
|
- [x] Security checklist
|
||||||
|
- [x] Troubleshooting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start Deployment
|
||||||
|
|
||||||
|
### Option 1: Vercel (Fastest)
|
||||||
|
```bash
|
||||||
|
npm i -g vercel
|
||||||
|
vercel login
|
||||||
|
vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Netlify
|
||||||
|
```bash
|
||||||
|
npm i -g netlify-cli
|
||||||
|
netlify login
|
||||||
|
netlify deploy --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Docker
|
||||||
|
```bash
|
||||||
|
docker build -t yaltopia-admin .
|
||||||
|
docker run -p 80:80 yaltopia-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
All platforms need these:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Required
|
||||||
|
VITE_API_URL=https://api.yourdomain.com/api/v1
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
VITE_SENTRY_DSN=https://your-sentry-dsn
|
||||||
|
VITE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cost Comparison
|
||||||
|
|
||||||
|
### Vercel
|
||||||
|
- **Free:** Hobby projects
|
||||||
|
- **Pro:** $20/month
|
||||||
|
- **Enterprise:** Custom
|
||||||
|
|
||||||
|
### Netlify
|
||||||
|
- **Free:** Personal projects
|
||||||
|
- **Pro:** $19/month
|
||||||
|
- **Business:** $99/month
|
||||||
|
|
||||||
|
### Docker (AWS)
|
||||||
|
- **ECS Fargate:** ~$15-50/month
|
||||||
|
- **EC2:** ~$10-100/month
|
||||||
|
- **Depends on:** Traffic, resources
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
### For This Project:
|
||||||
|
1. **Development:** Local + GitHub Actions
|
||||||
|
2. **Staging:** Vercel/Netlify (free tier)
|
||||||
|
3. **Production:**
|
||||||
|
- Small scale: Vercel/Netlify
|
||||||
|
- Large scale: Docker + AWS/GCP
|
||||||
|
- Enterprise: Kubernetes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Makes This Industry Standard?
|
||||||
|
|
||||||
|
✅ **Multiple Deployment Options**
|
||||||
|
- Not locked to one platform
|
||||||
|
- Can deploy anywhere
|
||||||
|
|
||||||
|
✅ **Security First**
|
||||||
|
- All security headers configured
|
||||||
|
- XSS, clickjacking protection
|
||||||
|
- HTTPS ready
|
||||||
|
|
||||||
|
✅ **Performance Optimized**
|
||||||
|
- Caching strategies
|
||||||
|
- Compression enabled
|
||||||
|
- CDN ready
|
||||||
|
|
||||||
|
✅ **CI/CD Ready**
|
||||||
|
- Automated testing
|
||||||
|
- Automated deployment
|
||||||
|
- Quality gates
|
||||||
|
|
||||||
|
✅ **Production Ready**
|
||||||
|
- Health checks
|
||||||
|
- Error monitoring
|
||||||
|
- Logging ready
|
||||||
|
|
||||||
|
✅ **Well Documented**
|
||||||
|
- Clear instructions
|
||||||
|
- Multiple options
|
||||||
|
- Troubleshooting guides
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Choose Platform:** Vercel, Netlify, or Docker
|
||||||
|
2. **Set Environment Variables**
|
||||||
|
3. **Deploy:** Follow quick start above
|
||||||
|
4. **Configure Domain:** Point to deployment
|
||||||
|
5. **Enable Monitoring:** Sentry, analytics
|
||||||
|
6. **Set Up Alerts:** Error notifications
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- [Vercel Docs](https://vercel.com/docs)
|
||||||
|
- [Netlify Docs](https://docs.netlify.com)
|
||||||
|
- [Docker Docs](https://docs.docker.com)
|
||||||
|
- [GitHub Actions Docs](https://docs.github.com/en/actions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Your project is deployment-ready for any platform!** 🚀
|
||||||
231
dev-docs/ERROR_MONITORING.md
Normal file
231
dev-docs/ERROR_MONITORING.md
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
# Error Monitoring with Sentry
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This project uses **Sentry** for error tracking and performance monitoring.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Create Sentry Account
|
||||||
|
1. Sign up at [sentry.io](https://sentry.io)
|
||||||
|
2. Create a new project
|
||||||
|
3. Select "React" as the platform
|
||||||
|
4. Copy your DSN
|
||||||
|
|
||||||
|
### 2. Configure Environment Variables
|
||||||
|
|
||||||
|
Add to `.env.production`:
|
||||||
|
```env
|
||||||
|
VITE_SENTRY_DSN=https://your-key@sentry.io/your-project-id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Sentry is Already Integrated
|
||||||
|
|
||||||
|
The following files have Sentry integration:
|
||||||
|
- `src/lib/sentry.ts` - Sentry initialization
|
||||||
|
- `src/main.tsx` - Sentry init on app start
|
||||||
|
- `src/components/ErrorBoundary.tsx` - Error boundary with Sentry
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 1. Error Tracking
|
||||||
|
All uncaught errors are automatically sent to Sentry:
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
// Your code
|
||||||
|
} catch (error) {
|
||||||
|
Sentry.captureException(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Performance Monitoring
|
||||||
|
Tracks page load times and API calls:
|
||||||
|
```typescript
|
||||||
|
tracesSampleRate: 0.1 // 10% of transactions
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Session Replay
|
||||||
|
Records user sessions when errors occur:
|
||||||
|
```typescript
|
||||||
|
replaysOnErrorSampleRate: 1.0 // 100% on errors
|
||||||
|
replaysSessionSampleRate: 0.1 // 10% of normal sessions
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Error Filtering
|
||||||
|
Filters out browser extension errors:
|
||||||
|
```typescript
|
||||||
|
beforeSend(event, hint) {
|
||||||
|
// Filter logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Error Logging
|
||||||
|
|
||||||
|
### Capture Exception
|
||||||
|
```typescript
|
||||||
|
import { Sentry } from '@/lib/sentry'
|
||||||
|
|
||||||
|
try {
|
||||||
|
// risky operation
|
||||||
|
} catch (error) {
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
section: 'user-management',
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Capture Message
|
||||||
|
```typescript
|
||||||
|
Sentry.captureMessage('Something important happened', 'info')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Breadcrumbs
|
||||||
|
```typescript
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
category: 'auth',
|
||||||
|
message: 'User logged in',
|
||||||
|
level: 'info',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set User Context
|
||||||
|
```typescript
|
||||||
|
Sentry.setUser({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
username: user.name,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dashboard Features
|
||||||
|
|
||||||
|
### 1. Issues
|
||||||
|
View all errors with:
|
||||||
|
- Stack traces
|
||||||
|
- User context
|
||||||
|
- Breadcrumbs
|
||||||
|
- Session replays
|
||||||
|
|
||||||
|
### 2. Performance
|
||||||
|
Monitor:
|
||||||
|
- Page load times
|
||||||
|
- API response times
|
||||||
|
- Slow transactions
|
||||||
|
|
||||||
|
### 3. Releases
|
||||||
|
Track errors by release version:
|
||||||
|
```bash
|
||||||
|
# Set release in build
|
||||||
|
VITE_SENTRY_RELEASE=1.0.0 npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Alerts
|
||||||
|
Configure alerts for:
|
||||||
|
- New issues
|
||||||
|
- Spike in errors
|
||||||
|
- Performance degradation
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Environment Configuration
|
||||||
|
```typescript
|
||||||
|
// Only enable in production
|
||||||
|
if (environment !== 'development') {
|
||||||
|
Sentry.init({ ... })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Sample Rates
|
||||||
|
```typescript
|
||||||
|
// Production
|
||||||
|
tracesSampleRate: 0.1 // 10%
|
||||||
|
replaysSessionSampleRate: 0.1 // 10%
|
||||||
|
|
||||||
|
// Staging
|
||||||
|
tracesSampleRate: 1.0 // 100%
|
||||||
|
replaysSessionSampleRate: 0.5 // 50%
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. PII Protection
|
||||||
|
```typescript
|
||||||
|
replaysIntegration({
|
||||||
|
maskAllText: true,
|
||||||
|
blockAllMedia: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Error Grouping
|
||||||
|
Use fingerprinting for better grouping:
|
||||||
|
```typescript
|
||||||
|
beforeSend(event) {
|
||||||
|
event.fingerprint = ['{{ default }}', event.message]
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Errors Not Appearing
|
||||||
|
1. Check DSN is correct
|
||||||
|
2. Verify environment is not 'development'
|
||||||
|
3. Check browser console for Sentry errors
|
||||||
|
4. Verify network requests to Sentry
|
||||||
|
|
||||||
|
### Too Many Events
|
||||||
|
1. Reduce sample rates
|
||||||
|
2. Add more filters in beforeSend
|
||||||
|
3. Set up rate limiting in Sentry dashboard
|
||||||
|
|
||||||
|
### Missing Context
|
||||||
|
1. Add more breadcrumbs
|
||||||
|
2. Set user context after login
|
||||||
|
3. Add custom tags and extra data
|
||||||
|
|
||||||
|
## Cost Management
|
||||||
|
|
||||||
|
Sentry pricing is based on:
|
||||||
|
- Number of events
|
||||||
|
- Number of replays
|
||||||
|
- Data retention
|
||||||
|
|
||||||
|
Tips to reduce costs:
|
||||||
|
1. Lower sample rates in production
|
||||||
|
2. Filter out noisy errors
|
||||||
|
3. Use error grouping effectively
|
||||||
|
4. Set up spike protection
|
||||||
|
|
||||||
|
## Integration with CI/CD
|
||||||
|
|
||||||
|
### Upload Source Maps
|
||||||
|
```yaml
|
||||||
|
# In .github/workflows/deploy.yml
|
||||||
|
- name: Upload source maps to Sentry
|
||||||
|
run: |
|
||||||
|
npm install -g @sentry/cli
|
||||||
|
sentry-cli releases new ${{ github.sha }}
|
||||||
|
sentry-cli releases files ${{ github.sha }} upload-sourcemaps ./dist
|
||||||
|
sentry-cli releases finalize ${{ github.sha }}
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: your-org
|
||||||
|
SENTRY_PROJECT: your-project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Sentry React Documentation](https://docs.sentry.io/platforms/javascript/guides/react/)
|
||||||
|
- [Sentry Performance Monitoring](https://docs.sentry.io/product/performance/)
|
||||||
|
- [Sentry Session Replay](https://docs.sentry.io/product/session-replay/)
|
||||||
|
- [Sentry Best Practices](https://docs.sentry.io/product/best-practices/)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues with Sentry integration:
|
||||||
|
1. Check Sentry documentation
|
||||||
|
2. Review browser console
|
||||||
|
3. Check Sentry dashboard
|
||||||
|
4. Contact Sentry support
|
||||||
357
dev-docs/LOGIN_API_DOCUMENTATION.md
Normal file
357
dev-docs/LOGIN_API_DOCUMENTATION.md
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
# Login API Documentation
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
```
|
||||||
|
POST /api/v1/auth/login
|
||||||
|
```
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Login user with email or phone number. This endpoint authenticates users using either email address or phone number along with password.
|
||||||
|
|
||||||
|
## Current Implementation
|
||||||
|
|
||||||
|
### Frontend Code
|
||||||
|
|
||||||
|
**API Client** (`src/lib/api-client.ts`):
|
||||||
|
```typescript
|
||||||
|
export const adminApiHelpers = {
|
||||||
|
// Auth - uses publicApi (no token required)
|
||||||
|
login: (data: { email: string; password: string }) =>
|
||||||
|
publicApi.post('/auth/login', data),
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Login Page** (`src/pages/login/index.tsx`):
|
||||||
|
```typescript
|
||||||
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await adminApiHelpers.login({ email, password })
|
||||||
|
const { access_token, user } = response.data
|
||||||
|
|
||||||
|
// Check if user is admin
|
||||||
|
if (user.role !== 'ADMIN') {
|
||||||
|
toast.error("Access denied. Admin privileges required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store credentials
|
||||||
|
if (access_token) {
|
||||||
|
localStorage.setItem('access_token', access_token)
|
||||||
|
}
|
||||||
|
localStorage.setItem('user', JSON.stringify(user))
|
||||||
|
|
||||||
|
toast.success("Login successful!")
|
||||||
|
navigate(from, { replace: true })
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error.response?.data?.message || "Invalid email or password"
|
||||||
|
toast.error(message)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Body (JSON)
|
||||||
|
|
||||||
|
**Option 1: Email + Password**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "your-password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Phone + Password** (if backend supports)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"phone": "+1234567890",
|
||||||
|
"password": "your-password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Request
|
||||||
|
```bash
|
||||||
|
curl -X POST https://api.yourdomain.com/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"password": "password123"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response
|
||||||
|
|
||||||
|
### Success Response (200 OK)
|
||||||
|
|
||||||
|
**Option 1: With Access Token in Body** (localStorage fallback)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"user": {
|
||||||
|
"id": "user-id-123",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"isActive": true,
|
||||||
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: With httpOnly Cookies** (recommended)
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Set-Cookie: access_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
|
||||||
|
Set-Cookie: refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "user-id-123",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe",
|
||||||
|
"role": "ADMIN",
|
||||||
|
"isActive": true,
|
||||||
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Responses
|
||||||
|
|
||||||
|
**401 Unauthorized** - Invalid credentials
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Invalid email or password",
|
||||||
|
"statusCode": 401
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**403 Forbidden** - Account inactive or not admin
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Account is inactive",
|
||||||
|
"statusCode": 403
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**400 Bad Request** - Validation error
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Validation failed",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"field": "email",
|
||||||
|
"message": "Invalid email format"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statusCode": 400
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**429 Too Many Requests** - Rate limit exceeded
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Too many login attempts. Please try again later.",
|
||||||
|
"statusCode": 429,
|
||||||
|
"retryAfter": 900
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**500 Internal Server Error** - Server error
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Internal server error",
|
||||||
|
"statusCode": 500
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend Behavior
|
||||||
|
|
||||||
|
### 1. Form Validation
|
||||||
|
- Email: Required, valid email format
|
||||||
|
- Password: Required, minimum 8 characters
|
||||||
|
- Show/hide password toggle
|
||||||
|
|
||||||
|
### 2. Loading State
|
||||||
|
- Disable form during submission
|
||||||
|
- Show "Logging in..." button text
|
||||||
|
- Prevent multiple submissions
|
||||||
|
|
||||||
|
### 3. Success Flow
|
||||||
|
1. Validate response contains user data
|
||||||
|
2. Check if user.role === 'ADMIN'
|
||||||
|
3. Store access_token (if provided)
|
||||||
|
4. Store user data in localStorage
|
||||||
|
5. Show success toast
|
||||||
|
6. Redirect to dashboard or original destination
|
||||||
|
|
||||||
|
### 4. Error Handling
|
||||||
|
- Display user-friendly error messages
|
||||||
|
- Show toast notification
|
||||||
|
- Keep form enabled for retry
|
||||||
|
- Don't expose sensitive error details
|
||||||
|
|
||||||
|
### 5. Security Features
|
||||||
|
- HTTPS only in production
|
||||||
|
- httpOnly cookies support
|
||||||
|
- CSRF protection (SameSite cookies)
|
||||||
|
- Automatic token refresh
|
||||||
|
- Role-based access control
|
||||||
|
|
||||||
|
## Backend Requirements
|
||||||
|
|
||||||
|
### Must Implement
|
||||||
|
1. **Password Hashing**: bcrypt with salt rounds >= 12
|
||||||
|
2. **Rate Limiting**: 5 attempts per 15 minutes per IP
|
||||||
|
3. **Account Lockout**: Lock after 5 failed attempts
|
||||||
|
4. **Role Verification**: Ensure user.role === 'ADMIN'
|
||||||
|
5. **Active Status Check**: Verify user.isActive === true
|
||||||
|
6. **Token Generation**: JWT with proper expiration
|
||||||
|
7. **Audit Logging**: Log all login attempts
|
||||||
|
|
||||||
|
### Recommended
|
||||||
|
1. **httpOnly Cookies**: Store tokens in cookies, not response body
|
||||||
|
2. **Refresh Tokens**: Long-lived tokens for session renewal
|
||||||
|
3. **2FA Support**: Two-factor authentication
|
||||||
|
4. **IP Whitelisting**: Restrict admin access by IP
|
||||||
|
5. **Session Management**: Track active sessions
|
||||||
|
6. **Email Notifications**: Alert on new login
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
```bash
|
||||||
|
# Test with valid credentials
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"admin@example.com","password":"password123"}'
|
||||||
|
|
||||||
|
# Test with invalid credentials
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"admin@example.com","password":"wrong"}'
|
||||||
|
|
||||||
|
# Test with non-admin user
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"user@example.com","password":"password123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
See `src/pages/login/__tests__/index.test.tsx` for component tests.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Development (`.env`)
|
||||||
|
```env
|
||||||
|
VITE_BACKEND_API_URL=http://localhost:3000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production (`.env.production`)
|
||||||
|
```env
|
||||||
|
VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Client Configuration
|
||||||
|
|
||||||
|
The login endpoint uses the `publicApi` instance which:
|
||||||
|
- Does NOT require authentication
|
||||||
|
- Does NOT send Authorization header
|
||||||
|
- DOES send cookies (`withCredentials: true`)
|
||||||
|
- DOES handle CORS properly
|
||||||
|
|
||||||
|
## Flow Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
User enters credentials
|
||||||
|
↓
|
||||||
|
Form validation
|
||||||
|
↓
|
||||||
|
POST /api/v1/auth/login
|
||||||
|
↓
|
||||||
|
Backend validates credentials
|
||||||
|
↓
|
||||||
|
Backend checks role === 'ADMIN'
|
||||||
|
↓
|
||||||
|
Backend generates tokens
|
||||||
|
↓
|
||||||
|
Backend returns user + tokens
|
||||||
|
↓
|
||||||
|
Frontend checks role === 'ADMIN'
|
||||||
|
↓
|
||||||
|
Frontend stores tokens
|
||||||
|
↓
|
||||||
|
Frontend redirects to dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
- [ ] Passwords hashed with bcrypt
|
||||||
|
- [ ] Rate limiting enabled
|
||||||
|
- [ ] Account lockout implemented
|
||||||
|
- [ ] HTTPS enforced
|
||||||
|
- [ ] CORS configured properly
|
||||||
|
- [ ] httpOnly cookies used
|
||||||
|
- [ ] Audit logging enabled
|
||||||
|
- [ ] Input validation
|
||||||
|
- [ ] SQL injection prevention
|
||||||
|
|
||||||
|
Frontend:
|
||||||
|
- [x] HTTPS only in production
|
||||||
|
- [x] Cookie support enabled
|
||||||
|
- [x] Role verification
|
||||||
|
- [x] Error handling
|
||||||
|
- [x] Loading states
|
||||||
|
- [x] Form validation
|
||||||
|
- [x] Token storage
|
||||||
|
- [x] Automatic token refresh
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "Network Error"
|
||||||
|
**Solution:** Check API URL in environment variables
|
||||||
|
|
||||||
|
### Issue: "CORS Error"
|
||||||
|
**Solution:** Backend must allow credentials and specific origin
|
||||||
|
|
||||||
|
### Issue: "Invalid credentials" for valid user
|
||||||
|
**Solution:** Check backend password hashing and comparison
|
||||||
|
|
||||||
|
### Issue: "Access denied" for admin user
|
||||||
|
**Solution:** Verify user.role === 'ADMIN' in database
|
||||||
|
|
||||||
|
### Issue: Token not persisting
|
||||||
|
**Solution:** Check if backend is setting httpOnly cookies or returning access_token
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Authentication Setup](./AUTHENTICATION.md)
|
||||||
|
- [API Standards](./API_STANDARDS.md)
|
||||||
|
- [Security Checklist](./SECURITY_CHECKLIST.md)
|
||||||
|
- [Backend Requirements](./SECURITY_CHECKLIST.md#backend-security)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues with login:
|
||||||
|
1. Check browser console for errors
|
||||||
|
2. Check network tab for API response
|
||||||
|
3. Verify environment variables
|
||||||
|
4. Check backend logs
|
||||||
|
5. Test with curl/Postman
|
||||||
|
|
||||||
203
dev-docs/PRE_DEPLOYMENT_CHECKLIST.md
Normal file
203
dev-docs/PRE_DEPLOYMENT_CHECKLIST.md
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
# Pre-Deployment Checklist
|
||||||
|
|
||||||
|
Use this checklist before deploying to production.
|
||||||
|
|
||||||
|
## ✅ Code Quality
|
||||||
|
|
||||||
|
- [x] All TypeScript errors resolved
|
||||||
|
- [x] Build completes successfully (`npm run build`)
|
||||||
|
- [x] Type checking passes (`npm run type-check`)
|
||||||
|
- [ ] ESLint warnings addressed (`npm run lint`)
|
||||||
|
- [ ] No console.log statements in production code
|
||||||
|
- [ ] All TODO comments resolved or documented
|
||||||
|
|
||||||
|
## ✅ Environment Setup
|
||||||
|
|
||||||
|
- [ ] `.env.production` file created
|
||||||
|
- [ ] `VITE_API_URL` set to production API endpoint
|
||||||
|
- [ ] Backend API is accessible from production domain
|
||||||
|
- [ ] CORS configured on backend for production domain
|
||||||
|
- [ ] All required environment variables documented
|
||||||
|
|
||||||
|
## ✅ Security
|
||||||
|
|
||||||
|
- [ ] HTTPS/SSL certificate obtained and configured
|
||||||
|
- [ ] Security headers configured (see nginx.conf or hosting config)
|
||||||
|
- [ ] API endpoints secured with authentication
|
||||||
|
- [ ] Sensitive data not exposed in client code
|
||||||
|
- [ ] Rate limiting configured on backend
|
||||||
|
- [ ] Error messages don't expose sensitive information
|
||||||
|
- [ ] Dependencies audited (`npm audit`)
|
||||||
|
|
||||||
|
## ✅ Testing
|
||||||
|
|
||||||
|
- [ ] Application tested in development mode
|
||||||
|
- [ ] Production build tested locally (`npm run preview`)
|
||||||
|
- [ ] Login/logout flow tested
|
||||||
|
- [ ] All main routes tested
|
||||||
|
- [ ] API calls tested and working
|
||||||
|
- [ ] Error handling tested (network errors, 401, 403, 404, 500)
|
||||||
|
- [ ] Mobile responsiveness verified
|
||||||
|
- [ ] Cross-browser testing completed:
|
||||||
|
- [ ] Chrome
|
||||||
|
- [ ] Firefox
|
||||||
|
- [ ] Safari
|
||||||
|
- [ ] Edge
|
||||||
|
|
||||||
|
## ✅ Performance
|
||||||
|
|
||||||
|
- [ ] Bundle size reviewed (should be ~970 KB uncompressed)
|
||||||
|
- [ ] Lighthouse performance score checked (aim for >80)
|
||||||
|
- [ ] Images optimized (if any)
|
||||||
|
- [ ] Code splitting configured (already done in vite.config.ts)
|
||||||
|
- [ ] Compression enabled on server (gzip/brotli)
|
||||||
|
|
||||||
|
## ✅ Monitoring & Analytics
|
||||||
|
|
||||||
|
- [ ] Error tracking service configured (Sentry, LogRocket, etc.)
|
||||||
|
- [ ] Analytics configured (Google Analytics, Plausible, etc.)
|
||||||
|
- [ ] Uptime monitoring set up
|
||||||
|
- [ ] Alert notifications configured
|
||||||
|
- [ ] Logging strategy defined
|
||||||
|
|
||||||
|
## ✅ Documentation
|
||||||
|
|
||||||
|
- [x] README.md updated with project info
|
||||||
|
- [x] Environment variables documented
|
||||||
|
- [x] Deployment instructions clear
|
||||||
|
- [ ] API documentation available
|
||||||
|
- [ ] Team trained on deployment process
|
||||||
|
|
||||||
|
## ✅ Deployment Configuration
|
||||||
|
|
||||||
|
Choose your deployment method and complete the relevant section:
|
||||||
|
|
||||||
|
### For Vercel
|
||||||
|
- [ ] Vercel account created
|
||||||
|
- [ ] Project connected to repository
|
||||||
|
- [ ] Environment variables set in Vercel dashboard
|
||||||
|
- [ ] Custom domain configured (if applicable)
|
||||||
|
- [ ] Build command: `npm run build:prod`
|
||||||
|
- [ ] Output directory: `dist`
|
||||||
|
|
||||||
|
### For Netlify
|
||||||
|
- [ ] Netlify account created
|
||||||
|
- [ ] Project connected to repository
|
||||||
|
- [ ] Environment variables set in Netlify dashboard
|
||||||
|
- [ ] Custom domain configured (if applicable)
|
||||||
|
- [ ] Build command: `npm run build:prod`
|
||||||
|
- [ ] Publish directory: `dist`
|
||||||
|
|
||||||
|
### For Docker
|
||||||
|
- [ ] Docker image built successfully
|
||||||
|
- [ ] Container tested locally
|
||||||
|
- [ ] Image pushed to container registry
|
||||||
|
- [ ] Deployment platform configured (ECS, Cloud Run, etc.)
|
||||||
|
- [ ] Environment variables configured in platform
|
||||||
|
- [ ] Health checks configured
|
||||||
|
|
||||||
|
### For VPS/Traditional Server
|
||||||
|
- [ ] Server provisioned and accessible
|
||||||
|
- [ ] Node.js 18+ installed
|
||||||
|
- [ ] Nginx installed and configured
|
||||||
|
- [ ] SSL certificate installed
|
||||||
|
- [ ] Firewall configured
|
||||||
|
- [ ] Automatic deployment script created
|
||||||
|
|
||||||
|
## ✅ Post-Deployment
|
||||||
|
|
||||||
|
After deploying, verify:
|
||||||
|
|
||||||
|
- [ ] Application loads at production URL
|
||||||
|
- [ ] HTTPS working (no mixed content warnings)
|
||||||
|
- [ ] All routes accessible (test deep links)
|
||||||
|
- [ ] Login/authentication working
|
||||||
|
- [ ] API calls successful
|
||||||
|
- [ ] No console errors
|
||||||
|
- [ ] Error tracking receiving data
|
||||||
|
- [ ] Analytics tracking pageviews
|
||||||
|
- [ ] Performance acceptable (run Lighthouse)
|
||||||
|
|
||||||
|
## ✅ Backup & Recovery
|
||||||
|
|
||||||
|
- [ ] Previous version tagged in git
|
||||||
|
- [ ] Rollback procedure documented
|
||||||
|
- [ ] Database backup completed (if applicable)
|
||||||
|
- [ ] Configuration backed up
|
||||||
|
|
||||||
|
## ✅ Communication
|
||||||
|
|
||||||
|
- [ ] Stakeholders notified of deployment
|
||||||
|
- [ ] Maintenance window communicated (if applicable)
|
||||||
|
- [ ] Support team briefed
|
||||||
|
- [ ] Documentation shared with team
|
||||||
|
|
||||||
|
## 🚨 Emergency Contacts
|
||||||
|
|
||||||
|
Document your emergency contacts:
|
||||||
|
|
||||||
|
- **Backend Team:** _________________
|
||||||
|
- **DevOps/Infrastructure:** _________________
|
||||||
|
- **Security Team:** _________________
|
||||||
|
- **On-Call Engineer:** _________________
|
||||||
|
|
||||||
|
## 📋 Deployment Steps
|
||||||
|
|
||||||
|
1. **Pre-deployment**
|
||||||
|
- [ ] Complete this checklist
|
||||||
|
- [ ] Create git tag: `git tag v1.0.0`
|
||||||
|
- [ ] Push tag: `git push origin v1.0.0`
|
||||||
|
|
||||||
|
2. **Deployment**
|
||||||
|
- [ ] Deploy to staging first (if available)
|
||||||
|
- [ ] Test on staging
|
||||||
|
- [ ] Deploy to production
|
||||||
|
- [ ] Monitor for 15-30 minutes
|
||||||
|
|
||||||
|
3. **Post-deployment**
|
||||||
|
- [ ] Verify application working
|
||||||
|
- [ ] Check error logs
|
||||||
|
- [ ] Monitor performance
|
||||||
|
- [ ] Notify stakeholders
|
||||||
|
|
||||||
|
4. **If issues occur**
|
||||||
|
- [ ] Check error tracking service
|
||||||
|
- [ ] Review server logs
|
||||||
|
- [ ] Rollback if necessary
|
||||||
|
- [ ] Document issue for post-mortem
|
||||||
|
|
||||||
|
## 📝 Deployment Log
|
||||||
|
|
||||||
|
Keep a record of deployments:
|
||||||
|
|
||||||
|
| Date | Version | Deployed By | Status | Notes |
|
||||||
|
|------|---------|-------------|--------|-------|
|
||||||
|
| YYYY-MM-DD | v1.0.0 | Name | ✅/❌ | Initial production release |
|
||||||
|
|
||||||
|
## 🎯 Success Criteria
|
||||||
|
|
||||||
|
Deployment is successful when:
|
||||||
|
|
||||||
|
- ✅ Application loads without errors
|
||||||
|
- ✅ All critical features working
|
||||||
|
- ✅ No increase in error rate
|
||||||
|
- ✅ Performance within acceptable range
|
||||||
|
- ✅ No security vulnerabilities detected
|
||||||
|
- ✅ Monitoring and alerts active
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
|
||||||
|
1. Check `DEPLOYMENT.md` troubleshooting section
|
||||||
|
2. Review error logs in monitoring service
|
||||||
|
3. Check browser console for client-side errors
|
||||||
|
4. Verify API connectivity
|
||||||
|
5. Contact backend team if API issues
|
||||||
|
6. Rollback if critical issues persist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Remember:** It's better to delay deployment than to deploy with known issues. Take your time and verify each step.
|
||||||
|
|
||||||
|
**Good luck with your deployment! 🚀**
|
||||||
233
dev-docs/PRODUCTION_READY_SUMMARY.md
Normal file
233
dev-docs/PRODUCTION_READY_SUMMARY.md
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
# Production Ready Summary
|
||||||
|
|
||||||
|
## ✅ Issues Fixed
|
||||||
|
|
||||||
|
### 1. Build Errors (27 TypeScript errors) - FIXED
|
||||||
|
- Removed all unused imports across the codebase
|
||||||
|
- Fixed type safety issues in api-client.ts
|
||||||
|
- Added proper type annotations for error responses
|
||||||
|
- Fixed undefined variable references
|
||||||
|
- All files now compile successfully
|
||||||
|
|
||||||
|
### 2. Environment Configuration - COMPLETED
|
||||||
|
- ✅ Created `.env.example` with all required variables
|
||||||
|
- ✅ Created `.env.production.example` for production setup
|
||||||
|
- ✅ Updated `.gitignore` to exclude environment files
|
||||||
|
- ✅ Documented all environment variables in README
|
||||||
|
|
||||||
|
### 3. Documentation - COMPLETED
|
||||||
|
- ✅ Comprehensive README.md with:
|
||||||
|
- Project overview and features
|
||||||
|
- Installation instructions
|
||||||
|
- Development and production build steps
|
||||||
|
- Deployment guides for multiple platforms
|
||||||
|
- Environment variable documentation
|
||||||
|
- ✅ DEPLOYMENT.md with detailed deployment checklist
|
||||||
|
- ✅ SECURITY.md with security best practices
|
||||||
|
- ✅ This summary document
|
||||||
|
|
||||||
|
### 4. Production Optimizations - COMPLETED
|
||||||
|
- ✅ Error boundary component for graceful error handling
|
||||||
|
- ✅ Code splitting configuration in vite.config.ts
|
||||||
|
- ✅ Manual chunks for better caching (react, ui, charts, query)
|
||||||
|
- ✅ Build optimization settings
|
||||||
|
- ✅ Version updated to 1.0.0
|
||||||
|
|
||||||
|
### 5. Deployment Configuration - COMPLETED
|
||||||
|
- ✅ Dockerfile for containerized deployment
|
||||||
|
- ✅ nginx.conf with security headers and SPA routing
|
||||||
|
- ✅ vercel.json for Vercel deployment
|
||||||
|
- ✅ netlify.toml for Netlify deployment
|
||||||
|
- ✅ .dockerignore for efficient Docker builds
|
||||||
|
- ✅ GitHub Actions CI workflow
|
||||||
|
|
||||||
|
### 6. Security Improvements - COMPLETED
|
||||||
|
- ✅ Security headers configured (X-Frame-Options, CSP, etc.)
|
||||||
|
- ✅ Error boundary prevents app crashes
|
||||||
|
- ✅ Comprehensive security documentation
|
||||||
|
- ✅ Security best practices guide
|
||||||
|
- ⚠️ Token storage still uses localStorage (documented for improvement)
|
||||||
|
|
||||||
|
## 📊 Build Status
|
||||||
|
|
||||||
|
```
|
||||||
|
✓ TypeScript compilation: SUCCESS
|
||||||
|
✓ Vite build: SUCCESS
|
||||||
|
✓ Bundle size: Optimized with code splitting
|
||||||
|
✓ No critical warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Output
|
||||||
|
- Total bundle size: ~970 KB (before gzip)
|
||||||
|
- Gzipped size: ~288 KB
|
||||||
|
- Code split into 6 chunks for optimal caching
|
||||||
|
|
||||||
|
## 📁 New Files Created
|
||||||
|
|
||||||
|
### Configuration Files
|
||||||
|
- `.env.example` - Development environment template
|
||||||
|
- `.env.production.example` - Production environment template
|
||||||
|
- `vite.config.ts` - Updated with production optimizations
|
||||||
|
- `vercel.json` - Vercel deployment configuration
|
||||||
|
- `netlify.toml` - Netlify deployment configuration
|
||||||
|
- `Dockerfile` - Docker containerization
|
||||||
|
- `nginx.conf` - Nginx server configuration
|
||||||
|
- `.dockerignore` - Docker build optimization
|
||||||
|
- `.github/workflows/ci.yml` - CI/CD pipeline
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `README.md` - Comprehensive project documentation
|
||||||
|
- `DEPLOYMENT.md` - Deployment guide and checklist
|
||||||
|
- `SECURITY.md` - Security best practices
|
||||||
|
- `PRODUCTION_READY_SUMMARY.md` - This file
|
||||||
|
|
||||||
|
### Components
|
||||||
|
- `src/components/ErrorBoundary.tsx` - Error boundary component
|
||||||
|
|
||||||
|
## 🚀 Quick Start for Production
|
||||||
|
|
||||||
|
### 1. Set Up Environment
|
||||||
|
```bash
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
# Edit .env.production with your production API URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Build
|
||||||
|
```bash
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test Locally
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Deploy
|
||||||
|
Choose your platform:
|
||||||
|
- **Vercel:** `vercel --prod`
|
||||||
|
- **Netlify:** `netlify deploy --prod`
|
||||||
|
- **Docker:** `docker build -t yaltopia-admin . && docker run -p 80:80 yaltopia-admin`
|
||||||
|
|
||||||
|
## ⚠️ Important Notes Before Production
|
||||||
|
|
||||||
|
### Must Do
|
||||||
|
1. **Set up HTTPS** - Never deploy without SSL/TLS
|
||||||
|
2. **Configure environment variables** - Set VITE_API_URL to production API
|
||||||
|
3. **Test authentication flow** - Ensure login/logout works
|
||||||
|
4. **Verify API connectivity** - Test all API endpoints
|
||||||
|
5. **Configure CORS** - Backend must allow your production domain
|
||||||
|
|
||||||
|
### Should Do
|
||||||
|
1. **Set up error tracking** - Sentry, LogRocket, or similar
|
||||||
|
2. **Configure analytics** - Google Analytics, Plausible, etc.
|
||||||
|
3. **Set up monitoring** - Uptime monitoring and alerts
|
||||||
|
4. **Review security checklist** - See SECURITY.md
|
||||||
|
5. **Test on multiple browsers** - Chrome, Firefox, Safari, Edge
|
||||||
|
|
||||||
|
### Consider Doing
|
||||||
|
1. **Implement httpOnly cookies** - More secure than localStorage
|
||||||
|
2. **Add rate limiting** - Protect against abuse
|
||||||
|
3. **Set up CDN** - Cloudflare, AWS CloudFront, etc.
|
||||||
|
4. **Enable compression** - Gzip/Brotli on server
|
||||||
|
5. **Add CSP headers** - Content Security Policy
|
||||||
|
|
||||||
|
## 🔒 Security Status
|
||||||
|
|
||||||
|
### Implemented ✅
|
||||||
|
- Error boundary for graceful failures
|
||||||
|
- Security headers in deployment configs
|
||||||
|
- HTTPS enforcement in configs
|
||||||
|
- Input validation on forms
|
||||||
|
- Error handling for API calls
|
||||||
|
- Environment variable management
|
||||||
|
|
||||||
|
### Recommended Improvements ⚠️
|
||||||
|
- Move from localStorage to httpOnly cookies for tokens
|
||||||
|
- Implement Content Security Policy (CSP)
|
||||||
|
- Add rate limiting on backend
|
||||||
|
- Set up error tracking service
|
||||||
|
- Implement session timeout
|
||||||
|
- Add security monitoring
|
||||||
|
|
||||||
|
See `SECURITY.md` for detailed security recommendations.
|
||||||
|
|
||||||
|
## 📈 Performance
|
||||||
|
|
||||||
|
### Current Status
|
||||||
|
- Bundle split into 6 optimized chunks
|
||||||
|
- React vendor: 47 KB (gzipped: 17 KB)
|
||||||
|
- UI vendor: 107 KB (gzipped: 32 KB)
|
||||||
|
- Chart vendor: 383 KB (gzipped: 112 KB)
|
||||||
|
- Main app: 396 KB (gzipped: 117 KB)
|
||||||
|
|
||||||
|
### Optimization Opportunities
|
||||||
|
- Lazy load routes (if needed)
|
||||||
|
- Optimize images (if any large images added)
|
||||||
|
- Consider removing unused Radix UI components
|
||||||
|
- Implement virtual scrolling for large tables
|
||||||
|
|
||||||
|
## 🧪 Testing Checklist
|
||||||
|
|
||||||
|
Before deploying to production:
|
||||||
|
|
||||||
|
- [ ] Build completes without errors
|
||||||
|
- [ ] Application loads in browser
|
||||||
|
- [ ] Login/authentication works
|
||||||
|
- [ ] All routes accessible
|
||||||
|
- [ ] API calls successful
|
||||||
|
- [ ] Error handling works
|
||||||
|
- [ ] No console errors
|
||||||
|
- [ ] Mobile responsive
|
||||||
|
- [ ] Cross-browser compatible
|
||||||
|
- [ ] Performance acceptable (Lighthouse score)
|
||||||
|
|
||||||
|
## 📞 Support & Maintenance
|
||||||
|
|
||||||
|
### Regular Tasks
|
||||||
|
- **Daily:** Monitor error logs
|
||||||
|
- **Weekly:** Review security alerts, check for updates
|
||||||
|
- **Monthly:** Run `npm audit`, update dependencies
|
||||||
|
- **Quarterly:** Security review, performance audit
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
See `DEPLOYMENT.md` for common issues and solutions.
|
||||||
|
|
||||||
|
## 🎯 Next Steps
|
||||||
|
|
||||||
|
1. **Immediate:**
|
||||||
|
- Set up production environment variables
|
||||||
|
- Deploy to staging environment
|
||||||
|
- Run full test suite
|
||||||
|
- Deploy to production
|
||||||
|
|
||||||
|
2. **Short-term (1-2 weeks):**
|
||||||
|
- Set up error tracking (Sentry)
|
||||||
|
- Configure analytics
|
||||||
|
- Set up monitoring and alerts
|
||||||
|
- Implement security improvements
|
||||||
|
|
||||||
|
3. **Long-term (1-3 months):**
|
||||||
|
- Add automated testing
|
||||||
|
- Implement CI/CD pipeline
|
||||||
|
- Performance optimization
|
||||||
|
- Security audit
|
||||||
|
|
||||||
|
## ✨ Summary
|
||||||
|
|
||||||
|
Your Yaltopia Ticket Admin application is now **production-ready** with:
|
||||||
|
|
||||||
|
- ✅ All TypeScript errors fixed
|
||||||
|
- ✅ Build successfully compiling
|
||||||
|
- ✅ Comprehensive documentation
|
||||||
|
- ✅ Multiple deployment options configured
|
||||||
|
- ✅ Security best practices documented
|
||||||
|
- ✅ Error handling implemented
|
||||||
|
- ✅ Production optimizations applied
|
||||||
|
|
||||||
|
**The application can be deployed to production**, but review the security recommendations and complete the pre-deployment checklist in `DEPLOYMENT.md` for best results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:** 1.0.0
|
||||||
|
**Last Updated:** February 24, 2026
|
||||||
|
**Status:** ✅ Production Ready
|
||||||
206
dev-docs/QUICK_REFERENCE.md
Normal file
206
dev-docs/QUICK_REFERENCE.md
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
# Quick Reference Guide
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
npm run dev # Start dev server (http://localhost:5173)
|
||||||
|
npm run build # Build for production
|
||||||
|
npm run build:prod # Build with production env
|
||||||
|
npm run preview # Preview production build
|
||||||
|
npm run lint # Run ESLint
|
||||||
|
npm run lint:fix # Fix ESLint errors
|
||||||
|
npm run type-check # TypeScript type checking
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
vercel --prod # Deploy to Vercel
|
||||||
|
netlify deploy --prod # Deploy to Netlify
|
||||||
|
docker build -t app . # Build Docker image
|
||||||
|
docker run -p 80:80 app # Run Docker container
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Required
|
||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
VITE_ENV=development
|
||||||
|
VITE_ANALYTICS_ID=
|
||||||
|
VITE_SENTRY_DSN=
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── app/ # App config (query client)
|
||||||
|
│ ├── components/ # Reusable components
|
||||||
|
│ │ └── ui/ # UI components
|
||||||
|
│ ├── layouts/ # Layout components
|
||||||
|
│ ├── lib/ # Utils & API client
|
||||||
|
│ ├── pages/ # Page components
|
||||||
|
│ │ └── admin/ # Admin pages
|
||||||
|
│ ├── App.tsx # Main app
|
||||||
|
│ └── main.tsx # Entry point
|
||||||
|
├── .env.example # Env template
|
||||||
|
├── vite.config.ts # Vite config
|
||||||
|
├── package.json # Dependencies
|
||||||
|
└── README.md # Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `src/lib/api-client.ts` | API configuration & helpers |
|
||||||
|
| `src/app/query-client.ts` | React Query setup |
|
||||||
|
| `src/components/ErrorBoundary.tsx` | Error handling |
|
||||||
|
| `vite.config.ts` | Build configuration |
|
||||||
|
| `.env.example` | Environment template |
|
||||||
|
|
||||||
|
## API Client Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { adminApiHelpers } from '@/lib/api-client';
|
||||||
|
|
||||||
|
// Get users
|
||||||
|
const response = await adminApiHelpers.getUsers({ page: 1, limit: 20 });
|
||||||
|
|
||||||
|
// Get user by ID
|
||||||
|
const user = await adminApiHelpers.getUser(userId);
|
||||||
|
|
||||||
|
// Update user
|
||||||
|
await adminApiHelpers.updateUser(userId, { isActive: false });
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
await adminApiHelpers.deleteUser(userId);
|
||||||
|
```
|
||||||
|
|
||||||
|
## React Query Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
// Fetch data
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: ['users', page],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await adminApiHelpers.getUsers({ page });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mutate data
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (data) => {
|
||||||
|
await adminApiHelpers.updateUser(id, data);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Navigate
|
||||||
|
const navigate = useNavigate();
|
||||||
|
navigate('/admin/users');
|
||||||
|
|
||||||
|
// Get params
|
||||||
|
const { id } = useParams();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Toast Notifications
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
toast.success('Success message');
|
||||||
|
toast.error('Error message');
|
||||||
|
toast.info('Info message');
|
||||||
|
toast.warning('Warning message');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Quick Start
|
||||||
|
|
||||||
|
### Vercel
|
||||||
|
```bash
|
||||||
|
npm i -g vercel
|
||||||
|
vercel login
|
||||||
|
vercel --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Netlify
|
||||||
|
```bash
|
||||||
|
npm i -g netlify-cli
|
||||||
|
netlify login
|
||||||
|
netlify deploy --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
```bash
|
||||||
|
docker build -t yaltopia-admin .
|
||||||
|
docker run -p 80:80 yaltopia-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build fails
|
||||||
|
```bash
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type errors
|
||||||
|
```bash
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Blank page after deploy
|
||||||
|
- Check browser console
|
||||||
|
- Verify API URL in env vars
|
||||||
|
- Check server config for SPA routing
|
||||||
|
|
||||||
|
### API calls failing
|
||||||
|
- Check CORS on backend
|
||||||
|
- Verify API URL
|
||||||
|
- Check network tab in DevTools
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
- [ ] HTTPS enabled
|
||||||
|
- [ ] Environment variables set
|
||||||
|
- [ ] CORS configured
|
||||||
|
- [ ] Security headers added
|
||||||
|
- [ ] Error tracking set up
|
||||||
|
- [ ] Monitoring configured
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
- Use code splitting for large routes
|
||||||
|
- Lazy load heavy components
|
||||||
|
- Optimize images
|
||||||
|
- Enable compression (gzip/brotli)
|
||||||
|
- Use CDN for static assets
|
||||||
|
|
||||||
|
## Useful Links
|
||||||
|
|
||||||
|
- [React Query Docs](https://tanstack.com/query/latest)
|
||||||
|
- [React Router Docs](https://reactrouter.com/)
|
||||||
|
- [Vite Docs](https://vitejs.dev/)
|
||||||
|
- [Tailwind CSS Docs](https://tailwindcss.com/)
|
||||||
|
- [Radix UI Docs](https://www.radix-ui.com/)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- Check `README.md` for detailed docs
|
||||||
|
- See `DEPLOYMENT.md` for deployment guide
|
||||||
|
- Review `SECURITY.md` for security best practices
|
||||||
|
- Read `PRODUCTION_READY_SUMMARY.md` for status
|
||||||
91
dev-docs/README.md
Normal file
91
dev-docs/README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Developer Documentation
|
||||||
|
|
||||||
|
This directory contains comprehensive documentation for the Yaltopia Ticket Admin project.
|
||||||
|
|
||||||
|
## 📚 Documentation Index
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
- **[Quick Reference](./QUICK_REFERENCE.md)** - Quick start guide and common commands
|
||||||
|
- **[Tech Stack](./TECH_STACK.md)** - Technologies and frameworks used
|
||||||
|
|
||||||
|
### Development
|
||||||
|
- **[Authentication](./AUTHENTICATION.md)** - Authentication setup and flow
|
||||||
|
- **[API Standards](./API_STANDARDS.md)** - API client implementation and best practices
|
||||||
|
- **[Login API Documentation](./LOGIN_API_DOCUMENTATION.md)** - Login endpoint specifications
|
||||||
|
- **[Troubleshooting](./TROUBLESHOOTING.md)** - Common issues and solutions
|
||||||
|
|
||||||
|
### Testing & Quality
|
||||||
|
- **[Testing Guide](./TESTING_GUIDE.md)** - Testing setup and best practices
|
||||||
|
- **[CI/CD Setup](./CI_CD_SETUP.md)** - Continuous integration and deployment
|
||||||
|
- **[Error Monitoring](./ERROR_MONITORING.md)** - Sentry integration and error tracking
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- **[Security Checklist](./SECURITY_CHECKLIST.md)** - Comprehensive security requirements
|
||||||
|
- **[Security](./SECURITY.md)** - Security best practices and guidelines
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- **[Deployment Options](./DEPLOYMENT_OPTIONS.md)** - All deployment configurations (Vercel, Netlify, Docker)
|
||||||
|
- **[Deployment Guide](./DEPLOYMENT.md)** - Step-by-step deployment instructions
|
||||||
|
- **[Pre-Deployment Checklist](./PRE_DEPLOYMENT_CHECKLIST.md)** - Checklist before going live
|
||||||
|
- **[Production Ready Summary](./PRODUCTION_READY_SUMMARY.md)** - Production readiness overview
|
||||||
|
|
||||||
|
## 🎯 Quick Links
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
1. Start with [Quick Reference](./QUICK_REFERENCE.md)
|
||||||
|
2. Understand [Tech Stack](./TECH_STACK.md)
|
||||||
|
3. Set up [Authentication](./AUTHENTICATION.md)
|
||||||
|
4. Review [API Standards](./API_STANDARDS.md)
|
||||||
|
|
||||||
|
### For DevOps
|
||||||
|
1. Review [CI/CD Setup](./CI_CD_SETUP.md)
|
||||||
|
2. Choose deployment from [Deployment Options](./DEPLOYMENT_OPTIONS.md)
|
||||||
|
3. Follow [Deployment Guide](./DEPLOYMENT.md)
|
||||||
|
4. Complete [Pre-Deployment Checklist](./PRE_DEPLOYMENT_CHECKLIST.md)
|
||||||
|
|
||||||
|
### For Security Review
|
||||||
|
1. Review [Security Checklist](./SECURITY_CHECKLIST.md)
|
||||||
|
2. Check [Security](./SECURITY.md) guidelines
|
||||||
|
3. Verify [API Standards](./API_STANDARDS.md) compliance
|
||||||
|
|
||||||
|
### For Troubleshooting
|
||||||
|
1. Check [Troubleshooting](./TROUBLESHOOTING.md) guide
|
||||||
|
2. Review [Error Monitoring](./ERROR_MONITORING.md) setup
|
||||||
|
3. Consult [API Standards](./API_STANDARDS.md) for API issues
|
||||||
|
|
||||||
|
## 📖 Documentation Standards
|
||||||
|
|
||||||
|
All documentation follows these principles:
|
||||||
|
- **Clear and Concise** - Easy to understand
|
||||||
|
- **Actionable** - Includes examples and commands
|
||||||
|
- **Up-to-date** - Reflects current implementation
|
||||||
|
- **Professional** - Industry-standard practices
|
||||||
|
|
||||||
|
## 🔄 Keeping Documentation Updated
|
||||||
|
|
||||||
|
When making changes to the project:
|
||||||
|
1. Update relevant documentation
|
||||||
|
2. Add new sections if needed
|
||||||
|
3. Remove outdated information
|
||||||
|
4. Keep examples current
|
||||||
|
|
||||||
|
## 📝 Contributing to Documentation
|
||||||
|
|
||||||
|
To improve documentation:
|
||||||
|
1. Identify gaps or unclear sections
|
||||||
|
2. Add examples and use cases
|
||||||
|
3. Include troubleshooting tips
|
||||||
|
4. Keep formatting consistent
|
||||||
|
|
||||||
|
## 🆘 Need Help?
|
||||||
|
|
||||||
|
If documentation is unclear or missing:
|
||||||
|
1. Check [Troubleshooting](./TROUBLESHOOTING.md)
|
||||||
|
2. Review related documentation
|
||||||
|
3. Check code comments
|
||||||
|
4. Consult team members
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2024
|
||||||
|
**Maintained By:** Development Team
|
||||||
339
dev-docs/SECURITY.md
Normal file
339
dev-docs/SECURITY.md
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
# Security Guide
|
||||||
|
|
||||||
|
## Current Security Implementation
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- JWT tokens stored in localStorage
|
||||||
|
- Automatic token attachment to API requests via Axios interceptor
|
||||||
|
- Automatic redirect to login on 401 (Unauthorized) responses
|
||||||
|
- Token removal on logout
|
||||||
|
|
||||||
|
### API Security
|
||||||
|
- HTTPS enforcement (production requirement)
|
||||||
|
- CORS configuration on backend
|
||||||
|
- Error handling for common HTTP status codes (401, 403, 404, 500)
|
||||||
|
- Network error handling
|
||||||
|
|
||||||
|
### Client-Side Protection
|
||||||
|
- Error boundary for graceful error handling
|
||||||
|
- Input validation on forms
|
||||||
|
- XSS protection via React's built-in escaping
|
||||||
|
|
||||||
|
## Security Improvements for Production
|
||||||
|
|
||||||
|
### 1. Token Storage (High Priority)
|
||||||
|
|
||||||
|
**Current Issue:** Tokens in localStorage are vulnerable to XSS attacks.
|
||||||
|
|
||||||
|
**Recommended Solution:** Use httpOnly cookies
|
||||||
|
|
||||||
|
Backend changes needed:
|
||||||
|
```javascript
|
||||||
|
// Set cookie on login
|
||||||
|
res.cookie('access_token', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true, // HTTPS only
|
||||||
|
sameSite: 'strict',
|
||||||
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Frontend changes:
|
||||||
|
```typescript
|
||||||
|
// Remove localStorage token handling
|
||||||
|
// Cookies are automatically sent with requests
|
||||||
|
// Update api-client.ts to remove token interceptor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Content Security Policy (CSP)
|
||||||
|
|
||||||
|
Add to your hosting platform or nginx configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
Content-Security-Policy:
|
||||||
|
default-src 'self';
|
||||||
|
script-src 'self' 'unsafe-inline' 'unsafe-eval';
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' data: https:;
|
||||||
|
font-src 'self' data:;
|
||||||
|
connect-src 'self' https://api.yourdomain.com;
|
||||||
|
frame-ancestors 'none';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Adjust `unsafe-inline` and `unsafe-eval` as you refine your CSP.
|
||||||
|
|
||||||
|
### 3. HTTPS Enforcement
|
||||||
|
|
||||||
|
**Required for production!**
|
||||||
|
|
||||||
|
- Obtain SSL/TLS certificate (Let's Encrypt, Cloudflare, etc.)
|
||||||
|
- Configure your hosting platform to enforce HTTPS
|
||||||
|
- Add HSTS header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Input Validation & Sanitization
|
||||||
|
|
||||||
|
Always validate and sanitize user inputs:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: Email validation
|
||||||
|
const validateEmail = (email: string): boolean => {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example: Sanitize HTML (if displaying user content)
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
const clean = DOMPurify.sanitize(dirty);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Rate Limiting
|
||||||
|
|
||||||
|
Implement on backend:
|
||||||
|
- Login attempts: 5 per 15 minutes per IP
|
||||||
|
- API calls: 100 per minute per user
|
||||||
|
- Password reset: 3 per hour per email
|
||||||
|
|
||||||
|
Consider using:
|
||||||
|
- express-rate-limit (Node.js)
|
||||||
|
- Cloudflare rate limiting
|
||||||
|
- API Gateway rate limiting (AWS, Azure, GCP)
|
||||||
|
|
||||||
|
### 6. Dependency Security
|
||||||
|
|
||||||
|
Regular security audits:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for vulnerabilities
|
||||||
|
npm audit
|
||||||
|
|
||||||
|
# Fix automatically (if possible)
|
||||||
|
npm audit fix
|
||||||
|
|
||||||
|
# Update dependencies
|
||||||
|
npm update
|
||||||
|
|
||||||
|
# Check for outdated packages
|
||||||
|
npm outdated
|
||||||
|
```
|
||||||
|
|
||||||
|
Set up automated dependency updates:
|
||||||
|
- Dependabot (GitHub)
|
||||||
|
- Renovate Bot
|
||||||
|
- Snyk
|
||||||
|
|
||||||
|
### 7. Error Handling
|
||||||
|
|
||||||
|
**Don't expose sensitive information in errors:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Bad
|
||||||
|
catch (error) {
|
||||||
|
toast.error(error.message); // May expose stack traces
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good
|
||||||
|
catch (error) {
|
||||||
|
console.error('Error details:', error); // Log for debugging
|
||||||
|
toast.error('An error occurred. Please try again.'); // Generic message
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Secrets Management
|
||||||
|
|
||||||
|
**Never commit secrets to git:**
|
||||||
|
|
||||||
|
- Use environment variables
|
||||||
|
- Use secret management services (AWS Secrets Manager, Azure Key Vault, etc.)
|
||||||
|
- Add `.env*` to `.gitignore` ✓
|
||||||
|
- Use different secrets for dev/staging/production
|
||||||
|
|
||||||
|
### 9. API Key Protection
|
||||||
|
|
||||||
|
If using third-party APIs:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Bad - API key in client code
|
||||||
|
const API_KEY = 'sk_live_123456789';
|
||||||
|
|
||||||
|
// Good - Proxy through your backend
|
||||||
|
const response = await fetch('/api/proxy/third-party-service', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Session Management
|
||||||
|
|
||||||
|
Implement proper session handling:
|
||||||
|
|
||||||
|
- Session timeout after inactivity (15-30 minutes)
|
||||||
|
- Logout on browser close (optional)
|
||||||
|
- Single session per user (optional)
|
||||||
|
- Session invalidation on password change
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: Auto-logout on inactivity
|
||||||
|
let inactivityTimer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const resetInactivityTimer = () => {
|
||||||
|
clearTimeout(inactivityTimer);
|
||||||
|
inactivityTimer = setTimeout(() => {
|
||||||
|
// Logout user
|
||||||
|
localStorage.removeItem('access_token');
|
||||||
|
window.location.href = '/login';
|
||||||
|
}, 30 * 60 * 1000); // 30 minutes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset timer on user activity
|
||||||
|
document.addEventListener('mousemove', resetInactivityTimer);
|
||||||
|
document.addEventListener('keypress', resetInactivityTimer);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Headers Checklist
|
||||||
|
|
||||||
|
Configure these headers on your server/hosting platform:
|
||||||
|
|
||||||
|
- [x] `X-Frame-Options: SAMEORIGIN` ✓
|
||||||
|
- [x] `X-Content-Type-Options: nosniff` ✓
|
||||||
|
- [x] `X-XSS-Protection: 1; mode=block` ✓
|
||||||
|
- [x] `Referrer-Policy: strict-origin-when-cross-origin` ✓
|
||||||
|
- [ ] `Strict-Transport-Security: max-age=31536000; includeSubDomains`
|
||||||
|
- [ ] `Content-Security-Policy: ...`
|
||||||
|
- [ ] `Permissions-Policy: geolocation=(), microphone=(), camera=()`
|
||||||
|
|
||||||
|
## Monitoring & Incident Response
|
||||||
|
|
||||||
|
### 1. Error Tracking
|
||||||
|
|
||||||
|
Implement error tracking service:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: Sentry integration
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||||
|
environment: import.meta.env.VITE_ENV,
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Security Monitoring
|
||||||
|
|
||||||
|
Monitor for:
|
||||||
|
- Failed login attempts
|
||||||
|
- Unusual API usage patterns
|
||||||
|
- Error rate spikes
|
||||||
|
- Slow response times
|
||||||
|
- Unauthorized access attempts
|
||||||
|
|
||||||
|
### 3. Incident Response Plan
|
||||||
|
|
||||||
|
1. **Detection:** Monitor logs and alerts
|
||||||
|
2. **Assessment:** Determine severity and impact
|
||||||
|
3. **Containment:** Isolate affected systems
|
||||||
|
4. **Eradication:** Remove threat
|
||||||
|
5. **Recovery:** Restore normal operations
|
||||||
|
6. **Lessons Learned:** Document and improve
|
||||||
|
|
||||||
|
## Compliance Considerations
|
||||||
|
|
||||||
|
### GDPR (if applicable)
|
||||||
|
- User data encryption
|
||||||
|
- Right to be forgotten
|
||||||
|
- Data export functionality
|
||||||
|
- Privacy policy
|
||||||
|
- Cookie consent
|
||||||
|
|
||||||
|
### HIPAA (if handling health data)
|
||||||
|
- Additional encryption requirements
|
||||||
|
- Audit logging
|
||||||
|
- Access controls
|
||||||
|
- Business Associate Agreements
|
||||||
|
|
||||||
|
### PCI DSS (if handling payments)
|
||||||
|
- Never store credit card data in frontend
|
||||||
|
- Use payment gateway (Stripe, PayPal, etc.)
|
||||||
|
- Secure transmission (HTTPS)
|
||||||
|
|
||||||
|
## Security Testing
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
- [ ] Test authentication flows
|
||||||
|
- [ ] Test authorization (role-based access)
|
||||||
|
- [ ] Test input validation
|
||||||
|
- [ ] Test error handling
|
||||||
|
- [ ] Test session management
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
- [ ] OWASP ZAP scan
|
||||||
|
- [ ] npm audit
|
||||||
|
- [ ] Lighthouse security audit
|
||||||
|
- [ ] SSL Labs test (https://www.ssllabs.com/ssltest/)
|
||||||
|
|
||||||
|
### Penetration Testing
|
||||||
|
Consider hiring security professionals for:
|
||||||
|
- Vulnerability assessment
|
||||||
|
- Penetration testing
|
||||||
|
- Security code review
|
||||||
|
|
||||||
|
## Security Checklist for Production
|
||||||
|
|
||||||
|
- [ ] HTTPS enabled with valid certificate
|
||||||
|
- [ ] All security headers configured
|
||||||
|
- [ ] CSP implemented
|
||||||
|
- [ ] Tokens stored securely (httpOnly cookies)
|
||||||
|
- [ ] Input validation on all forms
|
||||||
|
- [ ] Rate limiting configured
|
||||||
|
- [ ] Error tracking service active
|
||||||
|
- [ ] Regular security audits scheduled
|
||||||
|
- [ ] Dependency updates automated
|
||||||
|
- [ ] Secrets properly managed
|
||||||
|
- [ ] Backup and recovery plan in place
|
||||||
|
- [ ] Incident response plan documented
|
||||||
|
- [ ] Team trained on security best practices
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||||
|
- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
||||||
|
- [React Security Best Practices](https://react.dev/learn/security)
|
||||||
|
- [Vite Security](https://vitejs.dev/guide/security.html)
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
If you discover a security vulnerability:
|
||||||
|
|
||||||
|
1. **Do not** open a public issue
|
||||||
|
2. Email security@yourdomain.com
|
||||||
|
3. Include detailed description and steps to reproduce
|
||||||
|
4. Allow reasonable time for fix before disclosure
|
||||||
|
|
||||||
|
## Regular Security Tasks
|
||||||
|
|
||||||
|
### Daily
|
||||||
|
- Monitor error logs
|
||||||
|
- Check failed login attempts
|
||||||
|
|
||||||
|
### Weekly
|
||||||
|
- Review security alerts
|
||||||
|
- Check for dependency updates
|
||||||
|
|
||||||
|
### Monthly
|
||||||
|
- Run security audit: `npm audit`
|
||||||
|
- Update dependencies
|
||||||
|
- Review access logs
|
||||||
|
|
||||||
|
### Quarterly
|
||||||
|
- Security training for team
|
||||||
|
- Review and update security policies
|
||||||
|
- Penetration testing (if budget allows)
|
||||||
|
|
||||||
|
### Annually
|
||||||
|
- Comprehensive security audit
|
||||||
|
- Update incident response plan
|
||||||
|
- Review compliance requirements
|
||||||
406
dev-docs/SECURITY_CHECKLIST.md
Normal file
406
dev-docs/SECURITY_CHECKLIST.md
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
# Security Checklist
|
||||||
|
|
||||||
|
## Frontend Security (✅ Implemented)
|
||||||
|
|
||||||
|
### Authentication & Authorization
|
||||||
|
- ✅ **Protected Routes**: All admin routes require authentication
|
||||||
|
- ✅ **Role-Based Access**: Checks for ADMIN role before granting access
|
||||||
|
- ✅ **Cookie Support**: `withCredentials: true` for httpOnly cookies
|
||||||
|
- ✅ **Token Refresh**: Automatic token refresh on 401 errors
|
||||||
|
- ✅ **Centralized Logout**: Calls backend to clear cookies
|
||||||
|
- ✅ **Secure Redirects**: Prevents redirect loops on login page
|
||||||
|
- ✅ **localStorage Fallback**: Works with backends without cookie support
|
||||||
|
|
||||||
|
### API Security
|
||||||
|
- ✅ **Separate API Instances**: Public vs authenticated endpoints
|
||||||
|
- ✅ **Bearer Token**: Proper Authorization header format
|
||||||
|
- ✅ **Error Handling**: Consistent error responses with user feedback
|
||||||
|
- ✅ **Request Retry**: Automatic retry after token refresh
|
||||||
|
- ✅ **CORS Credentials**: Enabled for cross-origin cookie sharing
|
||||||
|
|
||||||
|
### Code Security
|
||||||
|
- ✅ **TypeScript**: Type safety throughout the application
|
||||||
|
- ✅ **Input Validation**: Form validation on login
|
||||||
|
- ✅ **Error Messages**: Generic error messages (no sensitive info leak)
|
||||||
|
- ✅ **No Hardcoded Secrets**: Uses environment variables
|
||||||
|
|
||||||
|
## Backend Security (⚠️ Must Implement)
|
||||||
|
|
||||||
|
### Critical Requirements
|
||||||
|
|
||||||
|
#### 1. httpOnly Cookies (Recommended)
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
res.cookie('access_token', token, {
|
||||||
|
httpOnly: true, // ✅ Prevents XSS attacks
|
||||||
|
secure: true, // ✅ HTTPS only (production)
|
||||||
|
sameSite: 'strict', // ✅ CSRF protection
|
||||||
|
maxAge: 900000, // ✅ 15 minutes
|
||||||
|
path: '/'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why httpOnly?**
|
||||||
|
- Prevents JavaScript access to tokens
|
||||||
|
- Protects against XSS (Cross-Site Scripting) attacks
|
||||||
|
- Industry standard for authentication
|
||||||
|
|
||||||
|
#### 2. Token Management
|
||||||
|
- ⚠️ **Short-lived Access Tokens**: 15 minutes max
|
||||||
|
- ⚠️ **Long-lived Refresh Tokens**: 7 days max
|
||||||
|
- ⚠️ **Token Rotation**: Generate new refresh token on each refresh
|
||||||
|
- ⚠️ **Token Revocation**: Invalidate tokens on logout
|
||||||
|
- ⚠️ **Token Blacklist**: Store revoked tokens (Redis recommended)
|
||||||
|
|
||||||
|
#### 3. Password Security
|
||||||
|
- ⚠️ **Hashing**: Use bcrypt/argon2 (NOT MD5/SHA1)
|
||||||
|
- ⚠️ **Salt**: Unique salt per password
|
||||||
|
- ⚠️ **Cost Factor**: bcrypt rounds >= 12
|
||||||
|
- ⚠️ **Password Policy**: Min 8 chars, complexity requirements
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const bcrypt = require('bcrypt')
|
||||||
|
const saltRounds = 12
|
||||||
|
const hashedPassword = await bcrypt.hash(password, saltRounds)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Rate Limiting
|
||||||
|
- ⚠️ **Login Endpoint**: 5 attempts per 15 minutes per IP
|
||||||
|
- ⚠️ **API Endpoints**: 100 requests per minute per user
|
||||||
|
- ⚠️ **Account Lockout**: Lock after 5 failed login attempts
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const rateLimit = require('express-rate-limit')
|
||||||
|
|
||||||
|
const loginLimiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 5, // 5 requests per window
|
||||||
|
message: 'Too many login attempts, please try again later'
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/auth/login', loginLimiter, loginHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. CORS Configuration
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
app.use(cors({
|
||||||
|
origin: process.env.FRONTEND_URL, // Specific origin, not '*'
|
||||||
|
credentials: true, // Allow cookies
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization']
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. Input Validation
|
||||||
|
- ⚠️ **Sanitize Inputs**: Prevent SQL injection, XSS
|
||||||
|
- ⚠️ **Validate Email**: Proper email format
|
||||||
|
- ⚠️ **Validate Types**: Check data types
|
||||||
|
- ⚠️ **Limit Payload Size**: Prevent DoS attacks
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const { body, validationResult } = require('express-validator')
|
||||||
|
|
||||||
|
app.post('/auth/login', [
|
||||||
|
body('email').isEmail().normalizeEmail(),
|
||||||
|
body('password').isLength({ min: 8 })
|
||||||
|
], (req, res) => {
|
||||||
|
const errors = validationResult(req)
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({ errors: errors.array() })
|
||||||
|
}
|
||||||
|
// Process login
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. SQL Injection Prevention
|
||||||
|
- ⚠️ **Parameterized Queries**: Use prepared statements
|
||||||
|
- ⚠️ **ORM**: Use Prisma, TypeORM, Sequelize
|
||||||
|
- ⚠️ **Never**: Concatenate user input into SQL
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ❌ VULNERABLE
|
||||||
|
const query = `SELECT * FROM users WHERE email = '${email}'`
|
||||||
|
|
||||||
|
// ✅ SAFE
|
||||||
|
const query = 'SELECT * FROM users WHERE email = ?'
|
||||||
|
db.query(query, [email])
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8. XSS Prevention
|
||||||
|
- ⚠️ **Escape Output**: Sanitize data before rendering
|
||||||
|
- ⚠️ **Content Security Policy**: Set CSP headers
|
||||||
|
- ⚠️ **httpOnly Cookies**: Prevent JavaScript access to tokens
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const helmet = require('helmet')
|
||||||
|
app.use(helmet.contentSecurityPolicy({
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
imgSrc: ["'self'", "data:", "https:"],
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 9. HTTPS/TLS
|
||||||
|
- ⚠️ **Production**: HTTPS only (no HTTP)
|
||||||
|
- ⚠️ **TLS 1.2+**: Disable older versions
|
||||||
|
- ⚠️ **HSTS Header**: Force HTTPS
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
app.use(helmet.hsts({
|
||||||
|
maxAge: 31536000, // 1 year
|
||||||
|
includeSubDomains: true,
|
||||||
|
preload: true
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 10. Security Headers
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const helmet = require('helmet')
|
||||||
|
app.use(helmet()) // Sets multiple security headers
|
||||||
|
|
||||||
|
// Or manually:
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff')
|
||||||
|
res.setHeader('X-Frame-Options', 'DENY')
|
||||||
|
res.setHeader('X-XSS-Protection', '1; mode=block')
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000')
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 11. Audit Logging
|
||||||
|
- ⚠️ **Log All Admin Actions**: Who, what, when, where
|
||||||
|
- ⚠️ **Log Failed Logins**: Track suspicious activity
|
||||||
|
- ⚠️ **Log Sensitive Operations**: User deletion, role changes
|
||||||
|
- ⚠️ **Secure Logs**: Store in separate database/service
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ⚠️ BACKEND MUST IMPLEMENT
|
||||||
|
const auditLog = async (userId, action, resource, details) => {
|
||||||
|
await db.auditLogs.create({
|
||||||
|
userId,
|
||||||
|
action,
|
||||||
|
resource,
|
||||||
|
details,
|
||||||
|
ipAddress: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'],
|
||||||
|
timestamp: new Date()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 12. Database Security
|
||||||
|
- ⚠️ **Least Privilege**: Database user with minimal permissions
|
||||||
|
- ⚠️ **Encrypted Connections**: Use SSL/TLS for database
|
||||||
|
- ⚠️ **Backup Encryption**: Encrypt database backups
|
||||||
|
- ⚠️ **Sensitive Data**: Encrypt PII at rest
|
||||||
|
|
||||||
|
#### 13. Environment Variables
|
||||||
|
- ⚠️ **Never Commit**: .env files in .gitignore
|
||||||
|
- ⚠️ **Secrets Management**: Use vault (AWS Secrets Manager, etc.)
|
||||||
|
- ⚠️ **Rotate Secrets**: Regular rotation of API keys, tokens
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ⚠️ BACKEND MUST CONFIGURE
|
||||||
|
JWT_SECRET=<strong-random-secret-256-bits>
|
||||||
|
JWT_REFRESH_SECRET=<different-strong-secret>
|
||||||
|
DATABASE_URL=<encrypted-connection-string>
|
||||||
|
FRONTEND_URL=https://admin.yourdomain.com
|
||||||
|
NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 14. Session Management
|
||||||
|
- ⚠️ **Session Timeout**: Auto-logout after inactivity
|
||||||
|
- ⚠️ **Concurrent Sessions**: Limit or track multiple sessions
|
||||||
|
- ⚠️ **Session Invalidation**: Clear on logout, password change
|
||||||
|
|
||||||
|
## Additional Security Measures
|
||||||
|
|
||||||
|
### Frontend (Optional Improvements)
|
||||||
|
|
||||||
|
#### 1. Content Security Policy (CSP)
|
||||||
|
```html
|
||||||
|
<!-- Add to index.html -->
|
||||||
|
<meta http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Subresource Integrity (SRI)
|
||||||
|
```html
|
||||||
|
<!-- For CDN resources -->
|
||||||
|
<script src="https://cdn.example.com/lib.js"
|
||||||
|
integrity="sha384-..."
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Input Sanitization
|
||||||
|
```typescript
|
||||||
|
// Install DOMPurify
|
||||||
|
import DOMPurify from 'dompurify'
|
||||||
|
|
||||||
|
const sanitizedInput = DOMPurify.sanitize(userInput)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Two-Factor Authentication (2FA)
|
||||||
|
- Add TOTP support (Google Authenticator)
|
||||||
|
- SMS verification
|
||||||
|
- Backup codes
|
||||||
|
|
||||||
|
#### 5. Password Strength Meter
|
||||||
|
```typescript
|
||||||
|
// Install zxcvbn
|
||||||
|
import zxcvbn from 'zxcvbn'
|
||||||
|
|
||||||
|
const strength = zxcvbn(password)
|
||||||
|
// Show strength indicator to user
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (Additional)
|
||||||
|
|
||||||
|
#### 1. API Versioning
|
||||||
|
```javascript
|
||||||
|
app.use('/api/v1', v1Routes)
|
||||||
|
app.use('/api/v2', v2Routes)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Request Signing
|
||||||
|
- Sign requests with HMAC
|
||||||
|
- Verify signature on backend
|
||||||
|
- Prevents request tampering
|
||||||
|
|
||||||
|
#### 3. IP Whitelisting (Admin Panel)
|
||||||
|
```javascript
|
||||||
|
const adminIpWhitelist = ['192.168.1.1', '10.0.0.1']
|
||||||
|
|
||||||
|
const ipWhitelistMiddleware = (req, res, next) => {
|
||||||
|
if (!adminIpWhitelist.includes(req.ip)) {
|
||||||
|
return res.status(403).json({ message: 'Access denied' })
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/admin', ipWhitelistMiddleware)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Geo-blocking
|
||||||
|
- Block requests from certain countries
|
||||||
|
- Use CloudFlare or similar service
|
||||||
|
|
||||||
|
#### 5. DDoS Protection
|
||||||
|
- Use CloudFlare, AWS Shield
|
||||||
|
- Rate limiting at infrastructure level
|
||||||
|
- CDN for static assets
|
||||||
|
|
||||||
|
## Security Testing
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
- ⚠️ **OWASP ZAP**: Automated security scanning
|
||||||
|
- ⚠️ **npm audit**: Check for vulnerable dependencies
|
||||||
|
- ⚠️ **Snyk**: Continuous security monitoring
|
||||||
|
- ⚠️ **SonarQube**: Code quality and security
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run security audit
|
||||||
|
npm audit
|
||||||
|
npm audit fix
|
||||||
|
|
||||||
|
# Check for outdated packages
|
||||||
|
npm outdated
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
- ⚠️ **Penetration Testing**: Hire security experts
|
||||||
|
- ⚠️ **Code Review**: Security-focused code reviews
|
||||||
|
- ⚠️ **Vulnerability Scanning**: Regular scans
|
||||||
|
|
||||||
|
## Compliance
|
||||||
|
|
||||||
|
### GDPR (EU)
|
||||||
|
- ⚠️ **Data Minimization**: Collect only necessary data
|
||||||
|
- ⚠️ **Right to Erasure**: Allow users to delete their data
|
||||||
|
- ⚠️ **Data Portability**: Export user data
|
||||||
|
- ⚠️ **Consent**: Explicit consent for data processing
|
||||||
|
- ⚠️ **Privacy Policy**: Clear privacy policy
|
||||||
|
|
||||||
|
### HIPAA (Healthcare - US)
|
||||||
|
- ⚠️ **Encryption**: Encrypt PHI at rest and in transit
|
||||||
|
- ⚠️ **Access Controls**: Role-based access
|
||||||
|
- ⚠️ **Audit Logs**: Track all PHI access
|
||||||
|
- ⚠️ **Business Associate Agreement**: With third parties
|
||||||
|
|
||||||
|
### PCI DSS (Payment Cards)
|
||||||
|
- ⚠️ **Never Store**: CVV, full card numbers
|
||||||
|
- ⚠️ **Tokenization**: Use payment gateway tokens
|
||||||
|
- ⚠️ **Encryption**: Encrypt cardholder data
|
||||||
|
|
||||||
|
## Monitoring & Alerting
|
||||||
|
|
||||||
|
### What to Monitor
|
||||||
|
- ⚠️ **Failed Login Attempts**: Alert on threshold
|
||||||
|
- ⚠️ **Unusual Activity**: Large data exports, bulk deletions
|
||||||
|
- ⚠️ **API Errors**: Spike in 401/403/500 errors
|
||||||
|
- ⚠️ **Performance**: Slow queries, high CPU
|
||||||
|
- ⚠️ **Security Events**: Unauthorized access attempts
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
- **Sentry**: Error tracking
|
||||||
|
- **DataDog**: Application monitoring
|
||||||
|
- **CloudWatch**: AWS monitoring
|
||||||
|
- **Prometheus + Grafana**: Metrics and dashboards
|
||||||
|
|
||||||
|
## Incident Response Plan
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
1. **Detect**: Identify security incident
|
||||||
|
2. **Contain**: Isolate affected systems
|
||||||
|
3. **Investigate**: Determine scope and impact
|
||||||
|
4. **Remediate**: Fix vulnerability
|
||||||
|
5. **Recover**: Restore normal operations
|
||||||
|
6. **Review**: Post-incident analysis
|
||||||
|
|
||||||
|
### Contacts
|
||||||
|
- Security team email
|
||||||
|
- On-call engineer
|
||||||
|
- Legal team (for data breaches)
|
||||||
|
- PR team (for public disclosure)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
### Current Status
|
||||||
|
✅ **Frontend**: Implements industry-standard security patterns
|
||||||
|
⚠️ **Backend**: Must implement security measures listed above
|
||||||
|
|
||||||
|
### Priority Actions (Backend)
|
||||||
|
1. 🔴 **Critical**: Implement httpOnly cookies
|
||||||
|
2. 🔴 **Critical**: Hash passwords with bcrypt
|
||||||
|
3. 🔴 **Critical**: Add rate limiting
|
||||||
|
4. 🔴 **Critical**: Enable HTTPS in production
|
||||||
|
5. 🟡 **High**: Implement token refresh
|
||||||
|
6. 🟡 **High**: Add input validation
|
||||||
|
7. 🟡 **High**: Configure CORS properly
|
||||||
|
8. 🟡 **High**: Add security headers
|
||||||
|
9. 🟢 **Medium**: Implement audit logging
|
||||||
|
10. 🟢 **Medium**: Add 2FA support
|
||||||
|
|
||||||
|
### Security Score
|
||||||
|
- **Frontend**: 9/10 ✅
|
||||||
|
- **Backend**: Depends on implementation ⚠️
|
||||||
|
- **Overall**: Requires backend security implementation
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
1. Review this checklist with backend team
|
||||||
|
2. Implement critical security measures
|
||||||
|
3. Conduct security audit
|
||||||
|
4. Set up monitoring and alerting
|
||||||
|
5. Create incident response plan
|
||||||
|
6. Regular security reviews and updates
|
||||||
437
dev-docs/TECH_STACK.md
Normal file
437
dev-docs/TECH_STACK.md
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
# Tech Stack & Frameworks
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
**Yaltopia Ticket Admin** - Admin dashboard for ticket management system
|
||||||
|
|
||||||
|
## Core Technologies
|
||||||
|
|
||||||
|
### Frontend Framework
|
||||||
|
- **React 19.2.0** - Latest version with modern features
|
||||||
|
- Component-based architecture
|
||||||
|
- Hooks for state management
|
||||||
|
- Concurrent rendering
|
||||||
|
- Automatic batching
|
||||||
|
|
||||||
|
### Language
|
||||||
|
- **TypeScript 5.9.3** - Type-safe JavaScript
|
||||||
|
- Static type checking
|
||||||
|
- Enhanced IDE support
|
||||||
|
- Better code documentation
|
||||||
|
- Reduced runtime errors
|
||||||
|
|
||||||
|
### Build Tool
|
||||||
|
- **Vite 7.2.4** - Next-generation frontend tooling
|
||||||
|
- Lightning-fast HMR (Hot Module Replacement)
|
||||||
|
- Optimized production builds
|
||||||
|
- Native ES modules
|
||||||
|
- Plugin ecosystem
|
||||||
|
- Code splitting and lazy loading
|
||||||
|
|
||||||
|
## UI & Styling
|
||||||
|
|
||||||
|
### CSS Framework
|
||||||
|
- **Tailwind CSS 3.4.17** - Utility-first CSS framework
|
||||||
|
- Rapid UI development
|
||||||
|
- Consistent design system
|
||||||
|
- Responsive design utilities
|
||||||
|
- Dark mode support
|
||||||
|
- Custom theme configuration
|
||||||
|
|
||||||
|
### Component Library
|
||||||
|
- **Radix UI** - Unstyled, accessible component primitives
|
||||||
|
- `@radix-ui/react-avatar` - Avatar component
|
||||||
|
- `@radix-ui/react-dialog` - Modal dialogs
|
||||||
|
- `@radix-ui/react-dropdown-menu` - Dropdown menus
|
||||||
|
- `@radix-ui/react-label` - Form labels
|
||||||
|
- `@radix-ui/react-scroll-area` - Custom scrollbars
|
||||||
|
- `@radix-ui/react-select` - Select dropdowns
|
||||||
|
- `@radix-ui/react-separator` - Visual separators
|
||||||
|
- `@radix-ui/react-slot` - Composition utility
|
||||||
|
- `@radix-ui/react-switch` - Toggle switches
|
||||||
|
- `@radix-ui/react-tabs` - Tab navigation
|
||||||
|
- `@radix-ui/react-toast` - Toast notifications
|
||||||
|
|
||||||
|
**Why Radix UI?**
|
||||||
|
- Fully accessible (WCAG compliant)
|
||||||
|
- Unstyled (full design control)
|
||||||
|
- Keyboard navigation
|
||||||
|
- Focus management
|
||||||
|
- Screen reader support
|
||||||
|
|
||||||
|
### UI Utilities
|
||||||
|
- **class-variance-authority (CVA)** - Component variant management
|
||||||
|
- **clsx** - Conditional className utility
|
||||||
|
- **tailwind-merge** - Merge Tailwind classes intelligently
|
||||||
|
- **tailwindcss-animate** - Animation utilities
|
||||||
|
|
||||||
|
### Icons
|
||||||
|
- **Lucide React 0.561.0** - Beautiful, consistent icon set
|
||||||
|
- 1000+ icons
|
||||||
|
- Tree-shakeable
|
||||||
|
- Customizable size and color
|
||||||
|
- Accessible
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
### Router
|
||||||
|
- **React Router v7.11.0** - Declarative routing
|
||||||
|
- Nested routes
|
||||||
|
- Protected routes
|
||||||
|
- Dynamic routing
|
||||||
|
- Navigation guards
|
||||||
|
- Location state management
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Server State
|
||||||
|
- **TanStack Query (React Query) 5.90.12** - Powerful data synchronization
|
||||||
|
- Automatic caching
|
||||||
|
- Background refetching
|
||||||
|
- Optimistic updates
|
||||||
|
- Pagination support
|
||||||
|
- Infinite queries
|
||||||
|
- Devtools for debugging
|
||||||
|
|
||||||
|
**Why React Query?**
|
||||||
|
- Eliminates boilerplate for API calls
|
||||||
|
- Automatic loading/error states
|
||||||
|
- Smart caching and invalidation
|
||||||
|
- Reduces global state complexity
|
||||||
|
|
||||||
|
### Local State
|
||||||
|
- **React Hooks** - Built-in state management
|
||||||
|
- `useState` - Component state
|
||||||
|
- `useEffect` - Side effects
|
||||||
|
- `useContext` - Context API
|
||||||
|
- Custom hooks for reusability
|
||||||
|
|
||||||
|
## Data Fetching
|
||||||
|
|
||||||
|
### HTTP Client
|
||||||
|
- **Axios 1.13.2** - Promise-based HTTP client
|
||||||
|
- Request/response interceptors
|
||||||
|
- Automatic JSON transformation
|
||||||
|
- Request cancellation
|
||||||
|
- Progress tracking
|
||||||
|
- Error handling
|
||||||
|
- TypeScript support
|
||||||
|
|
||||||
|
**Features Implemented:**
|
||||||
|
- Automatic token injection
|
||||||
|
- Cookie support (`withCredentials`)
|
||||||
|
- Centralized error handling
|
||||||
|
- Automatic token refresh
|
||||||
|
- Request retry logic
|
||||||
|
|
||||||
|
## Data Visualization
|
||||||
|
|
||||||
|
### Charts
|
||||||
|
- **Recharts 3.6.0** - Composable charting library
|
||||||
|
- Line charts
|
||||||
|
- Bar charts
|
||||||
|
- Area charts
|
||||||
|
- Pie charts
|
||||||
|
- Responsive design
|
||||||
|
- Customizable styling
|
||||||
|
|
||||||
|
**Used For:**
|
||||||
|
- User growth analytics
|
||||||
|
- Revenue trends
|
||||||
|
- API usage statistics
|
||||||
|
- Error rate monitoring
|
||||||
|
- Storage analytics
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
### Date Handling
|
||||||
|
- **date-fns 4.1.0** - Modern date utility library
|
||||||
|
- Lightweight (tree-shakeable)
|
||||||
|
- Immutable
|
||||||
|
- TypeScript support
|
||||||
|
- Timezone support
|
||||||
|
- Formatting and parsing
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
- **Sonner 2.0.7** - Toast notification system
|
||||||
|
- Beautiful default styling
|
||||||
|
- Promise-based toasts
|
||||||
|
- Custom positioning
|
||||||
|
- Dismissible
|
||||||
|
- Accessible
|
||||||
|
|
||||||
|
## Development Tools
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
- **ESLint 9.39.1** - JavaScript/TypeScript linter
|
||||||
|
- Code quality enforcement
|
||||||
|
- Best practices
|
||||||
|
- Error prevention
|
||||||
|
- Custom rules
|
||||||
|
|
||||||
|
**Plugins:**
|
||||||
|
- `eslint-plugin-react-hooks` - React Hooks rules
|
||||||
|
- `eslint-plugin-react-refresh` - Fast Refresh rules
|
||||||
|
- `typescript-eslint` - TypeScript-specific rules
|
||||||
|
|
||||||
|
### Build Tools
|
||||||
|
- **PostCSS 8.5.6** - CSS transformation
|
||||||
|
- **Autoprefixer 10.4.23** - Automatic vendor prefixes
|
||||||
|
- **TypeScript Compiler** - Type checking and transpilation
|
||||||
|
|
||||||
|
### Type Definitions
|
||||||
|
- `@types/node` - Node.js types
|
||||||
|
- `@types/react` - React types
|
||||||
|
- `@types/react-dom` - React DOM types
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Design Patterns Used
|
||||||
|
|
||||||
|
1. **Component Composition**
|
||||||
|
- Reusable UI components
|
||||||
|
- Props-based customization
|
||||||
|
- Compound components
|
||||||
|
|
||||||
|
2. **Custom Hooks**
|
||||||
|
- Reusable logic extraction
|
||||||
|
- State management
|
||||||
|
- Side effects handling
|
||||||
|
|
||||||
|
3. **Higher-Order Components (HOC)**
|
||||||
|
- `ProtectedRoute` for authentication
|
||||||
|
- Route guards
|
||||||
|
|
||||||
|
4. **Render Props**
|
||||||
|
- Flexible component APIs
|
||||||
|
- Logic sharing
|
||||||
|
|
||||||
|
5. **Container/Presentational Pattern**
|
||||||
|
- Separation of concerns
|
||||||
|
- Logic vs UI separation
|
||||||
|
|
||||||
|
6. **API Client Pattern**
|
||||||
|
- Centralized API calls
|
||||||
|
- Consistent error handling
|
||||||
|
- Interceptor-based auth
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
yaltopia-ticket-admin/
|
||||||
|
├── src/
|
||||||
|
│ ├── app/ # App configuration
|
||||||
|
│ │ └── query-client.ts # React Query setup
|
||||||
|
│ ├── assets/ # Static assets
|
||||||
|
│ ├── components/ # Reusable components
|
||||||
|
│ │ ├── ui/ # Radix UI components
|
||||||
|
│ │ ├── ErrorBoundary.tsx # Error handling
|
||||||
|
│ │ └── ProtectedRoute.tsx # Auth guard
|
||||||
|
│ ├── layouts/ # Layout components
|
||||||
|
│ │ └── app-shell.tsx # Main layout
|
||||||
|
│ ├── lib/ # Utilities
|
||||||
|
│ │ ├── api-client.ts # Axios configuration
|
||||||
|
│ │ └── utils.ts # Helper functions
|
||||||
|
│ ├── pages/ # Page components
|
||||||
|
│ │ ├── admin/ # Admin pages
|
||||||
|
│ │ ├── login/ # Login page
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── App.tsx # Root component
|
||||||
|
│ ├── main.tsx # Entry point
|
||||||
|
│ └── index.css # Global styles
|
||||||
|
├── public/ # Public assets
|
||||||
|
├── dev-docs/ # Documentation
|
||||||
|
├── .env.example # Environment template
|
||||||
|
├── vite.config.ts # Vite configuration
|
||||||
|
├── tailwind.config.js # Tailwind configuration
|
||||||
|
├── tsconfig.json # TypeScript configuration
|
||||||
|
└── package.json # Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### Code Splitting
|
||||||
|
- **Manual Chunks** - Vendor code separation
|
||||||
|
- `react-vendor` - React core libraries
|
||||||
|
- `ui-vendor` - Radix UI components
|
||||||
|
- `chart-vendor` - Recharts library
|
||||||
|
- `query-vendor` - TanStack Query
|
||||||
|
|
||||||
|
### Build Optimizations
|
||||||
|
- Tree shaking (unused code removal)
|
||||||
|
- Minification
|
||||||
|
- Compression
|
||||||
|
- Source map generation (disabled in production)
|
||||||
|
- Chunk size optimization (1000kb limit)
|
||||||
|
|
||||||
|
### Runtime Optimizations
|
||||||
|
- React Query caching
|
||||||
|
- Lazy loading routes
|
||||||
|
- Image optimization
|
||||||
|
- Debounced search inputs
|
||||||
|
- Memoization where needed
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
- Chrome (latest)
|
||||||
|
- Firefox (latest)
|
||||||
|
- Safari (latest)
|
||||||
|
- Edge (latest)
|
||||||
|
|
||||||
|
**Minimum Versions:**
|
||||||
|
- Chrome 90+
|
||||||
|
- Firefox 88+
|
||||||
|
- Safari 14+
|
||||||
|
- Edge 90+
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- **Node.js**: 18+ (LTS recommended)
|
||||||
|
- **npm**: 9+ or **yarn**: 1.22+
|
||||||
|
- **Git**: 2.0+
|
||||||
|
|
||||||
|
### Recommended IDE
|
||||||
|
- **VS Code** with extensions:
|
||||||
|
- ESLint
|
||||||
|
- Prettier
|
||||||
|
- Tailwind CSS IntelliSense
|
||||||
|
- TypeScript and JavaScript Language Features
|
||||||
|
- Auto Rename Tag
|
||||||
|
- Path Intellisense
|
||||||
|
|
||||||
|
### Development Server
|
||||||
|
- **Port**: 5173 (configurable)
|
||||||
|
- **Hot Module Replacement**: Enabled
|
||||||
|
- **Host**: 0.0.0.0 (accessible from network)
|
||||||
|
|
||||||
|
## Deployment Options
|
||||||
|
|
||||||
|
### Static Hosting
|
||||||
|
- **Netlify** - Recommended
|
||||||
|
- **Vercel** - Recommended
|
||||||
|
- **AWS S3 + CloudFront**
|
||||||
|
- **Azure Static Web Apps**
|
||||||
|
- **GitHub Pages**
|
||||||
|
|
||||||
|
### Container Deployment
|
||||||
|
- **Docker** - Nginx-based container
|
||||||
|
- **Kubernetes** - Scalable deployment
|
||||||
|
- **AWS ECS/Fargate**
|
||||||
|
- **Google Cloud Run**
|
||||||
|
|
||||||
|
### CDN
|
||||||
|
- **CloudFlare** - Recommended for caching and security
|
||||||
|
- **AWS CloudFront**
|
||||||
|
- **Fastly**
|
||||||
|
|
||||||
|
## Monitoring & Analytics (Optional)
|
||||||
|
|
||||||
|
### Error Tracking
|
||||||
|
- **Sentry** - Error monitoring
|
||||||
|
- **LogRocket** - Session replay
|
||||||
|
- **Rollbar** - Error tracking
|
||||||
|
|
||||||
|
### Analytics
|
||||||
|
- **Google Analytics 4**
|
||||||
|
- **Mixpanel** - Product analytics
|
||||||
|
- **Amplitude** - User behavior
|
||||||
|
|
||||||
|
### Performance Monitoring
|
||||||
|
- **Lighthouse** - Performance audits
|
||||||
|
- **Web Vitals** - Core metrics
|
||||||
|
- **New Relic** - APM
|
||||||
|
|
||||||
|
## Security Tools
|
||||||
|
|
||||||
|
### Dependency Scanning
|
||||||
|
- `npm audit` - Vulnerability scanning
|
||||||
|
- **Snyk** - Continuous security monitoring
|
||||||
|
- **Dependabot** - Automated updates
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- **SonarQube** - Code quality and security
|
||||||
|
- **CodeQL** - Security analysis
|
||||||
|
|
||||||
|
## Testing (Not Yet Implemented)
|
||||||
|
|
||||||
|
### Recommended Testing Stack
|
||||||
|
- **Vitest** - Unit testing (Vite-native)
|
||||||
|
- **React Testing Library** - Component testing
|
||||||
|
- **Playwright** - E2E testing
|
||||||
|
- **MSW** - API mocking
|
||||||
|
|
||||||
|
## Comparison with Alternatives
|
||||||
|
|
||||||
|
### Why React over Vue/Angular?
|
||||||
|
- Larger ecosystem
|
||||||
|
- Better TypeScript support
|
||||||
|
- More job opportunities
|
||||||
|
- Flexible architecture
|
||||||
|
- Strong community
|
||||||
|
|
||||||
|
### Why Vite over Webpack/CRA?
|
||||||
|
- 10-100x faster HMR
|
||||||
|
- Faster cold starts
|
||||||
|
- Better developer experience
|
||||||
|
- Modern ES modules
|
||||||
|
- Smaller bundle sizes
|
||||||
|
|
||||||
|
### Why Tailwind over CSS-in-JS?
|
||||||
|
- Better performance (no runtime)
|
||||||
|
- Smaller bundle size
|
||||||
|
- Easier to maintain
|
||||||
|
- Better IDE support
|
||||||
|
- Consistent design system
|
||||||
|
|
||||||
|
### Why React Query over Redux?
|
||||||
|
- Less boilerplate
|
||||||
|
- Automatic caching
|
||||||
|
- Better for server state
|
||||||
|
- Simpler API
|
||||||
|
- Built-in loading/error states
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
| Package | Current | Latest Stable | Notes |
|
||||||
|
|---------|---------|---------------|-------|
|
||||||
|
| React | 19.2.0 | 19.2.0 | ✅ Latest |
|
||||||
|
| TypeScript | 5.9.3 | 5.9.x | ✅ Latest |
|
||||||
|
| Vite | 7.2.4 | 7.x | ✅ Latest |
|
||||||
|
| React Router | 7.11.0 | 7.x | ✅ Latest |
|
||||||
|
| TanStack Query | 5.90.12 | 5.x | ✅ Latest |
|
||||||
|
| Tailwind CSS | 3.4.17 | 3.x | ✅ Latest |
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
### Potential Additions
|
||||||
|
- **React Hook Form** - Form management
|
||||||
|
- **Zod** - Schema validation
|
||||||
|
- **Zustand** - Lightweight state management
|
||||||
|
- **Framer Motion** - Advanced animations
|
||||||
|
- **i18next** - Internationalization
|
||||||
|
- **React Helmet** - SEO management
|
||||||
|
|
||||||
|
### Potential Upgrades
|
||||||
|
- **React 19 Features** - Use new concurrent features
|
||||||
|
- **Vite 6** - When stable
|
||||||
|
- **TypeScript 5.10** - When released
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [React Docs](https://react.dev)
|
||||||
|
- [TypeScript Docs](https://www.typescriptlang.org/docs)
|
||||||
|
- [Vite Docs](https://vitejs.dev)
|
||||||
|
- [Tailwind CSS Docs](https://tailwindcss.com/docs)
|
||||||
|
- [React Router Docs](https://reactrouter.com)
|
||||||
|
- [TanStack Query Docs](https://tanstack.com/query)
|
||||||
|
- [Radix UI Docs](https://www.radix-ui.com)
|
||||||
|
|
||||||
|
### Learning Resources
|
||||||
|
- [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app)
|
||||||
|
- [Tailwind CSS Best Practices](https://tailwindcss.com/docs/reusing-styles)
|
||||||
|
- [React Query Tutorial](https://tanstack.com/query/latest/docs/framework/react/overview)
|
||||||
|
|
||||||
|
## License
|
||||||
|
Proprietary - All rights reserved
|
||||||
118
dev-docs/TESTING_GUIDE.md
Normal file
118
dev-docs/TESTING_GUIDE.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Testing Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This project uses **Vitest** and **React Testing Library** for testing.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests in watch mode
|
||||||
|
npm run test
|
||||||
|
|
||||||
|
# Run tests once
|
||||||
|
npm run test:run
|
||||||
|
|
||||||
|
# Run tests with UI
|
||||||
|
npm run test:ui
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
npm run test:coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/
|
||||||
|
│ ├── __tests__/
|
||||||
|
│ │ └── ProtectedRoute.test.tsx
|
||||||
|
│ └── ProtectedRoute.tsx
|
||||||
|
├── lib/
|
||||||
|
│ ├── __tests__/
|
||||||
|
│ │ └── utils.test.ts
|
||||||
|
│ └── utils.ts
|
||||||
|
├── pages/
|
||||||
|
│ └── login/
|
||||||
|
│ ├── __tests__/
|
||||||
|
│ │ └── index.test.tsx
|
||||||
|
│ └── index.tsx
|
||||||
|
└── test/
|
||||||
|
├── setup.ts # Test setup
|
||||||
|
└── test-utils.tsx # Custom render with providers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Tests
|
||||||
|
|
||||||
|
### Component Tests
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { render, screen } from '@/test/test-utils'
|
||||||
|
import MyComponent from '../MyComponent'
|
||||||
|
|
||||||
|
describe('MyComponent', () => {
|
||||||
|
it('should render correctly', () => {
|
||||||
|
render(<MyComponent />)
|
||||||
|
expect(screen.getByText('Hello')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with User Interactions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
|
it('should handle click', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<Button>Click me</Button>)
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button'))
|
||||||
|
expect(screen.getByText('Clicked')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing API Calls
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
import { adminApiHelpers } from '@/lib/api-client'
|
||||||
|
|
||||||
|
vi.mock('@/lib/api-client', () => ({
|
||||||
|
adminApiHelpers: {
|
||||||
|
getUsers: vi.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
it('should fetch users', async () => {
|
||||||
|
const mockGetUsers = vi.mocked(adminApiHelpers.getUsers)
|
||||||
|
mockGetUsers.mockResolvedValue({ data: [] })
|
||||||
|
|
||||||
|
// Test component that calls getUsers
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coverage Goals
|
||||||
|
|
||||||
|
- **Statements**: 80%+
|
||||||
|
- **Branches**: 75%+
|
||||||
|
- **Functions**: 80%+
|
||||||
|
- **Lines**: 80%+
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Test behavior, not implementation**
|
||||||
|
2. **Use semantic queries** (getByRole, getByLabelText)
|
||||||
|
3. **Avoid testing implementation details**
|
||||||
|
4. **Mock external dependencies**
|
||||||
|
5. **Keep tests simple and focused**
|
||||||
|
6. **Use descriptive test names**
|
||||||
|
|
||||||
|
## CI Integration
|
||||||
|
|
||||||
|
Tests run automatically on:
|
||||||
|
- Every push to main/develop
|
||||||
|
- Every pull request
|
||||||
|
- Before deployment
|
||||||
|
|
||||||
|
See `.github/workflows/ci.yml` for details.
|
||||||
484
dev-docs/TROUBLESHOOTING.md
Normal file
484
dev-docs/TROUBLESHOOTING.md
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
# Troubleshooting Guide
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### 1. ERR_CONNECTION_REFUSED
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
POST http://localhost:3000/api/v1/auth/login net::ERR_CONNECTION_REFUSED
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Backend server is not running or running on a different port.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Start Your Backend Server
|
||||||
|
```bash
|
||||||
|
# Navigate to backend directory
|
||||||
|
cd path/to/backend
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
npm start
|
||||||
|
# or
|
||||||
|
node server.js
|
||||||
|
# or
|
||||||
|
python manage.py runserver # Django
|
||||||
|
# or
|
||||||
|
php artisan serve # Laravel
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Check Backend Port
|
||||||
|
1. Find which port your backend is running on
|
||||||
|
2. Update `.env` file:
|
||||||
|
```env
|
||||||
|
# If backend is on port 3001
|
||||||
|
VITE_API_URL=http://localhost:3001/api/v1
|
||||||
|
|
||||||
|
# If backend is on port 8000
|
||||||
|
VITE_API_URL=http://localhost:8000/api/v1
|
||||||
|
|
||||||
|
# If backend is on port 5000
|
||||||
|
VITE_API_URL=http://localhost:5000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Restart your frontend:
|
||||||
|
```bash
|
||||||
|
# Stop the dev server (Ctrl+C)
|
||||||
|
# Start again
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Verify Backend is Running
|
||||||
|
```bash
|
||||||
|
# Test if backend is accessible
|
||||||
|
curl http://localhost:3000/api/v1/auth/login
|
||||||
|
|
||||||
|
# Or open in browser
|
||||||
|
http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
#### D. Check for Port Conflicts
|
||||||
|
```bash
|
||||||
|
# Windows - Check what's using port 3000
|
||||||
|
netstat -ano | findstr :3000
|
||||||
|
|
||||||
|
# Kill process if needed (replace PID)
|
||||||
|
taskkill /PID <PID> /F
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. CORS Error
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Access to XMLHttpRequest at 'http://localhost:3000/api/v1/auth/login'
|
||||||
|
from origin 'http://localhost:5173' has been blocked by CORS policy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Backend not configured to allow requests from frontend.
|
||||||
|
|
||||||
|
**Solution:** Configure CORS on backend
|
||||||
|
|
||||||
|
**Node.js/Express:**
|
||||||
|
```javascript
|
||||||
|
const cors = require('cors')
|
||||||
|
|
||||||
|
app.use(cors({
|
||||||
|
origin: 'http://localhost:5173', // Your frontend URL
|
||||||
|
credentials: true
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Django:**
|
||||||
|
```python
|
||||||
|
# settings.py
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:5173",
|
||||||
|
]
|
||||||
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Laravel:**
|
||||||
|
```php
|
||||||
|
// config/cors.php
|
||||||
|
'allowed_origins' => ['http://localhost:5173'],
|
||||||
|
'supports_credentials' => true,
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 404 Not Found
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
POST http://localhost:3000/api/v1/auth/login 404 (Not Found)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Backend endpoint doesn't exist or path is wrong.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Verify Backend Route
|
||||||
|
Check if your backend has the login route:
|
||||||
|
```javascript
|
||||||
|
// Should have something like:
|
||||||
|
app.post('/api/v1/auth/login', loginController)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Check API Path
|
||||||
|
Your backend might use a different path:
|
||||||
|
```env
|
||||||
|
# If backend uses /api/auth/login
|
||||||
|
VITE_API_URL=http://localhost:3000/api
|
||||||
|
|
||||||
|
# If backend uses /auth/login
|
||||||
|
VITE_API_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# If backend uses /v1/auth/login
|
||||||
|
VITE_API_URL=http://localhost:3000/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Test Backend Directly
|
||||||
|
```bash
|
||||||
|
# Test with curl
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"test@example.com","password":"test123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 401 Unauthorized
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
POST http://localhost:3000/api/v1/auth/login 401 (Unauthorized)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Invalid credentials or backend authentication issue.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Check Credentials
|
||||||
|
- Verify email/password are correct
|
||||||
|
- Check if user exists in database
|
||||||
|
- Verify user is active
|
||||||
|
|
||||||
|
#### B. Check Backend Password Hashing
|
||||||
|
```javascript
|
||||||
|
// Backend should compare hashed passwords
|
||||||
|
const isValid = await bcrypt.compare(password, user.hashedPassword)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Check Database
|
||||||
|
```sql
|
||||||
|
-- Verify user exists
|
||||||
|
SELECT * FROM users WHERE email = 'admin@example.com';
|
||||||
|
|
||||||
|
-- Check if password is hashed
|
||||||
|
SELECT password FROM users WHERE email = 'admin@example.com';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 403 Forbidden
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
POST http://localhost:3000/api/v1/auth/login 403 (Forbidden)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** User doesn't have admin role or account is inactive.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Check User Role
|
||||||
|
```sql
|
||||||
|
-- Update user role to ADMIN
|
||||||
|
UPDATE users SET role = 'ADMIN' WHERE email = 'admin@example.com';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Check Active Status
|
||||||
|
```sql
|
||||||
|
-- Activate user account
|
||||||
|
UPDATE users SET is_active = true WHERE email = 'admin@example.com';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Frontend Validation
|
||||||
|
The frontend checks `user.role === 'ADMIN'`. Make sure backend returns correct role.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Network Error (No Response)
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
```
|
||||||
|
Network Error
|
||||||
|
```
|
||||||
|
|
||||||
|
**Causes & Solutions:**
|
||||||
|
|
||||||
|
#### A. Backend Crashed
|
||||||
|
Check backend console for errors and restart.
|
||||||
|
|
||||||
|
#### B. Firewall Blocking
|
||||||
|
Temporarily disable firewall or add exception.
|
||||||
|
|
||||||
|
#### C. Wrong Protocol
|
||||||
|
```env
|
||||||
|
# Use http for local development
|
||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
|
# NOT https
|
||||||
|
# VITE_API_URL=https://localhost:3000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Environment Variables Not Loading
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
API calls go to wrong URL or undefined.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Create .env File
|
||||||
|
```bash
|
||||||
|
# Copy example file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit with your values
|
||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Restart Dev Server
|
||||||
|
```bash
|
||||||
|
# Stop server (Ctrl+C)
|
||||||
|
# Start again
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Check Variable Name
|
||||||
|
Must start with `VITE_`:
|
||||||
|
```env
|
||||||
|
# ✅ Correct
|
||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
|
||||||
|
# ❌ Wrong (won't work)
|
||||||
|
API_URL=http://localhost:3000/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### D. Access in Code
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct
|
||||||
|
import.meta.env.VITE_API_URL
|
||||||
|
|
||||||
|
// ❌ Wrong
|
||||||
|
process.env.VITE_API_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Token Not Persisting
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
User logged out after page refresh.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Check localStorage
|
||||||
|
```javascript
|
||||||
|
// Open browser console
|
||||||
|
localStorage.getItem('access_token')
|
||||||
|
localStorage.getItem('user')
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Check Cookie Settings
|
||||||
|
If using httpOnly cookies, check browser DevTools > Application > Cookies.
|
||||||
|
|
||||||
|
#### C. Backend Must Return Token
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "...",
|
||||||
|
"user": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Infinite Redirect Loop
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
Page keeps redirecting between login and dashboard.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Check ProtectedRoute Logic
|
||||||
|
```typescript
|
||||||
|
// Should check for token
|
||||||
|
const token = localStorage.getItem('access_token')
|
||||||
|
if (!token) {
|
||||||
|
return <Navigate to="/login" />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Clear localStorage
|
||||||
|
```javascript
|
||||||
|
// Browser console
|
||||||
|
localStorage.clear()
|
||||||
|
// Then try logging in again
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Tests Hanging
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
Tests run forever without completing.
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
#### A. Add Timeout
|
||||||
|
```typescript
|
||||||
|
// In test file
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
|
||||||
|
vi.setConfig({ testTimeout: 10000 })
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Mock Timers
|
||||||
|
```typescript
|
||||||
|
vi.useFakeTimers()
|
||||||
|
// ... test code
|
||||||
|
vi.useRealTimers()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Check for Unresolved Promises
|
||||||
|
Make sure all async operations complete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
### 1. Check Browser Console
|
||||||
|
Press F12 and look for errors in Console tab.
|
||||||
|
|
||||||
|
### 2. Check Network Tab
|
||||||
|
1. Press F12
|
||||||
|
2. Go to Network tab
|
||||||
|
3. Try logging in
|
||||||
|
4. Click on the failed request
|
||||||
|
5. Check:
|
||||||
|
- Request URL
|
||||||
|
- Request Headers
|
||||||
|
- Request Payload
|
||||||
|
- Response
|
||||||
|
|
||||||
|
### 3. Check Backend Logs
|
||||||
|
Look at your backend console for error messages.
|
||||||
|
|
||||||
|
### 4. Test Backend Independently
|
||||||
|
Use curl or Postman to test backend without frontend.
|
||||||
|
|
||||||
|
### 5. Verify Environment Variables
|
||||||
|
```bash
|
||||||
|
# Check if .env file exists
|
||||||
|
ls -la .env
|
||||||
|
|
||||||
|
# Check contents
|
||||||
|
cat .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Clear Browser Cache
|
||||||
|
Sometimes old cached files cause issues:
|
||||||
|
1. Press Ctrl+Shift+Delete
|
||||||
|
2. Clear cache and cookies
|
||||||
|
3. Restart browser
|
||||||
|
|
||||||
|
### 7. Check Node Version
|
||||||
|
```bash
|
||||||
|
node --version
|
||||||
|
# Should be 18.x or 20.x
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Checklist
|
||||||
|
|
||||||
|
Before asking for help, verify:
|
||||||
|
|
||||||
|
- [ ] Backend server is running
|
||||||
|
- [ ] Backend is on correct port
|
||||||
|
- [ ] `.env` file exists with correct API URL
|
||||||
|
- [ ] Frontend dev server restarted after .env changes
|
||||||
|
- [ ] CORS configured on backend
|
||||||
|
- [ ] Login endpoint exists on backend
|
||||||
|
- [ ] Test user exists in database
|
||||||
|
- [ ] User has ADMIN role
|
||||||
|
- [ ] User account is active
|
||||||
|
- [ ] Browser console shows no errors
|
||||||
|
- [ ] Network tab shows request details
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
If still stuck:
|
||||||
|
|
||||||
|
1. **Check Documentation**
|
||||||
|
- [Authentication Setup](./AUTHENTICATION.md)
|
||||||
|
- [Login API Documentation](./LOGIN_API_DOCUMENTATION.md)
|
||||||
|
- [API Standards](./API_STANDARDS.md)
|
||||||
|
|
||||||
|
2. **Gather Information**
|
||||||
|
- Error message
|
||||||
|
- Browser console logs
|
||||||
|
- Network tab details
|
||||||
|
- Backend logs
|
||||||
|
- Environment variables
|
||||||
|
|
||||||
|
3. **Test Systematically**
|
||||||
|
- Test backend with curl
|
||||||
|
- Test with Postman
|
||||||
|
- Check database directly
|
||||||
|
- Verify each step
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Development Setup
|
||||||
|
|
||||||
|
### Typical Setup
|
||||||
|
```
|
||||||
|
Frontend: http://localhost:5173 (Vite)
|
||||||
|
Backend: http://localhost:3000 (Node.js)
|
||||||
|
Database: localhost:5432 (PostgreSQL)
|
||||||
|
```
|
||||||
|
|
||||||
|
### .env Configuration
|
||||||
|
```env
|
||||||
|
VITE_API_URL=http://localhost:3000/api/v1
|
||||||
|
VITE_ENV=development
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend CORS
|
||||||
|
```javascript
|
||||||
|
app.use(cors({
|
||||||
|
origin: 'http://localhost:5173',
|
||||||
|
credentials: true
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Login
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"admin@example.com","password":"admin123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2024
|
||||||
24
netlify.toml
Normal file
24
netlify.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
[build]
|
||||||
|
command = "npm run build:prod"
|
||||||
|
publish = "dist"
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NODE_VERSION = "18"
|
||||||
|
|
||||||
|
[[headers]]
|
||||||
|
for = "/*"
|
||||||
|
[headers.values]
|
||||||
|
X-Frame-Options = "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options = "nosniff"
|
||||||
|
X-XSS-Protection = "1; mode=block"
|
||||||
|
Referrer-Policy = "strict-origin-when-cross-origin"
|
||||||
|
|
||||||
|
[[headers]]
|
||||||
|
for = "/assets/*"
|
||||||
|
[headers.values]
|
||||||
|
Cache-Control = "public, max-age=31536000, immutable"
|
||||||
34
nginx.conf
Normal file
34
nginx.conf
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# SPA routing - serve index.html for all routes
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable access to hidden files
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
2237
package-lock.json
generated
2237
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
|
|
@ -1,13 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "yaltopia-ticket-admin",
|
"name": "yaltopia-ticket-admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
|
"build:prod": "tsc -b && vite build --mode production",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"lint:fix": "eslint . --fix",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"type-check": "tsc -b --noEmit",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:ui": "vitest --ui",
|
||||||
|
"test:run": "vitest run",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
|
|
@ -21,6 +28,7 @@
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
|
"@sentry/react": "^10.39.0",
|
||||||
"@tanstack/react-query": "^5.90.12",
|
"@tanstack/react-query": "^5.90.12",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|
@ -36,20 +44,26 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"@vitest/ui": "^4.0.18",
|
||||||
"autoprefixer": "^10.4.23",
|
"autoprefixer": "^10.4.23",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.4",
|
"typescript-eslint": "^8.46.4",
|
||||||
"vite": "^7.2.4"
|
"vite": "^7.2.4",
|
||||||
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/App.tsx
24
src/App.tsx
|
|
@ -1,13 +1,12 @@
|
||||||
import { Navigate, Route, Routes } from "react-router-dom"
|
import { Navigate, Route, Routes } from "react-router-dom"
|
||||||
import { AppShell } from "@/layouts/app-shell"
|
import { AppShell } from "@/layouts/app-shell"
|
||||||
|
import { ProtectedRoute } from "@/components/ProtectedRoute"
|
||||||
|
import LoginPage from "@/pages/login"
|
||||||
import DashboardPage from "@/pages/admin/dashboard"
|
import DashboardPage from "@/pages/admin/dashboard"
|
||||||
import UsersPage from "@/pages/admin/users"
|
import UsersPage from "@/pages/admin/users"
|
||||||
import UserDetailsPage from "@/pages/admin/users/[id]"
|
import UserDetailsPage from "@/pages/admin/users/[id]"
|
||||||
import UserActivityPage from "@/pages/admin/users/[id]/activity"
|
import UserActivityPage from "@/pages/admin/users/[id]/activity"
|
||||||
import LogsPage from "@/pages/admin/logs"
|
import ActivityLogPage from "@/pages/activity-log"
|
||||||
import ErrorLogsPage from "@/pages/admin/logs/errors"
|
|
||||||
import AccessLogsPage from "@/pages/admin/logs/access"
|
|
||||||
import LogDetailsPage from "@/pages/admin/logs/[id]"
|
|
||||||
import SettingsPage from "@/pages/admin/settings"
|
import SettingsPage from "@/pages/admin/settings"
|
||||||
import MaintenancePage from "@/pages/admin/maintenance"
|
import MaintenancePage from "@/pages/admin/maintenance"
|
||||||
import AnnouncementsPage from "@/pages/admin/announcements"
|
import AnnouncementsPage from "@/pages/admin/announcements"
|
||||||
|
|
@ -29,16 +28,23 @@ import HealthPage from "@/pages/admin/health"
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<AppShell />}>
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<AppShell />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Route index element={<Navigate to="/admin/dashboard" replace />} />
|
<Route index element={<Navigate to="/admin/dashboard" replace />} />
|
||||||
<Route path="admin/dashboard" element={<DashboardPage />} />
|
<Route path="admin/dashboard" element={<DashboardPage />} />
|
||||||
<Route path="admin/users" element={<UsersPage />} />
|
<Route path="admin/users" element={<UsersPage />} />
|
||||||
<Route path="admin/users/:id" element={<UserDetailsPage />} />
|
<Route path="admin/users/:id" element={<UserDetailsPage />} />
|
||||||
<Route path="admin/users/:id/activity" element={<UserActivityPage />} />
|
<Route path="admin/users/:id/activity" element={<UserActivityPage />} />
|
||||||
<Route path="admin/logs" element={<LogsPage />} />
|
<Route path="admin/logs" element={<ActivityLogPage />} />
|
||||||
<Route path="admin/logs/errors" element={<ErrorLogsPage />} />
|
<Route path="admin/logs/errors" element={<ActivityLogPage />} />
|
||||||
<Route path="admin/logs/access" element={<AccessLogsPage />} />
|
<Route path="admin/logs/access" element={<ActivityLogPage />} />
|
||||||
<Route path="admin/logs/:id" element={<LogDetailsPage />} />
|
<Route path="admin/logs/:id" element={<ActivityLogPage />} />
|
||||||
<Route path="admin/settings" element={<SettingsPage />} />
|
<Route path="admin/settings" element={<SettingsPage />} />
|
||||||
<Route path="admin/maintenance" element={<MaintenancePage />} />
|
<Route path="admin/maintenance" element={<MaintenancePage />} />
|
||||||
<Route path="admin/announcements" element={<AnnouncementsPage />} />
|
<Route path="admin/announcements" element={<AnnouncementsPage />} />
|
||||||
|
|
|
||||||
27
src/components/DebugLogin.tsx
Normal file
27
src/components/DebugLogin.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Temporary debug component - Remove after fixing login issue
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
export function DebugLogin() {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('=== LOGIN DEBUG INFO ===')
|
||||||
|
console.log('Current path:', window.location.pathname)
|
||||||
|
console.log('Access token:', localStorage.getItem('access_token'))
|
||||||
|
console.log('User:', localStorage.getItem('user'))
|
||||||
|
console.log('Token exists:', !!localStorage.getItem('access_token'))
|
||||||
|
|
||||||
|
// Parse user if exists
|
||||||
|
const userStr = localStorage.getItem('user')
|
||||||
|
if (userStr) {
|
||||||
|
try {
|
||||||
|
const user = JSON.parse(userStr)
|
||||||
|
console.log('User role:', user.role)
|
||||||
|
console.log('Is admin:', user.role === 'ADMIN')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse user:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('========================')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
87
src/components/ErrorBoundary.tsx
Normal file
87
src/components/ErrorBoundary.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Component } from 'react';
|
||||||
|
import type { ErrorInfo, ReactNode } from 'react';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { AlertCircle } from 'lucide-react';
|
||||||
|
import { Sentry } from '@/lib/sentry';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
hasError: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends Component<Props, State> {
|
||||||
|
public state: State = {
|
||||||
|
hasError: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static getDerivedStateFromError(error: Error): State {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||||
|
console.error('Uncaught error:', error, errorInfo);
|
||||||
|
|
||||||
|
// Log to Sentry
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
contexts: {
|
||||||
|
react: {
|
||||||
|
componentStack: errorInfo.componentStack,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleReset = () => {
|
||||||
|
this.setState({ hasError: false, error: null });
|
||||||
|
window.location.href = '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||||
|
<Card className="max-w-md w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-destructive">
|
||||||
|
<AlertCircle className="w-5 h-5" />
|
||||||
|
Something went wrong
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
An unexpected error occurred. The error has been logged and we'll look into it. Please try refreshing the page or contact support if the problem persists.
|
||||||
|
</p>
|
||||||
|
{import.meta.env.DEV && this.state.error && (
|
||||||
|
<div className="p-3 bg-muted rounded-md">
|
||||||
|
<p className="text-xs font-mono text-destructive break-all">
|
||||||
|
{this.state.error.message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button onClick={this.handleReset} className="flex-1">
|
||||||
|
Go to Home
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Refresh Page
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/components/ProtectedRoute.tsx
Normal file
17
src/components/ProtectedRoute.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Navigate, useLocation } from "react-router-dom"
|
||||||
|
|
||||||
|
interface ProtectedRouteProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProtectedRoute({ children }: ProtectedRouteProps) {
|
||||||
|
const location = useLocation()
|
||||||
|
const token = localStorage.getItem('access_token')
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
// Redirect to login page with return URL
|
||||||
|
return <Navigate to="/login" state={{ from: location }} replace />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
36
src/components/__tests__/ProtectedRoute.test.tsx
Normal file
36
src/components/__tests__/ProtectedRoute.test.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
import { render, screen } from '@/test/test-utils'
|
||||||
|
import { ProtectedRoute } from '../ProtectedRoute'
|
||||||
|
|
||||||
|
describe('ProtectedRoute', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clear localStorage before each test
|
||||||
|
localStorage.clear()
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redirect to login when no token exists', () => {
|
||||||
|
render(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>Protected Content</div>
|
||||||
|
</ProtectedRoute>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Should not render protected content
|
||||||
|
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render children when token exists', () => {
|
||||||
|
// Set token in localStorage
|
||||||
|
localStorage.setItem('access_token', 'fake-token')
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>Protected Content</div>
|
||||||
|
</ProtectedRoute>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Should render protected content
|
||||||
|
expect(screen.getByText('Protected Content')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Outlet, Link, useLocation } from "react-router-dom"
|
import { Outlet, Link, useLocation, useNavigate } from "react-router-dom"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Users,
|
Users,
|
||||||
|
|
@ -18,9 +19,15 @@ import {
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||||
import { Separator } from "@/components/ui/separator"
|
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
email: string
|
||||||
|
firstName?: string
|
||||||
|
lastName?: string
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
|
||||||
const adminNavigationItems = [
|
const adminNavigationItems = [
|
||||||
{ icon: LayoutDashboard, label: "Dashboard", path: "/admin/dashboard" },
|
{ icon: LayoutDashboard, label: "Dashboard", path: "/admin/dashboard" },
|
||||||
|
|
@ -37,6 +44,19 @@ const adminNavigationItems = [
|
||||||
|
|
||||||
export function AppShell() {
|
export function AppShell() {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [user, setUser] = useState<User | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const userStr = localStorage.getItem('user')
|
||||||
|
if (userStr) {
|
||||||
|
try {
|
||||||
|
setUser(JSON.parse(userStr))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse user data:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const isActive = (path: string) => {
|
const isActive = (path: string) => {
|
||||||
return location.pathname.startsWith(path)
|
return location.pathname.startsWith(path)
|
||||||
|
|
@ -50,6 +70,28 @@ export function AppShell() {
|
||||||
return item?.label || "Admin Panel"
|
return item?.label || "Admin Panel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
adminApiHelpers.logout()
|
||||||
|
navigate('/login', { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserInitials = () => {
|
||||||
|
if (user?.firstName && user?.lastName) {
|
||||||
|
return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase()
|
||||||
|
}
|
||||||
|
if (user?.email) {
|
||||||
|
return user.email.substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
return 'AD'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserDisplayName = () => {
|
||||||
|
if (user?.firstName && user?.lastName) {
|
||||||
|
return `${user.firstName} ${user.lastName}`
|
||||||
|
}
|
||||||
|
return user?.email || 'Admin User'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-background">
|
<div className="flex h-screen bg-background">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
|
|
@ -88,21 +130,18 @@ export function AppShell() {
|
||||||
<div className="p-4 border-t">
|
<div className="p-4 border-t">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarFallback>AD</AvatarFallback>
|
<AvatarFallback>{getUserInitials()}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium truncate">Admin User</p>
|
<p className="text-sm font-medium truncate">{getUserDisplayName()}</p>
|
||||||
<p className="text-xs text-muted-foreground truncate">admin@example.com</p>
|
<p className="text-xs text-muted-foreground truncate">{user?.email || 'admin@example.com'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => {
|
onClick={handleLogout}
|
||||||
localStorage.removeItem('access_token')
|
|
||||||
window.location.href = '/login'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LogOut className="w-4 h-4 mr-2" />
|
<LogOut className="w-4 h-4 mr-2" />
|
||||||
Logout
|
Logout
|
||||||
|
|
@ -132,7 +171,7 @@ export function AppShell() {
|
||||||
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" />
|
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" />
|
||||||
</Button>
|
</Button>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarFallback>AD</AvatarFallback>
|
<AvatarFallback>{getUserInitials()}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
25
src/lib/__tests__/utils.test.ts
Normal file
25
src/lib/__tests__/utils.test.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { cn } from '../utils'
|
||||||
|
|
||||||
|
describe('utils', () => {
|
||||||
|
describe('cn', () => {
|
||||||
|
it('should merge class names correctly', () => {
|
||||||
|
const result = cn('text-red-500', 'bg-blue-500')
|
||||||
|
expect(result).toContain('text-red-500')
|
||||||
|
expect(result).toContain('bg-blue-500')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle conditional classes', () => {
|
||||||
|
const result = cn('base-class', false && 'hidden', true && 'visible')
|
||||||
|
expect(result).toContain('base-class')
|
||||||
|
expect(result).toContain('visible')
|
||||||
|
expect(result).not.toContain('hidden')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should merge conflicting Tailwind classes', () => {
|
||||||
|
const result = cn('p-4', 'p-8')
|
||||||
|
// tailwind-merge should keep only p-8
|
||||||
|
expect(result).toBe('p-8')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,18 +1,30 @@
|
||||||
import axios, { type AxiosInstance, type AxiosError } from 'axios';
|
import axios, { type AxiosInstance, type AxiosError } from 'axios';
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1';
|
const API_BASE_URL = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:3000/api/v1';
|
||||||
|
|
||||||
// Create axios instance
|
// Create separate axios instance for public endpoints (no auth required)
|
||||||
|
const publicApi: AxiosInstance = axios.create({
|
||||||
|
baseURL: API_BASE_URL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
withCredentials: true, // Important: Send cookies with requests
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create axios instance for authenticated endpoints
|
||||||
const adminApi: AxiosInstance = axios.create({
|
const adminApi: AxiosInstance = axios.create({
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
withCredentials: true, // Important: Send cookies with requests
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add token interceptor
|
// Add token interceptor for localStorage fallback (if not using cookies)
|
||||||
adminApi.interceptors.request.use(
|
adminApi.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
// Only add Authorization header if token exists in localStorage
|
||||||
|
// (This is fallback - cookies are preferred)
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
|
@ -27,10 +39,35 @@ adminApi.interceptors.request.use(
|
||||||
// Add response interceptor for error handling
|
// Add response interceptor for error handling
|
||||||
adminApi.interceptors.response.use(
|
adminApi.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error: AxiosError) => {
|
async (error: AxiosError<{ message?: string }>) => {
|
||||||
|
const originalRequest = error.config as any;
|
||||||
|
|
||||||
|
// Handle 401 Unauthorized
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// Redirect to login
|
// Don't redirect if already on login page
|
||||||
localStorage.removeItem('access_token');
|
if (window.location.pathname.includes('/login')) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to refresh token if not already retrying
|
||||||
|
if (!originalRequest._retry) {
|
||||||
|
originalRequest._retry = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt token refresh
|
||||||
|
await adminApiHelpers.refreshToken();
|
||||||
|
// Retry original request
|
||||||
|
return adminApi(originalRequest);
|
||||||
|
} catch (refreshError) {
|
||||||
|
// Refresh failed, logout user
|
||||||
|
adminApiHelpers.logout();
|
||||||
|
window.location.href = '/login';
|
||||||
|
return Promise.reject(refreshError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If retry failed, logout
|
||||||
|
adminApiHelpers.logout();
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
} else if (error.response?.status === 403) {
|
} else if (error.response?.status === 403) {
|
||||||
// Show access denied
|
// Show access denied
|
||||||
|
|
@ -68,6 +105,27 @@ adminApi.interceptors.response.use(
|
||||||
|
|
||||||
// API helper functions
|
// API helper functions
|
||||||
export const adminApiHelpers = {
|
export const adminApiHelpers = {
|
||||||
|
// Auth - uses publicApi (no token required)
|
||||||
|
login: (data: { email: string; password: string }) =>
|
||||||
|
publicApi.post('/auth/login', data),
|
||||||
|
|
||||||
|
logout: async () => {
|
||||||
|
try {
|
||||||
|
// Call backend logout to clear httpOnly cookies
|
||||||
|
await adminApi.post('/auth/logout');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout error:', error);
|
||||||
|
} finally {
|
||||||
|
// Always clear localStorage
|
||||||
|
localStorage.removeItem('access_token');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshToken: () => adminApi.post('/auth/refresh'),
|
||||||
|
|
||||||
|
getCurrentUser: () => adminApi.get('/auth/me'),
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
getUsers: (params?: {
|
getUsers: (params?: {
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
|
||||||
46
src/lib/sentry.ts
Normal file
46
src/lib/sentry.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import * as Sentry from '@sentry/react'
|
||||||
|
|
||||||
|
export const initSentry = () => {
|
||||||
|
const dsn = import.meta.env.VITE_SENTRY_DSN
|
||||||
|
const environment = import.meta.env.VITE_ENV || 'development'
|
||||||
|
|
||||||
|
// Only initialize Sentry if DSN is provided and not in development
|
||||||
|
if (dsn && environment !== 'development') {
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
environment,
|
||||||
|
integrations: [
|
||||||
|
Sentry.browserTracingIntegration(),
|
||||||
|
Sentry.replayIntegration({
|
||||||
|
maskAllText: true,
|
||||||
|
blockAllMedia: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// Performance Monitoring
|
||||||
|
tracesSampleRate: environment === 'production' ? 0.1 : 1.0,
|
||||||
|
// Session Replay
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
// Error filtering
|
||||||
|
beforeSend(event, hint) {
|
||||||
|
// Filter out errors from browser extensions
|
||||||
|
if (event.exception) {
|
||||||
|
const error = hint.originalException
|
||||||
|
if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
const message = String(error.message)
|
||||||
|
if (
|
||||||
|
message.includes('chrome-extension://') ||
|
||||||
|
message.includes('moz-extension://')
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export Sentry for manual error logging
|
||||||
|
export { Sentry }
|
||||||
|
|
@ -6,14 +6,21 @@ import { BrowserRouter } from "react-router-dom"
|
||||||
import { QueryClientProvider } from "@tanstack/react-query"
|
import { QueryClientProvider } from "@tanstack/react-query"
|
||||||
import { queryClient } from "@/app/query-client"
|
import { queryClient } from "@/app/query-client"
|
||||||
import { Toaster } from "@/components/ui/toast"
|
import { Toaster } from "@/components/ui/toast"
|
||||||
|
import { ErrorBoundary } from "@/components/ErrorBoundary"
|
||||||
|
import { initSentry } from "@/lib/sentry"
|
||||||
|
|
||||||
|
// Initialize Sentry
|
||||||
|
initSentry()
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
<ErrorBoundary>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { BarChart3, Users, DollarSign, HardDrive, Activity } from "lucide-react"
|
import { BarChart3, Users, DollarSign, HardDrive, Activity } from "lucide-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,12 @@ export default function AnalyticsStoragePage() {
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="50%"
|
||||||
labelLine={false}
|
labelLine={false}
|
||||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
label={({ name, percent }) => `${name} ${((percent ?? 0) * 100).toFixed(0)}%`}
|
||||||
outerRadius={80}
|
outerRadius={80}
|
||||||
fill="#8884d8"
|
fill="#8884d8"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
{chartData.map((entry: any, index: number) => (
|
{chartData.map((_entry: any, index: number) => (
|
||||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,13 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Plus, Edit, Trash2 } from "lucide-react"
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { Megaphone, Plus, Edit, Trash2 } from "lucide-react"
|
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
||||||
export default function AnnouncementsPage() {
|
export default function AnnouncementsPage() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const [createDialogOpen, setCreateDialogOpen] = useState(false)
|
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<any>(null)
|
const [selectedAnnouncement, setSelectedAnnouncement] = useState<any>(null)
|
||||||
|
|
||||||
|
|
@ -64,7 +61,7 @@ export default function AnnouncementsPage() {
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-3xl font-bold">Announcements</h2>
|
<h2 className="text-3xl font-bold">Announcements</h2>
|
||||||
<Button onClick={() => setCreateDialogOpen(true)}>
|
<Button>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
Create Announcement
|
Create Announcement
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
||||||
export default function AuditPage() {
|
export default function AuditPage() {
|
||||||
const [page, setPage] = useState(1)
|
const [page] = useState(1)
|
||||||
const [limit] = useState(50)
|
const [limit] = useState(50)
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Download, Users, FileText, DollarSign, HardDrive, TrendingUp, AlertCircle } from "lucide-react"
|
import { Download, Users, FileText, DollarSign, HardDrive, AlertCircle } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"
|
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { AlertCircle, CheckCircle, XCircle, Database, Users, Activity } from "lucide-react"
|
import { AlertCircle, CheckCircle, XCircle, Users } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
|
|
||||||
export default function HealthPage() {
|
export default function HealthPage() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { Key, Ban } from "lucide-react"
|
import { Ban } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -16,7 +17,7 @@ import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
||||||
export default function FailedLoginsPage() {
|
export default function FailedLoginsPage() {
|
||||||
const [page, setPage] = useState(1)
|
const [page] = useState(1)
|
||||||
const [limit] = useState(50)
|
const [limit] = useState(50)
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Shield, AlertTriangle, Key, Gauge, Users } from "lucide-react"
|
import { Shield, AlertTriangle, Key, Gauge, Users } from "lucide-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Shield, Ban } from "lucide-react"
|
import { Shield, Ban } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
import { useParams, useNavigate } from "react-router-dom"
|
import { useParams, useNavigate } from "react-router-dom"
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { ArrowLeft, Edit, Key, Trash2 } from "lucide-react"
|
import { ArrowLeft, Edit, Key } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { toast } from "sonner"
|
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
||||||
export default function UserDetailsPage() {
|
export default function UserDetailsPage() {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
const { data: user, isLoading } = useQuery({
|
const { data: user, isLoading } = useQuery({
|
||||||
queryKey: ['admin', 'users', id],
|
queryKey: ['admin', 'users', id],
|
||||||
|
|
@ -23,19 +21,6 @@ export default function UserDetailsPage() {
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateUserMutation = useMutation({
|
|
||||||
mutationFn: async (data: any) => {
|
|
||||||
await adminApiHelpers.updateUser(id!, data)
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin', 'users', id] })
|
|
||||||
toast.success("User updated successfully")
|
|
||||||
},
|
|
||||||
onError: (error: any) => {
|
|
||||||
toast.error(error.response?.data?.message || "Failed to update user")
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className="text-center py-8">Loading user details...</div>
|
return <div className="text-center py-8">Loading user details...</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { Search, Download, Eye, MoreVertical, UserPlus, Edit, Trash2, Key, Upload } from "lucide-react"
|
import { Search, Download, Eye, UserPlus, Trash2, Key, Upload } from "lucide-react"
|
||||||
import { adminApiHelpers } from "@/lib/api-client"
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
|
|
|
||||||
114
src/pages/login/__tests__/index.test.tsx
Normal file
114
src/pages/login/__tests__/index.test.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { describe, it, expect, vi } from 'vitest'
|
||||||
|
import { render, screen, waitFor } from '@/test/test-utils'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import LoginPage from '../index'
|
||||||
|
import { adminApiHelpers } from '@/lib/api-client'
|
||||||
|
|
||||||
|
// Mock the API client
|
||||||
|
vi.mock('@/lib/api-client', () => ({
|
||||||
|
adminApiHelpers: {
|
||||||
|
login: vi.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock react-router-dom
|
||||||
|
vi.mock('react-router-dom', async () => {
|
||||||
|
const actual = await vi.importActual('react-router-dom')
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useNavigate: () => vi.fn(),
|
||||||
|
useLocation: () => ({ state: null }),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('LoginPage', () => {
|
||||||
|
it('should render login form', () => {
|
||||||
|
render(<LoginPage />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Admin Login')).toBeInTheDocument()
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument()
|
||||||
|
expect(screen.getByLabelText(/password/i)).toBeInTheDocument()
|
||||||
|
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show/hide password when eye icon is clicked', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
render(<LoginPage />)
|
||||||
|
|
||||||
|
const passwordInput = screen.getByLabelText(/password/i)
|
||||||
|
expect(passwordInput).toHaveAttribute('type', 'password')
|
||||||
|
|
||||||
|
// Click the eye icon to show password
|
||||||
|
const toggleButton = passwordInput.parentElement?.querySelector('button')
|
||||||
|
if (toggleButton) {
|
||||||
|
await user.click(toggleButton)
|
||||||
|
expect(passwordInput).toHaveAttribute('type', 'text')
|
||||||
|
|
||||||
|
// Click again to hide
|
||||||
|
await user.click(toggleButton)
|
||||||
|
expect(passwordInput).toHaveAttribute('type', 'password')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle form submission', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const mockLogin = vi.mocked(adminApiHelpers.login)
|
||||||
|
|
||||||
|
mockLogin.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
access_token: 'fake-token',
|
||||||
|
user: {
|
||||||
|
id: '1',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
role: 'ADMIN',
|
||||||
|
firstName: 'Admin',
|
||||||
|
lastName: 'User',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
render(<LoginPage />)
|
||||||
|
|
||||||
|
// Fill in the form
|
||||||
|
await user.type(screen.getByLabelText(/email/i), 'admin@example.com')
|
||||||
|
await user.type(screen.getByLabelText(/password/i), 'password123')
|
||||||
|
|
||||||
|
// Submit the form
|
||||||
|
await user.click(screen.getByRole('button', { name: /login/i }))
|
||||||
|
|
||||||
|
// Wait for API call
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockLogin).toHaveBeenCalledWith({
|
||||||
|
email: 'admin@example.com',
|
||||||
|
password: 'password123',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show error for non-admin users', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const mockLogin = vi.mocked(adminApiHelpers.login)
|
||||||
|
|
||||||
|
mockLogin.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
access_token: 'fake-token',
|
||||||
|
user: {
|
||||||
|
id: '1',
|
||||||
|
email: 'user@example.com',
|
||||||
|
role: 'USER', // Not ADMIN
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
render(<LoginPage />)
|
||||||
|
|
||||||
|
await user.type(screen.getByLabelText(/email/i), 'user@example.com')
|
||||||
|
await user.type(screen.getByLabelText(/password/i), 'password123')
|
||||||
|
await user.click(screen.getByRole('button', { name: /login/i }))
|
||||||
|
|
||||||
|
// Should show error toast (we'd need to mock sonner for this)
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockLogin).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
145
src/pages/login/index.tsx
Normal file
145
src/pages/login/index.tsx
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useNavigate, useLocation } from "react-router-dom"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Eye, EyeOff } from "lucide-react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { adminApiHelpers } from "@/lib/api-client"
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const from = (location.state as any)?.from?.pathname || "/admin/dashboard"
|
||||||
|
|
||||||
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await adminApiHelpers.login({ email, password })
|
||||||
|
console.log('Login response:', response.data) // Debug log
|
||||||
|
|
||||||
|
// Handle different response formats
|
||||||
|
const responseData = response.data
|
||||||
|
const access_token = responseData.access_token || responseData.token || responseData.accessToken
|
||||||
|
const refresh_token = responseData.refresh_token || responseData.refreshToken
|
||||||
|
const user = responseData.user || responseData.data?.user || responseData
|
||||||
|
|
||||||
|
console.log('Extracted token:', access_token) // Debug log
|
||||||
|
console.log('Extracted user:', user) // Debug log
|
||||||
|
|
||||||
|
// Check if user is admin
|
||||||
|
if (user.role !== 'ADMIN') {
|
||||||
|
toast.error("Access denied. Admin privileges required.")
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store tokens and user data
|
||||||
|
if (access_token) {
|
||||||
|
localStorage.setItem('access_token', access_token)
|
||||||
|
console.log('Access token stored in localStorage') // Debug log
|
||||||
|
} else {
|
||||||
|
console.warn('No access_token in response - assuming httpOnly cookies') // Debug log
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh_token) {
|
||||||
|
localStorage.setItem('refresh_token', refresh_token)
|
||||||
|
console.log('Refresh token stored in localStorage') // Debug log
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('user', JSON.stringify(user))
|
||||||
|
console.log('User stored in localStorage') // Debug log
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
toast.success("Login successful!")
|
||||||
|
|
||||||
|
// Small delay to ensure localStorage is persisted
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
|
// Verify token is stored before navigation
|
||||||
|
const storedToken = localStorage.getItem('access_token')
|
||||||
|
console.log('Token verification before navigation:', storedToken) // Debug log
|
||||||
|
|
||||||
|
// Navigate to dashboard
|
||||||
|
console.log('Navigating to:', from) // Debug log
|
||||||
|
navigate(from, { replace: true })
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Login error:', error) // Debug log
|
||||||
|
const message = error.response?.data?.message || "Invalid email or password"
|
||||||
|
toast.error(message)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="space-y-1">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<div className="w-16 h-16 bg-primary rounded-lg flex items-center justify-center">
|
||||||
|
<span className="text-primary-foreground font-bold text-2xl">A</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl text-center">Admin Login</CardTitle>
|
||||||
|
<CardDescription className="text-center">
|
||||||
|
Enter your credentials to access the admin panel
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleLogin} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="admin@example.com"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="••••••••"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
className="pr-10"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
|
{isLoading ? "Logging in..." : "Login"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
37
src/test/setup.ts
Normal file
37
src/test/setup.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { expect, afterEach } from 'vitest'
|
||||||
|
import { cleanup } from '@testing-library/react'
|
||||||
|
import * as matchers from '@testing-library/jest-dom/matchers'
|
||||||
|
|
||||||
|
// Extend Vitest's expect with jest-dom matchers
|
||||||
|
expect.extend(matchers)
|
||||||
|
|
||||||
|
// Cleanup after each test
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mock window.matchMedia
|
||||||
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
writable: true,
|
||||||
|
value: (query: string) => ({
|
||||||
|
matches: false,
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: () => {}, // deprecated
|
||||||
|
removeListener: () => {}, // deprecated
|
||||||
|
addEventListener: () => {},
|
||||||
|
removeEventListener: () => {},
|
||||||
|
dispatchEvent: () => {},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mock IntersectionObserver
|
||||||
|
global.IntersectionObserver = class IntersectionObserver {
|
||||||
|
constructor() {}
|
||||||
|
disconnect() {}
|
||||||
|
observe() {}
|
||||||
|
takeRecords() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
unobserve() {}
|
||||||
|
} as any
|
||||||
36
src/test/test-utils.tsx
Normal file
36
src/test/test-utils.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
import { render, RenderOptions } from '@testing-library/react'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
// Create a custom render function that includes providers
|
||||||
|
const createTestQueryClient = () =>
|
||||||
|
new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface AllTheProvidersProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const AllTheProviders = ({ children }: AllTheProvidersProps) => {
|
||||||
|
const testQueryClient = createTestQueryClient()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={testQueryClient}>
|
||||||
|
<BrowserRouter>{children}</BrowserRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const customRender = (
|
||||||
|
ui: ReactElement,
|
||||||
|
options?: Omit<RenderOptions, 'wrapper'>
|
||||||
|
) => render(ui, { wrapper: AllTheProviders, ...options })
|
||||||
|
|
||||||
|
export * from '@testing-library/react'
|
||||||
|
export { customRender as render }
|
||||||
42
vercel.json
Normal file
42
vercel.json
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"buildCommand": "npm run build:prod",
|
||||||
|
"outputDirectory": "dist",
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "/(.*)",
|
||||||
|
"destination": "/index.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "/(.*)",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "X-Frame-Options",
|
||||||
|
"value": "SAMEORIGIN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Content-Type-Options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-XSS-Protection",
|
||||||
|
"value": "1; mode=block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Referrer-Policy",
|
||||||
|
"value": "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/assets/(.*)",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cache-Control",
|
||||||
|
"value": "public, max-age=31536000, immutable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -10,4 +10,28 @@ export default defineConfig({
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
|
||||||
|
'ui-vendor': ['@radix-ui/react-avatar', '@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select'],
|
||||||
|
'chart-vendor': ['recharts'],
|
||||||
|
'query-vendor': ['@tanstack/react-query'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chunkSizeWarningLimit: 1000,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
strictPort: false,
|
||||||
|
host: true,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
port: 4173,
|
||||||
|
strictPort: false,
|
||||||
|
host: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
30
vitest.config.ts
Normal file
30
vitest.config.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: './src/test/setup.ts',
|
||||||
|
css: true,
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
reporter: ['text', 'json', 'html'],
|
||||||
|
exclude: [
|
||||||
|
'node_modules/',
|
||||||
|
'src/test/',
|
||||||
|
'**/*.d.ts',
|
||||||
|
'**/*.config.*',
|
||||||
|
'**/mockData',
|
||||||
|
'dist/',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user