@smicolon/ai-kit 0.1.0 → 0.2.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/.claude-plugin/CLAUDE.md +7 -0
- package/.claude-plugin/marketplace.json +373 -0
- package/README.md +26 -16
- package/dist/index.js +146 -38
- package/package.json +4 -3
- package/packs/architect/CHANGELOG.md +17 -0
- package/packs/architect/README.md +58 -0
- package/packs/architect/agents/system-architect.md +768 -0
- package/packs/architect/commands/diagram-create.md +300 -0
- package/packs/better-auth/.claude-plugin/plugin.json +14 -0
- package/packs/better-auth/.mcp.json +14 -0
- package/packs/better-auth/CHANGELOG.md +26 -0
- package/packs/better-auth/README.md +125 -0
- package/packs/better-auth/agents/auth-architect.md +278 -0
- package/packs/better-auth/commands/auth-provider-add.md +265 -0
- package/packs/better-auth/commands/auth-setup.md +298 -0
- package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
- package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
- package/packs/dev-loop/CHANGELOG.md +69 -0
- package/packs/dev-loop/README.md +155 -0
- package/packs/dev-loop/commands/cancel-dev.md +21 -0
- package/packs/dev-loop/commands/dev-loop.md +72 -0
- package/packs/dev-loop/commands/dev-plan.md +351 -0
- package/packs/dev-loop/hooks/hooks.json +15 -0
- package/packs/dev-loop/hooks/stop-hook.sh +178 -0
- package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
- package/packs/django/CHANGELOG.md +39 -0
- package/packs/django/README.md +92 -0
- package/packs/django/agents/django-architect.md +182 -0
- package/packs/django/agents/django-builder.md +250 -0
- package/packs/django/agents/django-feature-based.md +420 -0
- package/packs/django/agents/django-reviewer.md +253 -0
- package/packs/django/agents/django-tester.md +230 -0
- package/packs/django/commands/api-endpoint.md +285 -0
- package/packs/django/commands/model-create.md +178 -0
- package/packs/django/commands/test-generate.md +325 -0
- package/packs/django/rules/migrations.md +138 -0
- package/packs/django/rules/models.md +167 -0
- package/packs/django/rules/serializers.md +126 -0
- package/packs/django/rules/services.md +131 -0
- package/packs/django/rules/tests.md +140 -0
- package/packs/django/rules/views.md +102 -0
- package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
- package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
- package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
- package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
- package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
- package/packs/django/skills/security-first-validator/SKILL.md +435 -0
- package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
- package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
- package/packs/failure-log/.claude-plugin/plugin.json +14 -0
- package/packs/failure-log/CHANGELOG.md +20 -0
- package/packs/failure-log/README.md +168 -0
- package/packs/failure-log/commands/failure-add.md +106 -0
- package/packs/failure-log/commands/failure-list.md +89 -0
- package/packs/failure-log/hooks/hooks.json +16 -0
- package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
- package/packs/flutter/.claude-plugin/plugin.json +10 -0
- package/packs/flutter/CHANGELOG.md +19 -0
- package/packs/flutter/README.md +170 -0
- package/packs/flutter/agents/flutter-architect.md +166 -0
- package/packs/flutter/agents/flutter-builder.md +303 -0
- package/packs/flutter/agents/release-manager.md +355 -0
- package/packs/flutter/commands/fastlane-setup.md +188 -0
- package/packs/flutter/commands/flutter-build.md +90 -0
- package/packs/flutter/commands/flutter-deploy.md +133 -0
- package/packs/flutter/commands/flutter-test.md +117 -0
- package/packs/flutter/commands/signing-setup.md +209 -0
- package/packs/flutter/hooks/hooks.json +17 -0
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
- package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
- package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
- package/packs/hono/.claude-plugin/plugin.json +19 -0
- package/packs/hono/CHANGELOG.md +19 -0
- package/packs/hono/README.md +143 -0
- package/packs/hono/agents/hono-architect.md +240 -0
- package/packs/hono/agents/hono-builder.md +285 -0
- package/packs/hono/agents/hono-reviewer.md +279 -0
- package/packs/hono/agents/hono-tester.md +346 -0
- package/packs/hono/commands/middleware-create.md +223 -0
- package/packs/hono/commands/project-init.md +306 -0
- package/packs/hono/commands/route-create.md +153 -0
- package/packs/hono/commands/rpc-client.md +263 -0
- package/packs/hono/hooks/hooks.json +4 -0
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
- package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
- package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
- package/packs/hono/skills/zod-validation/SKILL.md +332 -0
- package/packs/nestjs/CHANGELOG.md +29 -0
- package/packs/nestjs/README.md +75 -0
- package/packs/nestjs/agents/nestjs-architect.md +402 -0
- package/packs/nestjs/agents/nestjs-builder.md +301 -0
- package/packs/nestjs/agents/nestjs-tester.md +437 -0
- package/packs/nestjs/commands/module-create.md +369 -0
- package/packs/nestjs/rules/controllers.md +92 -0
- package/packs/nestjs/rules/dto.md +124 -0
- package/packs/nestjs/rules/entities.md +102 -0
- package/packs/nestjs/rules/services.md +106 -0
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
- package/packs/nextjs/CHANGELOG.md +36 -0
- package/packs/nextjs/README.md +76 -0
- package/packs/nextjs/agents/frontend-tester.md +680 -0
- package/packs/nextjs/agents/frontend-visual.md +820 -0
- package/packs/nextjs/agents/nextjs-architect.md +331 -0
- package/packs/nextjs/agents/nextjs-modular.md +433 -0
- package/packs/nextjs/commands/component-create.md +398 -0
- package/packs/nextjs/rules/api-routes.md +129 -0
- package/packs/nextjs/rules/components.md +106 -0
- package/packs/nextjs/rules/hooks.md +132 -0
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
- package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
- package/packs/nuxtjs/CHANGELOG.md +30 -0
- package/packs/nuxtjs/README.md +56 -0
- package/packs/nuxtjs/agents/frontend-tester.md +680 -0
- package/packs/nuxtjs/agents/frontend-visual.md +820 -0
- package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
- package/packs/nuxtjs/commands/component-create.md +223 -0
- package/packs/nuxtjs/rules/components.md +101 -0
- package/packs/nuxtjs/rules/composables.md +118 -0
- package/packs/nuxtjs/rules/server-routes.md +127 -0
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
- package/packs/onboard/CHANGELOG.md +22 -0
- package/packs/onboard/README.md +103 -0
- package/packs/onboard/agents/onboard-guide.md +118 -0
- package/packs/onboard/commands/onboard.md +313 -0
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
- package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
- package/packs/tanstack-router/CHANGELOG.md +30 -0
- package/packs/tanstack-router/README.md +113 -0
- package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
- package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
- package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
- package/packs/tanstack-router/commands/form-create.md +313 -0
- package/packs/tanstack-router/commands/query-create.md +263 -0
- package/packs/tanstack-router/commands/route-create.md +190 -0
- package/packs/tanstack-router/commands/table-create.md +413 -0
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
- package/packs/worktree/.claude-plugin/plugin.json +19 -0
- package/packs/worktree/CHANGELOG.md +24 -0
- package/packs/worktree/README.md +110 -0
- package/packs/worktree/commands/wt.md +73 -0
- package/packs/worktree/scripts/wt.sh +396 -0
- package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-create
|
|
3
|
+
description: Create a new Next.js/React component following Smicolon conventions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js Component Creation
|
|
7
|
+
|
|
8
|
+
You are a Next.js/React component creation specialist. Your task is to create components that strictly follow Smicolon company standards.
|
|
9
|
+
|
|
10
|
+
## Core Requirements
|
|
11
|
+
|
|
12
|
+
### Component Standards
|
|
13
|
+
- **TypeScript strict mode** (no `any` types)
|
|
14
|
+
- **Functional components** with hooks
|
|
15
|
+
- **Proper type definitions** for props
|
|
16
|
+
- **Accessibility** (WCAG 2.1 AA)
|
|
17
|
+
- **Responsive design** with Tailwind CSS
|
|
18
|
+
- **Error boundaries** where appropriate
|
|
19
|
+
|
|
20
|
+
### File Structure
|
|
21
|
+
```
|
|
22
|
+
src/components/
|
|
23
|
+
├── ui/ # Reusable UI components
|
|
24
|
+
│ ├── Button.tsx
|
|
25
|
+
│ ├── Input.tsx
|
|
26
|
+
│ └── Card.tsx
|
|
27
|
+
├── features/ # Feature-specific components
|
|
28
|
+
│ └── auth/
|
|
29
|
+
│ ├── LoginForm.tsx
|
|
30
|
+
│ └── SignupForm.tsx
|
|
31
|
+
└── layouts/ # Layout components
|
|
32
|
+
├── Header.tsx
|
|
33
|
+
└── Footer.tsx
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Component Types
|
|
37
|
+
|
|
38
|
+
### 1. UI Components (Reusable)
|
|
39
|
+
```typescript
|
|
40
|
+
// src/components/ui/Button.tsx
|
|
41
|
+
import { ButtonHTMLAttributes, forwardRef } from 'react'
|
|
42
|
+
import { cn } from '@/lib/utils'
|
|
43
|
+
|
|
44
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
45
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
|
|
46
|
+
size?: 'sm' | 'md' | 'lg'
|
|
47
|
+
isLoading?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
51
|
+
(
|
|
52
|
+
{
|
|
53
|
+
className,
|
|
54
|
+
variant = 'primary',
|
|
55
|
+
size = 'md',
|
|
56
|
+
isLoading = false,
|
|
57
|
+
disabled,
|
|
58
|
+
children,
|
|
59
|
+
...props
|
|
60
|
+
},
|
|
61
|
+
ref
|
|
62
|
+
) => {
|
|
63
|
+
const baseStyles = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'
|
|
64
|
+
|
|
65
|
+
const variants = {
|
|
66
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-600',
|
|
67
|
+
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus-visible:ring-gray-500',
|
|
68
|
+
outline: 'border border-gray-300 bg-transparent hover:bg-gray-100 focus-visible:ring-gray-500',
|
|
69
|
+
ghost: 'hover:bg-gray-100 focus-visible:ring-gray-500',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sizes = {
|
|
73
|
+
sm: 'h-9 px-3 text-sm',
|
|
74
|
+
md: 'h-10 px-4',
|
|
75
|
+
lg: 'h-11 px-8 text-lg',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn(baseStyles, variants[variant], sizes[size], className)}
|
|
82
|
+
disabled={disabled || isLoading}
|
|
83
|
+
aria-busy={isLoading}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{isLoading && (
|
|
87
|
+
<svg
|
|
88
|
+
className="mr-2 h-4 w-4 animate-spin"
|
|
89
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
90
|
+
fill="none"
|
|
91
|
+
viewBox="0 0 24 24"
|
|
92
|
+
aria-hidden="true"
|
|
93
|
+
>
|
|
94
|
+
<circle
|
|
95
|
+
className="opacity-25"
|
|
96
|
+
cx="12"
|
|
97
|
+
cy="12"
|
|
98
|
+
r="10"
|
|
99
|
+
stroke="currentColor"
|
|
100
|
+
strokeWidth="4"
|
|
101
|
+
/>
|
|
102
|
+
<path
|
|
103
|
+
className="opacity-75"
|
|
104
|
+
fill="currentColor"
|
|
105
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
106
|
+
/>
|
|
107
|
+
</svg>
|
|
108
|
+
)}
|
|
109
|
+
{children}
|
|
110
|
+
</button>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
Button.displayName = 'Button'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Form Components with Validation
|
|
119
|
+
```typescript
|
|
120
|
+
// src/components/features/auth/LoginForm.tsx
|
|
121
|
+
'use client'
|
|
122
|
+
|
|
123
|
+
import { useState } from 'react'
|
|
124
|
+
import { useForm } from 'react-hook-form'
|
|
125
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
126
|
+
import { z } from 'zod'
|
|
127
|
+
import { useMutation } from '@tanstack/react-query'
|
|
128
|
+
import { Button } from '@/components/ui/Button'
|
|
129
|
+
import { Input } from '@/components/ui/Input'
|
|
130
|
+
import { useRouter } from 'next/navigation'
|
|
131
|
+
|
|
132
|
+
const loginSchema = z.object({
|
|
133
|
+
email: z.string().email('Invalid email address'),
|
|
134
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
type LoginFormData = z.infer<typeof loginSchema>
|
|
138
|
+
|
|
139
|
+
export function LoginForm() {
|
|
140
|
+
const router = useRouter()
|
|
141
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
|
142
|
+
|
|
143
|
+
const {
|
|
144
|
+
register,
|
|
145
|
+
handleSubmit,
|
|
146
|
+
formState: { errors },
|
|
147
|
+
} = useForm<LoginFormData>({
|
|
148
|
+
resolver: zodResolver(loginSchema),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const loginMutation = useMutation({
|
|
152
|
+
mutationFn: async (data: LoginFormData) => {
|
|
153
|
+
const response = await fetch('/api/auth/login', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: { 'Content-Type': 'application/json' },
|
|
156
|
+
body: JSON.stringify(data),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
const error = await response.json()
|
|
161
|
+
throw new Error(error.message || 'Login failed')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return response.json()
|
|
165
|
+
},
|
|
166
|
+
onSuccess: () => {
|
|
167
|
+
router.push('/dashboard')
|
|
168
|
+
},
|
|
169
|
+
onError: (error: Error) => {
|
|
170
|
+
setErrorMessage(error.message)
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const onSubmit = (data: LoginFormData) => {
|
|
175
|
+
setErrorMessage(null)
|
|
176
|
+
loginMutation.mutate(data)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6" noValidate>
|
|
181
|
+
<div>
|
|
182
|
+
<label
|
|
183
|
+
htmlFor="email"
|
|
184
|
+
className="block text-sm font-medium text-gray-700"
|
|
185
|
+
>
|
|
186
|
+
Email address
|
|
187
|
+
</label>
|
|
188
|
+
<Input
|
|
189
|
+
id="email"
|
|
190
|
+
type="email"
|
|
191
|
+
autoComplete="email"
|
|
192
|
+
{...register('email')}
|
|
193
|
+
aria-invalid={errors.email ? 'true' : 'false'}
|
|
194
|
+
aria-describedby={errors.email ? 'email-error' : undefined}
|
|
195
|
+
/>
|
|
196
|
+
{errors.email && (
|
|
197
|
+
<p id="email-error" className="mt-1 text-sm text-red-600" role="alert">
|
|
198
|
+
{errors.email.message}
|
|
199
|
+
</p>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div>
|
|
204
|
+
<label
|
|
205
|
+
htmlFor="password"
|
|
206
|
+
className="block text-sm font-medium text-gray-700"
|
|
207
|
+
>
|
|
208
|
+
Password
|
|
209
|
+
</label>
|
|
210
|
+
<Input
|
|
211
|
+
id="password"
|
|
212
|
+
type="password"
|
|
213
|
+
autoComplete="current-password"
|
|
214
|
+
{...register('password')}
|
|
215
|
+
aria-invalid={errors.password ? 'true' : 'false'}
|
|
216
|
+
aria-describedby={errors.password ? 'password-error' : undefined}
|
|
217
|
+
/>
|
|
218
|
+
{errors.password && (
|
|
219
|
+
<p id="password-error" className="mt-1 text-sm text-red-600" role="alert">
|
|
220
|
+
{errors.password.message}
|
|
221
|
+
</p>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{errorMessage && (
|
|
226
|
+
<div
|
|
227
|
+
className="rounded-md bg-red-50 p-4"
|
|
228
|
+
role="alert"
|
|
229
|
+
aria-live="assertive"
|
|
230
|
+
>
|
|
231
|
+
<p className="text-sm text-red-800">{errorMessage}</p>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
<Button
|
|
236
|
+
type="submit"
|
|
237
|
+
variant="primary"
|
|
238
|
+
className="w-full"
|
|
239
|
+
isLoading={loginMutation.isPending}
|
|
240
|
+
>
|
|
241
|
+
Sign in
|
|
242
|
+
</Button>
|
|
243
|
+
</form>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 3. Server Components with Data Fetching
|
|
249
|
+
```typescript
|
|
250
|
+
// src/components/features/products/ProductList.tsx
|
|
251
|
+
import { Suspense } from 'react'
|
|
252
|
+
import { ProductCard } from './ProductCard'
|
|
253
|
+
import { ProductCardSkeleton } from './ProductCardSkeleton'
|
|
254
|
+
|
|
255
|
+
interface Product {
|
|
256
|
+
id: string
|
|
257
|
+
name: string
|
|
258
|
+
price: number
|
|
259
|
+
imageUrl: string
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function getProducts(): Promise<Product[]> {
|
|
263
|
+
const response = await fetch('https://api.example.com/products', {
|
|
264
|
+
next: { revalidate: 60 }, // Revalidate every 60 seconds
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
throw new Error('Failed to fetch products')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return response.json()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function ProductListContent() {
|
|
275
|
+
const products = await getProducts()
|
|
276
|
+
|
|
277
|
+
if (products.length === 0) {
|
|
278
|
+
return (
|
|
279
|
+
<div className="py-12 text-center">
|
|
280
|
+
<p className="text-gray-500">No products found</p>
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
287
|
+
{products.map((product) => (
|
|
288
|
+
<ProductCard key={product.id} product={product} />
|
|
289
|
+
))}
|
|
290
|
+
</div>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function ProductList() {
|
|
295
|
+
return (
|
|
296
|
+
<Suspense
|
|
297
|
+
fallback={
|
|
298
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
299
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
300
|
+
<ProductCardSkeleton key={i} />
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
}
|
|
304
|
+
>
|
|
305
|
+
<ProductListContent />
|
|
306
|
+
</Suspense>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 4. Error Boundary
|
|
312
|
+
```typescript
|
|
313
|
+
// src/components/ErrorBoundary.tsx
|
|
314
|
+
'use client'
|
|
315
|
+
|
|
316
|
+
import { Component, ReactNode } from 'react'
|
|
317
|
+
import { Button } from '@/components/ui/Button'
|
|
318
|
+
|
|
319
|
+
interface Props {
|
|
320
|
+
children: ReactNode
|
|
321
|
+
fallback?: ReactNode
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
interface State {
|
|
325
|
+
hasError: boolean
|
|
326
|
+
error: Error | null
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
330
|
+
constructor(props: Props) {
|
|
331
|
+
super(props)
|
|
332
|
+
this.state = { hasError: false, error: null }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
static getDerivedStateFromError(error: Error): State {
|
|
336
|
+
return { hasError: true, error }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
340
|
+
console.error('Error caught by boundary:', error, errorInfo)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
render() {
|
|
344
|
+
if (this.state.hasError) {
|
|
345
|
+
if (this.props.fallback) {
|
|
346
|
+
return this.props.fallback
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<div className="flex min-h-screen flex-col items-center justify-center p-4">
|
|
351
|
+
<div className="max-w-md space-y-4 text-center">
|
|
352
|
+
<h2 className="text-2xl font-bold text-gray-900">
|
|
353
|
+
Something went wrong
|
|
354
|
+
</h2>
|
|
355
|
+
<p className="text-gray-600">
|
|
356
|
+
{this.state.error?.message || 'An unexpected error occurred'}
|
|
357
|
+
</p>
|
|
358
|
+
<Button
|
|
359
|
+
onClick={() => this.setState({ hasError: false, error: null })}
|
|
360
|
+
>
|
|
361
|
+
Try again
|
|
362
|
+
</Button>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return this.props.children
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Accessibility Checklist
|
|
374
|
+
|
|
375
|
+
- [ ] Semantic HTML elements
|
|
376
|
+
- [ ] Proper ARIA labels and roles
|
|
377
|
+
- [ ] Keyboard navigation support
|
|
378
|
+
- [ ] Focus management
|
|
379
|
+
- [ ] Error messages with `role="alert"`
|
|
380
|
+
- [ ] Loading states with `aria-busy`
|
|
381
|
+
- [ ] Form validation with `aria-invalid` and `aria-describedby`
|
|
382
|
+
- [ ] Sufficient color contrast
|
|
383
|
+
- [ ] Alt text for images
|
|
384
|
+
|
|
385
|
+
## Quality Checklist
|
|
386
|
+
|
|
387
|
+
- [ ] TypeScript strict mode (no `any`)
|
|
388
|
+
- [ ] Proper type definitions
|
|
389
|
+
- [ ] Zod validation for forms
|
|
390
|
+
- [ ] TanStack Query for API calls
|
|
391
|
+
- [ ] Error and loading states
|
|
392
|
+
- [ ] Responsive design
|
|
393
|
+
- [ ] Tailwind CSS
|
|
394
|
+
- [ ] WCAG 2.1 AA compliance
|
|
395
|
+
- [ ] Server components where possible
|
|
396
|
+
- [ ] Suspense boundaries for async content
|
|
397
|
+
|
|
398
|
+
Now, ask the user what component they want to create!
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/app/api/**/*.ts"
|
|
4
|
+
- "**/pages/api/**/*.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Next.js API Route Standards
|
|
8
|
+
|
|
9
|
+
## Structure (App Router)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { NextResponse } from 'next/server'
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
|
|
15
|
+
const createUserSchema = z.object({
|
|
16
|
+
email: z.string().email(),
|
|
17
|
+
name: z.string().min(1),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export async function POST(request: Request) {
|
|
21
|
+
try {
|
|
22
|
+
const body = await request.json()
|
|
23
|
+
const data = createUserSchema.parse(body)
|
|
24
|
+
|
|
25
|
+
// Business logic
|
|
26
|
+
const user = await createUser(data)
|
|
27
|
+
|
|
28
|
+
return NextResponse.json(user, { status: 201 })
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof z.ZodError) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{ errors: error.errors },
|
|
33
|
+
{ status: 400 }
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: 'Internal server error' },
|
|
38
|
+
{ status: 500 }
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Zod validation for all inputs
|
|
47
|
+
- Proper error handling
|
|
48
|
+
- Typed responses
|
|
49
|
+
- No secrets in responses
|
|
50
|
+
- Rate limiting consideration
|
|
51
|
+
|
|
52
|
+
## HTTP Methods
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// GET - Read
|
|
56
|
+
export async function GET(request: Request) {
|
|
57
|
+
const { searchParams } = new URL(request.url)
|
|
58
|
+
const id = searchParams.get('id')
|
|
59
|
+
// ...
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// POST - Create
|
|
63
|
+
export async function POST(request: Request) {
|
|
64
|
+
const body = await request.json()
|
|
65
|
+
// ...
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// PUT/PATCH - Update
|
|
69
|
+
export async function PATCH(
|
|
70
|
+
request: Request,
|
|
71
|
+
{ params }: { params: { id: string } }
|
|
72
|
+
) {
|
|
73
|
+
const body = await request.json()
|
|
74
|
+
// ...
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// DELETE - Remove
|
|
78
|
+
export async function DELETE(
|
|
79
|
+
request: Request,
|
|
80
|
+
{ params }: { params: { id: string } }
|
|
81
|
+
) {
|
|
82
|
+
// ...
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Error Handling Pattern
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { NextResponse } from 'next/server'
|
|
90
|
+
import { z } from 'zod'
|
|
91
|
+
|
|
92
|
+
class ApiError extends Error {
|
|
93
|
+
constructor(
|
|
94
|
+
message: string,
|
|
95
|
+
public statusCode: number,
|
|
96
|
+
public details?: unknown
|
|
97
|
+
) {
|
|
98
|
+
super(message)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleError(error: unknown) {
|
|
103
|
+
if (error instanceof z.ZodError) {
|
|
104
|
+
return NextResponse.json(
|
|
105
|
+
{ error: 'Validation error', details: error.errors },
|
|
106
|
+
{ status: 400 }
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
if (error instanceof ApiError) {
|
|
110
|
+
return NextResponse.json(
|
|
111
|
+
{ error: error.message, details: error.details },
|
|
112
|
+
{ status: error.statusCode }
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
console.error('Unexpected error:', error)
|
|
116
|
+
return NextResponse.json(
|
|
117
|
+
{ error: 'Internal server error' },
|
|
118
|
+
{ status: 500 }
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Forbidden Patterns
|
|
124
|
+
|
|
125
|
+
- Unvalidated inputs
|
|
126
|
+
- Exposing stack traces
|
|
127
|
+
- Hardcoded secrets
|
|
128
|
+
- Missing error handling
|
|
129
|
+
- Any type in request/response
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/components/**/*.tsx"
|
|
4
|
+
- "**/components/**/*.jsx"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Next.js Component Standards
|
|
8
|
+
|
|
9
|
+
## Accessibility (WCAG 2.1 AA)
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// WRONG - No keyboard access
|
|
13
|
+
<div onClick={handleClick}>Click me</div>
|
|
14
|
+
|
|
15
|
+
// CORRECT - Semantic HTML
|
|
16
|
+
<button type="button" onClick={handleClick}>
|
|
17
|
+
Click me
|
|
18
|
+
</button>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Form Pattern (React Hook Form + Zod)
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useForm } from 'react-hook-form'
|
|
25
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
26
|
+
import { z } from 'zod'
|
|
27
|
+
|
|
28
|
+
const schema = z.object({
|
|
29
|
+
email: z.string().email(),
|
|
30
|
+
password: z.string().min(8),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
type FormData = z.infer<typeof schema>
|
|
34
|
+
|
|
35
|
+
export function LoginForm() {
|
|
36
|
+
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
|
|
37
|
+
resolver: zodResolver(schema),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
42
|
+
<label htmlFor="email">Email</label>
|
|
43
|
+
<input id="email" {...register('email')} aria-describedby="email-error" />
|
|
44
|
+
{errors.email && <span id="email-error" role="alert">{errors.email.message}</span>}
|
|
45
|
+
</form>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Import Pattern
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// CORRECT - Path aliases
|
|
54
|
+
import { Button } from '@/components/ui/button'
|
|
55
|
+
import { useAuth } from '@/hooks/useAuth'
|
|
56
|
+
|
|
57
|
+
// WRONG - Deep relative paths
|
|
58
|
+
import { Button } from '../../../components/ui/button'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
- TypeScript strict mode (no `any`)
|
|
64
|
+
- Semantic HTML elements
|
|
65
|
+
- ARIA attributes where needed
|
|
66
|
+
- Error boundaries for async components
|
|
67
|
+
- Loading states for data fetching
|
|
68
|
+
|
|
69
|
+
## Component Structure
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { type FC } from 'react'
|
|
73
|
+
|
|
74
|
+
interface ButtonProps {
|
|
75
|
+
children: React.ReactNode
|
|
76
|
+
variant?: 'primary' | 'secondary'
|
|
77
|
+
disabled?: boolean
|
|
78
|
+
onClick?: () => void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const Button: FC<ButtonProps> = ({
|
|
82
|
+
children,
|
|
83
|
+
variant = 'primary',
|
|
84
|
+
disabled = false,
|
|
85
|
+
onClick,
|
|
86
|
+
}) => {
|
|
87
|
+
return (
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
className={`btn btn-${variant}`}
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
onClick={onClick}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</button>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Forbidden Patterns
|
|
101
|
+
|
|
102
|
+
- `any` type
|
|
103
|
+
- Non-semantic interactive elements
|
|
104
|
+
- Missing form labels
|
|
105
|
+
- Inline styles (use Tailwind/CSS modules)
|
|
106
|
+
- Missing error boundaries for async components
|