create-tigra 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +80 -87
- package/bin/create-tigra.js +259 -308
- package/package.json +49 -41
- package/template/_claude/QUICK_REFERENCE.md +193 -0
- package/template/_claude/README.md +53 -0
- package/template/_claude/commands/create-client.md +881 -0
- package/template/_claude/commands/create-server.md +383 -0
- package/template/_claude/rules/client/01-project-structure.md +133 -0
- package/template/_claude/rules/client/02-components-and-types.md +146 -0
- package/template/_claude/rules/client/03-data-and-state.md +156 -0
- package/template/_claude/rules/client/04-design-system.md +185 -0
- package/template/_claude/rules/client/05-security.md +55 -0
- package/template/_claude/rules/client/06-ux-checklist.md +81 -0
- package/template/_claude/rules/client/core.md +42 -0
- package/template/_claude/rules/global/core.md +77 -0
- package/template/_claude/rules/server/core.md +50 -0
- package/template/_claude/rules/server/database.md +124 -0
- package/template/_claude/rules/server/project-conventions.md +150 -0
- package/template/_claude/rules/server/response-handling.md +144 -0
- package/template/client/.env.example +5 -0
- package/template/client/README.md +36 -0
- package/template/client/components.json +23 -0
- package/template/client/eslint.config.mjs +18 -0
- package/template/client/next.config.ts +34 -0
- package/template/client/package.json +44 -0
- package/template/client/postcss.config.mjs +7 -0
- package/template/client/src/app/(auth)/layout.tsx +18 -0
- package/template/client/src/app/(auth)/login/page.tsx +13 -0
- package/template/client/src/app/(auth)/register/page.tsx +13 -0
- package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
- package/template/client/src/app/(main)/layout.tsx +11 -0
- package/template/client/src/app/error.tsx +27 -0
- package/template/client/src/app/favicon.ico +0 -0
- package/template/client/src/app/globals.css +145 -0
- package/template/client/src/app/layout.tsx +36 -0
- package/template/client/src/app/loading.tsx +11 -0
- package/template/client/src/app/not-found.tsx +23 -0
- package/template/client/src/app/page.tsx +45 -0
- package/template/client/src/app/providers.tsx +43 -0
- package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
- package/template/client/src/components/common/EmptyState.tsx +31 -0
- package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
- package/template/client/src/components/common/Pagination.tsx +55 -0
- package/template/client/src/components/layout/Footer.tsx +17 -0
- package/template/client/src/components/layout/Header.tsx +173 -0
- package/template/client/src/components/layout/MainLayout.tsx +18 -0
- package/template/client/src/components/ui/alert-dialog.tsx +196 -0
- package/template/client/src/components/ui/badge.tsx +48 -0
- package/template/client/src/components/ui/button.tsx +64 -0
- package/template/client/src/components/ui/card.tsx +92 -0
- package/template/client/src/components/ui/input.tsx +21 -0
- package/template/client/src/components/ui/label.tsx +24 -0
- package/template/client/src/components/ui/select.tsx +190 -0
- package/template/client/src/components/ui/skeleton.tsx +13 -0
- package/template/client/src/components/ui/table.tsx +116 -0
- package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
- package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
- package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
- package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
- package/template/client/src/features/auth/services/auth.service.ts +52 -0
- package/template/client/src/features/auth/store/authSlice.ts +38 -0
- package/template/client/src/features/auth/types/auth.types.ts +32 -0
- package/template/client/src/hooks/useDebounce.ts +14 -0
- package/template/client/src/hooks/useLocalStorage.ts +55 -0
- package/template/client/src/hooks/useMediaQuery.ts +27 -0
- package/template/client/src/lib/api/api.types.ts +34 -0
- package/template/client/src/lib/api/axios.config.ts +98 -0
- package/template/client/src/lib/constants/api-endpoints.ts +18 -0
- package/template/client/src/lib/constants/app.constants.ts +12 -0
- package/template/client/src/lib/constants/routes.ts +9 -0
- package/template/client/src/lib/utils/error.ts +32 -0
- package/template/client/src/lib/utils/format.ts +37 -0
- package/template/client/src/lib/utils/security.ts +34 -0
- package/template/client/src/lib/utils.ts +6 -0
- package/template/client/src/middleware.ts +57 -0
- package/template/client/src/store/hooks.ts +7 -0
- package/template/client/src/store/index.ts +12 -0
- package/template/client/src/types/index.ts +3 -0
- package/template/client/tsconfig.json +34 -0
- package/template/gitignore +34 -0
- package/template/server/.dockerignore +66 -0
- package/template/server/.env.example +96 -69
- package/template/server/.env.production.example +90 -0
- package/template/server/Dockerfile +94 -0
- package/template/server/docker-compose.yml +82 -111
- package/template/server/docs/logging.md +62 -0
- package/template/server/eslint.config.mjs +17 -0
- package/template/server/package.json +68 -81
- package/template/server/phpmyadmin-config.php +26 -0
- package/template/server/postman_collection.json +666 -0
- package/template/server/prisma/schema.prisma +77 -93
- package/template/server/prisma/seed.ts +46 -142
- package/template/server/scripts/flush-redis.ts +41 -0
- package/template/server/src/app.ts +243 -71
- package/template/server/src/config/env.ts +67 -94
- package/template/server/src/libs/auth.ts +88 -0
- package/template/server/src/libs/cleanup.ts +35 -0
- package/template/server/src/libs/cookies.ts +46 -0
- package/template/server/src/libs/logger.ts +33 -60
- package/template/server/src/libs/monitoring.ts +205 -0
- package/template/server/src/libs/password.ts +38 -0
- package/template/server/src/libs/prisma.ts +68 -0
- package/template/server/src/libs/redis.ts +60 -79
- package/template/server/src/libs/requestLogger.ts +66 -0
- package/template/server/src/libs/storage/file-storage.service.ts +211 -0
- package/template/server/src/libs/storage/file-validator.ts +97 -0
- package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
- package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
- package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
- package/template/server/src/modules/auth/auth.controller.ts +90 -141
- package/template/server/src/modules/auth/auth.repo.ts +120 -218
- package/template/server/src/modules/auth/auth.routes.ts +96 -83
- package/template/server/src/modules/auth/auth.schemas.ts +35 -137
- package/template/server/src/modules/auth/auth.service.ts +286 -329
- package/template/server/src/modules/auth/session.repo.ts +110 -0
- package/template/server/src/modules/users/users.controller.ts +120 -0
- package/template/server/src/modules/users/users.repo.ts +77 -0
- package/template/server/src/modules/users/users.routes.ts +89 -0
- package/template/server/src/modules/users/users.schemas.ts +21 -0
- package/template/server/src/modules/users/users.service.ts +169 -0
- package/template/server/src/server.ts +58 -139
- package/template/server/src/shared/errors/AppError.ts +21 -0
- package/template/server/src/shared/errors/errors.ts +43 -0
- package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
- package/template/server/src/shared/responses/successResponse.ts +17 -0
- package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
- package/template/server/src/shared/types/index.ts +26 -0
- package/template/server/src/test/setup.ts +74 -38
- package/template/server/tsconfig.json +27 -89
- package/template/server/uploads/avatars/.gitkeep +1 -0
- package/template/server/vitest.config.ts +43 -98
- package/template/.agent/rules/client/01-project-structure.md +0 -326
- package/template/.agent/rules/client/02-component-patterns.md +0 -249
- package/template/.agent/rules/client/03-typescript-rules.md +0 -226
- package/template/.agent/rules/client/04-state-management.md +0 -474
- package/template/.agent/rules/client/05-api-integration.md +0 -129
- package/template/.agent/rules/client/06-forms-validation.md +0 -129
- package/template/.agent/rules/client/07-common-patterns.md +0 -150
- package/template/.agent/rules/client/08-color-system.md +0 -93
- package/template/.agent/rules/client/09-security-rules.md +0 -97
- package/template/.agent/rules/client/10-testing-strategy.md +0 -370
- package/template/.agent/rules/global/ai-edit-safety.md +0 -38
- package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
- package/template/.agent/rules/server/02-general-rules.md +0 -111
- package/template/.agent/rules/server/03-migrations.md +0 -20
- package/template/.agent/rules/server/04-pagination.md +0 -130
- package/template/.agent/rules/server/05-project-conventions.md +0 -71
- package/template/.agent/rules/server/06-response-handling.md +0 -173
- package/template/.agent/rules/server/07-testing-strategy.md +0 -506
- package/template/.agent/rules/server/08-observability.md +0 -180
- package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
- package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
- package/template/.agent/rules/server/12-performance-optimization.md +0 -567
- package/template/.claude/rules/client-01-project-structure.md +0 -327
- package/template/.claude/rules/client-02-component-patterns.md +0 -250
- package/template/.claude/rules/client-03-typescript-rules.md +0 -227
- package/template/.claude/rules/client-04-state-management.md +0 -475
- package/template/.claude/rules/client-05-api-integration.md +0 -130
- package/template/.claude/rules/client-06-forms-validation.md +0 -130
- package/template/.claude/rules/client-07-common-patterns.md +0 -151
- package/template/.claude/rules/client-08-color-system.md +0 -94
- package/template/.claude/rules/client-09-security-rules.md +0 -98
- package/template/.claude/rules/client-10-testing-strategy.md +0 -371
- package/template/.claude/rules/global-ai-edit-safety.md +0 -39
- package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
- package/template/.claude/rules/server-02-general-rules.md +0 -112
- package/template/.claude/rules/server-03-migrations.md +0 -21
- package/template/.claude/rules/server-04-pagination.md +0 -131
- package/template/.claude/rules/server-05-project-conventions.md +0 -72
- package/template/.claude/rules/server-06-response-handling.md +0 -174
- package/template/.claude/rules/server-07-testing-strategy.md +0 -507
- package/template/.claude/rules/server-08-observability.md +0 -181
- package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
- package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
- package/template/.claude/rules/server-12-performance-optimization.md +0 -568
- package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
- package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
- package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
- package/template/.cursor/rules/client-04-state-management.mdc +0 -475
- package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
- package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
- package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
- package/template/.cursor/rules/client-08-color-system.mdc +0 -94
- package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
- package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
- package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
- package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
- package/template/.cursor/rules/server-03-migrations.mdc +0 -21
- package/template/.cursor/rules/server-04-pagination.mdc +0 -131
- package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
- package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
- package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
- package/template/.cursor/rules/server-08-observability.mdc +0 -181
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
- package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
- package/template/CLAUDE.md +0 -207
- package/template/server/.tsc-aliasrc.json +0 -13
- package/template/server/IMPORT_FIX_CHECKLIST.md +0 -98
- package/template/server/IMPORT_FIX_COMPLETE.md +0 -89
- package/template/server/README.md +0 -183
- package/template/server/REMAINING_IMPORT_FIXES.md +0 -150
- package/template/server/SECURITY.md +0 -190
- package/template/server/Tigra-API.postman_collection.json +0 -733
- package/template/server/biome.json +0 -42
- package/template/server/scripts/fix-all-imports.ps1 +0 -52
- package/template/server/scripts/fix-imports-reference.ps1 +0 -16
- package/template/server/scripts/fix-imports.mjs +0 -55
- package/template/server/scripts/setup-env.js +0 -50
- package/template/server/scripts/wait-for-db.js +0 -60
- package/template/server/src/hooks/request-timing.hook.ts +0 -26
- package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
- package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
- package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
- package/template/server/src/libs/db.ts +0 -76
- package/template/server/src/libs/error-handler.ts +0 -89
- package/template/server/src/libs/queue.ts +0 -79
- package/template/server/src/modules/admin/admin.controller.ts +0 -122
- package/template/server/src/modules/admin/admin.routes.ts +0 -62
- package/template/server/src/modules/admin/admin.schemas.ts +0 -35
- package/template/server/src/modules/admin/admin.service.ts +0 -167
- package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
- package/template/server/src/modules/auth/auth.service.test.ts +0 -119
- package/template/server/src/modules/auth/auth.types.ts +0 -97
- package/template/server/src/modules/resources/resources.controller.ts +0 -218
- package/template/server/src/modules/resources/resources.repo.ts +0 -253
- package/template/server/src/modules/resources/resources.routes.ts +0 -116
- package/template/server/src/modules/resources/resources.schemas.ts +0 -146
- package/template/server/src/modules/resources/resources.service.ts +0 -218
- package/template/server/src/modules/resources/resources.types.ts +0 -73
- package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
- package/template/server/src/plugins/security.plugin.ts +0 -21
- package/template/server/src/routes/health.routes.ts +0 -31
- package/template/server/src/types/fastify.d.ts +0 -36
- package/template/server/src/utils/errors.ts +0 -108
- package/template/server/src/utils/pagination.ts +0 -120
- package/template/server/src/utils/response.ts +0 -110
- package/template/server/src/workers/file.worker.ts +0 -106
- package/template/server/tsconfig.build.json +0 -30
- package/template/server/tsconfig.test.json +0 -22
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
trigger: always_on
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
6
|
-
|
|
7
|
-
# State Management Patterns
|
|
8
|
-
|
|
9
|
-
## State Strategy
|
|
10
|
-
|
|
11
|
-
### Decision Matrix
|
|
12
|
-
|
|
13
|
-
| State Type | Tool | Examples |
|
|
14
|
-
|------------|------|----------|
|
|
15
|
-
| **Server Data** | React Query | Resources, profiles, application data from API |
|
|
16
|
-
| **Global Client** | Redux | Auth tokens, current user, theme settings |
|
|
17
|
-
| **Local** | useState | Form inputs, modals, hover state, toggles |
|
|
18
|
-
| **URL** | React Router | Filters, pagination, search query |
|
|
19
|
-
|
|
20
|
-
## React Query (Server State)
|
|
21
|
-
|
|
22
|
-
### Setup
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
// app/providers.tsx
|
|
26
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
27
|
-
|
|
28
|
-
const queryClient = new QueryClient({
|
|
29
|
-
defaultOptions: {
|
|
30
|
-
queries: {
|
|
31
|
-
staleTime: 5 * 60 * 1000,
|
|
32
|
-
cacheTime: 10 * 60 * 1000,
|
|
33
|
-
refetchOnWindowFocus: false,
|
|
34
|
-
retry: 1,
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Query Key Factory Pattern (MANDATORY)
|
|
43
|
-
|
|
44
|
-
**Why?** Consistent query keys prevent cache bugs and enable powerful invalidation patterns.
|
|
45
|
-
|
|
46
|
-
### Factory Structure
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
// features/resources/lib/query-keys.ts
|
|
50
|
-
|
|
51
|
-
export const resourceKeys = {
|
|
52
|
-
// Base key
|
|
53
|
-
all: ['resources'] as const,
|
|
54
|
-
|
|
55
|
-
// List queries
|
|
56
|
-
lists: () => [...resourceKeys.all, 'list'] as const,
|
|
57
|
-
list: (filters: ResourceFilters) => [...resourceKeys.lists(), filters] as const,
|
|
58
|
-
|
|
59
|
-
// Detail queries
|
|
60
|
-
details: () => [...resourceKeys.all, 'detail'] as const,
|
|
61
|
-
detail: (id: string) => [...resourceKeys.details(), id] as const,
|
|
62
|
-
|
|
63
|
-
// User-specific queries
|
|
64
|
-
myResources: () => [...resourceKeys.all, 'my'] as const,
|
|
65
|
-
myResource: (filters: ResourceFilters) => [...resourceKeys.myResources(), filters] as const,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Usage in hooks
|
|
69
|
-
export const useResources = (filters: ResourceFilters) => {
|
|
70
|
-
return useQuery({
|
|
71
|
-
queryKey: resourceKeys.list(filters),
|
|
72
|
-
queryFn: () => resourceService.getResources(filters),
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const useResource = (id: string) => {
|
|
77
|
-
return useQuery({
|
|
78
|
-
queryKey: resourceKeys.detail(id),
|
|
79
|
-
queryFn: () => resourceService.getResource(id),
|
|
80
|
-
enabled: !!id, // Only fetch if ID exists
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Complex Example (Categories with Resources)
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
// features/categories/lib/query-keys.ts
|
|
89
|
-
export const categoryKeys = {
|
|
90
|
-
all: ['categories'] as const,
|
|
91
|
-
lists: () => [...categoryKeys.all, 'list'] as const,
|
|
92
|
-
list: (filters: CategoryFilters) => [...categoryKeys.lists(), filters] as const,
|
|
93
|
-
details: () => [...categoryKeys.all, 'detail'] as const,
|
|
94
|
-
detail: (id: string) => [...categoryKeys.details(), id] as const,
|
|
95
|
-
|
|
96
|
-
// Nested resources
|
|
97
|
-
resources: (categoryId: string) => [...categoryKeys.detail(categoryId), 'resources'] as const,
|
|
98
|
-
};
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Invalidation Patterns
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
// After creating a resource
|
|
105
|
-
useMutation({
|
|
106
|
-
mutationFn: resourceService.createResource,
|
|
107
|
-
onSuccess: () => {
|
|
108
|
-
// Invalidate all resource lists
|
|
109
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// After updating a specific resource
|
|
114
|
-
useMutation({
|
|
115
|
-
mutationFn: ({ id, data }) => resourceService.updateResource(id, data),
|
|
116
|
-
onSuccess: (_, variables) => {
|
|
117
|
-
// Invalidate this specific resource
|
|
118
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.detail(variables.id) });
|
|
119
|
-
// Also invalidate lists (resource might move categories)
|
|
120
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// After deleting
|
|
125
|
-
useMutation({
|
|
126
|
-
mutationFn: resourceService.deleteResource,
|
|
127
|
-
onSuccess: () => {
|
|
128
|
-
// Remove all resource-related queries
|
|
129
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.all });
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Query Pattern
|
|
137
|
-
|
|
138
|
-
```tsx
|
|
139
|
-
// features/resources/hooks/useResources.ts
|
|
140
|
-
import { useQuery } from '@tanstack/react-query';
|
|
141
|
-
import { resourceService } from '../services/resource.service';
|
|
142
|
-
import { resourceKeys } from '../lib/query-keys';
|
|
143
|
-
|
|
144
|
-
export const useResources = (filters = {}, page = 1, limit = 10) => {
|
|
145
|
-
return useQuery({
|
|
146
|
-
queryKey: resourceKeys.list({ ...filters, page, limit }),
|
|
147
|
-
queryFn: () => resourceService.getResources({ ...filters, page, limit }),
|
|
148
|
-
staleTime: 5 * 60 * 1000,
|
|
149
|
-
});
|
|
150
|
-
};
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Mutation Pattern
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
// features/resources/hooks/useCreateResource.ts
|
|
157
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
158
|
-
import { message } from 'antd'; // Ant Design message
|
|
159
|
-
import { resourceKeys } from '../lib/query-keys';
|
|
160
|
-
|
|
161
|
-
export const useCreateResource = () => {
|
|
162
|
-
const queryClient = useQueryClient();
|
|
163
|
-
|
|
164
|
-
return useMutation({
|
|
165
|
-
mutationFn: resourceService.createResource,
|
|
166
|
-
onSuccess: () => {
|
|
167
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
|
|
168
|
-
message.success('Resource created!');
|
|
169
|
-
},
|
|
170
|
-
onError: (error) => {
|
|
171
|
-
message.error(getErrorMessage(error));
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Optimistic Updates
|
|
178
|
-
|
|
179
|
-
```tsx
|
|
180
|
-
export const useToggleStatus = () => {
|
|
181
|
-
const queryClient = useQueryClient();
|
|
182
|
-
|
|
183
|
-
return useMutation({
|
|
184
|
-
mutationFn: ({ id, status }) => resourceService.updateStatus(id, status),
|
|
185
|
-
onMutate: async ({ id, status }) => {
|
|
186
|
-
// Cancel outgoing queries
|
|
187
|
-
await queryClient.cancelQueries({ queryKey: resourceKeys.detail(id) });
|
|
188
|
-
|
|
189
|
-
// Snapshot previous value
|
|
190
|
-
const previous = queryClient.getQueryData(resourceKeys.detail(id));
|
|
191
|
-
|
|
192
|
-
// Optimistically update
|
|
193
|
-
queryClient.setQueryData(resourceKeys.detail(id), (old: any) => ({
|
|
194
|
-
...old,
|
|
195
|
-
status
|
|
196
|
-
}));
|
|
197
|
-
|
|
198
|
-
return { previous, id };
|
|
199
|
-
},
|
|
200
|
-
onError: (err, variables, context) => {
|
|
201
|
-
// Rollback on error
|
|
202
|
-
if (context?.previous) {
|
|
203
|
-
queryClient.setQueryData(resourceKeys.detail(context.id), context.previous);
|
|
204
|
-
}
|
|
205
|
-
message.error('Failed to update status');
|
|
206
|
-
},
|
|
207
|
-
onSettled: (data, error, variables) => {
|
|
208
|
-
// Refetch to ensure sync
|
|
209
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.detail(variables.id) });
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Redux (Global Client State)
|
|
218
|
-
|
|
219
|
-
### When to Use Redux
|
|
220
|
-
- Authentication state (user, tokens)
|
|
221
|
-
- Global UI state (theme, sidebar collapsed)
|
|
222
|
-
- App-wide settings
|
|
223
|
-
|
|
224
|
-
### Slice Pattern
|
|
225
|
-
Standard Redux Toolkit pattern for auth and global UI state.
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
// features/auth/store/authSlice.ts
|
|
229
|
-
import { createSlice } from '@reduxjs/toolkit';
|
|
230
|
-
|
|
231
|
-
interface AuthState {
|
|
232
|
-
user: User | null;
|
|
233
|
-
tokens: { accessToken: string; refreshToken: string } | null;
|
|
234
|
-
isAuthenticated: boolean;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const initialState: AuthState = {
|
|
238
|
-
user: null,
|
|
239
|
-
tokens: null,
|
|
240
|
-
isAuthenticated: false,
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const authSlice = createSlice({
|
|
244
|
-
name: 'auth',
|
|
245
|
-
initialState,
|
|
246
|
-
reducers: {
|
|
247
|
-
setCredentials: (state, action) => {
|
|
248
|
-
state.user = action.payload.user;
|
|
249
|
-
state.tokens = action.payload.tokens;
|
|
250
|
-
state.isAuthenticated = true;
|
|
251
|
-
},
|
|
252
|
-
logout: (state) => {
|
|
253
|
-
state.user = null;
|
|
254
|
-
state.tokens = null;
|
|
255
|
-
state.isAuthenticated = false;
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
export const { setCredentials, logout } = authSlice.actions;
|
|
261
|
-
export default authSlice.reducer;
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## Local State
|
|
267
|
-
Use `useState` for UI-only state (e.g., `isModalOpen`, form inputs before submission).
|
|
268
|
-
|
|
269
|
-
```tsx
|
|
270
|
-
export const ResourceCard = ({ resource }) => {
|
|
271
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
272
|
-
const [showDetails, setShowDetails] = useState(false);
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<div
|
|
276
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
277
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
278
|
-
>
|
|
279
|
-
{/* UI */}
|
|
280
|
-
</div>
|
|
281
|
-
);
|
|
282
|
-
};
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
---
|
|
286
|
-
|
|
287
|
-
## URL State
|
|
288
|
-
Use `useSearchParams` from `react-router-dom` for filters and pagination to ensure page refreshes and direct links work as expected.
|
|
289
|
-
|
|
290
|
-
```tsx
|
|
291
|
-
import { useSearchParams } from 'react-router-dom';
|
|
292
|
-
|
|
293
|
-
export const ResourcesPage = () => {
|
|
294
|
-
const [searchParams, setSearchParams] = useSearchParams();
|
|
295
|
-
|
|
296
|
-
const page = parseInt(searchParams.get('page') || '1');
|
|
297
|
-
const category = searchParams.get('category') || '';
|
|
298
|
-
|
|
299
|
-
const handleFilterChange = (newCategory: string) => {
|
|
300
|
-
setSearchParams({ page: '1', category: newCategory });
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const { data } = useResources({ category }, page);
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
<div>
|
|
307
|
-
<CategoryFilter value={category} onChange={handleFilterChange} />
|
|
308
|
-
<ResourceList resources={data?.items} />
|
|
309
|
-
<Pagination
|
|
310
|
-
current={page}
|
|
311
|
-
total={data?.pagination.totalPages}
|
|
312
|
-
onChange={(newPage) => setSearchParams({ page: String(newPage), category })}
|
|
313
|
-
/>
|
|
314
|
-
</div>
|
|
315
|
-
);
|
|
316
|
-
};
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
---
|
|
320
|
-
|
|
321
|
-
## Advanced Query Patterns
|
|
322
|
-
|
|
323
|
-
### Dependent Queries
|
|
324
|
-
```tsx
|
|
325
|
-
export const useResourceWithOwner = (resourceId: string) => {
|
|
326
|
-
// First fetch resource
|
|
327
|
-
const { data: resource } = useQuery({
|
|
328
|
-
queryKey: resourceKeys.detail(resourceId),
|
|
329
|
-
queryFn: () => resourceService.getResource(resourceId),
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Then fetch owner (depends on resource)
|
|
333
|
-
const { data: owner } = useQuery({
|
|
334
|
-
queryKey: ['users', resource?.ownerId],
|
|
335
|
-
queryFn: () => userService.getUser(resource!.ownerId),
|
|
336
|
-
enabled: !!resource?.ownerId, // Only run when we have ownerId
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
return { resource, owner };
|
|
340
|
-
};
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Parallel Queries
|
|
344
|
-
```tsx
|
|
345
|
-
export const useDashboardData = () => {
|
|
346
|
-
const resources = useQuery({
|
|
347
|
-
queryKey: resourceKeys.lists(),
|
|
348
|
-
queryFn: resourceService.getResources,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
const categories = useQuery({
|
|
352
|
-
queryKey: categoryKeys.lists(),
|
|
353
|
-
queryFn: categoryService.getCategories,
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const stats = useQuery({
|
|
357
|
-
queryKey: ['stats'],
|
|
358
|
-
queryFn: statsService.getStats,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
return {
|
|
362
|
-
isLoading: resources.isLoading || categories.isLoading || stats.isLoading,
|
|
363
|
-
data: {
|
|
364
|
-
resources: resources.data,
|
|
365
|
-
categories: categories.data,
|
|
366
|
-
stats: stats.data,
|
|
367
|
-
},
|
|
368
|
-
};
|
|
369
|
-
};
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Infinite Queries (Load More)
|
|
373
|
-
```tsx
|
|
374
|
-
export const useInfiniteResources = (filters: ResourceFilters) => {
|
|
375
|
-
return useInfiniteQuery({
|
|
376
|
-
queryKey: [...resourceKeys.lists(), 'infinite', filters],
|
|
377
|
-
queryFn: ({ pageParam = 1 }) =>
|
|
378
|
-
resourceService.getResources({ ...filters, page: pageParam }),
|
|
379
|
-
getNextPageParam: (lastPage) =>
|
|
380
|
-
lastPage.pagination.hasNextPage ? lastPage.pagination.page + 1 : undefined,
|
|
381
|
-
});
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
// Usage in component
|
|
385
|
-
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteResources(filters);
|
|
386
|
-
|
|
387
|
-
<Button onClick={() => fetchNextPage()} disabled={!hasNextPage} loading={isFetchingNextPage}>
|
|
388
|
-
Load More
|
|
389
|
-
</Button>
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## Anti-Patterns
|
|
395
|
-
|
|
396
|
-
### ❌ DO NOT:
|
|
397
|
-
```tsx
|
|
398
|
-
// Don't store server data in Redux
|
|
399
|
-
const dispatch = useDispatch();
|
|
400
|
-
const resources = await resourceService.getResources();
|
|
401
|
-
dispatch(setResources(resources)); // BAD
|
|
402
|
-
|
|
403
|
-
// Don't use string literals for query keys
|
|
404
|
-
useQuery(['resources'], () => ...); // BAD - inconsistent
|
|
405
|
-
|
|
406
|
-
// Don't forget to invalidate after mutations
|
|
407
|
-
useMutation({
|
|
408
|
-
mutationFn: createResource,
|
|
409
|
-
// Missing onSuccess invalidation - cache will be stale!
|
|
410
|
-
});
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### ✅ DO:
|
|
414
|
-
```tsx
|
|
415
|
-
// Use React Query for server data
|
|
416
|
-
const { data: resources } = useQuery({
|
|
417
|
-
queryKey: resourceKeys.lists(),
|
|
418
|
-
queryFn: resourceService.getResources,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// Always use query key factories
|
|
422
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
|
|
423
|
-
|
|
424
|
-
// Always invalidate after mutations
|
|
425
|
-
useMutation({
|
|
426
|
-
mutationFn: createResource,
|
|
427
|
-
onSuccess: () => {
|
|
428
|
-
queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
## Prefetching Data
|
|
436
|
-
|
|
437
|
-
```tsx
|
|
438
|
-
export const ResourcesPage = () => {
|
|
439
|
-
const queryClient = useQueryClient();
|
|
440
|
-
|
|
441
|
-
// Prefetch next page on hover
|
|
442
|
-
const handleResourceHover = (id: string) => {
|
|
443
|
-
queryClient.prefetchQuery({
|
|
444
|
-
queryKey: resourceKeys.detail(id),
|
|
445
|
-
queryFn: () => resourceService.getResource(id),
|
|
446
|
-
});
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
return (
|
|
450
|
-
<div>
|
|
451
|
-
{resources.map((resource) => (
|
|
452
|
-
<ResourceCard
|
|
453
|
-
key={resource.id}
|
|
454
|
-
resource={resource}
|
|
455
|
-
onMouseEnter={() => handleResourceHover(resource.id)}
|
|
456
|
-
/>
|
|
457
|
-
))}
|
|
458
|
-
</div>
|
|
459
|
-
);
|
|
460
|
-
};
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## Checklist
|
|
466
|
-
|
|
467
|
-
- [ ] Query key factories created for all domains
|
|
468
|
-
- [ ] React Query for all server data
|
|
469
|
-
- [ ] Redux ONLY for auth and global UI state
|
|
470
|
-
- [ ] URL state for filters/pagination
|
|
471
|
-
- [ ] Proper invalidation after mutations
|
|
472
|
-
- [ ] Optimistic updates for instant feedback
|
|
473
|
-
- [ ] Error handling in mutations
|
|
474
|
-
- [ ] Loading states handled in UI
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
trigger: always_on
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
6
|
-
|
|
7
|
-
# API Integration & Error Handling
|
|
8
|
-
|
|
9
|
-
## Axios Configuration
|
|
10
|
-
Standard pattern for interceptors and token management.
|
|
11
|
-
|
|
12
|
-
```tsx
|
|
13
|
-
// lib/api/axios.config.ts
|
|
14
|
-
import axios from 'axios';
|
|
15
|
-
import { store } from '@/store';
|
|
16
|
-
import { logout, updateTokens } from '@/features/auth/store/authSlice';
|
|
17
|
-
|
|
18
|
-
export const apiClient = axios.create({
|
|
19
|
-
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
20
|
-
headers: { 'Content-Type': 'application/json' },
|
|
21
|
-
timeout: 30000,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
apiClient.interceptors.request.use((config) => {
|
|
25
|
-
const token = store.getState().auth.tokens?.accessToken;
|
|
26
|
-
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
27
|
-
return config;
|
|
28
|
-
});
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Service Pattern
|
|
32
|
-
Use classes or plain objects to group API calls by domain.
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
// features/resources/services/resource.service.ts
|
|
36
|
-
class ResourceService {
|
|
37
|
-
async getResources(params = {}) {
|
|
38
|
-
const response = await apiClient.get('/resources', { params });
|
|
39
|
-
return response.data.data;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export const resourceService = new ResourceService();
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Error Handling Utilities
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
// lib/utils/error.ts
|
|
49
|
-
export const getErrorMessage = (error: unknown): string => {
|
|
50
|
-
if (axios.isAxiosError(error)) {
|
|
51
|
-
return error.response?.data?.error?.message || error.message;
|
|
52
|
-
}
|
|
53
|
-
return 'An unexpected error occurred';
|
|
54
|
-
};
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## UI Feedback (Ant Design)
|
|
58
|
-
|
|
59
|
-
### Error Components
|
|
60
|
-
```tsx
|
|
61
|
-
// components/common/ErrorMessage.tsx
|
|
62
|
-
import { Alert } from 'antd';
|
|
63
|
-
import { getErrorMessage } from '@/lib/utils/error';
|
|
64
|
-
|
|
65
|
-
export const ErrorMessage = ({ error }: { error: any }) => (
|
|
66
|
-
<Alert
|
|
67
|
-
message="Error"
|
|
68
|
-
description={getErrorMessage(error)}
|
|
69
|
-
type="error"
|
|
70
|
-
showIcon
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Loading States
|
|
76
|
-
```tsx
|
|
77
|
-
// components/common/LoadingSpinner.tsx
|
|
78
|
-
import { Spin } from 'antd';
|
|
79
|
-
|
|
80
|
-
export const LoadingSpinner = () => (
|
|
81
|
-
<div className="spinner-container">
|
|
82
|
-
<Spin size="large" />
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Skeleton (Ant Design)
|
|
88
|
-
```tsx
|
|
89
|
-
// components/common/ResourceSkeleton.tsx
|
|
90
|
-
import { Skeleton, Card } from 'antd';
|
|
91
|
-
|
|
92
|
-
export const ResourceSkeleton = () => (
|
|
93
|
-
<Card>
|
|
94
|
-
<Skeleton active />
|
|
95
|
-
</Card>
|
|
96
|
-
);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Notifications (Ant Design)
|
|
100
|
-
Use `message` or `notification` from Ant Design.
|
|
101
|
-
|
|
102
|
-
```tsx
|
|
103
|
-
import { message } from 'antd';
|
|
104
|
-
|
|
105
|
-
// Success
|
|
106
|
-
message.success('Action completed!');
|
|
107
|
-
|
|
108
|
-
// Error
|
|
109
|
-
message.error(getErrorMessage(error));
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## File Upload Pattern
|
|
113
|
-
```tsx
|
|
114
|
-
// features/resources/hooks/useUploadFiles.ts
|
|
115
|
-
export const useUploadFiles = () => {
|
|
116
|
-
return useMutation({
|
|
117
|
-
mutationFn: async (files: File[]) => {
|
|
118
|
-
const formData = new FormData();
|
|
119
|
-
files.forEach((file) => formData.append('files', file));
|
|
120
|
-
return apiClient.post('/upload', formData, {
|
|
121
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
122
|
-
});
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Infinite Scroll Pattern
|
|
129
|
-
Standard React Query `useInfiniteQuery` implementation with Ant Design's `Button` for "Load More" or a custom observer.
|