create-tigra 1.0.0

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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/bin/create-tigra.js +292 -0
  4. package/package.json +41 -0
  5. package/template/.agent/rules/client/01-project-structure.md +326 -0
  6. package/template/.agent/rules/client/02-component-patterns.md +249 -0
  7. package/template/.agent/rules/client/03-typescript-rules.md +226 -0
  8. package/template/.agent/rules/client/04-state-management.md +474 -0
  9. package/template/.agent/rules/client/05-api-integration.md +129 -0
  10. package/template/.agent/rules/client/06-forms-validation.md +129 -0
  11. package/template/.agent/rules/client/07-common-patterns.md +150 -0
  12. package/template/.agent/rules/client/08-color-system.md +93 -0
  13. package/template/.agent/rules/client/09-security-rules.md +97 -0
  14. package/template/.agent/rules/client/10-testing-strategy.md +370 -0
  15. package/template/.agent/rules/global/ai-edit-safety.md +38 -0
  16. package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
  17. package/template/.agent/rules/server/02-general-rules.md +111 -0
  18. package/template/.agent/rules/server/03-migrations.md +20 -0
  19. package/template/.agent/rules/server/04-pagination.md +130 -0
  20. package/template/.agent/rules/server/05-project-conventions.md +71 -0
  21. package/template/.agent/rules/server/06-response-handling.md +173 -0
  22. package/template/.agent/rules/server/07-testing-strategy.md +506 -0
  23. package/template/.agent/rules/server/08-observability.md +180 -0
  24. package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
  25. package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
  26. package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
  27. package/template/.agent/rules/server/12-performance-optimization.md +567 -0
  28. package/template/.claude/rules/client-01-project-structure.md +327 -0
  29. package/template/.claude/rules/client-02-component-patterns.md +250 -0
  30. package/template/.claude/rules/client-03-typescript-rules.md +227 -0
  31. package/template/.claude/rules/client-04-state-management.md +475 -0
  32. package/template/.claude/rules/client-05-api-integration.md +130 -0
  33. package/template/.claude/rules/client-06-forms-validation.md +130 -0
  34. package/template/.claude/rules/client-07-common-patterns.md +151 -0
  35. package/template/.claude/rules/client-08-color-system.md +94 -0
  36. package/template/.claude/rules/client-09-security-rules.md +98 -0
  37. package/template/.claude/rules/client-10-testing-strategy.md +371 -0
  38. package/template/.claude/rules/global-ai-edit-safety.md +39 -0
  39. package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
  40. package/template/.claude/rules/server-02-general-rules.md +112 -0
  41. package/template/.claude/rules/server-03-migrations.md +21 -0
  42. package/template/.claude/rules/server-04-pagination.md +131 -0
  43. package/template/.claude/rules/server-05-project-conventions.md +72 -0
  44. package/template/.claude/rules/server-06-response-handling.md +174 -0
  45. package/template/.claude/rules/server-07-testing-strategy.md +507 -0
  46. package/template/.claude/rules/server-08-observability.md +181 -0
  47. package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
  48. package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
  49. package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
  50. package/template/.claude/rules/server-12-performance-optimization.md +568 -0
  51. package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
  52. package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
  53. package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
  54. package/template/.cursor/rules/client-04-state-management.mdc +475 -0
  55. package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
  56. package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
  57. package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
  58. package/template/.cursor/rules/client-08-color-system.mdc +94 -0
  59. package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
  60. package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
  61. package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
  62. package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
  63. package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
  64. package/template/.cursor/rules/server-03-migrations.mdc +21 -0
  65. package/template/.cursor/rules/server-04-pagination.mdc +131 -0
  66. package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
  67. package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
  68. package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
  69. package/template/.cursor/rules/server-08-observability.mdc +181 -0
  70. package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
  71. package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
  72. package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
  73. package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
  74. package/template/CLAUDE.md +207 -0
  75. package/template/server/.env.example +148 -0
  76. package/template/server/.tsc-aliasrc.json +12 -0
  77. package/template/server/README.md +175 -0
  78. package/template/server/SECURITY.md +190 -0
  79. package/template/server/biome.json +42 -0
  80. package/template/server/docker-compose.yml +111 -0
  81. package/template/server/package.json +83 -0
  82. package/template/server/postman_collection.json +733 -0
  83. package/template/server/prisma/schema.prisma +92 -0
  84. package/template/server/prisma/seed.ts +142 -0
  85. package/template/server/scripts/wait-for-db.js +60 -0
  86. package/template/server/src/app.ts +74 -0
  87. package/template/server/src/config/env.ts +101 -0
  88. package/template/server/src/hooks/request-timing.hook.ts +26 -0
  89. package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
  90. package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
  91. package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
  92. package/template/server/src/libs/db.ts +76 -0
  93. package/template/server/src/libs/error-handler.ts +89 -0
  94. package/template/server/src/libs/logger.ts +60 -0
  95. package/template/server/src/libs/queue.ts +79 -0
  96. package/template/server/src/libs/redis.ts +79 -0
  97. package/template/server/src/libs/swagger-schemas.ts +16 -0
  98. package/template/server/src/modules/admin/admin.controller.ts +122 -0
  99. package/template/server/src/modules/admin/admin.routes.ts +100 -0
  100. package/template/server/src/modules/admin/admin.schemas.ts +35 -0
  101. package/template/server/src/modules/admin/admin.service.ts +167 -0
  102. package/template/server/src/modules/auth/auth.controller.ts +141 -0
  103. package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
  104. package/template/server/src/modules/auth/auth.repo.ts +218 -0
  105. package/template/server/src/modules/auth/auth.routes.ts +204 -0
  106. package/template/server/src/modules/auth/auth.schemas.ts +137 -0
  107. package/template/server/src/modules/auth/auth.service.test.ts +119 -0
  108. package/template/server/src/modules/auth/auth.service.ts +329 -0
  109. package/template/server/src/modules/auth/auth.types.ts +97 -0
  110. package/template/server/src/modules/resources/resources.controller.ts +218 -0
  111. package/template/server/src/modules/resources/resources.repo.ts +253 -0
  112. package/template/server/src/modules/resources/resources.routes.ts +355 -0
  113. package/template/server/src/modules/resources/resources.schemas.ts +146 -0
  114. package/template/server/src/modules/resources/resources.service.ts +218 -0
  115. package/template/server/src/modules/resources/resources.types.ts +73 -0
  116. package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
  117. package/template/server/src/plugins/security.plugin.ts +21 -0
  118. package/template/server/src/plugins/swagger.plugin.ts +41 -0
  119. package/template/server/src/routes/health.routes.ts +31 -0
  120. package/template/server/src/server.ts +142 -0
  121. package/template/server/src/test/setup.ts +38 -0
  122. package/template/server/src/types/fastify.d.ts +36 -0
  123. package/template/server/src/utils/errors.ts +108 -0
  124. package/template/server/src/utils/pagination.ts +120 -0
  125. package/template/server/src/utils/response.ts +110 -0
  126. package/template/server/src/workers/file.worker.ts +106 -0
  127. package/template/server/tsconfig.build.json +30 -0
  128. package/template/server/tsconfig.build.tsbuildinfo +1 -0
  129. package/template/server/tsconfig.json +89 -0
  130. package/template/server/tsconfig.test.json +22 -0
  131. package/template/server/vitest.config.ts +98 -0
