@girardmedia/bootspring 1.2.0 → 2.0.3
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 +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
# Error Handling Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for handling errors gracefully.
|
|
4
|
-
|
|
5
|
-
## Custom Error Classes
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/errors.ts
|
|
9
|
-
export class AppError extends Error {
|
|
10
|
-
constructor(
|
|
11
|
-
message: string,
|
|
12
|
-
public code: string,
|
|
13
|
-
public statusCode: number = 500,
|
|
14
|
-
public details?: Record<string, any>
|
|
15
|
-
) {
|
|
16
|
-
super(message)
|
|
17
|
-
this.name = 'AppError'
|
|
18
|
-
Error.captureStackTrace(this, this.constructor)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
toJSON() {
|
|
22
|
-
return {
|
|
23
|
-
error: {
|
|
24
|
-
code: this.code,
|
|
25
|
-
message: this.message,
|
|
26
|
-
details: this.details
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export class ValidationError extends AppError {
|
|
33
|
-
constructor(message: string, details?: Record<string, string[]>) {
|
|
34
|
-
super(message, 'VALIDATION_ERROR', 400, details)
|
|
35
|
-
this.name = 'ValidationError'
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class NotFoundError extends AppError {
|
|
40
|
-
constructor(resource: string, id?: string) {
|
|
41
|
-
super(
|
|
42
|
-
id ? `${resource} with ID ${id} not found` : `${resource} not found`,
|
|
43
|
-
'NOT_FOUND',
|
|
44
|
-
404
|
|
45
|
-
)
|
|
46
|
-
this.name = 'NotFoundError'
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class UnauthorizedError extends AppError {
|
|
51
|
-
constructor(message = 'Unauthorized') {
|
|
52
|
-
super(message, 'UNAUTHORIZED', 401)
|
|
53
|
-
this.name = 'UnauthorizedError'
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export class ForbiddenError extends AppError {
|
|
58
|
-
constructor(message = 'Forbidden') {
|
|
59
|
-
super(message, 'FORBIDDEN', 403)
|
|
60
|
-
this.name = 'ForbiddenError'
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class ConflictError extends AppError {
|
|
65
|
-
constructor(message: string) {
|
|
66
|
-
super(message, 'CONFLICT', 409)
|
|
67
|
-
this.name = 'ConflictError'
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export class RateLimitError extends AppError {
|
|
72
|
-
constructor(retryAfter?: number) {
|
|
73
|
-
super('Too many requests', 'RATE_LIMIT_EXCEEDED', 429, { retryAfter })
|
|
74
|
-
this.name = 'RateLimitError'
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## API Error Handler
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// lib/api/error-handler.ts
|
|
83
|
-
import { NextResponse } from 'next/server'
|
|
84
|
-
import { AppError } from '@/lib/errors'
|
|
85
|
-
import { ZodError } from 'zod'
|
|
86
|
-
import { Prisma } from '@prisma/client'
|
|
87
|
-
|
|
88
|
-
export function handleApiError(error: unknown): NextResponse {
|
|
89
|
-
console.error('API Error:', error)
|
|
90
|
-
|
|
91
|
-
// Custom app errors
|
|
92
|
-
if (error instanceof AppError) {
|
|
93
|
-
return NextResponse.json(error.toJSON(), { status: error.statusCode })
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Zod validation errors
|
|
97
|
-
if (error instanceof ZodError) {
|
|
98
|
-
return NextResponse.json(
|
|
99
|
-
{
|
|
100
|
-
error: {
|
|
101
|
-
code: 'VALIDATION_ERROR',
|
|
102
|
-
message: 'Validation failed',
|
|
103
|
-
details: error.flatten().fieldErrors
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
{ status: 400 }
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Prisma errors
|
|
111
|
-
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
112
|
-
switch (error.code) {
|
|
113
|
-
case 'P2002':
|
|
114
|
-
return NextResponse.json(
|
|
115
|
-
{
|
|
116
|
-
error: {
|
|
117
|
-
code: 'CONFLICT',
|
|
118
|
-
message: 'A record with this value already exists',
|
|
119
|
-
details: { field: error.meta?.target }
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
{ status: 409 }
|
|
123
|
-
)
|
|
124
|
-
case 'P2025':
|
|
125
|
-
return NextResponse.json(
|
|
126
|
-
{
|
|
127
|
-
error: {
|
|
128
|
-
code: 'NOT_FOUND',
|
|
129
|
-
message: 'Record not found'
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
{ status: 404 }
|
|
133
|
-
)
|
|
134
|
-
default:
|
|
135
|
-
return NextResponse.json(
|
|
136
|
-
{
|
|
137
|
-
error: {
|
|
138
|
-
code: 'DATABASE_ERROR',
|
|
139
|
-
message: 'Database operation failed'
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
{ status: 500 }
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Unknown errors
|
|
148
|
-
return NextResponse.json(
|
|
149
|
-
{
|
|
150
|
-
error: {
|
|
151
|
-
code: 'INTERNAL_ERROR',
|
|
152
|
-
message:
|
|
153
|
-
process.env.NODE_ENV === 'development'
|
|
154
|
-
? (error as Error).message
|
|
155
|
-
: 'An unexpected error occurred'
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
{ status: 500 }
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Wrapper for API routes
|
|
163
|
-
type Handler = (...args: any[]) => Promise<NextResponse>
|
|
164
|
-
|
|
165
|
-
export function withErrorHandler(handler: Handler): Handler {
|
|
166
|
-
return async (...args) => {
|
|
167
|
-
try {
|
|
168
|
-
return await handler(...args)
|
|
169
|
-
} catch (error) {
|
|
170
|
-
return handleApiError(error)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## React Error Boundary
|
|
177
|
-
|
|
178
|
-
```tsx
|
|
179
|
-
// components/ErrorBoundary.tsx
|
|
180
|
-
'use client'
|
|
181
|
-
|
|
182
|
-
import { Component, ReactNode } from 'react'
|
|
183
|
-
|
|
184
|
-
interface Props {
|
|
185
|
-
children: ReactNode
|
|
186
|
-
fallback?: ReactNode
|
|
187
|
-
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
interface State {
|
|
191
|
-
hasError: boolean
|
|
192
|
-
error?: Error
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export class ErrorBoundary extends Component<Props, State> {
|
|
196
|
-
constructor(props: Props) {
|
|
197
|
-
super(props)
|
|
198
|
-
this.state = { hasError: false }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
static getDerivedStateFromError(error: Error): State {
|
|
202
|
-
return { hasError: true, error }
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
206
|
-
console.error('Error boundary caught:', error, errorInfo)
|
|
207
|
-
this.props.onError?.(error, errorInfo)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
render() {
|
|
211
|
-
if (this.state.hasError) {
|
|
212
|
-
if (this.props.fallback) {
|
|
213
|
-
return this.props.fallback
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return (
|
|
217
|
-
<div className="flex min-h-[200px] flex-col items-center justify-center rounded-lg border border-red-200 bg-red-50 p-6">
|
|
218
|
-
<h2 className="mb-2 text-lg font-semibold text-red-800">
|
|
219
|
-
Something went wrong
|
|
220
|
-
</h2>
|
|
221
|
-
<p className="mb-4 text-sm text-red-600">
|
|
222
|
-
{this.state.error?.message ?? 'An unexpected error occurred'}
|
|
223
|
-
</p>
|
|
224
|
-
<button
|
|
225
|
-
onClick={() => this.setState({ hasError: false, error: undefined })}
|
|
226
|
-
className="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700"
|
|
227
|
-
>
|
|
228
|
-
Try again
|
|
229
|
-
</button>
|
|
230
|
-
</div>
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return this.props.children
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Next.js Error Page
|
|
240
|
-
|
|
241
|
-
```tsx
|
|
242
|
-
// app/error.tsx
|
|
243
|
-
'use client'
|
|
244
|
-
|
|
245
|
-
import { useEffect } from 'react'
|
|
246
|
-
|
|
247
|
-
export default function Error({
|
|
248
|
-
error,
|
|
249
|
-
reset
|
|
250
|
-
}: {
|
|
251
|
-
error: Error & { digest?: string }
|
|
252
|
-
reset: () => void
|
|
253
|
-
}) {
|
|
254
|
-
useEffect(() => {
|
|
255
|
-
// Log to error reporting service
|
|
256
|
-
console.error('Page error:', error)
|
|
257
|
-
}, [error])
|
|
258
|
-
|
|
259
|
-
return (
|
|
260
|
-
<div className="flex min-h-screen flex-col items-center justify-center">
|
|
261
|
-
<div className="text-center">
|
|
262
|
-
<h1 className="mb-4 text-4xl font-bold">Something went wrong</h1>
|
|
263
|
-
<p className="mb-6 text-gray-600">
|
|
264
|
-
We apologize for the inconvenience. Please try again.
|
|
265
|
-
</p>
|
|
266
|
-
<button
|
|
267
|
-
onClick={reset}
|
|
268
|
-
className="rounded bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
|
|
269
|
-
>
|
|
270
|
-
Try again
|
|
271
|
-
</button>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// app/not-found.tsx
|
|
278
|
-
import Link from 'next/link'
|
|
279
|
-
|
|
280
|
-
export default function NotFound() {
|
|
281
|
-
return (
|
|
282
|
-
<div className="flex min-h-screen flex-col items-center justify-center">
|
|
283
|
-
<div className="text-center">
|
|
284
|
-
<h1 className="mb-2 text-6xl font-bold text-gray-900">404</h1>
|
|
285
|
-
<h2 className="mb-4 text-2xl font-semibold text-gray-700">
|
|
286
|
-
Page not found
|
|
287
|
-
</h2>
|
|
288
|
-
<p className="mb-6 text-gray-600">
|
|
289
|
-
The page you're looking for doesn't exist.
|
|
290
|
-
</p>
|
|
291
|
-
<Link
|
|
292
|
-
href="/"
|
|
293
|
-
className="rounded bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
|
|
294
|
-
>
|
|
295
|
-
Go home
|
|
296
|
-
</Link>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## Async Error Handling Hook
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
// hooks/useAsyncError.ts
|
|
307
|
-
'use client'
|
|
308
|
-
|
|
309
|
-
import { useState, useCallback } from 'react'
|
|
310
|
-
|
|
311
|
-
interface UseAsyncErrorReturn<T> {
|
|
312
|
-
execute: (...args: any[]) => Promise<T | undefined>
|
|
313
|
-
data: T | undefined
|
|
314
|
-
error: Error | null
|
|
315
|
-
isLoading: boolean
|
|
316
|
-
reset: () => void
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function useAsyncError<T>(
|
|
320
|
-
asyncFn: (...args: any[]) => Promise<T>
|
|
321
|
-
): UseAsyncErrorReturn<T> {
|
|
322
|
-
const [data, setData] = useState<T>()
|
|
323
|
-
const [error, setError] = useState<Error | null>(null)
|
|
324
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
325
|
-
|
|
326
|
-
const execute = useCallback(
|
|
327
|
-
async (...args: any[]) => {
|
|
328
|
-
setIsLoading(true)
|
|
329
|
-
setError(null)
|
|
330
|
-
|
|
331
|
-
try {
|
|
332
|
-
const result = await asyncFn(...args)
|
|
333
|
-
setData(result)
|
|
334
|
-
return result
|
|
335
|
-
} catch (err) {
|
|
336
|
-
const error = err instanceof Error ? err : new Error(String(err))
|
|
337
|
-
setError(error)
|
|
338
|
-
return undefined
|
|
339
|
-
} finally {
|
|
340
|
-
setIsLoading(false)
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
[asyncFn]
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
const reset = useCallback(() => {
|
|
347
|
-
setData(undefined)
|
|
348
|
-
setError(null)
|
|
349
|
-
setIsLoading(false)
|
|
350
|
-
}, [])
|
|
351
|
-
|
|
352
|
-
return { execute, data, error, isLoading, reset }
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
## Result Type Pattern
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
// lib/result.ts
|
|
360
|
-
type Result<T, E = Error> =
|
|
361
|
-
| { success: true; data: T }
|
|
362
|
-
| { success: false; error: E }
|
|
363
|
-
|
|
364
|
-
export function ok<T>(data: T): Result<T, never> {
|
|
365
|
-
return { success: true, data }
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export function err<E>(error: E): Result<never, E> {
|
|
369
|
-
return { success: false, error }
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Usage
|
|
373
|
-
async function createUser(data: UserInput): Promise<Result<User, AppError>> {
|
|
374
|
-
try {
|
|
375
|
-
const user = await prisma.user.create({ data })
|
|
376
|
-
return ok(user)
|
|
377
|
-
} catch (error) {
|
|
378
|
-
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
379
|
-
if (error.code === 'P2002') {
|
|
380
|
-
return err(new ConflictError('Email already exists'))
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return err(new AppError('Failed to create user', 'CREATE_FAILED'))
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// In handler
|
|
388
|
-
const result = await createUser(data)
|
|
389
|
-
|
|
390
|
-
if (!result.success) {
|
|
391
|
-
return NextResponse.json(result.error.toJSON(), {
|
|
392
|
-
status: result.error.statusCode
|
|
393
|
-
})
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return NextResponse.json(result.data)
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
## Toast Error Display
|
|
400
|
-
|
|
401
|
-
```tsx
|
|
402
|
-
// hooks/useErrorToast.ts
|
|
403
|
-
'use client'
|
|
404
|
-
|
|
405
|
-
import { useCallback } from 'react'
|
|
406
|
-
import { toast } from 'sonner'
|
|
407
|
-
|
|
408
|
-
interface ApiError {
|
|
409
|
-
error: {
|
|
410
|
-
code: string
|
|
411
|
-
message: string
|
|
412
|
-
details?: Record<string, any>
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export function useErrorToast() {
|
|
417
|
-
const showError = useCallback((error: unknown) => {
|
|
418
|
-
if (error instanceof Response) {
|
|
419
|
-
error.json().then((data: ApiError) => {
|
|
420
|
-
toast.error(data.error.message)
|
|
421
|
-
})
|
|
422
|
-
return
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (error instanceof Error) {
|
|
426
|
-
toast.error(error.message)
|
|
427
|
-
return
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
toast.error('An unexpected error occurred')
|
|
431
|
-
}, [])
|
|
432
|
-
|
|
433
|
-
return { showError }
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Usage
|
|
437
|
-
const { showError } = useErrorToast()
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
await submitForm(data)
|
|
441
|
-
} catch (error) {
|
|
442
|
-
showError(error)
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
## When to Use
|
|
447
|
-
|
|
448
|
-
- API error responses
|
|
449
|
-
- Form validation
|
|
450
|
-
- Async operations
|
|
451
|
-
- Component error boundaries
|