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.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/create-tigra.js +292 -0
- package/package.json +41 -0
- package/template/.agent/rules/client/01-project-structure.md +326 -0
- package/template/.agent/rules/client/02-component-patterns.md +249 -0
- package/template/.agent/rules/client/03-typescript-rules.md +226 -0
- package/template/.agent/rules/client/04-state-management.md +474 -0
- package/template/.agent/rules/client/05-api-integration.md +129 -0
- package/template/.agent/rules/client/06-forms-validation.md +129 -0
- package/template/.agent/rules/client/07-common-patterns.md +150 -0
- package/template/.agent/rules/client/08-color-system.md +93 -0
- package/template/.agent/rules/client/09-security-rules.md +97 -0
- package/template/.agent/rules/client/10-testing-strategy.md +370 -0
- package/template/.agent/rules/global/ai-edit-safety.md +38 -0
- package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
- package/template/.agent/rules/server/02-general-rules.md +111 -0
- package/template/.agent/rules/server/03-migrations.md +20 -0
- package/template/.agent/rules/server/04-pagination.md +130 -0
- package/template/.agent/rules/server/05-project-conventions.md +71 -0
- package/template/.agent/rules/server/06-response-handling.md +173 -0
- package/template/.agent/rules/server/07-testing-strategy.md +506 -0
- package/template/.agent/rules/server/08-observability.md +180 -0
- package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
- package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
- package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
- package/template/.agent/rules/server/12-performance-optimization.md +567 -0
- package/template/.claude/rules/client-01-project-structure.md +327 -0
- package/template/.claude/rules/client-02-component-patterns.md +250 -0
- package/template/.claude/rules/client-03-typescript-rules.md +227 -0
- package/template/.claude/rules/client-04-state-management.md +475 -0
- package/template/.claude/rules/client-05-api-integration.md +130 -0
- package/template/.claude/rules/client-06-forms-validation.md +130 -0
- package/template/.claude/rules/client-07-common-patterns.md +151 -0
- package/template/.claude/rules/client-08-color-system.md +94 -0
- package/template/.claude/rules/client-09-security-rules.md +98 -0
- package/template/.claude/rules/client-10-testing-strategy.md +371 -0
- package/template/.claude/rules/global-ai-edit-safety.md +39 -0
- package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
- package/template/.claude/rules/server-02-general-rules.md +112 -0
- package/template/.claude/rules/server-03-migrations.md +21 -0
- package/template/.claude/rules/server-04-pagination.md +131 -0
- package/template/.claude/rules/server-05-project-conventions.md +72 -0
- package/template/.claude/rules/server-06-response-handling.md +174 -0
- package/template/.claude/rules/server-07-testing-strategy.md +507 -0
- package/template/.claude/rules/server-08-observability.md +181 -0
- package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
- package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
- package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
- package/template/.claude/rules/server-12-performance-optimization.md +568 -0
- package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
- package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
- package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
- package/template/.cursor/rules/client-04-state-management.mdc +475 -0
- package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
- package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
- package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
- package/template/.cursor/rules/client-08-color-system.mdc +94 -0
- package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
- package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
- package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
- package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
- package/template/.cursor/rules/server-03-migrations.mdc +21 -0
- package/template/.cursor/rules/server-04-pagination.mdc +131 -0
- package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
- package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
- package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
- package/template/.cursor/rules/server-08-observability.mdc +181 -0
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
- package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
- package/template/CLAUDE.md +207 -0
- package/template/server/.env.example +148 -0
- package/template/server/.tsc-aliasrc.json +12 -0
- package/template/server/README.md +175 -0
- package/template/server/SECURITY.md +190 -0
- package/template/server/biome.json +42 -0
- package/template/server/docker-compose.yml +111 -0
- package/template/server/package.json +83 -0
- package/template/server/postman_collection.json +733 -0
- package/template/server/prisma/schema.prisma +92 -0
- package/template/server/prisma/seed.ts +142 -0
- package/template/server/scripts/wait-for-db.js +60 -0
- package/template/server/src/app.ts +74 -0
- package/template/server/src/config/env.ts +101 -0
- package/template/server/src/hooks/request-timing.hook.ts +26 -0
- package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
- package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
- package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
- package/template/server/src/libs/db.ts +76 -0
- package/template/server/src/libs/error-handler.ts +89 -0
- package/template/server/src/libs/logger.ts +60 -0
- package/template/server/src/libs/queue.ts +79 -0
- package/template/server/src/libs/redis.ts +79 -0
- package/template/server/src/libs/swagger-schemas.ts +16 -0
- package/template/server/src/modules/admin/admin.controller.ts +122 -0
- package/template/server/src/modules/admin/admin.routes.ts +100 -0
- package/template/server/src/modules/admin/admin.schemas.ts +35 -0
- package/template/server/src/modules/admin/admin.service.ts +167 -0
- package/template/server/src/modules/auth/auth.controller.ts +141 -0
- package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
- package/template/server/src/modules/auth/auth.repo.ts +218 -0
- package/template/server/src/modules/auth/auth.routes.ts +204 -0
- package/template/server/src/modules/auth/auth.schemas.ts +137 -0
- package/template/server/src/modules/auth/auth.service.test.ts +119 -0
- package/template/server/src/modules/auth/auth.service.ts +329 -0
- package/template/server/src/modules/auth/auth.types.ts +97 -0
- package/template/server/src/modules/resources/resources.controller.ts +218 -0
- package/template/server/src/modules/resources/resources.repo.ts +253 -0
- package/template/server/src/modules/resources/resources.routes.ts +355 -0
- package/template/server/src/modules/resources/resources.schemas.ts +146 -0
- package/template/server/src/modules/resources/resources.service.ts +218 -0
- package/template/server/src/modules/resources/resources.types.ts +73 -0
- package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
- package/template/server/src/plugins/security.plugin.ts +21 -0
- package/template/server/src/plugins/swagger.plugin.ts +41 -0
- package/template/server/src/routes/health.routes.ts +31 -0
- package/template/server/src/server.ts +142 -0
- package/template/server/src/test/setup.ts +38 -0
- package/template/server/src/types/fastify.d.ts +36 -0
- package/template/server/src/utils/errors.ts +108 -0
- package/template/server/src/utils/pagination.ts +120 -0
- package/template/server/src/utils/response.ts +110 -0
- package/template/server/src/workers/file.worker.ts +106 -0
- package/template/server/tsconfig.build.json +30 -0
- package/template/server/tsconfig.build.tsbuildinfo +1 -0
- package/template/server/tsconfig.json +89 -0
- package/template/server/tsconfig.test.json +22 -0
- package/template/server/vitest.config.ts +98 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# API Integration & Error Handling
|
|
9
|
+
|
|
10
|
+
## Axios Configuration
|
|
11
|
+
Standard pattern for interceptors and token management.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// lib/api/axios.config.ts
|
|
15
|
+
import axios from 'axios';
|
|
16
|
+
import { store } from '@/store';
|
|
17
|
+
import { logout, updateTokens } from '@/features/auth/store/authSlice';
|
|
18
|
+
|
|
19
|
+
export const apiClient = axios.create({
|
|
20
|
+
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
apiClient.interceptors.request.use((config) => {
|
|
26
|
+
const token = store.getState().auth.tokens?.accessToken;
|
|
27
|
+
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
28
|
+
return config;
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Service Pattern
|
|
33
|
+
Use classes or plain objects to group API calls by domain.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// features/resources/services/resource.service.ts
|
|
37
|
+
class ResourceService {
|
|
38
|
+
async getResources(params = {}) {
|
|
39
|
+
const response = await apiClient.get('/resources', { params });
|
|
40
|
+
return response.data.data;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const resourceService = new ResourceService();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Error Handling Utilities
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// lib/utils/error.ts
|
|
50
|
+
export const getErrorMessage = (error: unknown): string => {
|
|
51
|
+
if (axios.isAxiosError(error)) {
|
|
52
|
+
return error.response?.data?.error?.message || error.message;
|
|
53
|
+
}
|
|
54
|
+
return 'An unexpected error occurred';
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## UI Feedback (Ant Design)
|
|
59
|
+
|
|
60
|
+
### Error Components
|
|
61
|
+
```tsx
|
|
62
|
+
// components/common/ErrorMessage.tsx
|
|
63
|
+
import { Alert } from 'antd';
|
|
64
|
+
import { getErrorMessage } from '@/lib/utils/error';
|
|
65
|
+
|
|
66
|
+
export const ErrorMessage = ({ error }: { error: any }) => (
|
|
67
|
+
<Alert
|
|
68
|
+
message="Error"
|
|
69
|
+
description={getErrorMessage(error)}
|
|
70
|
+
type="error"
|
|
71
|
+
showIcon
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Loading States
|
|
77
|
+
```tsx
|
|
78
|
+
// components/common/LoadingSpinner.tsx
|
|
79
|
+
import { Spin } from 'antd';
|
|
80
|
+
|
|
81
|
+
export const LoadingSpinner = () => (
|
|
82
|
+
<div className="spinner-container">
|
|
83
|
+
<Spin size="large" />
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Skeleton (Ant Design)
|
|
89
|
+
```tsx
|
|
90
|
+
// components/common/ResourceSkeleton.tsx
|
|
91
|
+
import { Skeleton, Card } from 'antd';
|
|
92
|
+
|
|
93
|
+
export const ResourceSkeleton = () => (
|
|
94
|
+
<Card>
|
|
95
|
+
<Skeleton active />
|
|
96
|
+
</Card>
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Notifications (Ant Design)
|
|
101
|
+
Use `message` or `notification` from Ant Design.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { message } from 'antd';
|
|
105
|
+
|
|
106
|
+
// Success
|
|
107
|
+
message.success('Action completed!');
|
|
108
|
+
|
|
109
|
+
// Error
|
|
110
|
+
message.error(getErrorMessage(error));
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## File Upload Pattern
|
|
114
|
+
```tsx
|
|
115
|
+
// features/resources/hooks/useUploadFiles.ts
|
|
116
|
+
export const useUploadFiles = () => {
|
|
117
|
+
return useMutation({
|
|
118
|
+
mutationFn: async (files: File[]) => {
|
|
119
|
+
const formData = new FormData();
|
|
120
|
+
files.forEach((file) => formData.append('files', file));
|
|
121
|
+
return apiClient.post('/upload', formData, {
|
|
122
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Infinite Scroll Pattern
|
|
130
|
+
Standard React Query `useInfiniteQuery` implementation with Ant Design's `Button` for "Load More" or a custom observer.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# Forms & Validation
|
|
9
|
+
|
|
10
|
+
## Form Handling with React Hook Form + Zod + Ant Design
|
|
11
|
+
|
|
12
|
+
To maintain the project's logic and architecture while using Ant Design, use **React Hook Form (RHF)** for state management and **Zod** for validation, connecting them to **Ant Design** components via the `Controller` component.
|
|
13
|
+
|
|
14
|
+
### Basic Form Pattern
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// features/auth/components/LoginForm.tsx
|
|
18
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
19
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { Button, Input, Form } from 'antd';
|
|
22
|
+
|
|
23
|
+
const loginSchema = z.object({
|
|
24
|
+
email: z.string().email('Invalid email'),
|
|
25
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type LoginFormData = z.infer<typeof loginSchema>;
|
|
29
|
+
|
|
30
|
+
export const LoginForm = ({ onSubmit, isSubmitting }) => {
|
|
31
|
+
const { control, handleSubmit, formState: { errors } } = useForm<LoginFormData>({
|
|
32
|
+
resolver: zodResolver(loginSchema),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Form layout="vertical" onFinish={handleSubmit(onSubmit)}>
|
|
37
|
+
<Form.Item
|
|
38
|
+
label="Email"
|
|
39
|
+
validateStatus={errors.email ? 'error' : ''}
|
|
40
|
+
help={errors.email?.message}
|
|
41
|
+
>
|
|
42
|
+
<Controller
|
|
43
|
+
name="email"
|
|
44
|
+
control={control}
|
|
45
|
+
render={({ field }) => <Input {...field} type="email" placeholder="Email" />}
|
|
46
|
+
/>
|
|
47
|
+
</Form.Item>
|
|
48
|
+
|
|
49
|
+
<Form.Item
|
|
50
|
+
label="Password"
|
|
51
|
+
validateStatus={errors.password ? 'error' : ''}
|
|
52
|
+
help={errors.password?.message}
|
|
53
|
+
>
|
|
54
|
+
<Controller
|
|
55
|
+
name="password"
|
|
56
|
+
control={control}
|
|
57
|
+
render={({ field }) => <Input.Password {...field} placeholder="Password" />}
|
|
58
|
+
/>
|
|
59
|
+
</Form.Item>
|
|
60
|
+
|
|
61
|
+
<Button type="primary" htmlType="submit" loading={isSubmitting} block>
|
|
62
|
+
Login
|
|
63
|
+
</Button>
|
|
64
|
+
</Form>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Complete Form Example (Resources)
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
const resourceSchema = z.object({
|
|
73
|
+
title: z.string().min(1, 'Title is required'),
|
|
74
|
+
price: z.coerce.number().min(0, 'Price must be positive'),
|
|
75
|
+
category: z.string().min(1, 'Category is required'),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
type ResourceFormData = z.infer<typeof resourceSchema>;
|
|
79
|
+
|
|
80
|
+
export const CreateResourceForm = ({ onSubmit, isSubmitting }) => {
|
|
81
|
+
const { control, handleSubmit, formState: { errors } } = useForm<ResourceFormData>({
|
|
82
|
+
resolver: zodResolver(resourceSchema),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Form layout="vertical" onFinish={handleSubmit(onSubmit)}>
|
|
87
|
+
<Form.Item label="Title" validateStatus={errors.title ? 'error' : ''} help={errors.title?.message}>
|
|
88
|
+
<Controller name="title" control={control} render={({ field }) => <Input {...field} />} />
|
|
89
|
+
</Form.Item>
|
|
90
|
+
|
|
91
|
+
<Form.Item label="Price" validateStatus={errors.price ? 'error' : ''} help={errors.price?.message}>
|
|
92
|
+
<Controller name="price" control={control} render={({ field }) => <Input type="number" {...field} />} />
|
|
93
|
+
</Form.Item>
|
|
94
|
+
|
|
95
|
+
<Button type="primary" htmlType="submit" loading={isSubmitting}>
|
|
96
|
+
Create Resource
|
|
97
|
+
</Button>
|
|
98
|
+
</Form>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Form with Select (Ant Design)
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { Select } from 'antd';
|
|
107
|
+
|
|
108
|
+
<Form.Item label="Category" validateStatus={errors.category ? 'error' : ''} help={errors.category?.message}>
|
|
109
|
+
<Controller
|
|
110
|
+
name="category"
|
|
111
|
+
control={control}
|
|
112
|
+
render={({ field }) => (
|
|
113
|
+
<Select {...field} placeholder="Select Category">
|
|
114
|
+
<Select.Option value="cat1">Category 1</Select.Option>
|
|
115
|
+
<Select.Option value="cat2">Category 2</Select.Option>
|
|
116
|
+
</Select>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
</Form.Item>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Form with File Upload (Ant Design)
|
|
123
|
+
|
|
124
|
+
Use Ant Design's `Upload` component, often manually handling the file list to sync with RHF if needed, or using a local state and calling an upload service.
|
|
125
|
+
|
|
126
|
+
## Validation Best Practices
|
|
127
|
+
- ✅ **Consistent Schemas**: Keep Zod schemas in a dedicated `schemas/` folder within features.
|
|
128
|
+
- ✅ **Error Mapping**: Map API validation errors back to RHF fields using `setError`.
|
|
129
|
+
- ✅ **Loading States**: Use Ant Design's `loading` prop on buttons.
|
|
130
|
+
- ✅ **User Experience**: Don't show validation errors until the field is "touched" or the form is submitted.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# Common Patterns & Utilities
|
|
9
|
+
|
|
10
|
+
## Custom Hooks
|
|
11
|
+
|
|
12
|
+
### useAuth Hook
|
|
13
|
+
Standard pattern for authentication state and methods.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// features/auth/hooks/useAuth.ts
|
|
17
|
+
export const useAuth = () => {
|
|
18
|
+
const dispatch = useAppDispatch();
|
|
19
|
+
const { user, isAuthenticated } = useAppSelector((state) => state.auth);
|
|
20
|
+
|
|
21
|
+
const login = async (data: LoginRequest) => {
|
|
22
|
+
const response = await authService.login(data);
|
|
23
|
+
dispatch(setCredentials(response));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const logout = () => {
|
|
27
|
+
dispatch(logoutAction());
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return { user, isAuthenticated, login, logout };
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Common Components (Ant Design)
|
|
35
|
+
|
|
36
|
+
### Pagination
|
|
37
|
+
Use Ant Design's `Pagination` component.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Pagination } from 'antd';
|
|
41
|
+
|
|
42
|
+
export const CustomPagination = ({ current, total, onChange }) => (
|
|
43
|
+
<Pagination
|
|
44
|
+
current={current}
|
|
45
|
+
total={total}
|
|
46
|
+
onChange={onChange}
|
|
47
|
+
showSizeChanger={false}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Empty State
|
|
53
|
+
Use Ant Design's `Empty` component.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { Empty, Button } from 'antd';
|
|
57
|
+
|
|
58
|
+
export const NoData = ({ message, onAction, actionText }) => (
|
|
59
|
+
<Empty
|
|
60
|
+
description={message}
|
|
61
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
62
|
+
>
|
|
63
|
+
{onAction && (
|
|
64
|
+
<Button type="primary" onClick={onAction}>
|
|
65
|
+
{actionText}
|
|
66
|
+
</Button>
|
|
67
|
+
)}
|
|
68
|
+
</Empty>
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Confirm Dialog
|
|
73
|
+
Use Ant Design's `Modal.confirm`.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { Modal } from 'antd';
|
|
77
|
+
|
|
78
|
+
const showDeleteConfirm = (onConfirm: () => void) => {
|
|
79
|
+
Modal.confirm({
|
|
80
|
+
title: 'Are you sure you want to delete this resource?',
|
|
81
|
+
content: 'This action cannot be undone.',
|
|
82
|
+
okText: 'Yes, Delete',
|
|
83
|
+
okType: 'danger',
|
|
84
|
+
cancelText: 'No',
|
|
85
|
+
onOk() {
|
|
86
|
+
onConfirm();
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Utility Functions
|
|
93
|
+
|
|
94
|
+
### Format Utils
|
|
95
|
+
Standard internationalization utils.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// lib/utils/format.ts
|
|
99
|
+
export const formatCurrency = (amount: number, currency = 'USD') => {
|
|
100
|
+
return new Intl.NumberFormat('en-US', {
|
|
101
|
+
style: 'currency',
|
|
102
|
+
currency,
|
|
103
|
+
}).format(amount);
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Router & App Setup
|
|
108
|
+
|
|
109
|
+
### Router Configuration
|
|
110
|
+
```tsx
|
|
111
|
+
// app/router.tsx
|
|
112
|
+
export const router = createBrowserRouter([
|
|
113
|
+
{
|
|
114
|
+
path: '/',
|
|
115
|
+
element: <MainLayout />,
|
|
116
|
+
children: [
|
|
117
|
+
{ index: true, element: <HomePage /> },
|
|
118
|
+
{ path: 'resources', element: <ResourcesPage /> },
|
|
119
|
+
{ path: 'login', element: <LoginPage /> },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Table Pattern (Ant Design)
|
|
126
|
+
Use Ant Design's `Table` component for consistent data display.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// features/resources/components/ResourceTable.tsx
|
|
130
|
+
import { Table, Button, Space } from 'antd';
|
|
131
|
+
|
|
132
|
+
export const ResourceTable = ({ data, onEdit, onDelete, loading }) => {
|
|
133
|
+
const columns = [
|
|
134
|
+
{ title: 'Title', dataIndex: 'title', key: 'title' },
|
|
135
|
+
{ title: 'Category', dataIndex: 'category', key: 'category' },
|
|
136
|
+
{
|
|
137
|
+
title: 'Actions',
|
|
138
|
+
key: 'actions',
|
|
139
|
+
render: (_, record) => (
|
|
140
|
+
<Space size="middle">
|
|
141
|
+
<Button onClick={() => onEdit(record.id)}>Edit</Button>
|
|
142
|
+
<Button danger onClick={() => onDelete(record.id)}>Delete</Button>
|
|
143
|
+
</Space>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
return <Table dataSource={data} columns={columns} loading={loading} rowKey="id" />;
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
I use `Space` for horizontal layout and `Space.Compact` for grouped actions.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# Color System & Theming
|
|
9
|
+
|
|
10
|
+
## Color Architecture
|
|
11
|
+
All colors are defined as **CSS variables** in a global stylesheet and synchronized with **Ant Design's ConfigProvider** for component theming.
|
|
12
|
+
|
|
13
|
+
## CSS Variables Setup
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
/* styles/theme.css */
|
|
17
|
+
:root {
|
|
18
|
+
--primary-color: #1677ff;
|
|
19
|
+
--success-color: #52c41a;
|
|
20
|
+
--warning-color: #faad14;
|
|
21
|
+
--error-color: #f5222d;
|
|
22
|
+
--text-color: rgba(0, 0, 0, 0.88);
|
|
23
|
+
--bg-color: #ffffff;
|
|
24
|
+
--border-color: #d9d9d9;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
[data-theme='dark'] {
|
|
28
|
+
--primary-color: #1668dc;
|
|
29
|
+
--bg-color: #141414;
|
|
30
|
+
--text-color: rgba(255, 255, 255, 0.85);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Ant Design Configuration
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// app/providers.tsx
|
|
38
|
+
import { ConfigProvider, theme } from 'antd';
|
|
39
|
+
|
|
40
|
+
export const AppProviders = ({ children, isDarkMode }) => (
|
|
41
|
+
<ConfigProvider
|
|
42
|
+
theme={{
|
|
43
|
+
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
|
44
|
+
token: {
|
|
45
|
+
colorPrimary: '#1677ff',
|
|
46
|
+
borderRadius: 4,
|
|
47
|
+
},
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</ConfigProvider>
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage Rules
|
|
56
|
+
|
|
57
|
+
### ✅ CORRECT - Use CSS Variables in Vanilla CSS
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
/* features/resources/components/ResourceCard.css */
|
|
61
|
+
.resource-card {
|
|
62
|
+
background-color: var(--bg-color);
|
|
63
|
+
border: 1px solid var(--border-color);
|
|
64
|
+
color: var(--text-color);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.resource-card:hover {
|
|
68
|
+
border-color: var(--primary-color);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### ❌ NEVER Hardcode Colors in Components
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// ❌ BAD
|
|
76
|
+
<div style={{ color: '#ff0000' }}>Error</div>
|
|
77
|
+
|
|
78
|
+
// ✅ GOOD
|
|
79
|
+
<div className="error-text">Error</div> /* defined in CSS using var(--error-color) */
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Dark Mode Implementation
|
|
83
|
+
Use the `data-theme` attribute on the `html` or `body` tag and sync it with Ant Design's algorithm.
|
|
84
|
+
|
|
85
|
+
## AI Agent Rules
|
|
86
|
+
|
|
87
|
+
### Rule 1: No Hardcoded Colors
|
|
88
|
+
Always use `var(--color-name)` in CSS files. Never use hex/rgb values directly in component files.
|
|
89
|
+
|
|
90
|
+
### Rule 2: Semantic Naming
|
|
91
|
+
Use functional names like `--primary-color`, `--text-secondary`, not descriptive ones like `--blue`.
|
|
92
|
+
|
|
93
|
+
### Rule 3: Single Source of Truth
|
|
94
|
+
Global theme variables must stay in `styles/theme.css`. Components should import their own `.css` files that reference these variables.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "client/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **client** directory.
|
|
7
|
+
|
|
8
|
+
# Frontend Security Rules
|
|
9
|
+
|
|
10
|
+
## Core Security Principles
|
|
11
|
+
|
|
12
|
+
1. **Never trust user input**: Always validate and sanitize data from users.
|
|
13
|
+
2. **Never expose secrets**: API keys, database credentials, and secrets must never be in the client code.
|
|
14
|
+
3. **Always validate and sanitize**: Even if the backend validates, client-side sanitization is essential for UX and defense-in-depth.
|
|
15
|
+
4. **Defense in depth**: Multiple layers of security at every level of the stack.
|
|
16
|
+
|
|
17
|
+
## XSS Prevention
|
|
18
|
+
|
|
19
|
+
### React's Built-in Protection
|
|
20
|
+
React automatically escapes values to prevent XSS.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// ✅ SAFE - React escapes by default
|
|
24
|
+
<div>{userInput}</div>
|
|
25
|
+
|
|
26
|
+
// ❌ DANGEROUS - Avoid dangerouslySetInnerHTML unless strictly sanitized
|
|
27
|
+
<div dangerouslySetInnerHTML={{ __html: userInput }} />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### URL Sanitization
|
|
31
|
+
Always validate URLs before using them in `href` attributes.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
export const isSafeUrl = (url: string): boolean => {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Authentication Token Security
|
|
45
|
+
|
|
46
|
+
### Token Storage
|
|
47
|
+
Avoid storing sensitive tokens in `localStorage`. Use Redux for access tokens (in-memory) and `httpOnly` cookies or encrypted storage for long-lived tokens.
|
|
48
|
+
|
|
49
|
+
### Token Transmission
|
|
50
|
+
Always use HTTPS and send tokens in the `Authorization` header.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
axios.interceptors.request.use((config) => {
|
|
54
|
+
const token = store.getState().auth.tokens?.accessToken;
|
|
55
|
+
if (token) {
|
|
56
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
57
|
+
}
|
|
58
|
+
return config;
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Input & File Validation
|
|
63
|
+
|
|
64
|
+
### Client + Server Validation
|
|
65
|
+
Use Zod for robust client-side validation, matching the backend's expectations.
|
|
66
|
+
|
|
67
|
+
### File Upload Security
|
|
68
|
+
Validate file type, size, and extension before uploading.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
|
|
72
|
+
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
|
73
|
+
|
|
74
|
+
export const validateFile = (file: File) => {
|
|
75
|
+
if (!ALLOWED_TYPES.includes(file.type)) return { valid: false, error: 'File type not allowed' };
|
|
76
|
+
if (file.size > MAX_SIZE) return { valid: false, error: 'File too large' };
|
|
77
|
+
return { valid: true };
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Environment Variables
|
|
82
|
+
- Prefix with `VITE_` for client availability.
|
|
83
|
+
- Never commit `.env` files.
|
|
84
|
+
- Validate environment variables at runtime using a Zod schema.
|
|
85
|
+
|
|
86
|
+
## AI Agent Security Rules
|
|
87
|
+
|
|
88
|
+
### Rule 1: Never Trust User Input
|
|
89
|
+
Never use `dangerouslySetInnerHTML` with raw user input.
|
|
90
|
+
|
|
91
|
+
### Rule 2: Never Expose Secrets
|
|
92
|
+
Do not hardcode API keys or credentials. Use `import.meta.env`.
|
|
93
|
+
|
|
94
|
+
### Rule 3: Secure Tokens
|
|
95
|
+
Do not recommend plain `localStorage` for sensitive authentication tokens.
|
|
96
|
+
|
|
97
|
+
### Rule 4: Sanitize Content
|
|
98
|
+
Sanitize any HTML content with DOMPurify before rendering.
|