@@ -0,0 +1,227 @@
1
+ ---
2
+ trigger: always_on
3
+ globs: "client/**/*"
4
+ ---
5
+
6
+ > **SCOPE**: These rules apply specifically to the **client** directory.
7
+
8
+ # TypeScript Rules & Types
9
+
10
+ ## TypeScript Configuration
11
+
12
+ ```json
13
+ // tsconfig.json
14
+ {
15
+ "compilerOptions": {
16
+ "target": "ES2020",
17
+ "useDefineForClassFields": true,
18
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
19
+ "module": "ESNext",
20
+ "skipLibCheck": true,
21
+ "moduleResolution": "bundler",
22
+ "allowImportingTsExtensions": true,
23
+ "resolveJsonModule": true,
24
+ "isolatedModules": true,
25
+ "noEmit": true,
26
+ "jsx": "react-jsx",
27
+ "strict": true,
28
+ "noUnusedLocals": true,
29
+ "noUnusedParameters": true,
30
+ "noFallthroughCasesInSwitch": true
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Strict Mode Rules
36
+
37
+ - **Always use strict mode**
38
+ - **NO `any` type** (use `unknown` if needed)
39
+ - **Explicit return types** for functions
40
+ - **No implicit any**
41
+
42
+ ```tsx
43
+ // ❌ BAD
44
+ const fetchResource = async (id) => {
45
+ const response = await api.get(`/resources/${id}`);
46
+ return response.data;
47
+ };
48
+
49
+ // ✅ GOOD
50
+ const fetchResource = async (id: string): Promise<Resource> => {
51
+ const response = await api.get<ApiResponse<Resource>>(`/resources/${id}`);
52
+ return response.data.data;
53
+ };
54
+ ```
55
+
56
+ ## Type vs Interface
57
+
58
+ ### Use `interface` for:
59
+ - Component props
60
+ - Object shapes
61
+ - Extendable structures
62
+
63
+ ### Use `type` for:
64
+ - Unions
65
+ - Intersections
66
+ - Utility types
67
+ - Type aliases
68
+
69
+ ```tsx
70
+ // ✅ Interface for props
71
+ interface ResourceCardProps {
72
+ resource: Resource;
73
+ onClick?: () => void;
74
+ }
75
+
76
+ // ✅ Type for unions
77
+ type UserRole = 'USER' | 'ORG' | 'ADMIN';
78
+ type ResourceStatus = 'active' | 'inactive' | 'deleted';
79
+
80
+ // ✅ Type for intersections
81
+ type ResourceWithOwner = Resource & { owner: User };
82
+ ```
83
+
84
+ ## API Response Types
85
+
86
+ Match backend response structure:
87
+
88
+ ```tsx
89
+ // lib/api/api.types.ts
90
+
91
+ // Base response
92
+ export interface ApiResponse<T> {
93
+ success: boolean;
94
+ message: string;
95
+ data: T;
96
+ }
97
+
98
+ // Paginated response
99
+ export interface PaginatedApiResponse<T> {
100
+ success: boolean;
101
+ message: string;
102
+ data: {
103
+ items: T[];
104
+ pagination: {
105
+ page: number;
106
+ limit: number;
107
+ totalItems: number;
108
+ totalPages: number;
109
+ hasNextPage: boolean;
110
+ hasPreviousPage: boolean;
111
+ };
112
+ };
113
+ }
114
+
115
+ // Error response
116
+ export interface ApiError {
117
+ success: false;
118
+ error: {
119
+ code: string;
120
+ message: string;
121
+ };
122
+ }
123
+ ```
124
+
125
+ ## Domain Types
126
+
127
+ Create types matching backend entities:
128
+
129
+ ```tsx
130
+ // features/resources/types/resource.types.ts
131
+
132
+ export interface Resource {
133
+ id: string;
134
+ ownerId: string;
135
+ title: string;
136
+ summary: string | null;
137
+ price: number;
138
+ status: ResourceStatus;
139
+ createdAt: string;
140
+ updatedAt: string;
141
+ }
142
+
143
+ export type ResourceStatus = 'active' | 'inactive' | 'deleted';
144
+
145
+ // Request types
146
+ export interface CreateResourceRequest {
147
+ title: string;
148
+ summary?: string;
149
+ price: number;
150
+ }
151
+ ```
152
+
153
+ ## Function Type Signatures
154
+
155
+ ```tsx
156
+ // Event handlers
157
+ type ClickHandler = (event: React.MouseEvent<HTMLElement>) => void;
158
+ type ChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => void;
159
+
160
+ // Async functions
161
+ type AsyncFunction<T> = () => Promise<T>;
162
+ ```
163
+
164
+ ## Component Prop Types
165
+
166
+ ```tsx
167
+ // Base component props
168
+ interface BaseComponentProps {
169
+ className?: string; // Standard for CSS and Ant Design
170
+ children?: React.ReactNode;
171
+ }
172
+
173
+ // With generic data
174
+ interface DataComponentProps<T> extends BaseComponentProps {
175
+ data: T;
176
+ onSelect?: (item: T) => void;
177
+ }
178
+ ```
179
+
180
+ ## Type Guards
181
+
182
+ ```tsx
183
+ // Type guard functions
184
+ export const isApiError = (error: unknown): error is ApiError => {
185
+ return (
186
+ typeof error === 'object' &&
187
+ error !== null &&
188
+ 'success' in error &&
189
+ error.success === false
190
+ );
191
+ };
192
+ ```
193
+
194
+ ## Enum Alternatives (String Unions)
195
+
196
+ ```tsx
197
+ // ✅ Use string unions + const object
198
+ export const USER_ROLES = {
199
+ USER: 'USER',
200
+ ORG: 'ORGANIZATION',
201
+ ADMIN: 'ADMIN',
202
+ } as const;
203
+
204
+ export type UserRole = (typeof USER_ROLES)[keyof typeof USER_ROLES];
205
+ ```
206
+
207
+ ## Discriminated Unions
208
+
209
+ ```tsx
210
+ // State machine pattern
211
+ type RequestState<T> =
212
+ | { status: 'idle' }
213
+ | { status: 'loading' }
214
+ | { status: 'success'; data: T }
215
+ | { status: 'error'; error: Error };
216
+ ```
217
+
218
+ ## Type Safety Checklist
219
+
220
+ - [ ] Strict mode enabled
221
+ - [ ] No `any` types used
222
+ - [ ] All functions have return types
223
+ - [ ] Props interfaces defined
224
+ - [ ] API response types match backend
225
+ - [ ] Domain types match backend models
226
+ - [ ] Type guards for runtime checks
227
+ - [ ] Discriminated unions for state machines
@@ -0,0 +1,475 @@
1
+ ---
2
+ trigger: always_on
3
+ globs: "client/**/*"
4
+ ---
5
+
6
+ > **SCOPE**: These rules apply specifically to the **client** directory.
7
+
8
+ # State Management Patterns
9
+
10
+ ## State Strategy
11
+
12
+ ### Decision Matrix
13
+
14
+ | State Type | Tool | Examples |
15
+ |------------|------|----------|
16
+ | **Server Data** | React Query | Resources, profiles, application data from API |
17
+ | **Global Client** | Redux | Auth tokens, current user, theme settings |
18
+ | **Local** | useState | Form inputs, modals, hover state, toggles |
19
+ | **URL** | React Router | Filters, pagination, search query |
20
+
21
+ ## React Query (Server State)
22
+
23
+ ### Setup
24
+
25
+ ```tsx
26
+ // app/providers.tsx
27
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
28
+
29
+ const queryClient = new QueryClient({
30
+ defaultOptions: {
31
+ queries: {
32
+ staleTime: 5 * 60 * 1000,
33
+ cacheTime: 10 * 60 * 1000,
34
+ refetchOnWindowFocus: false,
35
+ retry: 1,
36
+ },
37
+ },
38
+ });
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Query Key Factory Pattern (MANDATORY)
44
+
45
+ **Why?** Consistent query keys prevent cache bugs and enable powerful invalidation patterns.
46
+
47
+ ### Factory Structure
48
+
49
+ ```tsx
50
+ // features/resources/lib/query-keys.ts
51
+
52
+ export const resourceKeys = {
53
+ // Base key
54
+ all: ['resources'] as const,
55
+
56
+ // List queries
57
+ lists: () => [...resourceKeys.all, 'list'] as const,
58
+ list: (filters: ResourceFilters) => [...resourceKeys.lists(), filters] as const,
59
+
60
+ // Detail queries
61
+ details: () => [...resourceKeys.all, 'detail'] as const,
62
+ detail: (id: string) => [...resourceKeys.details(), id] as const,
63
+
64
+ // User-specific queries
65
+ myResources: () => [...resourceKeys.all, 'my'] as const,
66
+ myResource: (filters: ResourceFilters) => [...resourceKeys.myResources(), filters] as const,
67
+ };
68
+
69
+ // Usage in hooks
70
+ export const useResources = (filters: ResourceFilters) => {
71
+ return useQuery({
72
+ queryKey: resourceKeys.list(filters),
73
+ queryFn: () => resourceService.getResources(filters),
74
+ });
75
+ };
76
+
77
+ export const useResource = (id: string) => {
78
+ return useQuery({
79
+ queryKey: resourceKeys.detail(id),
80
+ queryFn: () => resourceService.getResource(id),
81
+ enabled: !!id, // Only fetch if ID exists
82
+ });
83
+ };
84
+ ```
85
+
86
+ ### Complex Example (Categories with Resources)
87
+
88
+ ```tsx
89
+ // features/categories/lib/query-keys.ts
90
+ export const categoryKeys = {
91
+ all: ['categories'] as const,
92
+ lists: () => [...categoryKeys.all, 'list'] as const,
93
+ list: (filters: CategoryFilters) => [...categoryKeys.lists(), filters] as const,
94
+ details: () => [...categoryKeys.all, 'detail'] as const,
95
+ detail: (id: string) => [...categoryKeys.details(), id] as const,
96
+
97
+ // Nested resources
98
+ resources: (categoryId: string) => [...categoryKeys.detail(categoryId), 'resources'] as const,
99
+ };
100
+ ```
101
+
102
+ ### Invalidation Patterns
103
+
104
+ ```tsx
105
+ // After creating a resource
106
+ useMutation({
107
+ mutationFn: resourceService.createResource,
108
+ onSuccess: () => {
109
+ // Invalidate all resource lists
110
+ queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
111
+ },
112
+ });
113
+
114
+ // After updating a specific resource
115
+ useMutation({
116
+ mutationFn: ({ id, data }) => resourceService.updateResource(id, data),
117
+ onSuccess: (_, variables) => {
118
+ // Invalidate this specific resource
119
+ queryClient.invalidateQueries({ queryKey: resourceKeys.detail(variables.id) });
120
+ // Also invalidate lists (resource might move categories)
121
+ queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
122
+ },
123
+ });
124
+
125
+ // After deleting
126
+ useMutation({
127
+ mutationFn: resourceService.deleteResource,
128
+ onSuccess: () => {
129
+ // Remove all resource-related queries
130
+ queryClient.invalidateQueries({ queryKey: resourceKeys.all });
131
+ },
132
+ });
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Query Pattern
138
+
139
+ ```tsx
140
+ // features/resources/hooks/useResources.ts
141
+ import { useQuery } from '@tanstack/react-query';
142
+ import { resourceService } from '../services/resource.service';
143
+ import { resourceKeys } from '../lib/query-keys';
144
+
145
+ export const useResources = (filters = {}, page = 1, limit = 10) => {
146
+ return useQuery({
147
+ queryKey: resourceKeys.list({ ...filters, page, limit }),
148
+ queryFn: () => resourceService.getResources({ ...filters, page, limit }),
149
+ staleTime: 5 * 60 * 1000,
150
+ });
151
+ };
152
+ ```
153
+
154
+ ## Mutation Pattern
155
+
156
+ ```tsx
157
+ // features/resources/hooks/useCreateResource.ts
158
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
159
+ import { message } from 'antd'; // Ant Design message
160
+ import { resourceKeys } from '../lib/query-keys';
161
+
162
+ export const useCreateResource = () => {
163
+ const queryClient = useQueryClient();
164
+
165
+ return useMutation({
166
+ mutationFn: resourceService.createResource,
167
+ onSuccess: () => {
168
+ queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
169
+ message.success('Resource created!');
170
+ },
171
+ onError: (error) => {
172
+ message.error(getErrorMessage(error));
173
+ },
174
+ });
175
+ };
176
+ ```
177
+
178
+ ## Optimistic Updates
179
+
180
+ ```tsx
181
+ export const useToggleStatus = () => {
182
+ const queryClient = useQueryClient();
183
+
184
+ return useMutation({
185
+ mutationFn: ({ id, status }) => resourceService.updateStatus(id, status),
186
+ onMutate: async ({ id, status }) => {
187
+ // Cancel outgoing queries
188
+ await queryClient.cancelQueries({ queryKey: resourceKeys.detail(id) });
189
+
190
+ // Snapshot previous value
191
+ const previous = queryClient.getQueryData(resourceKeys.detail(id));
192
+
193
+ // Optimistically update
194
+ queryClient.setQueryData(resourceKeys.detail(id), (old: any) => ({
195
+ ...old,
196
+ status
197
+ }));
198
+
199
+ return { previous, id };
200
+ },
201
+ onError: (err, variables, context) => {
202
+ // Rollback on error
203
+ if (context?.previous) {
204
+ queryClient.setQueryData(resourceKeys.detail(context.id), context.previous);
205
+ }
206
+ message.error('Failed to update status');
207
+ },
208
+ onSettled: (data, error, variables) => {
209
+ // Refetch to ensure sync
210
+ queryClient.invalidateQueries({ queryKey: resourceKeys.detail(variables.id) });
211
+ },
212
+ });
213
+ };
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Redux (Global Client State)
219
+
220
+ ### When to Use Redux
221
+ - Authentication state (user, tokens)
222
+ - Global UI state (theme, sidebar collapsed)
223
+ - App-wide settings
224
+
225
+ ### Slice Pattern
226
+ Standard Redux Toolkit pattern for auth and global UI state.
227
+
228
+ ```tsx
229
+ // features/auth/store/authSlice.ts
230
+ import { createSlice } from '@reduxjs/toolkit';
231
+
232
+ interface AuthState {
233
+ user: User | null;
234
+ tokens: { accessToken: string; refreshToken: string } | null;
235
+ isAuthenticated: boolean;
236
+ }
237
+
238
+ const initialState: AuthState = {
239
+ user: null,
240
+ tokens: null,
241
+ isAuthenticated: false,
242
+ };
243
+
244
+ const authSlice = createSlice({
245
+ name: 'auth',
246
+ initialState,
247
+ reducers: {
248
+ setCredentials: (state, action) => {
249
+ state.user = action.payload.user;
250
+ state.tokens = action.payload.tokens;
251
+ state.isAuthenticated = true;
252
+ },
253
+ logout: (state) => {
254
+ state.user = null;
255
+ state.tokens = null;
256
+ state.isAuthenticated = false;
257
+ },
258
+ },
259
+ });
260
+
261
+ export const { setCredentials, logout } = authSlice.actions;
262
+ export default authSlice.reducer;
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Local State
268
+ Use `useState` for UI-only state (e.g., `isModalOpen`, form inputs before submission).
269
+
270
+ ```tsx
271
+ export const ResourceCard = ({ resource }) => {
272
+ const [isHovered, setIsHovered] = useState(false);
273
+ const [showDetails, setShowDetails] = useState(false);
274
+
275
+ return (
276
+ <div
277
+ onMouseEnter={() => setIsHovered(true)}
278
+ onMouseLeave={() => setIsHovered(false)}
279
+ >
280
+ {/* UI */}
281
+ </div>
282
+ );
283
+ };
284
+ ```
285
+
286
+ ---
287
+
288
+ ## URL State
289
+ Use `useSearchParams` from `react-router-dom` for filters and pagination to ensure page refreshes and direct links work as expected.
290
+
291
+ ```tsx
292
+ import { useSearchParams } from 'react-router-dom';
293
+
294
+ export const ResourcesPage = () => {
295
+ const [searchParams, setSearchParams] = useSearchParams();
296
+
297
+ const page = parseInt(searchParams.get('page') || '1');
298
+ const category = searchParams.get('category') || '';
299
+
300
+ const handleFilterChange = (newCategory: string) => {
301
+ setSearchParams({ page: '1', category: newCategory });
302
+ };
303
+
304
+ const { data } = useResources({ category }, page);
305
+
306
+ return (
307
+ <div>
308
+ <CategoryFilter value={category} onChange={handleFilterChange} />
309
+ <ResourceList resources={data?.items} />
310
+ <Pagination
311
+ current={page}
312
+ total={data?.pagination.totalPages}
313
+ onChange={(newPage) => setSearchParams({ page: String(newPage), category })}
314
+ />
315
+ </div>
316
+ );
317
+ };
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Advanced Query Patterns
323
+
324
+ ### Dependent Queries
325
+ ```tsx
326
+ export const useResourceWithOwner = (resourceId: string) => {
327
+ // First fetch resource
328
+ const { data: resource } = useQuery({
329
+ queryKey: resourceKeys.detail(resourceId),
330
+ queryFn: () => resourceService.getResource(resourceId),
331
+ });
332
+
333
+ // Then fetch owner (depends on resource)
334
+ const { data: owner } = useQuery({
335
+ queryKey: ['users', resource?.ownerId],
336
+ queryFn: () => userService.getUser(resource!.ownerId),
337
+ enabled: !!resource?.ownerId, // Only run when we have ownerId
338
+ });
339
+
340
+ return { resource, owner };
341
+ };
342
+ ```
343
+
344
+ ### Parallel Queries
345
+ ```tsx
346
+ export const useDashboardData = () => {
347
+ const resources = useQuery({
348
+ queryKey: resourceKeys.lists(),
349
+ queryFn: resourceService.getResources,
350
+ });
351
+
352
+ const categories = useQuery({
353
+ queryKey: categoryKeys.lists(),
354
+ queryFn: categoryService.getCategories,
355
+ });
356
+
357
+ const stats = useQuery({
358
+ queryKey: ['stats'],
359
+ queryFn: statsService.getStats,
360
+ });
361
+
362
+ return {
363
+ isLoading: resources.isLoading || categories.isLoading || stats.isLoading,
364
+ data: {
365
+ resources: resources.data,
366
+ categories: categories.data,
367
+ stats: stats.data,
368
+ },
369
+ };
370
+ };
371
+ ```
372
+
373
+ ### Infinite Queries (Load More)
374
+ ```tsx
375
+ export const useInfiniteResources = (filters: ResourceFilters) => {
376
+ return useInfiniteQuery({
377
+ queryKey: [...resourceKeys.lists(), 'infinite', filters],
378
+ queryFn: ({ pageParam = 1 }) =>
379
+ resourceService.getResources({ ...filters, page: pageParam }),
380
+ getNextPageParam: (lastPage) =>
381
+ lastPage.pagination.hasNextPage ? lastPage.pagination.page + 1 : undefined,
382
+ });
383
+ };
384
+
385
+ // Usage in component
386
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteResources(filters);
387
+
388
+ <Button onClick={() => fetchNextPage()} disabled={!hasNextPage} loading={isFetchingNextPage}>
389
+ Load More
390
+ </Button>
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Anti-Patterns
396
+
397
+ ### ❌ DO NOT:
398
+ ```tsx
399
+ // Don't store server data in Redux
400
+ const dispatch = useDispatch();
401
+ const resources = await resourceService.getResources();
402
+ dispatch(setResources(resources)); // BAD
403
+
404
+ // Don't use string literals for query keys
405
+ useQuery(['resources'], () => ...); // BAD - inconsistent
406
+
407
+ // Don't forget to invalidate after mutations
408
+ useMutation({
409
+ mutationFn: createResource,
410
+ // Missing onSuccess invalidation - cache will be stale!
411
+ });
412
+ ```
413
+
414
+ ### ✅ DO:
415
+ ```tsx
416
+ // Use React Query for server data
417
+ const { data: resources } = useQuery({
418
+ queryKey: resourceKeys.lists(),
419
+ queryFn: resourceService.getResources,
420
+ });
421
+
422
+ // Always use query key factories
423
+ queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
424
+
425
+ // Always invalidate after mutations
426
+ useMutation({
427
+ mutationFn: createResource,
428
+ onSuccess: () => {
429
+ queryClient.invalidateQueries({ queryKey: resourceKeys.lists() });
430
+ },
431
+ });
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Prefetching Data
437
+
438
+ ```tsx
439
+ export const ResourcesPage = () => {
440
+ const queryClient = useQueryClient();
441
+
442
+ // Prefetch next page on hover
443
+ const handleResourceHover = (id: string) => {
444
+ queryClient.prefetchQuery({
445
+ queryKey: resourceKeys.detail(id),
446
+ queryFn: () => resourceService.getResource(id),
447
+ });
448
+ };
449
+
450
+ return (
451
+ <div>
452
+ {resources.map((resource) => (
453
+ <ResourceCard
454
+ key={resource.id}
455
+ resource={resource}
456
+ onMouseEnter={() => handleResourceHover(resource.id)}
457
+ />
458
+ ))}
459
+ </div>
460
+ );
461
+ };
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Checklist
467
+
468
+ - [ ] Query key factories created for all domains
469
+ - [ ] React Query for all server data
470
+ - [ ] Redux ONLY for auth and global UI state
471
+ - [ ] URL state for filters/pagination
472
+ - [ ] Proper invalidation after mutations
473
+ - [ ] Optimistic updates for instant feedback
474
+ - [ ] Error handling in mutations
475
+ - [ ] Loading states handled in UI