ginskill-init 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 +77 -0
- package/agents/developer.md +56 -0
- package/agents/frontend-design.md +69 -0
- package/agents/mobile-reviewer.md +36 -0
- package/agents/review-code.md +49 -0
- package/agents/security-scanner.md +50 -0
- package/agents/tester.md +72 -0
- package/bin/cli.js +226 -0
- package/package.json +20 -0
- package/skills/ai-asset-generator/SKILL.md +255 -0
- package/skills/ai-asset-generator/docs/gen-image.md +274 -0
- package/skills/ai-asset-generator/docs/genvideo.md +341 -0
- package/skills/ai-asset-generator/docs/remove-background.md +19 -0
- package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
- package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
- package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
- package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
- package/skills/ai-asset-generator/lib/env.mjs +38 -0
- package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
- package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
- package/skills/ai-build-ai/SKILL.md +124 -0
- package/skills/ai-build-ai/docs/agent-teams.md +293 -0
- package/skills/ai-build-ai/docs/checkpointing.md +161 -0
- package/skills/ai-build-ai/docs/create-agent.md +399 -0
- package/skills/ai-build-ai/docs/create-mcp.md +395 -0
- package/skills/ai-build-ai/docs/create-skill.md +299 -0
- package/skills/ai-build-ai/docs/headless-mode.md +614 -0
- package/skills/ai-build-ai/docs/hooks.md +578 -0
- package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
- package/skills/ai-build-ai/docs/output-styles.md +208 -0
- package/skills/ai-build-ai/docs/overview.md +162 -0
- package/skills/ai-build-ai/docs/permissions.md +391 -0
- package/skills/ai-build-ai/docs/plugins.md +396 -0
- package/skills/ai-build-ai/docs/sandbox.md +262 -0
- package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
- package/skills/icon-generator/SKILL.md +270 -0
- package/skills/mobile-app-review/SKILL.md +321 -0
- package/skills/mobile-app-review/references/apple-review.md +132 -0
- package/skills/mobile-app-review/references/google-play-review.md +203 -0
- package/skills/mongodb/SKILL.md +667 -0
- package/skills/mongodb/references/mongoose-patterns.md +368 -0
- package/skills/nestjs-architecture/SKILL.md +1086 -0
- package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
- package/skills/performance/SKILL.md +509 -0
- package/skills/react-fsd-architecture/SKILL.md +693 -0
- package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
- package/skills/react-query/SKILL.md +685 -0
- package/skills/react-query/references/query-patterns.md +365 -0
- package/skills/review-code/SKILL.md +321 -0
- package/skills/review-code/references/clean-code-principles.md +395 -0
- package/skills/review-code/references/frontend-patterns.md +136 -0
- package/skills/review-code/references/nestjs-patterns.md +184 -0
- package/skills/review-code/scripts/check-module.sh +201 -0
- package/skills/review-code/scripts/deep-scan.sh +604 -0
- package/skills/review-code/scripts/dep-check.sh +522 -0
- package/skills/review-code/scripts/detect-duplicates.sh +466 -0
- package/skills/review-code/scripts/format-check.sh +577 -0
- package/skills/review-code/scripts/run-review.sh +167 -0
- package/skills/review-code/scripts/scan-codebase.sh +152 -0
- package/skills/security-scanner/SKILL.md +327 -0
- package/skills/security-scanner/references/nestjs-security.md +260 -0
- package/skills/security-scanner/references/nextjs-security.md +201 -0
- package/skills/security-scanner/references/react-native-security.md +199 -0
- package/skills/security-scanner/scripts/security-scan.sh +478 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Clean Code Principles — Review Reference
|
|
2
|
+
|
|
3
|
+
Practical guide for reviewing code against clean code, SOLID, and design principles. All examples use TypeScript in a NestJS/Next.js context.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [SOLID Principles](#solid-principles)
|
|
7
|
+
2. [DRY, KISS, YAGNI](#dry-kiss-yagni)
|
|
8
|
+
3. [Code Smells Catalog](#code-smells-catalog)
|
|
9
|
+
4. [Anti-Patterns](#anti-patterns)
|
|
10
|
+
5. [Refactoring Recipes](#refactoring-recipes)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## SOLID Principles
|
|
15
|
+
|
|
16
|
+
### S — Single Responsibility Principle (SRP)
|
|
17
|
+
|
|
18
|
+
**Rule:** A class should have only one reason to change.
|
|
19
|
+
|
|
20
|
+
Bad — service does too much:
|
|
21
|
+
```typescript
|
|
22
|
+
@Injectable()
|
|
23
|
+
export class OrderService {
|
|
24
|
+
async createOrder(dto: CreateOrderDto, user: User) { /* ... */ }
|
|
25
|
+
async sendOrderEmail(order: Order) { /* ... */ } // ← email is not order logic
|
|
26
|
+
async generateInvoicePdf(order: Order) { /* ... */ } // ← PDF generation is not order logic
|
|
27
|
+
async syncToAnalytics(order: Order) { /* ... */ } // ← analytics is not order logic
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Good — each class has one job:
|
|
32
|
+
```typescript
|
|
33
|
+
@Injectable()
|
|
34
|
+
export class OrderService {
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly emailService: EmailService,
|
|
37
|
+
private readonly invoiceService: InvoiceService,
|
|
38
|
+
private readonly eventEmitter: EventEmitter2,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
async createOrder(dto: CreateOrderDto, user: User) {
|
|
42
|
+
const order = await this.orderModel.create({ ...dto, userId: user._id });
|
|
43
|
+
this.eventEmitter.emit('order.created', order); // Other services react to events
|
|
44
|
+
return order;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Review checklist:**
|
|
50
|
+
- Service has >10 public methods → likely violates SRP, split by domain concern
|
|
51
|
+
- Service injects >5 dependencies → may be doing too much
|
|
52
|
+
- Controller has business logic beyond validation + delegation → move to service
|
|
53
|
+
- A change in one feature requires editing this class → it has multiple responsibilities
|
|
54
|
+
|
|
55
|
+
### O — Open/Closed Principle (OCP)
|
|
56
|
+
|
|
57
|
+
**Rule:** Open for extension, closed for modification.
|
|
58
|
+
|
|
59
|
+
Bad — modifying existing code for each new case:
|
|
60
|
+
```typescript
|
|
61
|
+
@Injectable()
|
|
62
|
+
export class NotificationService {
|
|
63
|
+
async send(type: string, data: any) {
|
|
64
|
+
if (type === 'email') { /* send email */ }
|
|
65
|
+
else if (type === 'sms') { /* send SMS */ }
|
|
66
|
+
else if (type === 'push') { /* send push */ }
|
|
67
|
+
// Adding Slack? Must modify this class.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Good — extend through new implementations:
|
|
73
|
+
```typescript
|
|
74
|
+
interface NotificationChannel {
|
|
75
|
+
send(data: NotificationPayload): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Injectable()
|
|
79
|
+
export class EmailChannel implements NotificationChannel { /* ... */ }
|
|
80
|
+
@Injectable()
|
|
81
|
+
export class SmsChannel implements NotificationChannel { /* ... */ }
|
|
82
|
+
@Injectable()
|
|
83
|
+
export class SlackChannel implements NotificationChannel { /* ... */ }
|
|
84
|
+
|
|
85
|
+
@Injectable()
|
|
86
|
+
export class NotificationService {
|
|
87
|
+
constructor(
|
|
88
|
+
@Inject('NOTIFICATION_CHANNELS') private channels: NotificationChannel[],
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
async send(channelType: string, data: NotificationPayload) {
|
|
92
|
+
const channel = this.channels.find(c => c.type === channelType);
|
|
93
|
+
await channel.send(data);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Review checklist:**
|
|
99
|
+
- Growing if/else or switch chains for variants → use strategy/registry pattern
|
|
100
|
+
- Adding a new feature type requires editing existing service → violates OCP
|
|
101
|
+
- Configuration-driven behavior is hardcoded → extract to config or plugins
|
|
102
|
+
|
|
103
|
+
### L — Liskov Substitution Principle (LSP)
|
|
104
|
+
|
|
105
|
+
**Rule:** Subtypes must be substitutable for their base types without breaking behavior.
|
|
106
|
+
|
|
107
|
+
Bad — subclass changes expected behavior:
|
|
108
|
+
```typescript
|
|
109
|
+
class ReadOnlyRepository<T> {
|
|
110
|
+
async findById(id: string): Promise<T> { /* ... */ }
|
|
111
|
+
async save(entity: T): Promise<T> { throw new Error('Read only!'); } // ← breaks contract
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Good — separate interfaces for different capabilities:
|
|
116
|
+
```typescript
|
|
117
|
+
interface Readable<T> {
|
|
118
|
+
findById(id: string): Promise<T>;
|
|
119
|
+
}
|
|
120
|
+
interface Writable<T> {
|
|
121
|
+
save(entity: T): Promise<T>;
|
|
122
|
+
}
|
|
123
|
+
interface Repository<T> extends Readable<T>, Writable<T> {}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Review checklist:**
|
|
127
|
+
- Overridden methods that throw "not supported" → violates LSP
|
|
128
|
+
- Subclasses that ignore or nullify parent behavior → redesign the hierarchy
|
|
129
|
+
- Type assertions needed after using a base type → polymorphism is broken
|
|
130
|
+
|
|
131
|
+
### I — Interface Segregation Principle (ISP)
|
|
132
|
+
|
|
133
|
+
**Rule:** Don't force implementations to depend on methods they don't use.
|
|
134
|
+
|
|
135
|
+
Bad — fat interface:
|
|
136
|
+
```typescript
|
|
137
|
+
interface UserService {
|
|
138
|
+
findById(id: string): Promise<User>;
|
|
139
|
+
createUser(dto: CreateUserDto): Promise<User>;
|
|
140
|
+
deleteUser(id: string): Promise<void>;
|
|
141
|
+
sendWelcomeEmail(user: User): Promise<void>; // ← not every consumer needs this
|
|
142
|
+
generateReport(userId: string): Promise<Buffer>; // ← not every consumer needs this
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Good — focused interfaces:
|
|
147
|
+
```typescript
|
|
148
|
+
interface UserReader {
|
|
149
|
+
findById(id: string): Promise<User>;
|
|
150
|
+
}
|
|
151
|
+
interface UserWriter {
|
|
152
|
+
createUser(dto: CreateUserDto): Promise<User>;
|
|
153
|
+
deleteUser(id: string): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Review checklist:**
|
|
158
|
+
- Interface has methods that some implementations leave empty → split it
|
|
159
|
+
- Classes implement interfaces where half the methods are no-ops → ISP violation
|
|
160
|
+
- Consumers import a service but only use 1-2 of its 10+ methods → interface is too broad
|
|
161
|
+
|
|
162
|
+
### D — Dependency Inversion Principle (DIP)
|
|
163
|
+
|
|
164
|
+
**Rule:** Depend on abstractions, not concretions.
|
|
165
|
+
|
|
166
|
+
Bad — direct dependency on implementation:
|
|
167
|
+
```typescript
|
|
168
|
+
@Injectable()
|
|
169
|
+
export class OrderService {
|
|
170
|
+
private stripe = new Stripe(process.env.STRIPE_KEY); // ← concrete dependency
|
|
171
|
+
|
|
172
|
+
async charge(amount: number) {
|
|
173
|
+
return this.stripe.charges.create({ amount });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Good — depend on abstraction via NestJS DI:
|
|
179
|
+
```typescript
|
|
180
|
+
interface PaymentProvider {
|
|
181
|
+
charge(amount: number, currency: string): Promise<PaymentResult>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@Injectable()
|
|
185
|
+
export class StripeProvider implements PaymentProvider { /* ... */ }
|
|
186
|
+
|
|
187
|
+
@Injectable()
|
|
188
|
+
export class OrderService {
|
|
189
|
+
constructor(
|
|
190
|
+
@Inject('PAYMENT_PROVIDER') private paymentProvider: PaymentProvider,
|
|
191
|
+
) {}
|
|
192
|
+
|
|
193
|
+
async charge(amount: number) {
|
|
194
|
+
return this.paymentProvider.charge(amount, 'usd');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Review checklist:**
|
|
200
|
+
- `new ExternalService()` inside business logic → inject it instead
|
|
201
|
+
- Direct SDK imports in services (aws-sdk, stripe, etc.) → wrap in abstraction
|
|
202
|
+
- High-level modules importing from low-level modules → invert the dependency
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## DRY, KISS, YAGNI
|
|
207
|
+
|
|
208
|
+
### DRY — Don't Repeat Yourself
|
|
209
|
+
|
|
210
|
+
**Rule:** Every piece of knowledge has a single, unambiguous representation.
|
|
211
|
+
|
|
212
|
+
Common DRY violations in our stack:
|
|
213
|
+
|
|
214
|
+
| Violation | Fix |
|
|
215
|
+
|---|---|
|
|
216
|
+
| Same validation logic in DTO and frontend Zod schema | Share validation via a common schema package or derive one from the other |
|
|
217
|
+
| Identical error handling in every controller method | Use NestJS exception filters |
|
|
218
|
+
| Copy-pasted Mongoose query patterns across services | Extract to a base repository or shared query builder |
|
|
219
|
+
| Same API response shape defined in multiple DTOs | Create a base DTO and extend |
|
|
220
|
+
| Repeated auth check logic | Use guards and decorators |
|
|
221
|
+
|
|
222
|
+
**But beware of wrong DRY:** Don't merge things that happen to look similar but serve different purposes. Two functions with identical code TODAY might evolve differently. Ask: "If one changes, must the other change too?" If no, they aren't true duplicates.
|
|
223
|
+
|
|
224
|
+
### KISS — Keep It Simple, Stupid
|
|
225
|
+
|
|
226
|
+
**Rule:** The simplest solution that works correctly is the best solution.
|
|
227
|
+
|
|
228
|
+
Red flags for over-engineering:
|
|
229
|
+
- Abstract factory pattern for 2 variants → just use a simple if/else
|
|
230
|
+
- Generic base class with 5 type parameters used by 1 subclass → remove the generics
|
|
231
|
+
- Custom event bus when NestJS EventEmitter2 works fine
|
|
232
|
+
- Hand-rolled caching layer when `@nestjs/cache-manager` exists
|
|
233
|
+
- Complex class hierarchy when composition would be simpler
|
|
234
|
+
|
|
235
|
+
**Review question:** "Could a mid-level developer understand this code in 5 minutes?" If not, it might be too complex.
|
|
236
|
+
|
|
237
|
+
### YAGNI — You Ain't Gonna Need It
|
|
238
|
+
|
|
239
|
+
**Rule:** Don't build it until you actually need it.
|
|
240
|
+
|
|
241
|
+
Red flags:
|
|
242
|
+
- Unused exported functions, types, or interfaces
|
|
243
|
+
- Configuration options that are never set to anything other than the default
|
|
244
|
+
- Abstract base classes with only one concrete implementation
|
|
245
|
+
- Plugin/extension architecture with zero plugins
|
|
246
|
+
- Feature flags for features that shipped months ago
|
|
247
|
+
- "Flexible" data structures that only store one type of data
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Code Smells Catalog
|
|
252
|
+
|
|
253
|
+
Quick-reference for the most common code smells in our stack:
|
|
254
|
+
|
|
255
|
+
### Bloaters (too big)
|
|
256
|
+
| Smell | Threshold | Action |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| Long method | >30 lines | Extract methods by responsibility |
|
|
259
|
+
| Large class | >200 lines or >10 public methods | Split into focused classes |
|
|
260
|
+
| Long parameter list | >3 params | Use options object / DTO |
|
|
261
|
+
| Primitive obsession | Strings/numbers for domain concepts | Create value objects or enums |
|
|
262
|
+
| Data clumps | Same group of params passed together | Extract to a class/interface |
|
|
263
|
+
|
|
264
|
+
### Object-Orientation Abusers
|
|
265
|
+
| Smell | Sign | Action |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| Switch/if chains on type | `if (type === 'A') ... else if (type === 'B')` | Use polymorphism or strategy pattern |
|
|
268
|
+
| Refused bequest | Subclass doesn't use inherited methods | Rethink hierarchy, prefer composition |
|
|
269
|
+
| Temporary field | Fields only set in some scenarios | Extract to separate class or use Optional |
|
|
270
|
+
|
|
271
|
+
### Change Preventers
|
|
272
|
+
| Smell | Sign | Action |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| Divergent change | One class changed for many different reasons | Split by SRP |
|
|
275
|
+
| Shotgun surgery | One change touches many files | Consolidate related logic |
|
|
276
|
+
| Parallel inheritance | Adding a subclass requires adding sibling subclass | Merge hierarchies or use composition |
|
|
277
|
+
|
|
278
|
+
### Dispensables (remove them)
|
|
279
|
+
| Smell | Sign | Action |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| Dead code | Unreachable or uncalled code | Delete it (git has history) |
|
|
282
|
+
| Speculative generality | Unused abstractions "for the future" | Delete until needed (YAGNI) |
|
|
283
|
+
| Duplicate code | Same logic in multiple places | Extract to shared utility |
|
|
284
|
+
| Lazy class | Class that does too little | Inline it into its caller |
|
|
285
|
+
| Comments explaining bad code | `// This increments i by 1` | Rewrite the code to be clear |
|
|
286
|
+
|
|
287
|
+
### Couplers (too connected)
|
|
288
|
+
| Smell | Sign | Action |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| Feature envy | Method uses another class's data more than its own | Move method to that class |
|
|
291
|
+
| Inappropriate intimacy | Classes access each other's private details | Establish proper public interfaces |
|
|
292
|
+
| Message chains | `a.getB().getC().getD()` | Provide a direct method |
|
|
293
|
+
| Middle man | Class delegates everything without adding value | Remove and call directly |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Anti-Patterns
|
|
298
|
+
|
|
299
|
+
### Backend Anti-Patterns (NestJS)
|
|
300
|
+
|
|
301
|
+
**Fat Controller:**
|
|
302
|
+
```typescript
|
|
303
|
+
// BAD: Controller with business logic
|
|
304
|
+
@Post()
|
|
305
|
+
async create(@Body() dto: CreateItemDto) {
|
|
306
|
+
const exists = await this.itemModel.findOne({ name: dto.name }); // ← DB access in controller
|
|
307
|
+
if (exists) throw new ConflictException();
|
|
308
|
+
const item = await this.itemModel.create(dto);
|
|
309
|
+
await this.emailService.send(item.userId, 'Item created'); // ← side effects in controller
|
|
310
|
+
return item;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Service Locator (anti-DI):**
|
|
315
|
+
```typescript
|
|
316
|
+
// BAD: Fetching services manually instead of injecting
|
|
317
|
+
@Injectable()
|
|
318
|
+
export class OrderService {
|
|
319
|
+
async process(orderId: string) {
|
|
320
|
+
const emailService = this.moduleRef.get(EmailService); // ← defeats DI, untestable
|
|
321
|
+
await emailService.send(...);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Catch and Ignore:**
|
|
327
|
+
```typescript
|
|
328
|
+
// BAD: Swallowing errors
|
|
329
|
+
try {
|
|
330
|
+
await this.paymentService.charge(order);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
console.log(e); // ← error logged but not handled, order proceeds as if payment succeeded
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Stringly Typed:**
|
|
337
|
+
```typescript
|
|
338
|
+
// BAD: Using strings where enums/types belong
|
|
339
|
+
async updateStatus(id: string, status: string) { /* ... */ }
|
|
340
|
+
// GOOD:
|
|
341
|
+
async updateStatus(id: string, status: OrderStatus) { /* ... */ }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Frontend Anti-Patterns (Next.js / React)
|
|
345
|
+
|
|
346
|
+
**Prop Drilling:**
|
|
347
|
+
```typescript
|
|
348
|
+
// BAD: Passing props through 4+ levels of components
|
|
349
|
+
<App user={user}>
|
|
350
|
+
<Layout user={user}>
|
|
351
|
+
<Sidebar user={user}>
|
|
352
|
+
<UserAvatar user={user} /> // ← just use context or Zustand
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**useEffect for Everything:**
|
|
356
|
+
```typescript
|
|
357
|
+
// BAD: Derived state in useEffect
|
|
358
|
+
const [fullName, setFullName] = useState('');
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
setFullName(`${firstName} ${lastName}`); // ← just compute it
|
|
361
|
+
}, [firstName, lastName]);
|
|
362
|
+
|
|
363
|
+
// GOOD: Compute directly
|
|
364
|
+
const fullName = `${firstName} ${lastName}`;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**God Component:**
|
|
368
|
+
```typescript
|
|
369
|
+
// BAD: 500-line component with mixed concerns
|
|
370
|
+
export function Dashboard() {
|
|
371
|
+
// 20 state variables
|
|
372
|
+
// 10 useEffects
|
|
373
|
+
// 15 handler functions
|
|
374
|
+
// 300 lines of JSX
|
|
375
|
+
}
|
|
376
|
+
// GOOD: Split into focused components with custom hooks
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Refactoring Recipes
|
|
382
|
+
|
|
383
|
+
Common refactoring patterns to suggest during reviews:
|
|
384
|
+
|
|
385
|
+
| Current Code | Refactoring | Result |
|
|
386
|
+
|---|---|---|
|
|
387
|
+
| Long method with comment sections | **Extract Method** | Small, named methods that self-document |
|
|
388
|
+
| Repeated if/else on type | **Replace Conditional with Polymorphism** | Strategy pattern or class hierarchy |
|
|
389
|
+
| Multiple params always passed together | **Introduce Parameter Object** | DTO or interface |
|
|
390
|
+
| Nested callbacks or promise chains | **Replace with async/await** | Flat, readable async code |
|
|
391
|
+
| Boolean method params | **Replace Parameter with Explicit Methods** | `publish()` and `saveDraft()` instead of `save(isPublished)` |
|
|
392
|
+
| Raw string/number domain values | **Replace Primitive with Value Object** | `OrderId`, `Money`, `Email` types |
|
|
393
|
+
| God service | **Extract Class** | Multiple focused services |
|
|
394
|
+
| Copy-paste with small variations | **Extract with Parameters** | Shared function with config |
|
|
395
|
+
| Manual resource cleanup | **Use try/finally or disposable** | Guaranteed cleanup |
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Frontend Patterns — Next.js Review Reference
|
|
2
|
+
|
|
3
|
+
Quick reference for reviewing Next.js frontend code.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [App Router Structure](#app-router-structure)
|
|
7
|
+
2. [Component Patterns](#component-patterns)
|
|
8
|
+
3. [State Management](#state-management)
|
|
9
|
+
4. [Data Fetching](#data-fetching)
|
|
10
|
+
5. [Form Handling](#form-handling)
|
|
11
|
+
6. [Styling](#styling)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## App Router Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/app/
|
|
19
|
+
├── api/ # Next.js API routes (server functions)
|
|
20
|
+
├── (public)/ # Public pages (landing, marketing)
|
|
21
|
+
├── (dashboard)/ # Authenticated pages
|
|
22
|
+
├── layout.tsx # Root layout
|
|
23
|
+
└── page.tsx # Home page
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Review checklist:
|
|
27
|
+
- Route groups should have their own layouts
|
|
28
|
+
- Authenticated routes should check session in layout/middleware
|
|
29
|
+
- API routes should validate inputs and handle errors
|
|
30
|
+
|
|
31
|
+
## Component Patterns
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
src/
|
|
35
|
+
├── shared/components/ # Reusable UI (buttons, modals, cards)
|
|
36
|
+
├── features/ # Feature-specific components
|
|
37
|
+
├── components/ # Global components (navbar, sidebar)
|
|
38
|
+
└── entities/ # Domain model components
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Review checklist:
|
|
42
|
+
- Use `"use client"` only where React hooks or browser APIs are needed
|
|
43
|
+
- Prefer server components for data fetching and static content
|
|
44
|
+
- Keep components focused — one component, one responsibility (SRP). If a component does too many things, split it
|
|
45
|
+
- Props should be typed with TypeScript interfaces, not `any`
|
|
46
|
+
- Components should be <150 lines of JSX. Split larger ones into sub-components
|
|
47
|
+
- Extract reusable logic into custom hooks (DRY) — if 2+ components share the same state/effect pattern, make a hook
|
|
48
|
+
- Prefer composition over prop drilling — use context, Zustand, or render props for deeply shared state
|
|
49
|
+
- Avoid `useEffect` for derived state — compute values directly or use `useMemo`
|
|
50
|
+
- Name components and hooks descriptively: `useItemFilters` not `useData`, `OrderSummaryCard` not `Card2`
|
|
51
|
+
|
|
52
|
+
## State Management
|
|
53
|
+
|
|
54
|
+
**Zustand** for client-side state:
|
|
55
|
+
```typescript
|
|
56
|
+
import { create } from 'zustand';
|
|
57
|
+
|
|
58
|
+
interface ItemStore {
|
|
59
|
+
items: Item[];
|
|
60
|
+
addItem: (item: Item) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const useItemStore = create<ItemStore>((set) => ({
|
|
64
|
+
items: [],
|
|
65
|
+
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
|
|
66
|
+
}));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Review checklist:
|
|
70
|
+
- Stores should be small and focused (one per domain) — SRP for state (ISP)
|
|
71
|
+
- Avoid god-stores that manage everything — split by feature/concern
|
|
72
|
+
- Use selectors to prevent unnecessary rerenders
|
|
73
|
+
- Don't duplicate server state in Zustand — that's what React Query is for (single source of truth / DRY)
|
|
74
|
+
- Keep store actions simple — complex business logic belongs in services, not stores
|
|
75
|
+
- Avoid derived state in stores — compute it in components or with selectors (KISS)
|
|
76
|
+
|
|
77
|
+
## Data Fetching
|
|
78
|
+
|
|
79
|
+
**React Query** for server state:
|
|
80
|
+
```typescript
|
|
81
|
+
import { useQuery } from '@tanstack/react-query';
|
|
82
|
+
import { itemService } from '@/shared/services';
|
|
83
|
+
|
|
84
|
+
function useItems() {
|
|
85
|
+
return useQuery({
|
|
86
|
+
queryKey: ['items'],
|
|
87
|
+
queryFn: () => itemService.getAll(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Review checklist:
|
|
93
|
+
- Query keys should follow a consistent namespace: `['items']`, `['items', id]`, `['items', { filter }]` — use query key factories (DRY)
|
|
94
|
+
- API calls should go through the service layer, not directly in components (separation of concerns)
|
|
95
|
+
- Handle loading, error, and empty states — never show a blank screen
|
|
96
|
+
- Use `useMutation` for writes, not manual state updates
|
|
97
|
+
- Invalidate relevant queries after mutations
|
|
98
|
+
- Extract query hooks into dedicated files (e.g., `useItems.ts`) — don't inline `useQuery` calls throughout components (DRY)
|
|
99
|
+
- Set appropriate `staleTime` / `gcTime` — don't refetch data that rarely changes on every mount (KISS)
|
|
100
|
+
|
|
101
|
+
## Form Handling
|
|
102
|
+
|
|
103
|
+
**React Hook Form + Zod**:
|
|
104
|
+
```typescript
|
|
105
|
+
const schema = z.object({
|
|
106
|
+
name: z.string().min(1, 'Required'),
|
|
107
|
+
category: z.enum(['tops', 'bottoms', 'shoes']),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
type FormData = z.infer<typeof schema>;
|
|
111
|
+
|
|
112
|
+
function ItemForm() {
|
|
113
|
+
const { register, handleSubmit } = useForm<FormData>({
|
|
114
|
+
resolver: zodResolver(schema),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Review checklist:
|
|
120
|
+
- Use Zod schemas for validation (matches backend DTOs)
|
|
121
|
+
- Derive TypeScript types from Zod schemas with `z.infer`
|
|
122
|
+
- Handle form submission errors and show to user
|
|
123
|
+
- Disable submit button during loading
|
|
124
|
+
|
|
125
|
+
## Styling
|
|
126
|
+
|
|
127
|
+
**Tailwind CSS + shadcn/ui (Radix)**:
|
|
128
|
+
|
|
129
|
+
Review checklist:
|
|
130
|
+
- Use Tailwind utility classes, not custom CSS (unless truly necessary)
|
|
131
|
+
- shadcn/ui components should be used for standard UI elements — don't reinvent existing components (DRY)
|
|
132
|
+
- Responsive design: mobile-first (`sm:`, `md:`, `lg:` breakpoints)
|
|
133
|
+
- Dark mode: use `dark:` variants where appropriate
|
|
134
|
+
- Avoid inline styles
|
|
135
|
+
- Extract repeated class combinations into reusable components, not utility strings (KISS)
|
|
136
|
+
- Use design tokens / CSS variables for colors, spacing — don't hardcode hex values throughout (DRY, maintainability)
|