@famgia/omnify-typescript 0.0.67 → 0.0.69
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/dist/{chunk-4L77AHAC.js → chunk-6I4O23X6.js} +521 -66
- package/dist/chunk-6I4O23X6.js.map +1 -0
- package/dist/index.cjs +761 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -2
- package/dist/index.d.ts +138 -2
- package/dist/index.js +227 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +624 -75
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +96 -11
- package/dist/plugin.js.map +1 -1
- package/package.json +3 -3
- package/scripts/postinstall.js +29 -40
- package/stubs/JapaneseAddressField.tsx.stub +289 -0
- package/stubs/JapaneseBankField.tsx.stub +212 -0
- package/stubs/JapaneseNameField.tsx.stub +194 -0
- package/stubs/ai-guides/checklists/react.md.stub +108 -0
- package/stubs/ai-guides/cursor/react-design.mdc.stub +289 -0
- package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
- package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
- package/stubs/ai-guides/cursor/react.mdc.stub +254 -0
- package/stubs/ai-guides/react/README.md.stub +221 -0
- package/stubs/ai-guides/react/antd-guide.md.stub +294 -0
- package/stubs/ai-guides/react/checklist.md.stub +108 -0
- package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
- package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
- package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
- package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
- package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
- package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
- package/stubs/ai-guides/react/types-guide.md.stub +524 -0
- package/stubs/components-index.ts.stub +13 -0
- package/stubs/form-validation.ts.stub +106 -0
- package/stubs/rules/index.ts.stub +48 -0
- package/stubs/rules/kana.ts.stub +291 -0
- package/stubs/use-form-mutation.ts.stub +117 -0
- package/stubs/zod-i18n.ts.stub +32 -0
- package/ai-guides/antdesign-guide.md +0 -401
- package/ai-guides/typescript-guide.md +0 -310
- package/dist/chunk-4L77AHAC.js.map +0 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Frontend Services layer - API communication with backend"
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/services/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend Services Rules
|
|
8
|
+
|
|
9
|
+
> **API Reference:** `storage/api-docs/api-docs.json` (OpenAPI 3.0)
|
|
10
|
+
> **Base Config:** `lib/api.ts`
|
|
11
|
+
|
|
12
|
+
## ⛔ MUST: Check api-docs.json FIRST!
|
|
13
|
+
|
|
14
|
+
**BEFORE writing ANY service code, MUST read `storage/api-docs/api-docs.json`:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 1. ALWAYS read api-docs.json first
|
|
18
|
+
cat storage/api-docs/api-docs.json | jq '.paths'
|
|
19
|
+
|
|
20
|
+
# 2. Check specific endpoint
|
|
21
|
+
cat storage/api-docs/api-docs.json | jq '.paths["/api/users"]'
|
|
22
|
+
|
|
23
|
+
# 3. Check request/response schemas
|
|
24
|
+
cat storage/api-docs/api-docs.json | jq '.components.schemas'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Checklist (MANDATORY)
|
|
28
|
+
|
|
29
|
+
- [ ] **Read api-docs.json** - NO guessing!
|
|
30
|
+
- [ ] **Verify endpoint exists** - URL, method must match
|
|
31
|
+
- [ ] **Copy exact parameter names** - `filter[search]` not `search`
|
|
32
|
+
- [ ] **Match request body schema** - required fields, types
|
|
33
|
+
- [ ] **Match response schema** - data structure
|
|
34
|
+
|
|
35
|
+
### ❌ DON'T:
|
|
36
|
+
```typescript
|
|
37
|
+
// ❌ Guessing parameter names
|
|
38
|
+
search?: string; // WRONG - API uses filter[search]
|
|
39
|
+
sort_by?: string; // WRONG - API uses sort
|
|
40
|
+
sort_order?: "asc"|"desc"; // WRONG - API uses "-field" format
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ✅ DO:
|
|
44
|
+
```typescript
|
|
45
|
+
// ✅ Copy exactly from api-docs.json
|
|
46
|
+
|
|
47
|
+
// Sort has enum? → Create union type!
|
|
48
|
+
export type UserSortField =
|
|
49
|
+
| "id" | "-id"
|
|
50
|
+
| "name_lastname" | "-name_lastname"
|
|
51
|
+
| "email" | "-email"
|
|
52
|
+
| "created_at" | "-created_at";
|
|
53
|
+
|
|
54
|
+
export interface UserListParams {
|
|
55
|
+
sort?: UserSortField; // Correct: type-safe from enum
|
|
56
|
+
filter?: {
|
|
57
|
+
search?: string; // Correct: maps to filter[search]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Regenerate OpenAPI docs if needed
|
|
64
|
+
php artisan l5-swagger:generate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Service File Structure
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
/**
|
|
71
|
+
* {Resource} Service
|
|
72
|
+
*
|
|
73
|
+
* CRUD operations for {Resource} resource.
|
|
74
|
+
* Maps to Laravel {Resource}Controller.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
import api, { PaginatedResponse } from "@/lib/api";
|
|
78
|
+
import type { Resource, ResourceCreate, ResourceUpdate } from "@/types/model";
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Types - Only query params (Create/Update come from Omnify)
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/** Sort fields - MUST match api-docs.json enum! */
|
|
85
|
+
export type ResourceSortField =
|
|
86
|
+
| "id" | "-id"
|
|
87
|
+
| "created_at" | "-created_at"
|
|
88
|
+
| "updated_at" | "-updated_at";
|
|
89
|
+
// Add other fields from api-docs.json enum
|
|
90
|
+
|
|
91
|
+
/** Query params for listing resources */
|
|
92
|
+
export interface ResourceListParams {
|
|
93
|
+
page?: number;
|
|
94
|
+
per_page?: number;
|
|
95
|
+
sort?: ResourceSortField; // Type-safe from enum
|
|
96
|
+
filter?: {
|
|
97
|
+
search?: string;
|
|
98
|
+
// Add other filters from api-docs.json
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Service
|
|
104
|
+
// =============================================================================
|
|
105
|
+
|
|
106
|
+
const BASE_URL = "/api/resources";
|
|
107
|
+
|
|
108
|
+
export const resourceService = {
|
|
109
|
+
/**
|
|
110
|
+
* Axios auto-serializes nested objects:
|
|
111
|
+
* { filter: { search: "x" } } → ?filter[search]=x
|
|
112
|
+
*/
|
|
113
|
+
list: async (params?: ResourceListParams): Promise<PaginatedResponse<Resource>> => {
|
|
114
|
+
const { data } = await api.get(BASE_URL, { params });
|
|
115
|
+
return data;
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
get: async (id: number): Promise<Resource> => {
|
|
119
|
+
const { data } = await api.get(`${BASE_URL}/${id}`);
|
|
120
|
+
return data.data ?? data;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
create: async (input: ResourceCreate): Promise<Resource> => {
|
|
124
|
+
const { data } = await api.post(BASE_URL, input);
|
|
125
|
+
return data.data ?? data;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
update: async (id: number, input: ResourceUpdate): Promise<Resource> => {
|
|
129
|
+
const { data } = await api.put(`${BASE_URL}/${id}`, input);
|
|
130
|
+
return data.data ?? data;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
delete: async (id: number): Promise<void> => {
|
|
134
|
+
await api.delete(`${BASE_URL}/${id}`);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Types Rule
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// ✅ Import model types from Omnify
|
|
143
|
+
import type { User, UserCreate, UserUpdate } from "@/types/model";
|
|
144
|
+
|
|
145
|
+
// ✅ Define only query/filter params locally
|
|
146
|
+
export interface UserListParams {
|
|
147
|
+
search?: string;
|
|
148
|
+
page?: number;
|
|
149
|
+
per_page?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ❌ DON'T redefine model types
|
|
153
|
+
export interface User { ... } // WRONG - use Omnify
|
|
154
|
+
export interface UserCreateInput { ... } // WRONG - use UserCreate
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Response Handling
|
|
158
|
+
|
|
159
|
+
### Paginated List (GET /api/resources)
|
|
160
|
+
```typescript
|
|
161
|
+
// OpenAPI: Returns { data: T[], links: {...}, meta: {...} }
|
|
162
|
+
list: async (params): Promise<PaginatedResponse<Resource>> => {
|
|
163
|
+
const { data } = await api.get(BASE_URL, { params });
|
|
164
|
+
return data; // Full response with data, links, meta
|
|
165
|
+
},
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Single Resource (GET /api/resources/:id)
|
|
169
|
+
```typescript
|
|
170
|
+
// OpenAPI: Returns { data: T }
|
|
171
|
+
get: async (id: number): Promise<Resource> => {
|
|
172
|
+
const { data } = await api.get(`${BASE_URL}/${id}`);
|
|
173
|
+
return data.data ?? data; // Unwrap { data: T } wrapper
|
|
174
|
+
},
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Create/Update (POST/PUT)
|
|
178
|
+
```typescript
|
|
179
|
+
// OpenAPI: Returns { data: T }
|
|
180
|
+
create: async (input: ResourceCreate): Promise<Resource> => {
|
|
181
|
+
const { data } = await api.post(BASE_URL, input);
|
|
182
|
+
return data.data ?? data; // Unwrap { data: T } wrapper
|
|
183
|
+
},
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Delete (DELETE /api/resources/:id)
|
|
187
|
+
```typescript
|
|
188
|
+
// OpenAPI: Returns 204 No Content
|
|
189
|
+
delete: async (id: number): Promise<void> => {
|
|
190
|
+
await api.delete(`${BASE_URL}/${id}`);
|
|
191
|
+
},
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Auth Service Pattern (Sanctum)
|
|
195
|
+
|
|
196
|
+
For auth endpoints that need CSRF:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import api, { csrf } from "@/lib/api";
|
|
200
|
+
|
|
201
|
+
export const authService = {
|
|
202
|
+
login: async (input: LoginInput): Promise<void> => {
|
|
203
|
+
await csrf(); // Required for Sanctum
|
|
204
|
+
await api.post("/login", input);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
logout: async (): Promise<void> => {
|
|
208
|
+
await api.post("/logout");
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
me: async (): Promise<User> => {
|
|
212
|
+
const { data } = await api.get<User>("/api/user");
|
|
213
|
+
return data;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## OpenAPI Parameter Mapping
|
|
219
|
+
|
|
220
|
+
Axios auto-serializes nested objects → **NO transform needed!**
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Frontend interface
|
|
224
|
+
{ filter: { search: "John" }, sort: "-created_at", page: 1 }
|
|
225
|
+
|
|
226
|
+
// Axios auto-serializes to:
|
|
227
|
+
// ?filter[search]=John&sort=-created_at&page=1
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
| OpenAPI Parameter | Frontend Interface | Notes |
|
|
231
|
+
| ----------------- | ------------------ | --------------------- |
|
|
232
|
+
| `filter[search]` | `filter.search` | Axios auto-serializes |
|
|
233
|
+
| `page` | `page` | Direct |
|
|
234
|
+
| `per_page` | `per_page` | Direct |
|
|
235
|
+
| `sort` | `sort` | Use enum type! |
|
|
236
|
+
|
|
237
|
+
### Sort Field - MUST use enum!
|
|
238
|
+
```typescript
|
|
239
|
+
// ❌ WRONG - not type-safe
|
|
240
|
+
sort?: string;
|
|
241
|
+
|
|
242
|
+
// ✅ CORRECT - copy enum from api-docs.json
|
|
243
|
+
export type ResourceSortField =
|
|
244
|
+
| "id" | "-id"
|
|
245
|
+
| "created_at" | "-created_at";
|
|
246
|
+
|
|
247
|
+
sort?: ResourceSortField; // Type-safe!
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Error Handling
|
|
251
|
+
|
|
252
|
+
Errors are handled globally in `lib/api.ts`. Services should:
|
|
253
|
+
- **NOT** catch errors (let them propagate to mutations/queries)
|
|
254
|
+
- **NOT** show messages (handled by TanStack Query callbacks)
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// ❌ Wrong
|
|
258
|
+
create: async (input) => {
|
|
259
|
+
try {
|
|
260
|
+
const { data } = await api.post(BASE_URL, input);
|
|
261
|
+
return data.data;
|
|
262
|
+
} catch (e) {
|
|
263
|
+
console.error(e); // DON'T catch here
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// ✅ Correct - let errors propagate
|
|
268
|
+
create: async (input) => {
|
|
269
|
+
const { data } = await api.post(BASE_URL, input);
|
|
270
|
+
return data.data ?? data;
|
|
271
|
+
},
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Naming Conventions
|
|
275
|
+
|
|
276
|
+
| Item | Convention | Example |
|
|
277
|
+
| -------------- | --------------------------- | ---------------------------- |
|
|
278
|
+
| File | `{resource}.ts` (singular) | `users.ts`, `auth.ts` |
|
|
279
|
+
| Service object | `{resource}Service` | `userService`, `authService` |
|
|
280
|
+
| List params | `{Resource}ListParams` | `UserListParams` |
|
|
281
|
+
| Base URL | `/api/{resources}` (plural) | `/api/users` |
|
|
282
|
+
|
|
283
|
+
## Checklist Before Creating Service
|
|
284
|
+
|
|
285
|
+
- [ ] API endpoint exists in `storage/api-docs/api-docs.json`
|
|
286
|
+
- [ ] Omnify types exist in `types/model/`
|
|
287
|
+
- [ ] Request body matches OpenAPI `requestBody.content.application/json.schema`
|
|
288
|
+
- [ ] Response matches OpenAPI `responses.200.content.application/json.schema`
|
|
289
|
+
- [ ] Parameters match OpenAPI `parameters[]`
|
|
290
|
+
|
|
291
|
+
## Quick Reference: Current API Endpoints
|
|
292
|
+
|
|
293
|
+
> From `storage/api-docs/api-docs.json`:
|
|
294
|
+
|
|
295
|
+
### Users API
|
|
296
|
+
| Method | Endpoint | Description |
|
|
297
|
+
| ------ | ----------------- | -------------------------------- |
|
|
298
|
+
| GET | `/api/users` | Paginated list with search, sort |
|
|
299
|
+
| POST | `/api/users` | Create user |
|
|
300
|
+
| GET | `/api/users/{id}` | Get single user |
|
|
301
|
+
| PUT | `/api/users/{id}` | Update user |
|
|
302
|
+
| DELETE | `/api/users/{id}` | Delete user |
|
|
303
|
+
|
|
304
|
+
**Sort fields:** `id`, `name_lastname`, `name_firstname`, `email`, `created_at`, `updated_at`
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "React + Ant Design frontend development rules"
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React + Ant Design Rules
|
|
8
|
+
|
|
9
|
+
> **Related Rules:**
|
|
10
|
+
> - **Form Development:** `.cursor/rules/omnify/react-form.mdc`
|
|
11
|
+
> - **Design System:** `.cursor/rules/omnify/react-design.mdc`
|
|
12
|
+
> - **Services:** `.cursor/rules/omnify/react-services.mdc`
|
|
13
|
+
|
|
14
|
+
## Guide Documentation
|
|
15
|
+
|
|
16
|
+
- Read `.claude/guides/react/` for patterns
|
|
17
|
+
|
|
18
|
+
## Critical Rules
|
|
19
|
+
|
|
20
|
+
1. **Use Ant Design** - Don't recreate existing components
|
|
21
|
+
2. **Use Omnify types** - Import from `@/types/model`, don't duplicate
|
|
22
|
+
3. **Use i18n** - Use `useTranslations()` for UI text
|
|
23
|
+
4. **Service Layer** - API calls in `services/`, not components
|
|
24
|
+
5. **TanStack Query** - For all server state, no `useState` + `useEffect`
|
|
25
|
+
|
|
26
|
+
## Quick Patterns
|
|
27
|
+
|
|
28
|
+
### Service
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
export const userService = {
|
|
32
|
+
list: (params?: UserListParams) => api.get("/api/users", { params }).then(r => r.data),
|
|
33
|
+
create: (input: UserCreate) => api.post("/api/users", input).then(r => r.data.data),
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Query
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const { data, isLoading } = useQuery({
|
|
41
|
+
queryKey: queryKeys.users.list(filters),
|
|
42
|
+
queryFn: () => userService.list(filters),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Mutation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const mutation = useMutation({
|
|
50
|
+
mutationFn: userService.create,
|
|
51
|
+
onSuccess: () => {
|
|
52
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
|
53
|
+
message.success(t("messages.created"));
|
|
54
|
+
},
|
|
55
|
+
onError: (error) => form.setFields(getFormErrors(error)),
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Types Rule
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// ✅ Use Omnify-generated types
|
|
63
|
+
import type { User, UserCreate } from "@/types/model";
|
|
64
|
+
|
|
65
|
+
// ✅ Only define query params locally
|
|
66
|
+
export interface UserListParams { ... }
|
|
67
|
+
|
|
68
|
+
// ❌ Don't redefine Omnify types
|
|
69
|
+
export interface UserCreateInput { ... } // WRONG
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Form Pattern (Omnify)
|
|
73
|
+
|
|
74
|
+
> **Detailed Guide:** See `react-form.mdc` for complete form development guide.
|
|
75
|
+
|
|
76
|
+
**Quick Reference:**
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// 1. Imports
|
|
80
|
+
import { zodRule, setZodLocale } from '@/omnify/lib';
|
|
81
|
+
import { OmnifyForm } from '@/omnify/components';
|
|
82
|
+
import { customerSchemas, customerI18n, getCustomerFieldLabel } from '@/omnify/schemas';
|
|
83
|
+
|
|
84
|
+
// 2. Helper functions (MUST define in every form)
|
|
85
|
+
const LOCALE = 'ja';
|
|
86
|
+
const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
|
|
87
|
+
const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
|
|
88
|
+
|
|
89
|
+
// 3. Form.Item pattern
|
|
90
|
+
<Form.Item name="email" label={label('email')} rules={[rule('email')]}>
|
|
91
|
+
<Input />
|
|
92
|
+
</Form.Item>
|
|
93
|
+
|
|
94
|
+
// 4. Japanese compound fields
|
|
95
|
+
<OmnifyForm.JapaneseName schemas={customerSchemas} i18n={customerI18n} prefix="name" />
|
|
96
|
+
<OmnifyForm.JapaneseAddress form={form} schemas={customerSchemas} i18n={customerI18n} prefix="address" />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## File Location
|
|
100
|
+
|
|
101
|
+
| What | Where |
|
|
102
|
+
| ----------------------- | ------------------------------------------- |
|
|
103
|
+
| Component (1 feature) | `features/{feature}/` |
|
|
104
|
+
| Component (2+ features) | `components/common/` |
|
|
105
|
+
| Service (API) | `services/` (ALWAYS) |
|
|
106
|
+
| Hook (1 feature) | `features/{feature}/` |
|
|
107
|
+
| Hook (2+ features) | `hooks/` |
|
|
108
|
+
| Zod Schema | `schemas/{model}.ts` |
|
|
109
|
+
| Validation utils | `lib/form-validation.ts`, `lib/zod-i18n.ts` |
|
|
110
|
+
|
|
111
|
+
## Ant Design v6 Deprecated Props
|
|
112
|
+
|
|
113
|
+
| Deprecated | Use Instead |
|
|
114
|
+
| -------------------------- | ----------------------- |
|
|
115
|
+
| `visible` | `open` |
|
|
116
|
+
| `direction` (Space) | `orientation` |
|
|
117
|
+
| `dropdownMatchSelectWidth` | `popupMatchSelectWidth` |
|
|
118
|
+
|
|
119
|
+
## Ant Design Static Method Warning
|
|
120
|
+
|
|
121
|
+
⚠️ **Warning:** `Static function can not consume context like dynamic theme`
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// ❌ Wrong - Static import has no context
|
|
125
|
+
import { message } from "antd";
|
|
126
|
+
message.success("Done"); // Warning!
|
|
127
|
+
|
|
128
|
+
// ✅ Correct - Use App.useApp() hook
|
|
129
|
+
import { App } from "antd";
|
|
130
|
+
|
|
131
|
+
function MyComponent() {
|
|
132
|
+
const { message, notification, modal } = App.useApp();
|
|
133
|
+
message.success("Done"); // ✅ No warning
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Note:**
|
|
138
|
+
- `App` component is already wrapped in `AntdThemeProvider`
|
|
139
|
+
- Just use `App.useApp()` in your component/hook
|
|
140
|
+
- Applies to: `message`, `notification`, `modal`
|
|
141
|
+
|
|
142
|
+
## Common Mistakes
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// ❌ Wrong
|
|
146
|
+
useEffect(() => { fetchData() }, []); // Use useQuery
|
|
147
|
+
const [users, setUsers] = useState([]); // Use TanStack for server state
|
|
148
|
+
<Button>Save</Button> // Use i18n
|
|
149
|
+
|
|
150
|
+
// ✅ Correct
|
|
151
|
+
const { data } = useQuery({ queryKey, queryFn });
|
|
152
|
+
<Button>{t("common.save")}</Button>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Form Validation Mistakes
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// ❌ Wrong - Hardcoded message in schema
|
|
159
|
+
z.string().min(1, "Name is required")
|
|
160
|
+
|
|
161
|
+
// ❌ Wrong - Define schema in component
|
|
162
|
+
const schema = z.object({ name: z.string() });
|
|
163
|
+
|
|
164
|
+
// ❌ Wrong - No field label comment
|
|
165
|
+
<Form.Item name="name" label={label("name")}>
|
|
166
|
+
|
|
167
|
+
// ✅ Correct - Schema in schemas/, messages in i18n/
|
|
168
|
+
import { userSchemas } from "@/schemas/user";
|
|
169
|
+
{/* Name */}
|
|
170
|
+
<Form.Item name="name" label={label("name")} rules={[zodRule(userSchemas.name, label("name"))]}>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Form.useForm() Warning
|
|
174
|
+
|
|
175
|
+
⚠️ **Warning:** `Instance created by useForm is not connected to any Form element`
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// ❌ Wrong - Export hook creates unused form instance
|
|
179
|
+
export function useUserForm() {
|
|
180
|
+
return Form.useForm(); // Instance not connected to any Form!
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ❌ Wrong - Create form instance but don't pass to Form
|
|
184
|
+
const [form] = Form.useForm();
|
|
185
|
+
return <Form>...</Form> // Missing form={form}
|
|
186
|
+
|
|
187
|
+
// ✅ Correct - Form instance must connect to Form
|
|
188
|
+
const [form] = Form.useForm();
|
|
189
|
+
return <Form form={form}>...</Form>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Rule:** Each `Form.useForm()` must have exactly one corresponding `<Form form={form}>`.
|
|
193
|
+
|
|
194
|
+
### Backend Validation Errors (422)
|
|
195
|
+
|
|
196
|
+
⚠️ **IMPORTANT:** Form must display errors from backend!
|
|
197
|
+
|
|
198
|
+
**Form component MUST receive `form` prop from parent:**
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Form component
|
|
202
|
+
interface UserFormProps {
|
|
203
|
+
form: FormInstance; // ← REQUIRED
|
|
204
|
+
onSubmit: (values: UserCreate) => void;
|
|
205
|
+
// ...
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function UserForm({ form, onSubmit, ... }: UserFormProps) {
|
|
209
|
+
return (
|
|
210
|
+
<Form form={form} onFinish={onSubmit}>
|
|
211
|
+
...
|
|
212
|
+
</Form>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Page creates form and handles backend errors:**
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Page component
|
|
221
|
+
export default function NewUserPage() {
|
|
222
|
+
const [form] = Form.useForm(); // ← Create in parent
|
|
223
|
+
|
|
224
|
+
const mutation = useMutation({
|
|
225
|
+
mutationFn: userService.create,
|
|
226
|
+
onSuccess: () => { ... },
|
|
227
|
+
onError: (error) => {
|
|
228
|
+
form.setFields(getFormErrors(error)); // ← Set errors from backend
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<UserForm
|
|
234
|
+
form={form} // ← Pass to form component
|
|
235
|
+
onSubmit={(values) => mutation.mutate(values)}
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Helper `getFormErrors`** (available in `lib/api.ts`):
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { getFormErrors } from "@/lib/api";
|
|
245
|
+
|
|
246
|
+
// Converts Laravel 422 response to Ant Design format:
|
|
247
|
+
// { errors: { email: ["Email already exists"] } }
|
|
248
|
+
// → [{ name: "email", errors: ["Email already exists"] }]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Rules:**
|
|
252
|
+
1. Form component does NOT create `Form.useForm()` - receives from parent
|
|
253
|
+
2. Page creates form instance and passes down
|
|
254
|
+
3. `onError` calls `form.setFields(getFormErrors(error))`
|