chore: init
This commit is contained in:
BIN
.cursor/.DS_Store
vendored
Normal file
BIN
.cursor/.DS_Store
vendored
Normal file
Binary file not shown.
32
.cursor/rules/api-routes.mdc
Normal file
32
.cursor/rules/api-routes.mdc
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
globs: app/api/**/*.ts
|
||||||
|
---
|
||||||
|
|
||||||
|
# API Routes Rules
|
||||||
|
|
||||||
|
1. Routes MUST only use services for data access
|
||||||
|
2. Routes MUST handle input validation
|
||||||
|
3. Routes MUST return typed responses
|
||||||
|
4. Routes MUST use proper error handling
|
||||||
|
|
||||||
|
Example of correct API route:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { MyService } from "@/services/my-service";
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const service = new MyService(pool);
|
||||||
|
const data = await service.getData();
|
||||||
|
return Response.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
return Response.error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ FORBIDDEN:
|
||||||
|
|
||||||
|
- Direct database queries
|
||||||
|
- Business logic implementation
|
||||||
|
- Untyped responses
|
||||||
167
.cursor/rules/business-logic-separation.mdc
Normal file
167
.cursor/rules/business-logic-separation.mdc
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
description: Enforce business logic separation between frontend and backend
|
||||||
|
---
|
||||||
|
|
||||||
|
# Business Logic Separation Rules
|
||||||
|
|
||||||
|
## Core Principle: NO Business Logic in Frontend
|
||||||
|
|
||||||
|
All business logic, data processing, and domain rules MUST be implemented in the backend services layer. The frontend is purely for presentation and user interaction.
|
||||||
|
|
||||||
|
## ✅ ALLOWED in Frontend ([src/components/](mdc:src/components/), [src/hooks/](mdc:src/hooks/), [src/clients/](mdc:src/clients/))
|
||||||
|
|
||||||
|
### Components
|
||||||
|
- UI rendering and presentation logic
|
||||||
|
- Form validation (UI-level only, not business rules)
|
||||||
|
- User interaction handling (clicks, inputs, navigation)
|
||||||
|
- Visual state management (loading, errors, UI states)
|
||||||
|
- Data formatting for display purposes only
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
- React state management
|
||||||
|
- API call orchestration (using clients)
|
||||||
|
- UI-specific logic (modals, forms, animations)
|
||||||
|
- Data fetching and caching coordination
|
||||||
|
|
||||||
|
### Clients
|
||||||
|
- HTTP requests to API routes
|
||||||
|
- Request/response transformation (serialization only)
|
||||||
|
- Error handling and retry logic
|
||||||
|
- Authentication token management
|
||||||
|
|
||||||
|
## ❌ FORBIDDEN in Frontend
|
||||||
|
|
||||||
|
### Business Rules
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD: Business logic in component
|
||||||
|
const TaskCard = ({ task }) => {
|
||||||
|
const canEdit = task.status === 'open' && task.assignee === currentUser.id;
|
||||||
|
const priority = task.dueDate < new Date() ? 'high' : 'normal';
|
||||||
|
// This is business logic!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: Get computed values from backend
|
||||||
|
const TaskCard = ({ task }) => {
|
||||||
|
const { canEdit, priority } = task; // Computed by backend service
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Processing
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD: Data transformation in frontend
|
||||||
|
const processJiraTasks = (tasks) => {
|
||||||
|
return tasks.map(task => ({
|
||||||
|
...task,
|
||||||
|
normalizedStatus: mapJiraStatus(task.status),
|
||||||
|
estimatedHours: calculateEstimate(task.storyPoints)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: Data already processed by backend service
|
||||||
|
const { processedTasks } = await tasksClient.getTasks();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Logic
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD: Domain calculations in frontend
|
||||||
|
const calculateTeamVelocity = (sprints) => {
|
||||||
|
// Complex business calculation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: Domain logic in service
|
||||||
|
// This belongs in services/team-analytics.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ REQUIRED in Backend ([src/services/](mdc:src/services/), [src/app/api/](mdc:src/app/api/))
|
||||||
|
|
||||||
|
### Services Layer
|
||||||
|
- All business rules and domain logic
|
||||||
|
- Data validation and processing
|
||||||
|
- Integration with external APIs (Jira, macOS Reminders)
|
||||||
|
- Complex calculations and algorithms
|
||||||
|
- Data aggregation and analytics
|
||||||
|
- Permission and authorization logic
|
||||||
|
|
||||||
|
### API Routes
|
||||||
|
- Input validation and sanitization
|
||||||
|
- Service orchestration
|
||||||
|
- Response formatting
|
||||||
|
- Error handling and logging
|
||||||
|
- Authentication and authorization
|
||||||
|
|
||||||
|
## Implementation Pattern
|
||||||
|
|
||||||
|
### ✅ Correct Flow
|
||||||
|
```
|
||||||
|
User Action → Component → Client → API Route → Service → Database
|
||||||
|
↑ ↓
|
||||||
|
Pure UI Logic Business Logic
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Incorrect Flow
|
||||||
|
```
|
||||||
|
User Action → Component with Business Logic → Database
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Task Status Management
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD: In component
|
||||||
|
const updateTaskStatus = (taskId, newStatus) => {
|
||||||
|
if (newStatus === 'done' && !task.hasAllSubtasks) {
|
||||||
|
throw new Error('Cannot complete task with pending subtasks');
|
||||||
|
}
|
||||||
|
// Business rule in frontend!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: In services/task-processor.ts
|
||||||
|
export const updateTaskStatus = async (taskId: string, newStatus: TaskStatus) => {
|
||||||
|
const task = await getTask(taskId);
|
||||||
|
|
||||||
|
// Business rules in service
|
||||||
|
if (newStatus === 'done' && !await hasAllSubtasksCompleted(taskId)) {
|
||||||
|
throw new BusinessError('Cannot complete task with pending subtasks');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateTask(taskId, { status: newStatus });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Team Analytics
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD: In component
|
||||||
|
const TeamDashboard = () => {
|
||||||
|
const calculateBurndown = (tasks) => {
|
||||||
|
// Complex business calculation in component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ GOOD: In services/team-analytics.ts
|
||||||
|
export const getTeamBurndown = async (teamId: string, sprintId: string) => {
|
||||||
|
// All calculation logic in service
|
||||||
|
const tasks = await getSprintTasks(sprintId);
|
||||||
|
return calculateBurndownMetrics(tasks);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
When reviewing code:
|
||||||
|
1. **Components**: Should only contain JSX, event handlers, and UI state
|
||||||
|
2. **Hooks**: Should only orchestrate API calls and manage React state
|
||||||
|
3. **Clients**: Should only make HTTP requests and handle responses
|
||||||
|
4. **Services**: Should contain ALL business logic and data processing
|
||||||
|
5. **API Routes**: Should validate input and call appropriate services
|
||||||
|
|
||||||
|
## Red Flags
|
||||||
|
|
||||||
|
Watch for these patterns that indicate business logic in frontend:
|
||||||
|
- Complex calculations in components/hooks
|
||||||
|
- Business rule validation in forms
|
||||||
|
- Data transformation beyond display formatting
|
||||||
|
- Domain-specific constants and rules
|
||||||
|
- Integration logic with external systems
|
||||||
|
|
||||||
|
Remember: **The frontend is a thin presentation layer. All intelligence lives in the backend.**
|
||||||
31
.cursor/rules/clients.mdc
Normal file
31
.cursor/rules/clients.mdc
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
globs: clients/**/*.ts
|
||||||
|
---
|
||||||
|
|
||||||
|
# HTTP Clients Rules
|
||||||
|
|
||||||
|
1. All HTTP calls MUST be in clients/domains/
|
||||||
|
2. Each domain MUST have its own client
|
||||||
|
3. Clients MUST use the base HTTP client
|
||||||
|
4. Clients MUST type their responses
|
||||||
|
|
||||||
|
Example of correct client:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { HttpClient } from "@/clients/base/http-client";
|
||||||
|
import { MyData } from "@/lib/types";
|
||||||
|
|
||||||
|
export class MyClient {
|
||||||
|
constructor(private httpClient: HttpClient) {}
|
||||||
|
|
||||||
|
async getData(): Promise<MyData[]> {
|
||||||
|
return this.httpClient.get("/api/data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ FORBIDDEN:
|
||||||
|
|
||||||
|
- Direct fetch calls
|
||||||
|
- Business logic in clients
|
||||||
|
- Untyped responses
|
||||||
28
.cursor/rules/components.mdc
Normal file
28
.cursor/rules/components.mdc
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
globs: src/components/**/*.tsx
|
||||||
|
---
|
||||||
|
|
||||||
|
# Components Rules
|
||||||
|
|
||||||
|
1. UI components MUST be in src/components/ui/
|
||||||
|
2. Feature components MUST be in their feature folder
|
||||||
|
3. Components MUST use clients for data fetching
|
||||||
|
4. Components MUST be properly typed
|
||||||
|
|
||||||
|
Example of correct component:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useMyClient } from '@/hooks/use-my-client';
|
||||||
|
|
||||||
|
export const MyComponent = () => {
|
||||||
|
const { data } = useMyClient();
|
||||||
|
return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ FORBIDDEN:
|
||||||
|
|
||||||
|
- Direct service usage
|
||||||
|
- Direct database queries
|
||||||
|
- Direct fetch calls
|
||||||
|
- Untyped props
|
||||||
167
.cursor/rules/css-variables-theme.mdc
Normal file
167
.cursor/rules/css-variables-theme.mdc
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
description: CSS Variables theme system best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# CSS Variables Theme System
|
||||||
|
|
||||||
|
## Core Principle: Pure CSS Variables for Theming
|
||||||
|
|
||||||
|
This project uses **CSS Variables exclusively** for theming. No Tailwind `dark:` classes or conditional CSS classes.
|
||||||
|
|
||||||
|
## ✅ Architecture Pattern
|
||||||
|
|
||||||
|
### CSS Structure
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
/* Light theme (default values) */
|
||||||
|
--background: #f1f5f9;
|
||||||
|
--foreground: #0f172a;
|
||||||
|
--primary: #0891b2;
|
||||||
|
--success: #059669;
|
||||||
|
--destructive: #dc2626;
|
||||||
|
--accent: #d97706;
|
||||||
|
--purple: #8b5cf6;
|
||||||
|
--yellow: #eab308;
|
||||||
|
--green: #059669;
|
||||||
|
--blue: #2563eb;
|
||||||
|
--gray: #6b7280;
|
||||||
|
--gray-light: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
/* Dark theme (override values) */
|
||||||
|
--background: #1e293b;
|
||||||
|
--foreground: #f1f5f9;
|
||||||
|
--primary: #06b6d4;
|
||||||
|
--success: #10b981;
|
||||||
|
--destructive: #ef4444;
|
||||||
|
--accent: #f59e0b;
|
||||||
|
--purple: #8b5cf6;
|
||||||
|
--yellow: #eab308;
|
||||||
|
--green: #10b981;
|
||||||
|
--blue: #3b82f6;
|
||||||
|
--gray: #9ca3af;
|
||||||
|
--gray-light: #374151;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Application
|
||||||
|
- **Single source of truth**: [ThemeContext.tsx](mdc:src/contexts/ThemeContext.tsx) applies theme class to `document.documentElement`
|
||||||
|
- **No duplication**: Theme is applied only once, not in multiple places
|
||||||
|
- **SSR safe**: Initial theme from server-side preferences
|
||||||
|
|
||||||
|
## ✅ Component Usage Patterns
|
||||||
|
|
||||||
|
### Correct: Using CSS Variables
|
||||||
|
```tsx
|
||||||
|
// ✅ GOOD: CSS Variables in className
|
||||||
|
<div className="bg-[var(--card)] text-[var(--foreground)] border-[var(--border)]">
|
||||||
|
|
||||||
|
// ✅ GOOD: CSS Variables in style prop
|
||||||
|
<div style={{ color: 'var(--primary)', backgroundColor: 'var(--card)' }}>
|
||||||
|
|
||||||
|
// ✅ GOOD: CSS Variables with color-mix for transparency
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: 'color-mix(in srgb, var(--primary) 10%, transparent)',
|
||||||
|
borderColor: 'color-mix(in srgb, var(--primary) 20%, var(--border))'
|
||||||
|
}}>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Forbidden: Tailwind Dark Mode Classes
|
||||||
|
```tsx
|
||||||
|
// ❌ BAD: Tailwind dark: classes
|
||||||
|
<div className="bg-white dark:bg-gray-800 text-black dark:text-white">
|
||||||
|
|
||||||
|
// ❌ BAD: Conditional classes
|
||||||
|
<div className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>
|
||||||
|
|
||||||
|
// ❌ BAD: Hardcoded colors
|
||||||
|
<div className="bg-red-500 text-blue-600">
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Color System
|
||||||
|
|
||||||
|
### Semantic Color Tokens
|
||||||
|
- `--background`: Main background color
|
||||||
|
- `--foreground`: Main text color
|
||||||
|
- `--card`: Card/panel background
|
||||||
|
- `--card-hover`: Card hover state
|
||||||
|
- `--card-column`: Column background (darker than cards)
|
||||||
|
- `--border`: Border color
|
||||||
|
- `--input`: Input field background
|
||||||
|
- `--primary`: Primary brand color
|
||||||
|
- `--primary-foreground`: Text on primary background
|
||||||
|
- `--muted`: Muted text color
|
||||||
|
- `--muted-foreground`: Secondary text color
|
||||||
|
- `--accent`: Accent color (orange/amber)
|
||||||
|
- `--destructive`: Error/danger color (red)
|
||||||
|
- `--success`: Success color (green)
|
||||||
|
- `--purple`: Purple accent
|
||||||
|
- `--yellow`: Yellow accent
|
||||||
|
- `--green`: Green accent
|
||||||
|
- `--blue`: Blue accent
|
||||||
|
- `--gray`: Gray color
|
||||||
|
- `--gray-light`: Light gray background
|
||||||
|
|
||||||
|
### Color Mixing Patterns
|
||||||
|
```css
|
||||||
|
/* Background with transparency */
|
||||||
|
background-color: color-mix(in srgb, var(--primary) 10%, transparent);
|
||||||
|
|
||||||
|
/* Border with transparency */
|
||||||
|
border-color: color-mix(in srgb, var(--primary) 20%, var(--border));
|
||||||
|
|
||||||
|
/* Text with opacity */
|
||||||
|
color: color-mix(in srgb, var(--destructive) 80%, transparent);
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Theme Context Usage
|
||||||
|
|
||||||
|
### ThemeProvider Setup
|
||||||
|
```tsx
|
||||||
|
// In layout.tsx
|
||||||
|
<ThemeProvider initialTheme={initialPreferences.viewPreferences.theme}>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Usage
|
||||||
|
```tsx
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { theme, toggleTheme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button onClick={toggleTheme}>
|
||||||
|
Switch to {theme === 'dark' ? 'light' : 'dark'} theme
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Future Extensibility
|
||||||
|
|
||||||
|
This system is designed to support:
|
||||||
|
- **Custom color themes**: Easy to add new color variables
|
||||||
|
- **User preferences**: Colors can be dynamically changed
|
||||||
|
- **Theme presets**: Multiple predefined themes
|
||||||
|
- **Accessibility**: High contrast modes
|
||||||
|
|
||||||
|
## 🚨 Anti-patterns to Avoid
|
||||||
|
|
||||||
|
1. **Don't mix approaches**: Never use both CSS variables and Tailwind dark: classes
|
||||||
|
2. **Don't duplicate theme application**: Theme should be applied only in ThemeContext
|
||||||
|
3. **Don't hardcode colors**: Always use semantic color tokens
|
||||||
|
4. **Don't use conditional classes**: Use CSS variables instead
|
||||||
|
5. **Don't forget transparency**: Use `color-mix()` for semi-transparent colors
|
||||||
|
|
||||||
|
## 📁 Key Files
|
||||||
|
|
||||||
|
- [globals.css](mdc:src/app/globals.css) - CSS Variables definitions
|
||||||
|
- [ThemeContext.tsx](mdc:src/contexts/ThemeContext.tsx) - Theme management
|
||||||
|
- [UserPreferencesContext.tsx](mdc:src/contexts/UserPreferencesContext.tsx) - Preferences sync
|
||||||
|
- [layout.tsx](mdc:src/app/layout.tsx) - Theme provider setup
|
||||||
|
|
||||||
|
Remember: **CSS Variables are the single source of truth for theming. Keep it pure and consistent.**
|
||||||
30
.cursor/rules/project-structure.mdc
Normal file
30
.cursor/rules/project-structure.mdc
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Project Structure Rules
|
||||||
|
|
||||||
|
1. Backend:
|
||||||
|
- [src/services/](mdc:src/services/) - ALL database access
|
||||||
|
- [src/app/api/](mdc:src/app/api/) - API routes using services
|
||||||
|
|
||||||
|
2. Frontend:
|
||||||
|
- [src/clients/](mdc:src/clients/) - HTTP clients
|
||||||
|
- [src/components/](mdc:src/components/) - React components (organized by domain)
|
||||||
|
- [src/hooks/](mdc:src/hooks/) - React hooks
|
||||||
|
|
||||||
|
3. Shared:
|
||||||
|
- [src/lib/](mdc:src/lib/) - Types and utilities
|
||||||
|
- [scripts/](mdc:scripts/) - Utility scripts
|
||||||
|
|
||||||
|
Key Files:
|
||||||
|
|
||||||
|
- [src/services/database.ts](mdc:src/services/database.ts) - Database pool
|
||||||
|
- [src/clients/base/http-client.ts](mdc:src/clients/base/http-client.ts) - Base HTTP client
|
||||||
|
- [src/lib/types.ts](mdc:src/lib/types.ts) - Shared types
|
||||||
|
|
||||||
|
❌ FORBIDDEN:
|
||||||
|
|
||||||
|
- Database access outside src/services/
|
||||||
|
- HTTP calls outside src/clients/
|
||||||
|
- Business logic in src/components/
|
||||||
113
.cursor/rules/server-actions.mdc
Normal file
113
.cursor/rules/server-actions.mdc
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
description: Guide for when to use Server Actions vs API Routes in Next.js App Router
|
||||||
|
---
|
||||||
|
|
||||||
|
# Server Actions vs API Routes - Decision Guide
|
||||||
|
|
||||||
|
## ✅ USE SERVER ACTIONS for:
|
||||||
|
|
||||||
|
### Quick Actions & Mutations
|
||||||
|
- **TaskCard actions**: `updateTaskStatus()`, `updateTaskTitle()`, `deleteTask()`
|
||||||
|
- **Daily checkboxes**: `toggleCheckbox()`, `addCheckbox()`, `updateCheckbox()`
|
||||||
|
- **User preferences**: `updateTheme()`, `updateViewPreferences()`, `updateFilters()`
|
||||||
|
- **Simple CRUD**: `createTag()`, `updateTag()`, `deleteTag()`
|
||||||
|
|
||||||
|
### Characteristics of Server Action candidates:
|
||||||
|
- Simple, frequent mutations
|
||||||
|
- No complex business logic
|
||||||
|
- Used in interactive components (forms, buttons, toggles)
|
||||||
|
- Need immediate UI feedback with `useTransition`
|
||||||
|
- Benefit from automatic cache revalidation
|
||||||
|
|
||||||
|
## ❌ KEEP API ROUTES for:
|
||||||
|
|
||||||
|
### Complex Endpoints
|
||||||
|
- **Initial data fetching**: `GET /api/tasks` with complex filters
|
||||||
|
- **External integrations**: `POST /api/jira/sync` with complex logic
|
||||||
|
- **Analytics & reports**: Complex data aggregation
|
||||||
|
- **Public API**: Endpoints that might be called from mobile/external
|
||||||
|
|
||||||
|
### Characteristics that require API Routes:
|
||||||
|
- Complex business logic or data processing
|
||||||
|
- Multiple service orchestration
|
||||||
|
- Need for HTTP monitoring/logging
|
||||||
|
- External consumption (mobile apps, webhooks)
|
||||||
|
- Real-time features (WebSockets, SSE)
|
||||||
|
- File uploads or special content types
|
||||||
|
|
||||||
|
## 🔄 Implementation Pattern
|
||||||
|
|
||||||
|
### Server Actions Structure
|
||||||
|
```typescript
|
||||||
|
// actions/tasks.ts
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
import { tasksService } from '@/services/tasks';
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
|
export async function updateTaskStatus(taskId: string, status: TaskStatus) {
|
||||||
|
try {
|
||||||
|
const task = await tasksService.updateTask(taskId, { status });
|
||||||
|
revalidatePath('/'); // Auto cache invalidation
|
||||||
|
return { success: true, data: task };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Usage with useTransition
|
||||||
|
```typescript
|
||||||
|
// components/TaskCard.tsx
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { updateTaskStatus } from '@/actions/tasks';
|
||||||
|
import { useTransition } from 'react';
|
||||||
|
|
||||||
|
export function TaskCard({ task }) {
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const handleStatusChange = (status) => {
|
||||||
|
startTransition(async () => {
|
||||||
|
const result = await updateTaskStatus(task.id, status);
|
||||||
|
if (!result.success) {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={isPending ? 'opacity-50' : ''}>
|
||||||
|
{/* UI with loading state */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Migration Strategy
|
||||||
|
|
||||||
|
When migrating from API Routes to Server Actions:
|
||||||
|
|
||||||
|
1. **Create server action** in `actions/` directory
|
||||||
|
2. **Update component** to use server action directly
|
||||||
|
3. **Remove API route** (PATCH, POST, DELETE for mutations)
|
||||||
|
4. **Simplify client** (remove mutation methods, keep GET only)
|
||||||
|
5. **Update hooks** to use server actions instead of HTTP calls
|
||||||
|
|
||||||
|
## 🎯 Benefits of Server Actions
|
||||||
|
|
||||||
|
- **🚀 Performance**: No HTTP serialization overhead
|
||||||
|
- **🔄 Cache intelligence**: Automatic revalidation with `revalidatePath()`
|
||||||
|
- **📦 Bundle reduction**: Less client-side HTTP code
|
||||||
|
- **⚡ UX**: Native loading states with `useTransition`
|
||||||
|
- **🎯 Simplicity**: Direct service calls, less boilerplate
|
||||||
|
|
||||||
|
## 🚨 Anti-patterns to Avoid
|
||||||
|
|
||||||
|
- Don't use server actions for complex data fetching
|
||||||
|
- Don't use server actions for endpoints that need HTTP monitoring
|
||||||
|
- Don't use server actions for public API endpoints
|
||||||
|
- Don't mix server actions with client-side state management for the same data
|
||||||
|
|
||||||
|
Remember: Server Actions are for **direct mutations**, API Routes are for **complex operations** and **public interfaces**.
|
||||||
42
.cursor/rules/services.mdc
Normal file
42
.cursor/rules/services.mdc
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
globs: src/services/*.ts
|
||||||
|
---
|
||||||
|
|
||||||
|
# Services Rules
|
||||||
|
|
||||||
|
1. Services MUST contain ALL PostgreSQL queries
|
||||||
|
2. Services are the ONLY layer allowed to communicate with the database
|
||||||
|
3. Each service MUST:
|
||||||
|
- Use the pool from [src/services/database.ts](mdc:src/services/database.ts)
|
||||||
|
- Implement proper transaction management
|
||||||
|
- Handle errors and logging
|
||||||
|
- Validate data before insertion
|
||||||
|
- Have a clear interface
|
||||||
|
|
||||||
|
Example of correct service implementation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class MyService {
|
||||||
|
constructor(private pool: Pool) {}
|
||||||
|
|
||||||
|
async myMethod(): Promise<Result> {
|
||||||
|
const client = await this.pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
// ... queries
|
||||||
|
await client.query("COMMIT");
|
||||||
|
} catch (error) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ FORBIDDEN:
|
||||||
|
|
||||||
|
- Direct database queries outside src/services
|
||||||
|
- Raw SQL in API routes
|
||||||
|
- Database logic in components
|
||||||
54
.cursor/rules/todo-tracking.mdc
Normal file
54
.cursor/rules/todo-tracking.mdc
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
description: Automatic TODO tracking and task completion management
|
||||||
|
---
|
||||||
|
|
||||||
|
# TODO Task Tracking Rules
|
||||||
|
|
||||||
|
## Automatic Task Completion
|
||||||
|
|
||||||
|
Whenever you complete a task or implement a feature mentioned in [TODO.md](mdc:TODO.md), you MUST:
|
||||||
|
|
||||||
|
1. **Immediately update the TODO.md** by changing `- [ ]` to `- [x]` for the completed task
|
||||||
|
2. **Use the exact task description** from the TODO - don't modify the text
|
||||||
|
3. **Update related sub-tasks** if completing a parent task affects them
|
||||||
|
4. **Add completion timestamp** in a comment if the task was significant
|
||||||
|
|
||||||
|
## Task Completion Examples
|
||||||
|
|
||||||
|
### ✅ Correct completion marking:
|
||||||
|
```markdown
|
||||||
|
- [x] Initialiser Next.js avec TypeScript
|
||||||
|
- [x] Configurer ESLint, Prettier
|
||||||
|
- [x] Setup structure de dossiers selon les règles du workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ With timestamp for major milestones:
|
||||||
|
```markdown
|
||||||
|
- [x] Créer `services/database.ts` - Pool de connexion DB <!-- Completed 2025-01-15 -->
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Update TODO.md
|
||||||
|
|
||||||
|
Update the TODO immediately after:
|
||||||
|
- Creating/modifying files mentioned in tasks
|
||||||
|
- Implementing features described in tasks
|
||||||
|
- Completing configuration steps
|
||||||
|
- Finishing any work item listed in the TODO
|
||||||
|
|
||||||
|
## Task Dependencies
|
||||||
|
|
||||||
|
When completing tasks, consider:
|
||||||
|
- **Parent tasks**: Mark parent complete only when ALL sub-tasks are done
|
||||||
|
- **Blocking tasks**: Some tasks may unblock others - mention this in updates
|
||||||
|
- **Phase completion**: Note when entire phases are completed
|
||||||
|
|
||||||
|
## Progress Tracking
|
||||||
|
|
||||||
|
Always maintain visibility of:
|
||||||
|
- Current phase progress
|
||||||
|
- Next logical task to tackle
|
||||||
|
- Any blockers or issues encountered
|
||||||
|
- Completed vs remaining work ratio
|
||||||
|
|
||||||
|
This ensures the TODO.md remains an accurate reflection of project progress and helps maintain momentum.
|
||||||
313
devbook.md
Normal file
313
devbook.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# SWOT Manager - Development Book
|
||||||
|
|
||||||
|
Application de gestion d'ateliers SWOT pour entretiens managériaux.
|
||||||
|
|
||||||
|
## Stack Technique
|
||||||
|
|
||||||
|
- **Framework**: Next.js 15 (App Router)
|
||||||
|
- **Styling**: Tailwind CSS + CSS Variables theming
|
||||||
|
- **Auth**: NextAuth.js v5
|
||||||
|
- **Database**: PostgreSQL + Prisma
|
||||||
|
- **Drag & Drop**: @hello-pangea/dnd (fork maintenu de react-beautiful-dnd)
|
||||||
|
- **State**: React Context + Server Actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 : Setup Initial
|
||||||
|
|
||||||
|
- [ ] Initialiser Next.js 15 avec TypeScript
|
||||||
|
- [ ] Configurer Tailwind CSS avec le système de CSS Variables
|
||||||
|
- [ ] Setup ESLint + Prettier
|
||||||
|
- [ ] Créer la structure de dossiers
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│ ├── api/
|
||||||
|
│ ├── (auth)/
|
||||||
|
│ │ ├── login/
|
||||||
|
│ │ └── register/
|
||||||
|
│ ├── sessions/
|
||||||
|
│ │ ├── [id]/
|
||||||
|
│ │ └── new/
|
||||||
|
│ └── layout.tsx
|
||||||
|
├── components/
|
||||||
|
│ ├── ui/ # Composants réutilisables
|
||||||
|
│ ├── swot/ # Composants SWOT
|
||||||
|
│ └── layout/ # Header, Sidebar, etc.
|
||||||
|
├── services/ # Logique métier + DB
|
||||||
|
├── lib/ # Types, utils
|
||||||
|
├── contexts/ # React Contexts
|
||||||
|
└── actions/ # Server Actions
|
||||||
|
```
|
||||||
|
- [ ] Configurer les CSS Variables pour le theming (light/dark)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 : Base de Données
|
||||||
|
|
||||||
|
- [ ] Installer et configurer Prisma
|
||||||
|
- [ ] Créer le schéma de base de données :
|
||||||
|
```prisma
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
email String @unique
|
||||||
|
name String?
|
||||||
|
password String
|
||||||
|
sessions Session[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
title String
|
||||||
|
collaborator String # Nom du collaborateur évalué
|
||||||
|
date DateTime @default(now())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
items SwotItem[]
|
||||||
|
actions Action[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SwotCategory {
|
||||||
|
STRENGTH
|
||||||
|
WEAKNESS
|
||||||
|
OPPORTUNITY
|
||||||
|
THREAT
|
||||||
|
}
|
||||||
|
|
||||||
|
model SwotItem {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
content String
|
||||||
|
category SwotCategory
|
||||||
|
order Int @default(0)
|
||||||
|
sessionId String
|
||||||
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
|
actionLinks ActionLink[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Action {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
title String
|
||||||
|
description String?
|
||||||
|
priority Int @default(0) # 0=low, 1=medium, 2=high
|
||||||
|
status String @default("todo") # todo, in_progress, done
|
||||||
|
dueDate DateTime?
|
||||||
|
sessionId String
|
||||||
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
|
links ActionLink[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model ActionLink {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
actionId String
|
||||||
|
action Action @relation(fields: [actionId], references: [id], onDelete: Cascade)
|
||||||
|
swotItemId String
|
||||||
|
swotItem SwotItem @relation(fields: [swotItemId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([actionId, swotItemId])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [ ] Générer le client Prisma
|
||||||
|
- [ ] Créer les migrations initiales
|
||||||
|
- [ ] Créer le service database.ts (pool de connexion)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 : Authentification
|
||||||
|
|
||||||
|
- [ ] Installer NextAuth.js v5
|
||||||
|
- [ ] Configurer le provider Credentials (email/password)
|
||||||
|
- [ ] Créer les pages :
|
||||||
|
- [ ] `/login` - Page de connexion
|
||||||
|
- [ ] `/register` - Page d'inscription
|
||||||
|
- [ ] Créer le service auth.ts
|
||||||
|
- [ ] Protéger les routes avec middleware
|
||||||
|
- [ ] Créer le composant AuthProvider
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 : Layout & Navigation
|
||||||
|
|
||||||
|
- [ ] Créer le layout principal avec :
|
||||||
|
- [ ] Header avec navigation et user menu
|
||||||
|
- [ ] Theme toggle (dark/light)
|
||||||
|
- [ ] Créer les composants UI de base :
|
||||||
|
- [ ] Button
|
||||||
|
- [ ] Card
|
||||||
|
- [ ] Input
|
||||||
|
- [ ] Modal
|
||||||
|
- [ ] Badge
|
||||||
|
- [ ] Créer la page d'accueil `/` - Dashboard avec liste des sessions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 : Gestion des Sessions SWOT
|
||||||
|
|
||||||
|
- [ ] Créer le service `sessions.ts`
|
||||||
|
- [ ] Créer les Server Actions pour les sessions :
|
||||||
|
- [ ] `createSession`
|
||||||
|
- [ ] `updateSession`
|
||||||
|
- [ ] `deleteSession`
|
||||||
|
- [ ] `getSession`
|
||||||
|
- [ ] `getUserSessions`
|
||||||
|
- [ ] Créer les pages :
|
||||||
|
- [ ] `/sessions` - Liste des sessions
|
||||||
|
- [ ] `/sessions/new` - Création de session
|
||||||
|
- [ ] `/sessions/[id]` - Vue détaillée de la session SWOT
|
||||||
|
- [ ] Créer les composants :
|
||||||
|
- [ ] `SessionCard` - Carte de session dans la liste
|
||||||
|
- [ ] `SessionForm` - Formulaire création/édition
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6 : Matrice SWOT Interactive
|
||||||
|
|
||||||
|
- [ ] Installer @hello-pangea/dnd
|
||||||
|
- [ ] Créer les composants SWOT :
|
||||||
|
- [ ] `SwotBoard` - Container principal de la matrice
|
||||||
|
- [ ] `SwotQuadrant` - Un quadrant (S, W, O, T)
|
||||||
|
- [ ] `SwotCard` - Une carte dans un quadrant
|
||||||
|
- [ ] `SwotCardForm` - Formulaire ajout/édition de carte
|
||||||
|
- [ ] Implémenter le drag & drop :
|
||||||
|
- [ ] Réorganisation dans un même quadrant
|
||||||
|
- [ ] Déplacement entre quadrants
|
||||||
|
- [ ] Créer les Server Actions pour les items :
|
||||||
|
- [ ] `createSwotItem`
|
||||||
|
- [ ] `updateSwotItem`
|
||||||
|
- [ ] `deleteSwotItem`
|
||||||
|
- [ ] `reorderSwotItems`
|
||||||
|
- [ ] Édition inline des cartes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7 : Système de Liaison & Actions
|
||||||
|
|
||||||
|
- [ ] Créer le mode "liaison" :
|
||||||
|
- [ ] Bouton pour activer le mode liaison
|
||||||
|
- [ ] Sélection multiple d'items SWOT
|
||||||
|
- [ ] Visualisation des items sélectionnés (highlight)
|
||||||
|
- [ ] Créer les composants Actions :
|
||||||
|
- [ ] `ActionPanel` - Panneau des actions croisées
|
||||||
|
- [ ] `ActionCard` - Une action avec ses liens
|
||||||
|
- [ ] `ActionForm` - Formulaire création/édition d'action
|
||||||
|
- [ ] `LinkedItemsBadges` - Badges des items liés
|
||||||
|
- [ ] Créer les Server Actions pour les actions :
|
||||||
|
- [ ] `createAction`
|
||||||
|
- [ ] `updateAction`
|
||||||
|
- [ ] `deleteAction`
|
||||||
|
- [ ] `linkItemToAction`
|
||||||
|
- [ ] `unlinkItemFromAction`
|
||||||
|
- [ ] Visualisation des liens sur la matrice (highlight on hover)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8 : Roadmap & Priorisation
|
||||||
|
|
||||||
|
- [ ] Créer la vue Roadmap :
|
||||||
|
- [ ] `RoadmapView` - Vue timeline des actions
|
||||||
|
- [ ] Filtres par statut, priorité
|
||||||
|
- [ ] Tri par date d'échéance
|
||||||
|
- [ ] Drag & drop pour réordonner les priorités
|
||||||
|
- [ ] Mise à jour du statut des actions (todo → in_progress → done)
|
||||||
|
- [ ] Vue Kanban alternative des actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9 : Export & Partage
|
||||||
|
|
||||||
|
- [ ] Export PDF de la matrice SWOT
|
||||||
|
- [ ] Export PDF de la roadmap
|
||||||
|
- [ ] Génération de lien de partage en lecture seule
|
||||||
|
- [ ] Copie au format texte pour coller dans un email
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10 : Polish & UX
|
||||||
|
|
||||||
|
- [ ] Animations et transitions fluides
|
||||||
|
- [ ] États de chargement (skeletons)
|
||||||
|
- [ ] Messages de confirmation et toasts
|
||||||
|
- [ ] Raccourcis clavier :
|
||||||
|
- [ ] `N` - Nouvelle carte
|
||||||
|
- [ ] `Escape` - Annuler
|
||||||
|
- [ ] `Enter` - Valider
|
||||||
|
- [ ] Responsive design (tablette minimum)
|
||||||
|
- [ ] Mode présentation (masquer les contrôles)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11 : Bonus (Optionnel)
|
||||||
|
|
||||||
|
- [ ] Templates de sessions SWOT prédéfinis
|
||||||
|
- [ ] Collaboration temps réel (plusieurs personnes sur la même session)
|
||||||
|
- [ ] Historique des modifications
|
||||||
|
- [ ] Import depuis Miro/Trello
|
||||||
|
- [ ] Intégration calendrier pour les échéances
|
||||||
|
- [ ] Notifications par email pour les deadlines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes Techniques
|
||||||
|
|
||||||
|
### Structure des Server Actions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// actions/swot-items.ts
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
import { swotService } from '@/services/swot';
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
|
export async function createSwotItem(sessionId: string, data: CreateSwotItemInput) {
|
||||||
|
const item = await swotService.createItem(sessionId, data);
|
||||||
|
revalidatePath(`/sessions/${sessionId}`);
|
||||||
|
return { success: true, data: item };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern de Liaison
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Mode liaison : state dans le composant parent
|
||||||
|
const [linkMode, setLinkMode] = useState(false);
|
||||||
|
const [selectedItems, setSelectedItems] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Quand 2+ items sélectionnés, proposer création d'action
|
||||||
|
if (selectedItems.length >= 2) {
|
||||||
|
// Ouvrir modal de création d'action avec items pré-sélectionnés
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visualisation des Liens
|
||||||
|
|
||||||
|
- Au hover d'une action : highlight des items liés
|
||||||
|
- Au hover d'un item : highlight des actions liées
|
||||||
|
- Couleurs par type de croisement (SO, ST, WO, WT)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes Utiles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lancer le dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Prisma
|
||||||
|
npx prisma migrate dev --name <name>
|
||||||
|
npx prisma generate
|
||||||
|
npx prisma studio
|
||||||
|
|
||||||
|
# Build
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
Reference in New Issue
Block a user