analyze-codebase 1.2.2 → 1.3.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/.claude/settings.local.json +16 -0
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +137 -0
- package/ENHANCEMENTS.md +257 -0
- package/FEATURES.md +225 -0
- package/dist/analyze/i18n/index.js +477 -0
- package/dist/analyze/index.js +187 -25
- package/dist/index.js +203 -16
- package/dist/utils/__tests__/config.test.js +94 -0
- package/dist/utils/__tests__/progress.test.js +37 -0
- package/dist/utils/cancellation.js +75 -0
- package/dist/utils/config.js +83 -0
- package/dist/utils/export.js +218 -0
- package/dist/utils/output.js +137 -41
- package/dist/utils/progress.js +51 -0
- package/dist/utils/spinner.js +61 -0
- package/dist/utils/watch.js +99 -0
- package/docs/agents/developer-agent.md +818 -0
- package/docs/agents/research-agent.md +244 -0
- package/package.json +44 -20
- package/readme.md +163 -86
- package/tsconfig.json +5 -2
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
# Developer Agent Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document defines the rules and guidelines for the Developer Agent, an AI agent designed to write, maintain, and refactor code for the Uygar Koleji project. The agent follows the project's established patterns, conventions, and coding style.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
The Developer Agent is responsible for:
|
|
10
|
+
|
|
11
|
+
- Writing new features following project conventions
|
|
12
|
+
- Refactoring existing code while maintaining consistency
|
|
13
|
+
- Fixing bugs and implementing improvements
|
|
14
|
+
- Ensuring code quality and type safety
|
|
15
|
+
- Following established architectural patterns
|
|
16
|
+
- Maintaining consistency with existing codebase
|
|
17
|
+
|
|
18
|
+
## Language
|
|
19
|
+
|
|
20
|
+
**All Developer Agent code and comments should be in English**, following the project's standard:
|
|
21
|
+
|
|
22
|
+
- Code comments: English
|
|
23
|
+
- Variable/function names: English
|
|
24
|
+
- Type definitions: English
|
|
25
|
+
- Error messages: Use i18next for user-facing messages
|
|
26
|
+
- Documentation: English for technical docs
|
|
27
|
+
|
|
28
|
+
## Design System and UI Guidelines
|
|
29
|
+
|
|
30
|
+
**When implementing UI components, animations, or visual effects, always refer to the [Design System documentation](../design/design-system.md).**
|
|
31
|
+
|
|
32
|
+
The design system defines:
|
|
33
|
+
|
|
34
|
+
- **Visual Language**: Color palettes, typography, spacing, and gradients
|
|
35
|
+
- **Animation Patterns**: Entrance animations, hover effects, transitions, and timing guidelines
|
|
36
|
+
- **Component Patterns**: Standard patterns for cards, buttons, forms, and navigation
|
|
37
|
+
- **Motion Guidelines**: When and how to use animations, performance considerations, and reduced motion support
|
|
38
|
+
|
|
39
|
+
**Key Design Principles**:
|
|
40
|
+
|
|
41
|
+
- Use the established color gradients (emerald to sky for primary actions, rose to pink for destructive actions)
|
|
42
|
+
- Follow animation timing guidelines (300ms for transitions, 500-600ms for entrances)
|
|
43
|
+
- Implement hover effects consistently (lift, shadow, border color changes, gradient overlays)
|
|
44
|
+
- Use Framer Motion variants from the animation library for consistency
|
|
45
|
+
- Ensure all animations respect `prefers-reduced-motion`
|
|
46
|
+
|
|
47
|
+
**Example**: When creating a new card component, use the interactive card pattern with gradient overlay on hover as documented in the design system.
|
|
48
|
+
|
|
49
|
+
## Code Style and Conventions
|
|
50
|
+
|
|
51
|
+
### 1. File Organization
|
|
52
|
+
|
|
53
|
+
#### API Routes (`src/app/api/`)
|
|
54
|
+
|
|
55
|
+
**Structure**:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Imports (grouped by type)
|
|
59
|
+
import {
|
|
60
|
+
buildOkResponse,
|
|
61
|
+
TSuccessResponse,
|
|
62
|
+
} from "@/lib/api/build-success-response";
|
|
63
|
+
import { BadRequestError } from "@/lib/api/errors/bad-request.error";
|
|
64
|
+
import { parseRequestBodyMiddleware } from "@/lib/api/middlewares";
|
|
65
|
+
import {
|
|
66
|
+
ProtectedRequestContext,
|
|
67
|
+
protectedRouter,
|
|
68
|
+
} from "@/lib/api/routers/protected-router";
|
|
69
|
+
import { buildRouteHandler } from "@/lib/api/routers/route-handler";
|
|
70
|
+
|
|
71
|
+
// Type definitions
|
|
72
|
+
export type GetResourceResponse = TSuccessResponse<Resource[]>;
|
|
73
|
+
|
|
74
|
+
// #region get
|
|
75
|
+
async function getResource(
|
|
76
|
+
_req: NextRequest,
|
|
77
|
+
ctx: ProtectedRequestContext<never, never, GetResourceSchema>,
|
|
78
|
+
): Promise<NextResponse<GetResourceResponse>> {
|
|
79
|
+
// Implementation
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const getRouter = protectedRouter<never, never, GetResourceSchema>()
|
|
83
|
+
.use(parseRequestSearchParamsMiddleware(getResourceSchema))
|
|
84
|
+
.get(getResource);
|
|
85
|
+
|
|
86
|
+
export const GET = buildRouteHandler((...params) => getRouter.run(...params));
|
|
87
|
+
//#endregion
|
|
88
|
+
|
|
89
|
+
// #region post
|
|
90
|
+
// Similar structure for POST
|
|
91
|
+
//#endregion
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Rules**:
|
|
95
|
+
|
|
96
|
+
- Use `#region` and `#endregion` to group HTTP methods
|
|
97
|
+
- Always use `ProtectedRequestContext` for protected routes
|
|
98
|
+
- Use `protectedRouter` with proper type parameters
|
|
99
|
+
- Export HTTP methods using `buildRouteHandler`
|
|
100
|
+
- Use `buildOkResponse` for successful responses
|
|
101
|
+
- Always include soft delete check: `eq(table.isDeleted, false)`
|
|
102
|
+
- Use appropriate error types: `BadRequestError`, `NotFoundError`
|
|
103
|
+
- Use i18next for error messages: `i18next.t('ApiRoutes.Utils.creationFailed')`
|
|
104
|
+
|
|
105
|
+
#### Services (`src/lib/services/`)
|
|
106
|
+
|
|
107
|
+
**Structure**:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
111
|
+
import { HttpClient } from "../utils/http-client";
|
|
112
|
+
import {
|
|
113
|
+
CreateResourceSchema,
|
|
114
|
+
GetResourceSchema,
|
|
115
|
+
} from "../schemas/resource/...";
|
|
116
|
+
|
|
117
|
+
export const resourceRoutes = {
|
|
118
|
+
resources: () => `/resources`,
|
|
119
|
+
resourcesById: (id: string) => `/resources/${id}`,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const getResources = (data: GetResourceSchema) =>
|
|
123
|
+
HttpClient.get<GetResourceResponse>({
|
|
124
|
+
url: resourceRoutes.resources(),
|
|
125
|
+
params: data,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const getResourcesQueryKey = (data: GetResourceSchema) => [
|
|
129
|
+
resourceRoutes.resources(),
|
|
130
|
+
data.page,
|
|
131
|
+
data.limit,
|
|
132
|
+
// ... other relevant params
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
export const useGetResourcesQuery = (data: GetResourceSchema) =>
|
|
136
|
+
useQuery({
|
|
137
|
+
queryKey: getResourcesQueryKey(data),
|
|
138
|
+
queryFn: () => getResources(data),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const createResource = (data: CreateResourceSchema) =>
|
|
142
|
+
HttpClient.post<CreateResourceResponse>({
|
|
143
|
+
url: resourceRoutes.resources(),
|
|
144
|
+
data,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export const useCreateResourceMutation = () =>
|
|
148
|
+
useMutation({
|
|
149
|
+
mutationFn: createResource,
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Rules**:
|
|
154
|
+
|
|
155
|
+
- Define route constants using object with functions
|
|
156
|
+
- Create query key functions for React Query
|
|
157
|
+
- Use `useQuery` and `useMutation` hooks
|
|
158
|
+
- Follow naming: `useGetXQuery`, `useCreateXMutation`, `useUpdateXMutation`, `useDeleteXMutation`
|
|
159
|
+
- Import types from API route files
|
|
160
|
+
|
|
161
|
+
#### Schemas (`src/lib/schemas/`)
|
|
162
|
+
|
|
163
|
+
**Structure**:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import z from "zod";
|
|
167
|
+
import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from "@/lib/constants";
|
|
168
|
+
|
|
169
|
+
export const RESOURCE_ORDERABLE_COLUMNS = [
|
|
170
|
+
"title",
|
|
171
|
+
"createdAt",
|
|
172
|
+
// ...
|
|
173
|
+
] as const;
|
|
174
|
+
|
|
175
|
+
export const createResourceSchema = () =>
|
|
176
|
+
z.object({
|
|
177
|
+
title: z.string().min(1),
|
|
178
|
+
categoryId: z.string().uuid(),
|
|
179
|
+
// ...
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
export type CreateResourceSchema = z.infer<
|
|
183
|
+
ReturnType<typeof createResourceSchema>
|
|
184
|
+
>;
|
|
185
|
+
|
|
186
|
+
export const createResourceSchemaClient = () =>
|
|
187
|
+
createResourceSchema().extend({
|
|
188
|
+
file: z.instanceof(File).optional(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export type CreateResourceSchemaClient = z.infer<
|
|
192
|
+
ReturnType<typeof createResourceSchemaClient>
|
|
193
|
+
>;
|
|
194
|
+
|
|
195
|
+
export const getResourcesSchema = () =>
|
|
196
|
+
z.object({
|
|
197
|
+
page: z.coerce.number().min(1).default(1),
|
|
198
|
+
limit: z.coerce
|
|
199
|
+
.number()
|
|
200
|
+
.min(1)
|
|
201
|
+
.max(MAX_PAGE_SIZE)
|
|
202
|
+
.default(DEFAULT_PAGE_SIZE),
|
|
203
|
+
search: z.string().optional(),
|
|
204
|
+
orderByColumn: z.enum(RESOURCE_ORDERABLE_COLUMNS).optional(),
|
|
205
|
+
orderBy: z.enum(["asc", "desc"]).optional(),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
export type GetResourcesSchema = z.infer<ReturnType<typeof getResourcesSchema>>;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Rules**:
|
|
212
|
+
|
|
213
|
+
- Use factory pattern: `export const createResourceSchema = () => z.object({...})`
|
|
214
|
+
- Use type inference: `export type CreateResourceSchema = z.infer<ReturnType<typeof createResourceSchema>>`
|
|
215
|
+
- Separate client and server schemas (client may include File types)
|
|
216
|
+
- Define orderable columns as const array
|
|
217
|
+
- Use `z.coerce` for query params that need type conversion
|
|
218
|
+
- Use transformations for date strings: `.transform((value) => value ? new Date(value).toISOString().split('T')[0] : undefined)`
|
|
219
|
+
|
|
220
|
+
#### Page Components (`src/app/[locale]/dashboard/`)
|
|
221
|
+
|
|
222
|
+
**Structure**:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
"use client";
|
|
226
|
+
|
|
227
|
+
import { useSetBreadcrumbs } from "@/components/dashboard/DashboardLayout";
|
|
228
|
+
import { BaseTable, useBaseTable } from "@/components/ui/BaseTable";
|
|
229
|
+
import { Button } from "@/components/ui/Button";
|
|
230
|
+
import { useQsHydratedState } from "@/hooks/use-qs-hydrated-state";
|
|
231
|
+
import { DEFAULT_PAGE_SIZE } from "@/lib/constants";
|
|
232
|
+
import { paths } from "@/lib/constants/paths";
|
|
233
|
+
import { useGetResourcesQuery } from "@/lib/services/resource";
|
|
234
|
+
import { useTranslation } from "react-i18next";
|
|
235
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
236
|
+
import { motion } from "framer-motion";
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Design Guidelines**:
|
|
240
|
+
|
|
241
|
+
- Use Framer Motion for page-level animations (see [Design System](../design/design-system.md))
|
|
242
|
+
- Implement staggered entrance animations for lists/grids
|
|
243
|
+
- Add hover effects to interactive cards following the design system patterns
|
|
244
|
+
- Use semantic gradient colors for action buttons (emerald/sky for updates, rose/pink for deletes)
|
|
245
|
+
|
|
246
|
+
const ResourcesPage = () => {
|
|
247
|
+
const { t, i18n: { language } } = useTranslation();
|
|
248
|
+
|
|
249
|
+
const [filters, setFilters] = useQsHydratedState(getResourcesSchema, {
|
|
250
|
+
defaultValue: {
|
|
251
|
+
page: 1,
|
|
252
|
+
limit: DEFAULT_PAGE_SIZE,
|
|
253
|
+
orderBy: 'desc',
|
|
254
|
+
orderByColumn: 'createdAt',
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const queryResult = useGetResourcesQuery(filters);
|
|
259
|
+
|
|
260
|
+
useSetBreadcrumbs([
|
|
261
|
+
{ label: t('Navigation.dashboard'), href: paths.dashboard.index },
|
|
262
|
+
{ label: t('Navigation.Resources'), href: paths.dashboard.resources },
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
// #region table config
|
|
266
|
+
const columns: ColumnDef<Resource>[] = useMemo(
|
|
267
|
+
() => [
|
|
268
|
+
// Column definitions
|
|
269
|
+
],
|
|
270
|
+
[dependencies],
|
|
271
|
+
);
|
|
272
|
+
//#endregion
|
|
273
|
+
|
|
274
|
+
const table = useBaseTable({
|
|
275
|
+
data: queryResult.data?.data?.data || [],
|
|
276
|
+
columns,
|
|
277
|
+
// ... other config
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
|
|
282
|
+
<div className="w-full">
|
|
283
|
+
{/_ Component JSX _/}
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export default ResourcesPage;
|
|
289
|
+
|
|
290
|
+
````
|
|
291
|
+
|
|
292
|
+
**Rules**:
|
|
293
|
+
- Always use `'use client'` directive
|
|
294
|
+
- Use `useTranslation` from `react-i18next`
|
|
295
|
+
- Use `useQsHydratedState` for URL query state management
|
|
296
|
+
- Use `useSetBreadcrumbs` for breadcrumb navigation
|
|
297
|
+
- Use `useMemo` for column definitions
|
|
298
|
+
- Use `#region` comments for table configuration
|
|
299
|
+
- Always handle loading states
|
|
300
|
+
- Use `BaseTable` component for tables
|
|
301
|
+
- Use `Pagination` component for pagination
|
|
302
|
+
|
|
303
|
+
#### Sheet Components (`_components/`)
|
|
304
|
+
|
|
305
|
+
**Structure**:
|
|
306
|
+
```typescript
|
|
307
|
+
'use client';
|
|
308
|
+
|
|
309
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/Sheet';
|
|
310
|
+
import { useCreateResourceMutation } from '@/lib/services/resource';
|
|
311
|
+
import { StorageService } from '@/lib/services/storage';
|
|
312
|
+
import { queryClient } from '@/lib/utils/query-client';
|
|
313
|
+
import { useState } from 'react';
|
|
314
|
+
import { useTranslation } from 'react-i18next';
|
|
315
|
+
import { toast } from 'sonner';
|
|
316
|
+
|
|
317
|
+
export const CreateResourceSheet: React.FC<React.PropsWithChildren> = ({
|
|
318
|
+
children,
|
|
319
|
+
}) => {
|
|
320
|
+
const { t } = useTranslation();
|
|
321
|
+
const { session } = useAuthStore();
|
|
322
|
+
|
|
323
|
+
const [isSheetOpen, setSheetOpen] = useState(false);
|
|
324
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
325
|
+
|
|
326
|
+
const createResourceMutation = useCreateResourceMutation();
|
|
327
|
+
|
|
328
|
+
const onSubmit = async (data: CreateResourceSchemaClient) => {
|
|
329
|
+
try {
|
|
330
|
+
setIsLoading(true);
|
|
331
|
+
|
|
332
|
+
// File upload logic if needed
|
|
333
|
+
let fileUrl: string | undefined;
|
|
334
|
+
if (data.file) {
|
|
335
|
+
const fileName = `${session!.user.id}/resource-${Date.now()}-${createSafeURLForUploadingFileName(data.file.name)}`;
|
|
336
|
+
toast.loading(t('ResourcesPage.uploadingFile'));
|
|
337
|
+
|
|
338
|
+
const { error, uploadResult } = await StorageService.uploadFileSigned(
|
|
339
|
+
Bucket.RESOURCES,
|
|
340
|
+
fileName,
|
|
341
|
+
data.file,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
if (error || !uploadResult) {
|
|
345
|
+
throw new Error(error.message);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
fileUrl = uploadResult.path;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await createResourceMutation.mutateAsync({
|
|
352
|
+
...data,
|
|
353
|
+
fileUrl,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
onOpenChange(false);
|
|
357
|
+
setIsLoading(false);
|
|
358
|
+
|
|
359
|
+
queryClient.invalidateQueries({
|
|
360
|
+
queryKey: [resourceRoutes.resources()],
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('🚀 ~ onSubmit ~ error:', error);
|
|
364
|
+
setIsLoading(false);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<Sheet open={isSheetOpen} onOpenChange={setSheetOpen}>
|
|
370
|
+
<SheetTrigger asChild>{children}</SheetTrigger>
|
|
371
|
+
{isSheetOpen && (
|
|
372
|
+
<SheetContent>
|
|
373
|
+
<SheetHeader>
|
|
374
|
+
<SheetTitle>{t('ResourcesPage.createResource')}</SheetTitle>
|
|
375
|
+
</SheetHeader>
|
|
376
|
+
<ResourceForm onSubmit={onSubmit} isLoading={isLoading} />
|
|
377
|
+
</SheetContent>
|
|
378
|
+
)}
|
|
379
|
+
</Sheet>
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
````
|
|
383
|
+
|
|
384
|
+
**Rules**:
|
|
385
|
+
|
|
386
|
+
- Use `Sheet` component from `@/components/ui/Sheet`
|
|
387
|
+
- Handle file uploads using `StorageService.uploadFileSigned`
|
|
388
|
+
- Use `toast` from `sonner` for notifications
|
|
389
|
+
- Invalidate queries after mutations
|
|
390
|
+
- Always handle loading states
|
|
391
|
+
- Use `console.error` with emoji prefix for error logging: `console.error('🚀 ~ onSubmit ~ error:', error)`
|
|
392
|
+
- Clear query strings on close if needed
|
|
393
|
+
|
|
394
|
+
### 2. Naming Conventions
|
|
395
|
+
|
|
396
|
+
**Files**:
|
|
397
|
+
|
|
398
|
+
- API routes: `route.ts` (in `src/app/api/[resource]/`)
|
|
399
|
+
- Services: `[resource].ts` (in `src/lib/services/`)
|
|
400
|
+
- Schemas: `[action]-[resource]-schema.ts` (e.g., `create-income-schema.ts`)
|
|
401
|
+
- Pages: `page.tsx` (in `src/app/[locale]/dashboard/[resource]/`)
|
|
402
|
+
- Components: `[ComponentName].tsx` (PascalCase)
|
|
403
|
+
- Hooks: `use-[hook-name].ts` (kebab-case)
|
|
404
|
+
|
|
405
|
+
**Functions**:
|
|
406
|
+
|
|
407
|
+
- API handlers: `getResource`, `createResource`, `updateResource`, `deleteResource`
|
|
408
|
+
- Service functions: `getResource`, `createResource`, etc.
|
|
409
|
+
- React Query hooks: `useGetResourceQuery`, `useCreateResourceMutation`
|
|
410
|
+
- Query keys: `getResourceQueryKey`
|
|
411
|
+
|
|
412
|
+
**Variables**:
|
|
413
|
+
|
|
414
|
+
- Use camelCase: `isLoading`, `queryResult`, `setFilters`
|
|
415
|
+
- Boolean prefixes: `is`, `has`, `should` (e.g., `isLoading`, `hasError`)
|
|
416
|
+
- Constants: UPPER_SNAKE_CASE (e.g., `DEFAULT_PAGE_SIZE`)
|
|
417
|
+
|
|
418
|
+
**Types**:
|
|
419
|
+
|
|
420
|
+
- Use PascalCase: `GetResourceResponse`, `CreateResourceSchema`
|
|
421
|
+
- Suffix with type: `Resource`, `ResourceSchema`, `ResourceResponse`
|
|
422
|
+
|
|
423
|
+
### 3. TypeScript Patterns
|
|
424
|
+
|
|
425
|
+
**Always**:
|
|
426
|
+
|
|
427
|
+
- Use strict TypeScript
|
|
428
|
+
- Define types explicitly, avoid `any`
|
|
429
|
+
- Use type inference where appropriate
|
|
430
|
+
- Use `as const` for literal types
|
|
431
|
+
- Use `ReturnType<typeof function>` for function return types
|
|
432
|
+
- Use `z.infer` for Zod schema types
|
|
433
|
+
|
|
434
|
+
**Type Definitions**:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// In API routes
|
|
438
|
+
export type Resource = {
|
|
439
|
+
id: string;
|
|
440
|
+
title: string;
|
|
441
|
+
// ...
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export type GetResourceResponse = TSuccessResponse<{
|
|
445
|
+
totalItemsCount: number;
|
|
446
|
+
data: Resource[];
|
|
447
|
+
}>;
|
|
448
|
+
|
|
449
|
+
// In services
|
|
450
|
+
import { GetResourceResponse } from "@/app/api/resources/route";
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### 4. Database Patterns
|
|
454
|
+
|
|
455
|
+
**Always**:
|
|
456
|
+
|
|
457
|
+
- Use Drizzle ORM
|
|
458
|
+
- Include soft delete check: `eq(table.isDeleted, false)`
|
|
459
|
+
- Use `db.query` for relations when possible
|
|
460
|
+
- Use `db.select().from()` for complex queries
|
|
461
|
+
- Use `and()` for multiple conditions
|
|
462
|
+
- Use `sql` template for complex SQL
|
|
463
|
+
|
|
464
|
+
**Example**:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
const where = and(
|
|
468
|
+
eq(resources.isDeleted, false),
|
|
469
|
+
search?.trim().length
|
|
470
|
+
? sql`LOWER(${resources.title}) LIKE LOWER(${`%${search.trim()}%`})`
|
|
471
|
+
: undefined,
|
|
472
|
+
categoryId ? eq(resources.categoryId, categoryId) : undefined,
|
|
473
|
+
);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 5. Error Handling
|
|
477
|
+
|
|
478
|
+
**API Routes**:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
if (!result) {
|
|
482
|
+
throw new NotFoundError(i18next.t("ApiRoutes.Utils.dataNotFound"));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (count === 0) {
|
|
486
|
+
throw new NotFoundError(i18next.t("ApiRoutes.Utils.dataNotFound"));
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Components**:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
try {
|
|
494
|
+
// Operation
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error("🚀 ~ operation ~ error:", error);
|
|
497
|
+
// Handle error
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Error Types**:
|
|
502
|
+
|
|
503
|
+
- `BadRequestError` - 400 errors
|
|
504
|
+
- `NotFoundError` - 404 errors
|
|
505
|
+
- Use i18next for error messages
|
|
506
|
+
|
|
507
|
+
### 6. Internationalization (i18n)
|
|
508
|
+
|
|
509
|
+
**Always**:
|
|
510
|
+
|
|
511
|
+
- Use `useTranslation` hook in components
|
|
512
|
+
- Use `i18next.t()` in API routes
|
|
513
|
+
- Use translation keys from `src/messages/`
|
|
514
|
+
- Never hardcode user-facing strings
|
|
515
|
+
|
|
516
|
+
**Example**:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// In components
|
|
520
|
+
const { t } = useTranslation();
|
|
521
|
+
<Button>{t('Navigation.AddResource')}</Button>
|
|
522
|
+
|
|
523
|
+
// In API routes
|
|
524
|
+
import i18next from 'i18next';
|
|
525
|
+
throw new NotFoundError(i18next.t('ApiRoutes.Utils.dataNotFound'));
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 7. React Query Patterns
|
|
529
|
+
|
|
530
|
+
**Query Keys**:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
export const getResourcesQueryKey = (data: GetResourcesSchema) => [
|
|
534
|
+
resourceRoutes.resources(),
|
|
535
|
+
data.page,
|
|
536
|
+
data.limit,
|
|
537
|
+
data.search,
|
|
538
|
+
// ... all relevant params
|
|
539
|
+
];
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Invalidation**:
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
queryClient.invalidateQueries({
|
|
546
|
+
queryKey: [resourceRoutes.resources()],
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Mutations**:
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
export const useCreateResourceMutation = () =>
|
|
554
|
+
useMutation({
|
|
555
|
+
mutationFn: createResource,
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### 8. Code Organization
|
|
560
|
+
|
|
561
|
+
**Imports Order**:
|
|
562
|
+
|
|
563
|
+
1. External libraries (React, Next.js, etc.)
|
|
564
|
+
2. Internal components (`@/components/`)
|
|
565
|
+
3. Internal utilities (`@/lib/utils/`)
|
|
566
|
+
4. Internal services (`@/lib/services/`)
|
|
567
|
+
5. Internal schemas (`@/lib/schemas/`)
|
|
568
|
+
6. Internal types (`@/app/api/`)
|
|
569
|
+
7. Server-side imports (`@/server/`)
|
|
570
|
+
|
|
571
|
+
**Grouping**:
|
|
572
|
+
|
|
573
|
+
- Use `#region` comments for logical grouping
|
|
574
|
+
- Group related functions together
|
|
575
|
+
- Keep related types near their usage
|
|
576
|
+
|
|
577
|
+
### 9. Comments and Documentation
|
|
578
|
+
|
|
579
|
+
**Comments**:
|
|
580
|
+
|
|
581
|
+
- Use `//` for single-line comments
|
|
582
|
+
- Use `/* */` for multi-line comments
|
|
583
|
+
- Use `#region` / `#endregion` for code sections
|
|
584
|
+
- Explain "why" not "what"
|
|
585
|
+
- Use English for all comments
|
|
586
|
+
|
|
587
|
+
**Example**:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
// Determine if we need join-based query for sorting
|
|
591
|
+
const needsJoinForSorting =
|
|
592
|
+
orderByColumn && ["accountTitle", "paymentStatus"].includes(orderByColumn);
|
|
593
|
+
|
|
594
|
+
// #region table config
|
|
595
|
+
const columns: ColumnDef<Resource>[] = useMemo(/* ... */);
|
|
596
|
+
//#endregion
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### 10. Testing Considerations
|
|
600
|
+
|
|
601
|
+
**When Writing Code**:
|
|
602
|
+
|
|
603
|
+
- Make functions testable (pure functions where possible)
|
|
604
|
+
- Avoid side effects in utility functions
|
|
605
|
+
- Use dependency injection for services
|
|
606
|
+
- Keep components focused and small
|
|
607
|
+
|
|
608
|
+
**Type Checking**:
|
|
609
|
+
|
|
610
|
+
- **Always run `npm run format:fix && npm run typecheck` at the end of code changes**
|
|
611
|
+
- Ensure there are no TypeScript type errors before completing the task
|
|
612
|
+
- Fix any type errors that are discovered
|
|
613
|
+
- Type checking is mandatory and must pass before considering code complete
|
|
614
|
+
|
|
615
|
+
### 11. Performance Best Practices
|
|
616
|
+
|
|
617
|
+
**Always**:
|
|
618
|
+
|
|
619
|
+
- Use `useMemo` for expensive computations
|
|
620
|
+
- Use `useCallback` for event handlers passed to children
|
|
621
|
+
- Use `React.memo` for expensive components (when needed)
|
|
622
|
+
- Optimize database queries (use indexes, avoid N+1)
|
|
623
|
+
- Use pagination for large datasets
|
|
624
|
+
- Use `Promise.all` for parallel operations
|
|
625
|
+
|
|
626
|
+
**Example**:
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
const [countResult, data] = await Promise.all([
|
|
630
|
+
db
|
|
631
|
+
.select({ count: sql<number>`COUNT(*)::INTEGER` })
|
|
632
|
+
.from(resources)
|
|
633
|
+
.where(where),
|
|
634
|
+
db.query.resources.findMany({ where, limit, offset }),
|
|
635
|
+
]);
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### 12. Security Best Practices
|
|
639
|
+
|
|
640
|
+
**Always**:
|
|
641
|
+
|
|
642
|
+
- Validate all inputs using Zod schemas
|
|
643
|
+
- Use `ProtectedRequestContext` for protected routes
|
|
644
|
+
- Never trust client-side data
|
|
645
|
+
- Use parameterized queries (Drizzle handles this)
|
|
646
|
+
- Check permissions before operations
|
|
647
|
+
|
|
648
|
+
### 13. Common Patterns
|
|
649
|
+
|
|
650
|
+
#### Soft Delete Pattern
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
await db
|
|
654
|
+
.update(resources)
|
|
655
|
+
.set({ isDeleted: true })
|
|
656
|
+
.where(and(eq(resources.id, id), eq(resources.isDeleted, false)));
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
#### File Upload Pattern
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
if (file) {
|
|
663
|
+
const fileName = `${session!.user.id}/resource-${Date.now()}-${createSafeURLForUploadingFileName(file.name)}`;
|
|
664
|
+
const { error, uploadResult } = await StorageService.uploadFileSigned(
|
|
665
|
+
Bucket.RESOURCES,
|
|
666
|
+
fileName,
|
|
667
|
+
file,
|
|
668
|
+
);
|
|
669
|
+
if (error || !uploadResult) {
|
|
670
|
+
throw new Error(error.message);
|
|
671
|
+
}
|
|
672
|
+
fileUrl = uploadResult.path;
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### Date Handling Pattern
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
// In schemas
|
|
680
|
+
startDate: z
|
|
681
|
+
.string()
|
|
682
|
+
.optional()
|
|
683
|
+
.transform((value) =>
|
|
684
|
+
value ? new Date(value).toISOString().split('T')[0] : undefined,
|
|
685
|
+
),
|
|
686
|
+
|
|
687
|
+
// In API routes
|
|
688
|
+
date: new Date(ctx.payload.date),
|
|
689
|
+
createdAt: result.createdAt.toISOString(),
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### Sorting Pattern
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
const needsJoinForSorting =
|
|
696
|
+
orderByColumn && ["accountTitle", "paymentStatus"].includes(orderByColumn);
|
|
697
|
+
|
|
698
|
+
if (needsJoinForSorting) {
|
|
699
|
+
// Use join-based sorting
|
|
700
|
+
} else {
|
|
701
|
+
// Use direct column sorting
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
## Code Review Checklist
|
|
706
|
+
|
|
707
|
+
When reviewing or writing code, ensure:
|
|
708
|
+
|
|
709
|
+
- [ ] Soft delete checks are included (`isDeleted: false`)
|
|
710
|
+
- [ ] Types are properly defined (no `any`)
|
|
711
|
+
- [ ] i18next is used for all user-facing strings
|
|
712
|
+
- [ ] Error handling is implemented
|
|
713
|
+
- [ ] Loading states are handled
|
|
714
|
+
- [ ] React Query patterns are followed
|
|
715
|
+
- [ ] File organization matches project structure
|
|
716
|
+
- [ ] Naming conventions are followed
|
|
717
|
+
- [ ] Comments explain "why" not "what"
|
|
718
|
+
- [ ] Code is properly formatted
|
|
719
|
+
- [ ] Imports are organized correctly
|
|
720
|
+
- [ ] `#region` comments are used for logical grouping
|
|
721
|
+
- [ ] **Type checking passes: `npm run typecheck` runs without errors**
|
|
722
|
+
- [ ] UI components follow [Design System](../design/design-system.md) patterns
|
|
723
|
+
- [ ] Animations use established Framer Motion variants
|
|
724
|
+
- [ ] Hover effects are consistent with design system guidelines
|
|
725
|
+
|
|
726
|
+
## Common Mistakes to Avoid
|
|
727
|
+
|
|
728
|
+
1. **Missing Soft Delete Check**: Always include `eq(table.isDeleted, false)`
|
|
729
|
+
2. **Hardcoded Strings**: Always use i18next for user-facing strings
|
|
730
|
+
3. **Using `any` Type**: Always define proper types
|
|
731
|
+
4. **Missing Error Handling**: Always handle errors properly
|
|
732
|
+
5. **Missing Loading States**: Always show loading indicators
|
|
733
|
+
6. **N+1 Queries**: Use `db.query` with relations instead of multiple queries
|
|
734
|
+
7. **Missing Query Invalidation**: Always invalidate queries after mutations
|
|
735
|
+
8. **Incorrect Import Paths**: Use `@/` alias for internal imports
|
|
736
|
+
9. **Missing Type Exports**: Export types from API routes for use in services
|
|
737
|
+
|
|
738
|
+
## Examples
|
|
739
|
+
|
|
740
|
+
### Complete API Route Example
|
|
741
|
+
|
|
742
|
+
See: `src/app/api/incomes/route.ts`
|
|
743
|
+
|
|
744
|
+
### Complete Service Example
|
|
745
|
+
|
|
746
|
+
See: `src/lib/services/income.ts`
|
|
747
|
+
|
|
748
|
+
### Complete Schema Example
|
|
749
|
+
|
|
750
|
+
See: `src/lib/schemas/income/create-income-schema.ts`
|
|
751
|
+
|
|
752
|
+
### Complete Page Component Example
|
|
753
|
+
|
|
754
|
+
See: `src/app/[locale]/dashboard/incomes/page.tsx`
|
|
755
|
+
|
|
756
|
+
### Complete Sheet Component Example
|
|
757
|
+
|
|
758
|
+
See: `src/app/[locale]/dashboard/incomes/_components/CreateIncomeSheet.tsx`
|
|
759
|
+
|
|
760
|
+
## Working with the Codebase
|
|
761
|
+
|
|
762
|
+
### When Adding a New Feature
|
|
763
|
+
|
|
764
|
+
1. Create schema files in `src/lib/schemas/[resource]/`
|
|
765
|
+
2. Create API route in `src/app/api/[resource]/route.ts`
|
|
766
|
+
3. Create service file in `src/lib/services/[resource].ts`
|
|
767
|
+
4. Create page component in `src/app/[locale]/dashboard/[resource]/page.tsx`
|
|
768
|
+
5. Create form components in `_components/` directory
|
|
769
|
+
6. Add translation keys to `src/messages/`
|
|
770
|
+
7. **Run `npm run typecheck` to verify there are no type errors**
|
|
771
|
+
|
|
772
|
+
### When Refactoring
|
|
773
|
+
|
|
774
|
+
1. Maintain existing patterns
|
|
775
|
+
2. Don't change working code unnecessarily
|
|
776
|
+
3. Update types if structure changes
|
|
777
|
+
4. Update related components
|
|
778
|
+
5. Test thoroughly
|
|
779
|
+
6. **Run `npm run typecheck` to verify there are no type errors**
|
|
780
|
+
|
|
781
|
+
### When Fixing Bugs
|
|
782
|
+
|
|
783
|
+
1. Identify the root cause
|
|
784
|
+
2. Fix the issue following project patterns
|
|
785
|
+
3. Add error handling if missing
|
|
786
|
+
4. Add tests if possible
|
|
787
|
+
5. Update related code if needed
|
|
788
|
+
6. **Run `npm run typecheck` to verify there are no type errors**
|
|
789
|
+
|
|
790
|
+
## Agent Instructions
|
|
791
|
+
|
|
792
|
+
When working as the Developer Agent:
|
|
793
|
+
|
|
794
|
+
1. **Always follow project patterns** - Refer to existing code examples
|
|
795
|
+
2. **Maintain type safety** - Never use `any`, always define proper types
|
|
796
|
+
3. **Test your code** - Run `npm run typecheck` at the end of every code change
|
|
797
|
+
4. **Verify type checking** - Ensure `npm run typecheck` passes without errors before completing tasks
|
|
798
|
+
5. **Follow naming conventions** - Use established patterns for files, functions, and variables
|
|
799
|
+
6. **Use i18next** - Never hardcode user-facing strings
|
|
800
|
+
7. **Handle errors properly** - Always implement error handling
|
|
801
|
+
8. **Update related code** - When changing types or structures, update all related files
|
|
802
|
+
9. **Document your changes** - Use comments to explain "why" not "what"
|
|
803
|
+
10. **Follow design system** - Refer to [Design System](../design/design-system.md) for UI patterns, animations, and visual guidelines
|
|
804
|
+
11. **Implement consistent animations** - Use Framer Motion variants from the design system for all animations
|
|
805
|
+
|
|
806
|
+
**Critical**: Type checking with `npm run typecheck` is mandatory and must pass before any code changes are considered complete.
|
|
807
|
+
|
|
808
|
+
## Conclusion
|
|
809
|
+
|
|
810
|
+
The Developer Agent must follow these conventions strictly to maintain code consistency and quality. When in doubt, refer to existing code examples in the codebase, particularly in the `incomes` feature which serves as a reference implementation.
|
|
811
|
+
|
|
812
|
+
**Remember**: Always run `npm run typecheck` at the end of code changes to ensure there are no type errors.
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
**Document Version**: 1.0
|
|
817
|
+
**Last Updated**: 2026-01-17
|
|
818
|
+
**Language**: English (as per agent documentation standards)
|