@adlas/create-app 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/README.md +476 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/figma.d.ts +16 -0
- package/dist/commands/figma.d.ts.map +1 -0
- package/dist/commands/figma.js +172 -0
- package/dist/commands/figma.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +5 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1471 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/swagger.d.ts +16 -0
- package/dist/commands/swagger.d.ts.map +1 -0
- package/dist/commands/swagger.js +404 -0
- package/dist/commands/swagger.js.map +1 -0
- package/dist/commands/update.d.ts +15 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/package.json +63 -0
- package/templates/.vscode/extensions.json +9 -0
- package/templates/.vscode/launch.json +26 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/.vscode/tasks.json +21 -0
- package/templates/boilerplate/config/fonts.ts +10 -0
- package/templates/boilerplate/config/navigationUrls.ts +47 -0
- package/templates/boilerplate/config/site.ts +96 -0
- package/templates/boilerplate/libs/I18n.ts +15 -0
- package/templates/boilerplate/libs/I18nNavigation.ts +5 -0
- package/templates/boilerplate/libs/I18nRouting.ts +9 -0
- package/templates/boilerplate/libs/env.ts +21 -0
- package/templates/boilerplate/libs/react-query/ReactQueryProvider.tsx +21 -0
- package/templates/boilerplate/libs/react-query/index.ts +2 -0
- package/templates/boilerplate/libs/react-query/queryClient.ts +62 -0
- package/templates/boilerplate/libs/react-query/queryKeys.ts +5 -0
- package/templates/boilerplate/public/images/index.ts +1 -0
- package/templates/boilerplate/reset.d.ts +2 -0
- package/templates/boilerplate/styles/globals.css +308 -0
- package/templates/boilerplate/types/i18n.ts +10 -0
- package/templates/boilerplate/types/locale.ts +8 -0
- package/templates/boilerplate/utils/file/fileConfig.ts +123 -0
- package/templates/boilerplate/utils/file/fileValidation.ts +78 -0
- package/templates/boilerplate/utils/file/imageCompression.ts +182 -0
- package/templates/boilerplate/utils/file/index.ts +3 -0
- package/templates/boilerplate/utils/helpers.ts +55 -0
- package/templates/boilerplate/validations/auth.validation.ts +92 -0
- package/templates/boilerplate/validations/commonValidations.ts +258 -0
- package/templates/boilerplate/validations/zodErrorMap.ts +101 -0
- package/templates/configs/.env.example +8 -0
- package/templates/configs/.prettierignore +23 -0
- package/templates/configs/.prettierrc.cjs +26 -0
- package/templates/configs/.prettierrc.icons.cjs +11 -0
- package/templates/configs/Dockerfile +6 -0
- package/templates/configs/commitlint.config.ts +8 -0
- package/templates/configs/eslint.config.mjs +119 -0
- package/templates/configs/knip.config.ts +32 -0
- package/templates/configs/lefthook.yml +42 -0
- package/templates/configs/lint-staged.config.js +8 -0
- package/templates/configs/next.config.template.ts +77 -0
- package/templates/configs/next.config.ts +43 -0
- package/templates/configs/package.json +75 -0
- package/templates/configs/postcss.config.mjs +15 -0
- package/templates/configs/svgr.config.mjs +129 -0
- package/templates/configs/tsconfig.json +75 -0
- package/templates/docs/AI_QUICK_REFERENCE.md +379 -0
- package/templates/docs/ARCHITECTURE_PATTERNS.md +927 -0
- package/templates/docs/DOCUMENTATION_INDEX.md +411 -0
- package/templates/docs/FIGMA_TO_CODE_GUIDE.md +768 -0
- package/templates/docs/IMPLEMENTATION_GUIDE.md +892 -0
- package/templates/docs/PROJECT_OVERVIEW.md +302 -0
- package/templates/docs/REFACTOR_PROGRESS.md +1113 -0
- package/templates/docs/SHADCN_TO_HEROUI_MIGRATION.md +1375 -0
- package/templates/docs/UI_COMPONENTS_GUIDE.md +893 -0
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
# Implementation Guide
|
|
2
|
+
|
|
3
|
+
Step-by-step guide for implementing features in the Berndorf e-commerce project. Follow this guide to ensure consistency and quality.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Pre-Implementation Checklist](#pre-implementation-checklist)
|
|
8
|
+
2. [Implementation Steps](#implementation-steps)
|
|
9
|
+
3. [Quality Checklist](#quality-checklist)
|
|
10
|
+
4. [Troubleshooting](#troubleshooting)
|
|
11
|
+
5. [Complete Example](#complete-example)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Pre-Implementation Checklist
|
|
16
|
+
|
|
17
|
+
Before starting any implementation, verify:
|
|
18
|
+
|
|
19
|
+
### 1. Requirements Clarity
|
|
20
|
+
|
|
21
|
+
- [ ] Figma design is complete and reviewed
|
|
22
|
+
- [ ] Feature requirements are documented
|
|
23
|
+
- [ ] API endpoints are defined
|
|
24
|
+
- [ ] Data models are specified
|
|
25
|
+
- [ ] User flows are clear
|
|
26
|
+
|
|
27
|
+
### 2. Technical Review
|
|
28
|
+
|
|
29
|
+
- [ ] Checked existing components in `/src/components/ui/`
|
|
30
|
+
- [ ] Checked existing elements in `/src/components/elements/`
|
|
31
|
+
- [ ] Reviewed similar features in `/src/services/`
|
|
32
|
+
- [ ] Identified reusable patterns
|
|
33
|
+
- [ ] No new UI libraries needed
|
|
34
|
+
|
|
35
|
+
### 3. Planning
|
|
36
|
+
|
|
37
|
+
- [ ] Service module structure planned
|
|
38
|
+
- [ ] Component hierarchy defined
|
|
39
|
+
- [ ] State management approach decided
|
|
40
|
+
- [ ] Translation keys prepared
|
|
41
|
+
- [ ] Validation rules defined
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Implementation Steps
|
|
46
|
+
|
|
47
|
+
Follow these steps in order for any new feature.
|
|
48
|
+
|
|
49
|
+
### Step 1: Create Service Module Structure
|
|
50
|
+
|
|
51
|
+
Create the directory structure for your feature.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Create directory structure
|
|
55
|
+
mkdir -p src/services/[feature]/{api,queries,hooks,types,actions,store,constants,helpers}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Create `index.ts` to export all modules:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// src/services/[feature]/index.ts
|
|
62
|
+
export * from './api/[feature].api';
|
|
63
|
+
export * from './queries/[feature].queries';
|
|
64
|
+
export * from './hooks/use[Feature]';
|
|
65
|
+
export * from './types/[feature].types';
|
|
66
|
+
export * from './actions/[feature].actions';
|
|
67
|
+
export * from './store/[feature].store';
|
|
68
|
+
export * from './constants/[feature].constants';
|
|
69
|
+
export * from './helpers/[feature].helpers';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Step 2: Define TypeScript Types
|
|
73
|
+
|
|
74
|
+
Define all types for your feature.
|
|
75
|
+
|
|
76
|
+
**File:** `src/services/[feature]/types/[feature].types.ts`
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// API Response types (match backend structure)
|
|
80
|
+
export interface FeatureApiResponse {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
created_at: string; // snake_case from API
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Frontend Model types (camelCase for frontend)
|
|
87
|
+
export interface Feature {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
createdAt: Date;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Request types
|
|
94
|
+
export interface CreateFeatureRequest {
|
|
95
|
+
name: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface UpdateFeatureRequest {
|
|
99
|
+
name?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Filter types
|
|
103
|
+
export interface FeatureFilters {
|
|
104
|
+
search?: string;
|
|
105
|
+
status?: 'active' | 'inactive';
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Step 3: Create Helper Functions
|
|
110
|
+
|
|
111
|
+
Create transformation and utility functions.
|
|
112
|
+
|
|
113
|
+
**File:** `src/services/[feature]/helpers/[feature].helpers.ts`
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import type { Feature, FeatureApiResponse } from '../types/[feature].types';
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Transform API response to frontend model
|
|
120
|
+
*/
|
|
121
|
+
export function transformFeatureResponse(data: FeatureApiResponse): Feature {
|
|
122
|
+
return {
|
|
123
|
+
id: data.id,
|
|
124
|
+
name: data.name,
|
|
125
|
+
createdAt: new Date(data.created_at),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format date for display
|
|
131
|
+
*/
|
|
132
|
+
export function formatFeatureDate(date: Date): string {
|
|
133
|
+
return new Intl.DateTimeFormat('de-AT', {
|
|
134
|
+
year: 'numeric',
|
|
135
|
+
month: 'long',
|
|
136
|
+
day: 'numeric',
|
|
137
|
+
}).format(date);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Step 4: Create API Client
|
|
142
|
+
|
|
143
|
+
Create API client functions.
|
|
144
|
+
|
|
145
|
+
**File:** `src/services/[feature]/api/[feature].api.ts`
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { apiRequest } from '@/lib/apiRequest';
|
|
149
|
+
import type {
|
|
150
|
+
Feature,
|
|
151
|
+
FeatureApiResponse,
|
|
152
|
+
CreateFeatureRequest,
|
|
153
|
+
UpdateFeatureRequest,
|
|
154
|
+
FeatureFilters,
|
|
155
|
+
} from '../types/[feature].types';
|
|
156
|
+
import { transformFeatureResponse } from '../helpers/[feature].helpers';
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fetch all features
|
|
160
|
+
*/
|
|
161
|
+
export async function getFeatures(filters?: FeatureFilters): Promise<Feature[]> {
|
|
162
|
+
const queryParams = new URLSearchParams();
|
|
163
|
+
|
|
164
|
+
if (filters?.search) queryParams.append('search', filters.search);
|
|
165
|
+
if (filters?.status) queryParams.append('status', filters.status);
|
|
166
|
+
|
|
167
|
+
const url = `/api/features${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
|
|
168
|
+
|
|
169
|
+
const response = await apiRequest<FeatureApiResponse[]>({
|
|
170
|
+
url,
|
|
171
|
+
method: 'GET',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return response.map(transformFeatureResponse);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Fetch single feature
|
|
179
|
+
*/
|
|
180
|
+
export async function getFeature(id: string): Promise<Feature> {
|
|
181
|
+
const response = await apiRequest<FeatureApiResponse>({
|
|
182
|
+
url: `/api/features/${id}`,
|
|
183
|
+
method: 'GET',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return transformFeatureResponse(response);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create new feature
|
|
191
|
+
*/
|
|
192
|
+
export async function createFeature(data: CreateFeatureRequest): Promise<Feature> {
|
|
193
|
+
const response = await apiRequest<FeatureApiResponse>({
|
|
194
|
+
url: '/api/features',
|
|
195
|
+
method: 'POST',
|
|
196
|
+
data,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return transformFeatureResponse(response);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Update feature
|
|
204
|
+
*/
|
|
205
|
+
export async function updateFeature(id: string, data: UpdateFeatureRequest): Promise<Feature> {
|
|
206
|
+
const response = await apiRequest<FeatureApiResponse>({
|
|
207
|
+
url: `/api/features/${id}`,
|
|
208
|
+
method: 'PATCH',
|
|
209
|
+
data,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return transformFeatureResponse(response);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Delete feature
|
|
217
|
+
*/
|
|
218
|
+
export async function deleteFeature(id: string): Promise<void> {
|
|
219
|
+
await apiRequest<void>({
|
|
220
|
+
url: `/api/features/${id}`,
|
|
221
|
+
method: 'DELETE',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Step 5: Create React Query Hooks
|
|
227
|
+
|
|
228
|
+
Create React Query hooks for data fetching.
|
|
229
|
+
|
|
230
|
+
**File:** `src/services/[feature]/queries/[feature].queries.ts`
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
234
|
+
import { toast } from 'sonner';
|
|
235
|
+
import { useTranslations } from 'next-intl';
|
|
236
|
+
import type { FeatureFilters, CreateFeatureRequest, UpdateFeatureRequest } from '../types/[feature].types';
|
|
237
|
+
import {
|
|
238
|
+
getFeatures,
|
|
239
|
+
getFeature,
|
|
240
|
+
createFeature,
|
|
241
|
+
updateFeature,
|
|
242
|
+
deleteFeature,
|
|
243
|
+
} from '../api/[feature].api';
|
|
244
|
+
|
|
245
|
+
// Query Keys
|
|
246
|
+
export const featureKeys = {
|
|
247
|
+
all: ['features'] as const,
|
|
248
|
+
lists: () => [...featureKeys.all, 'list'] as const,
|
|
249
|
+
list: (filters?: FeatureFilters) => [...featureKeys.lists(), filters] as const,
|
|
250
|
+
details: () => [...featureKeys.all, 'detail'] as const,
|
|
251
|
+
detail: (id: string) => [...featureKeys.details(), id] as const,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Fetch all features
|
|
256
|
+
*/
|
|
257
|
+
export function useFeaturesQuery(filters?: FeatureFilters) {
|
|
258
|
+
return useQuery({
|
|
259
|
+
queryKey: featureKeys.list(filters),
|
|
260
|
+
queryFn: () => getFeatures(filters),
|
|
261
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Fetch single feature
|
|
267
|
+
*/
|
|
268
|
+
export function useFeatureQuery(id: string) {
|
|
269
|
+
return useQuery({
|
|
270
|
+
queryKey: featureKeys.detail(id),
|
|
271
|
+
queryFn: () => getFeature(id),
|
|
272
|
+
enabled: !!id,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create feature mutation
|
|
278
|
+
*/
|
|
279
|
+
export function useCreateFeatureMutation() {
|
|
280
|
+
const queryClient = useQueryClient();
|
|
281
|
+
const t = useTranslations('Feature');
|
|
282
|
+
|
|
283
|
+
return useMutation({
|
|
284
|
+
mutationFn: createFeature,
|
|
285
|
+
onSuccess: () => {
|
|
286
|
+
queryClient.invalidateQueries({ queryKey: featureKeys.lists() });
|
|
287
|
+
toast.success(t('createSuccess'));
|
|
288
|
+
},
|
|
289
|
+
onError: (error: Error) => {
|
|
290
|
+
toast.error(error.message || t('createError'));
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Update feature mutation
|
|
297
|
+
*/
|
|
298
|
+
export function useUpdateFeatureMutation() {
|
|
299
|
+
const queryClient = useQueryClient();
|
|
300
|
+
const t = useTranslations('Feature');
|
|
301
|
+
|
|
302
|
+
return useMutation({
|
|
303
|
+
mutationFn: ({ id, data }: { id: string; data: UpdateFeatureRequest }) =>
|
|
304
|
+
updateFeature(id, data),
|
|
305
|
+
onSuccess: (_, variables) => {
|
|
306
|
+
queryClient.invalidateQueries({ queryKey: featureKeys.lists() });
|
|
307
|
+
queryClient.invalidateQueries({ queryKey: featureKeys.detail(variables.id) });
|
|
308
|
+
toast.success(t('updateSuccess'));
|
|
309
|
+
},
|
|
310
|
+
onError: (error: Error) => {
|
|
311
|
+
toast.error(error.message || t('updateError'));
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Delete feature mutation
|
|
318
|
+
*/
|
|
319
|
+
export function useDeleteFeatureMutation() {
|
|
320
|
+
const queryClient = useQueryClient();
|
|
321
|
+
const t = useTranslations('Feature');
|
|
322
|
+
|
|
323
|
+
return useMutation({
|
|
324
|
+
mutationFn: deleteFeature,
|
|
325
|
+
onSuccess: () => {
|
|
326
|
+
queryClient.invalidateQueries({ queryKey: featureKeys.lists() });
|
|
327
|
+
toast.success(t('deleteSuccess'));
|
|
328
|
+
},
|
|
329
|
+
onError: (error: Error) => {
|
|
330
|
+
toast.error(error.message || t('deleteError'));
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Step 6: Create Custom Hooks (Optional)
|
|
337
|
+
|
|
338
|
+
Create custom hooks for complex logic.
|
|
339
|
+
|
|
340
|
+
**File:** `src/services/[feature]/hooks/useFeature.ts`
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { useMemo } from 'react';
|
|
344
|
+
import { useFeaturesQuery } from '../queries/[feature].queries';
|
|
345
|
+
import type { FeatureFilters } from '../types/[feature].types';
|
|
346
|
+
|
|
347
|
+
export function useFeatures(filters?: FeatureFilters) {
|
|
348
|
+
const { data, isLoading, error } = useFeaturesQuery(filters);
|
|
349
|
+
|
|
350
|
+
const features = useMemo(() => data || [], [data]);
|
|
351
|
+
|
|
352
|
+
const activeFeatures = useMemo(
|
|
353
|
+
() => features.filter(f => f.status === 'active'),
|
|
354
|
+
[features],
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
features,
|
|
359
|
+
activeFeatures,
|
|
360
|
+
isLoading,
|
|
361
|
+
error,
|
|
362
|
+
isEmpty: features.length === 0,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Step 7: Create Zustand Store (If Needed)
|
|
368
|
+
|
|
369
|
+
Create Zustand store for global state.
|
|
370
|
+
|
|
371
|
+
**File:** `src/services/[feature]/store/[feature].store.ts`
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { create } from 'zustand';
|
|
375
|
+
import type { FeatureFilters } from '../types/[feature].types';
|
|
376
|
+
|
|
377
|
+
interface FeatureStore {
|
|
378
|
+
filters: FeatureFilters;
|
|
379
|
+
selectedId: string | null;
|
|
380
|
+
setFilters: (filters: FeatureFilters) => void;
|
|
381
|
+
setSelectedId: (id: string | null) => void;
|
|
382
|
+
resetFilters: () => void;
|
|
383
|
+
reset: () => void;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const initialState = {
|
|
387
|
+
filters: {},
|
|
388
|
+
selectedId: null,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const useFeatureStore = create<FeatureStore>((set) => ({
|
|
392
|
+
...initialState,
|
|
393
|
+
|
|
394
|
+
setFilters: (filters) => set({ filters }),
|
|
395
|
+
|
|
396
|
+
setSelectedId: (id) => set({ selectedId: id }),
|
|
397
|
+
|
|
398
|
+
resetFilters: () => set({ filters: {} }),
|
|
399
|
+
|
|
400
|
+
reset: () => set(initialState),
|
|
401
|
+
}));
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Step 8: Create Server Actions (If Needed)
|
|
405
|
+
|
|
406
|
+
Create server actions for server-side operations.
|
|
407
|
+
|
|
408
|
+
**File:** `src/services/[feature]/actions/[feature].actions.ts`
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
'use server';
|
|
412
|
+
|
|
413
|
+
import { revalidatePath } from 'next/cache';
|
|
414
|
+
import type { CreateFeatureRequest } from '../types/[feature].types';
|
|
415
|
+
|
|
416
|
+
export async function createFeatureAction(data: CreateFeatureRequest) {
|
|
417
|
+
try {
|
|
418
|
+
const response = await fetch(`${process.env.API_URL}/features`, {
|
|
419
|
+
method: 'POST',
|
|
420
|
+
headers: {
|
|
421
|
+
'Content-Type': 'application/json',
|
|
422
|
+
},
|
|
423
|
+
body: JSON.stringify(data),
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
if (!response.ok) {
|
|
427
|
+
const error = await response.json();
|
|
428
|
+
return { success: false, error: error.message };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const feature = await response.json();
|
|
432
|
+
|
|
433
|
+
// Revalidate relevant paths
|
|
434
|
+
revalidatePath('/features');
|
|
435
|
+
revalidatePath(`/features/${feature.id}`);
|
|
436
|
+
|
|
437
|
+
return { success: true, data: feature };
|
|
438
|
+
} catch (error) {
|
|
439
|
+
console.error('Create feature action error:', error);
|
|
440
|
+
return { success: false, error: 'Failed to create feature' };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Step 9: Add Translations
|
|
446
|
+
|
|
447
|
+
Add translation keys to both English and German files.
|
|
448
|
+
|
|
449
|
+
**English:** `/src/locales/en.json`
|
|
450
|
+
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"Feature": {
|
|
454
|
+
"title": "Features",
|
|
455
|
+
"create": "Create Feature",
|
|
456
|
+
"edit": "Edit Feature",
|
|
457
|
+
"delete": "Delete Feature",
|
|
458
|
+
"name": "Name",
|
|
459
|
+
"description": "Description",
|
|
460
|
+
"createSuccess": "Feature created successfully",
|
|
461
|
+
"createError": "Failed to create feature",
|
|
462
|
+
"updateSuccess": "Feature updated successfully",
|
|
463
|
+
"updateError": "Failed to update feature",
|
|
464
|
+
"deleteSuccess": "Feature deleted successfully",
|
|
465
|
+
"deleteError": "Failed to delete feature"
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**German:** `/src/locales/de.json`
|
|
471
|
+
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"Feature": {
|
|
475
|
+
"title": "Funktionen",
|
|
476
|
+
"create": "Funktion erstellen",
|
|
477
|
+
"edit": "Funktion bearbeiten",
|
|
478
|
+
"delete": "Funktion löschen",
|
|
479
|
+
"name": "Name",
|
|
480
|
+
"description": "Beschreibung",
|
|
481
|
+
"createSuccess": "Funktion erfolgreich erstellt",
|
|
482
|
+
"createError": "Funktion konnte nicht erstellt werden",
|
|
483
|
+
"updateSuccess": "Funktion erfolgreich aktualisiert",
|
|
484
|
+
"updateError": "Funktion konnte nicht aktualisiert werden",
|
|
485
|
+
"deleteSuccess": "Funktion erfolgreich gelöscht",
|
|
486
|
+
"deleteError": "Funktion konnte nicht gelöscht werden"
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Step 10: Create Page Components
|
|
492
|
+
|
|
493
|
+
Create page components using the service module.
|
|
494
|
+
|
|
495
|
+
**File:** `src/app/[locale]/features/page.tsx`
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
'use client';
|
|
499
|
+
|
|
500
|
+
import { useTranslations } from 'next-intl';
|
|
501
|
+
import { Button } from '@/components/ui/Button';
|
|
502
|
+
import { useFeaturesQuery } from '@/services/feature';
|
|
503
|
+
import { FeatureList } from './_components/FeatureList';
|
|
504
|
+
|
|
505
|
+
export default function FeaturesPage() {
|
|
506
|
+
const t = useTranslations('Feature');
|
|
507
|
+
const { data: features, isLoading } = useFeaturesQuery();
|
|
508
|
+
|
|
509
|
+
return (
|
|
510
|
+
<div className="container mx-auto px-4 py-8">
|
|
511
|
+
<div className="flex items-center justify-between mb-6">
|
|
512
|
+
<h1 className="text-2xl font-bold">{t('title')}</h1>
|
|
513
|
+
<Button href="/features/new">{t('create')}</Button>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
{isLoading ? (
|
|
517
|
+
<div>Loading...</div>
|
|
518
|
+
) : (
|
|
519
|
+
<FeatureList features={features || []} />
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Step 11: Create Form Components
|
|
527
|
+
|
|
528
|
+
Create form components with validation.
|
|
529
|
+
|
|
530
|
+
**File:** `src/app/[locale]/features/_components/FeatureForm.tsx`
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
'use client';
|
|
534
|
+
|
|
535
|
+
import { useForm } from 'react-hook-form';
|
|
536
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
537
|
+
import { z } from 'zod';
|
|
538
|
+
import { useTranslations } from 'next-intl';
|
|
539
|
+
import { Form } from '@/components/ui/form/Form';
|
|
540
|
+
import { Input } from '@/components/ui/Input';
|
|
541
|
+
import { Textarea } from '@/components/ui/Textarea';
|
|
542
|
+
import { Button } from '@/components/ui/Button';
|
|
543
|
+
import { useCreateFeatureMutation } from '@/services/feature';
|
|
544
|
+
|
|
545
|
+
const schema = z.object({
|
|
546
|
+
name: z.string().min(1, 'Errors.validation.required'),
|
|
547
|
+
description: z.string().min(1, 'Errors.validation.required'),
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
type FormData = z.infer<typeof schema>;
|
|
551
|
+
|
|
552
|
+
export function FeatureForm() {
|
|
553
|
+
const t = useTranslations('Feature');
|
|
554
|
+
const createMutation = useCreateFeatureMutation();
|
|
555
|
+
|
|
556
|
+
const form = useForm<FormData>({
|
|
557
|
+
resolver: zodResolver(schema),
|
|
558
|
+
defaultValues: {
|
|
559
|
+
name: '',
|
|
560
|
+
description: '',
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const onSubmit = (data: FormData) => {
|
|
565
|
+
createMutation.mutate(data);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
return (
|
|
569
|
+
<Form form={form} validationSchema={schema}>
|
|
570
|
+
<Form.Item name="name">
|
|
571
|
+
{(props) => <Input {...props} label={t('name')} />}
|
|
572
|
+
</Form.Item>
|
|
573
|
+
|
|
574
|
+
<Form.Item name="description">
|
|
575
|
+
{(props) => <Textarea {...props} label={t('description')} />}
|
|
576
|
+
</Form.Item>
|
|
577
|
+
|
|
578
|
+
<div className="col-span-10 flex justify-end gap-4">
|
|
579
|
+
<Button variant="bordered" href="/features">
|
|
580
|
+
{t('cancel')}
|
|
581
|
+
</Button>
|
|
582
|
+
<Button
|
|
583
|
+
onPress={form.handleSubmit(onSubmit)}
|
|
584
|
+
isLoading={createMutation.isPending}
|
|
585
|
+
>
|
|
586
|
+
{t('create')}
|
|
587
|
+
</Button>
|
|
588
|
+
</div>
|
|
589
|
+
</Form>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Step 12: Create Element Components (If Needed)
|
|
595
|
+
|
|
596
|
+
Create reusable element components.
|
|
597
|
+
|
|
598
|
+
**File:** `src/components/elements/FeatureCard.tsx`
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
import { Card, CardBody, CardHeader } from '@heroui/react';
|
|
602
|
+
import { Button } from '@/components/ui/Button';
|
|
603
|
+
import type { Feature } from '@/services/feature';
|
|
604
|
+
|
|
605
|
+
interface FeatureCardProps {
|
|
606
|
+
feature: Feature;
|
|
607
|
+
onEdit?: (id: string) => void;
|
|
608
|
+
onDelete?: (id: string) => void;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function FeatureCard({ feature, onEdit, onDelete }: FeatureCardProps) {
|
|
612
|
+
return (
|
|
613
|
+
<Card>
|
|
614
|
+
<CardHeader>
|
|
615
|
+
<h3 className="text-lg font-semibold">{feature.name}</h3>
|
|
616
|
+
</CardHeader>
|
|
617
|
+
<CardBody>
|
|
618
|
+
<p className="text-default-600">{feature.description}</p>
|
|
619
|
+
<div className="flex gap-2 mt-4">
|
|
620
|
+
{onEdit && (
|
|
621
|
+
<Button size="sm" onPress={() => onEdit(feature.id)}>
|
|
622
|
+
Edit
|
|
623
|
+
</Button>
|
|
624
|
+
)}
|
|
625
|
+
{onDelete && (
|
|
626
|
+
<Button
|
|
627
|
+
size="sm"
|
|
628
|
+
variant="light"
|
|
629
|
+
color="danger"
|
|
630
|
+
onPress={() => onDelete(feature.id)}
|
|
631
|
+
>
|
|
632
|
+
Delete
|
|
633
|
+
</Button>
|
|
634
|
+
)}
|
|
635
|
+
</div>
|
|
636
|
+
</CardBody>
|
|
637
|
+
</Card>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## Quality Checklist
|
|
645
|
+
|
|
646
|
+
Before marking implementation as complete:
|
|
647
|
+
|
|
648
|
+
### Code Quality
|
|
649
|
+
|
|
650
|
+
- [ ] TypeScript types are defined for all data
|
|
651
|
+
- [ ] No `any` types used
|
|
652
|
+
- [ ] ESLint shows no errors
|
|
653
|
+
- [ ] Code follows existing patterns
|
|
654
|
+
- [ ] Functions have JSDoc comments
|
|
655
|
+
- [ ] Complex logic is documented
|
|
656
|
+
|
|
657
|
+
### Functionality
|
|
658
|
+
|
|
659
|
+
- [ ] All CRUD operations work correctly
|
|
660
|
+
- [ ] Form validation works as expected
|
|
661
|
+
- [ ] Error handling is implemented
|
|
662
|
+
- [ ] Loading states are shown
|
|
663
|
+
- [ ] Success/error messages are displayed
|
|
664
|
+
- [ ] Data refreshes after mutations
|
|
665
|
+
|
|
666
|
+
### Translations
|
|
667
|
+
|
|
668
|
+
- [ ] All strings use translations
|
|
669
|
+
- [ ] English translations added
|
|
670
|
+
- [ ] German translations added
|
|
671
|
+
- [ ] Translation keys are organized
|
|
672
|
+
- [ ] No hardcoded text
|
|
673
|
+
|
|
674
|
+
### UI/UX
|
|
675
|
+
|
|
676
|
+
- [ ] Responsive design works on all screens
|
|
677
|
+
- [ ] Components use existing UI library
|
|
678
|
+
- [ ] Styling follows design system
|
|
679
|
+
- [ ] Accessibility considerations met
|
|
680
|
+
- [ ] Loading states are user-friendly
|
|
681
|
+
- [ ] Error states are clear
|
|
682
|
+
|
|
683
|
+
### Performance
|
|
684
|
+
|
|
685
|
+
- [ ] React Query cache configured correctly
|
|
686
|
+
- [ ] Unnecessary re-renders avoided
|
|
687
|
+
- [ ] Images are optimized
|
|
688
|
+
- [ ] Data fetching is efficient
|
|
689
|
+
|
|
690
|
+
### Testing
|
|
691
|
+
|
|
692
|
+
- [ ] Manual testing completed
|
|
693
|
+
- [ ] Edge cases tested
|
|
694
|
+
- [ ] Error scenarios tested
|
|
695
|
+
- [ ] Browser compatibility checked
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## Troubleshooting
|
|
700
|
+
|
|
701
|
+
### Common Issues and Solutions
|
|
702
|
+
|
|
703
|
+
#### Issue: Form validation not working
|
|
704
|
+
|
|
705
|
+
**Solution:**
|
|
706
|
+
- Ensure `resolver: zodResolver(schema)` is in `useForm`
|
|
707
|
+
- Verify Zod schema is correct
|
|
708
|
+
- Check that field names match schema keys
|
|
709
|
+
- Ensure `validationSchema` prop is passed to `<Form>`
|
|
710
|
+
|
|
711
|
+
#### Issue: React Query not refetching
|
|
712
|
+
|
|
713
|
+
**Solution:**
|
|
714
|
+
- Check `queryKey` is unique and consistent
|
|
715
|
+
- Verify `invalidateQueries` is called after mutations
|
|
716
|
+
- Check `staleTime` configuration
|
|
717
|
+
- Ensure `enabled` condition is correct
|
|
718
|
+
|
|
719
|
+
#### Issue: Translations not showing
|
|
720
|
+
|
|
721
|
+
**Solution:**
|
|
722
|
+
- Verify translation keys exist in both `en.json` and `de.json`
|
|
723
|
+
- Check namespace matches `useTranslations('Namespace')`
|
|
724
|
+
- Ensure JSON syntax is valid
|
|
725
|
+
- Restart dev server after changing translations
|
|
726
|
+
|
|
727
|
+
#### Issue: TypeScript errors
|
|
728
|
+
|
|
729
|
+
**Solution:**
|
|
730
|
+
- Run `pnpm check:types` to see all errors
|
|
731
|
+
- Ensure types are imported correctly
|
|
732
|
+
- Check that API response types match actual API
|
|
733
|
+
- Verify generic types in React Query hooks
|
|
734
|
+
|
|
735
|
+
#### Issue: Components not updating
|
|
736
|
+
|
|
737
|
+
**Solution:**
|
|
738
|
+
- Check if component is wrapped in 'use client'
|
|
739
|
+
- Verify state updates are triggering re-renders
|
|
740
|
+
- Check React Query cache invalidation
|
|
741
|
+
- Ensure Zustand selectors are correct
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Complete Example
|
|
746
|
+
|
|
747
|
+
Here's a complete example implementing a "Tasks" feature:
|
|
748
|
+
|
|
749
|
+
### 1. Types
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
// src/services/tasks/types/tasks.types.ts
|
|
753
|
+
export interface TaskApiResponse {
|
|
754
|
+
id: string;
|
|
755
|
+
title: string;
|
|
756
|
+
description: string;
|
|
757
|
+
completed: boolean;
|
|
758
|
+
created_at: string;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export interface Task {
|
|
762
|
+
id: string;
|
|
763
|
+
title: string;
|
|
764
|
+
description: string;
|
|
765
|
+
completed: boolean;
|
|
766
|
+
createdAt: Date;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export interface CreateTaskRequest {
|
|
770
|
+
title: string;
|
|
771
|
+
description: string;
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### 2. API Client
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
// src/services/tasks/api/tasks.api.ts
|
|
779
|
+
import { apiRequest } from '@/lib/apiRequest';
|
|
780
|
+
import type { Task, TaskApiResponse, CreateTaskRequest } from '../types/tasks.types';
|
|
781
|
+
|
|
782
|
+
export async function getTasks(): Promise<Task[]> {
|
|
783
|
+
const response = await apiRequest<TaskApiResponse[]>({
|
|
784
|
+
url: '/api/tasks',
|
|
785
|
+
method: 'GET',
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
return response.map(task => ({
|
|
789
|
+
...task,
|
|
790
|
+
createdAt: new Date(task.created_at),
|
|
791
|
+
}));
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export async function createTask(data: CreateTaskRequest): Promise<Task> {
|
|
795
|
+
const response = await apiRequest<TaskApiResponse>({
|
|
796
|
+
url: '/api/tasks',
|
|
797
|
+
method: 'POST',
|
|
798
|
+
data,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
return {
|
|
802
|
+
...response,
|
|
803
|
+
createdAt: new Date(response.created_at),
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### 3. Queries
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
// src/services/tasks/queries/tasks.queries.ts
|
|
812
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
813
|
+
import { toast } from 'sonner';
|
|
814
|
+
import { getTasks, createTask } from '../api/tasks.api';
|
|
815
|
+
|
|
816
|
+
export const taskKeys = {
|
|
817
|
+
all: ['tasks'] as const,
|
|
818
|
+
lists: () => [...taskKeys.all, 'list'] as const,
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
export function useTasksQuery() {
|
|
822
|
+
return useQuery({
|
|
823
|
+
queryKey: taskKeys.lists(),
|
|
824
|
+
queryFn: getTasks,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
export function useCreateTaskMutation() {
|
|
829
|
+
const queryClient = useQueryClient();
|
|
830
|
+
|
|
831
|
+
return useMutation({
|
|
832
|
+
mutationFn: createTask,
|
|
833
|
+
onSuccess: () => {
|
|
834
|
+
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
|
|
835
|
+
toast.success('Task created');
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
### 4. Component
|
|
842
|
+
|
|
843
|
+
```tsx
|
|
844
|
+
// src/app/[locale]/tasks/page.tsx
|
|
845
|
+
'use client';
|
|
846
|
+
|
|
847
|
+
import { useTasksQuery, useCreateTaskMutation } from '@/services/tasks';
|
|
848
|
+
|
|
849
|
+
export default function TasksPage() {
|
|
850
|
+
const { data: tasks, isLoading } = useTasksQuery();
|
|
851
|
+
const createMutation = useCreateTaskMutation();
|
|
852
|
+
|
|
853
|
+
const handleCreate = () => {
|
|
854
|
+
createMutation.mutate({
|
|
855
|
+
title: 'New Task',
|
|
856
|
+
description: 'Task description',
|
|
857
|
+
});
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
if (isLoading) return <div>Loading...</div>;
|
|
861
|
+
|
|
862
|
+
return (
|
|
863
|
+
<div>
|
|
864
|
+
<h1>Tasks</h1>
|
|
865
|
+
<button onClick={handleCreate}>Create Task</button>
|
|
866
|
+
<ul>
|
|
867
|
+
{tasks?.map(task => (
|
|
868
|
+
<li key={task.id}>{task.title}</li>
|
|
869
|
+
))}
|
|
870
|
+
</ul>
|
|
871
|
+
</div>
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
---
|
|
877
|
+
|
|
878
|
+
## Summary
|
|
879
|
+
|
|
880
|
+
Follow this implementation guide to:
|
|
881
|
+
|
|
882
|
+
1. **Maintain consistency** across the codebase
|
|
883
|
+
2. **Ensure quality** through checklists
|
|
884
|
+
3. **Avoid common pitfalls** with troubleshooting guide
|
|
885
|
+
4. **Speed up development** with complete examples
|
|
886
|
+
|
|
887
|
+
## Need More Information?
|
|
888
|
+
|
|
889
|
+
- **Quick Reference** → See `AI_QUICK_REFERENCE.md`
|
|
890
|
+
- **UI Components** → See `UI_COMPONENTS_GUIDE.md`
|
|
891
|
+
- **Architecture** → See `ARCHITECTURE_PATTERNS.md`
|
|
892
|
+
- **Figma to Code** → See `FIGMA_TO_CODE_GUIDE.md`
|