@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-patterns
|
|
3
|
+
description: |
|
|
4
|
+
Modern React patterns for this Next.js 15 application.
|
|
5
|
+
Covers Server/Client Components, Context API, TanStack Query, Suspense, Error Boundaries, and performance patterns.
|
|
6
|
+
Use this skill when implementing React components or understanding component architecture.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# React Patterns Skill
|
|
12
|
+
|
|
13
|
+
Patterns for working with React 19 and Next.js 15 App Router in this application.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
REACT COMPONENT ARCHITECTURE:
|
|
19
|
+
|
|
20
|
+
Server Components (Default):
|
|
21
|
+
├── app/layout.tsx # Root layout with providers
|
|
22
|
+
├── app/page.tsx # Pages (default server)
|
|
23
|
+
└── app/dashboard/layout.tsx # Nested layouts
|
|
24
|
+
|
|
25
|
+
Client Components ('use client'):
|
|
26
|
+
├── core/components/ # Interactive UI components
|
|
27
|
+
├── core/contexts/ # React Context providers
|
|
28
|
+
├── core/hooks/ # Custom hooks
|
|
29
|
+
└── core/providers/ # Provider wrappers
|
|
30
|
+
|
|
31
|
+
Data Fetching Strategy:
|
|
32
|
+
├── TanStack Query # Client-side data fetching
|
|
33
|
+
├── Server Actions # Server mutations
|
|
34
|
+
└── Route Handlers # API endpoints
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## When to Use This Skill
|
|
38
|
+
|
|
39
|
+
- Deciding between Server and Client Components
|
|
40
|
+
- Implementing Context API patterns
|
|
41
|
+
- Using TanStack Query for data fetching
|
|
42
|
+
- Adding Suspense boundaries
|
|
43
|
+
- Implementing error handling
|
|
44
|
+
- Performance optimization with useCallback/useMemo
|
|
45
|
+
|
|
46
|
+
## Server Components vs Client Components
|
|
47
|
+
|
|
48
|
+
### Server Components (Default)
|
|
49
|
+
|
|
50
|
+
All components in the `app/` directory are Server Components by default.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// app/layout.tsx - Server Component
|
|
54
|
+
export default async function RootLayout({
|
|
55
|
+
children,
|
|
56
|
+
}: {
|
|
57
|
+
children: React.ReactNode
|
|
58
|
+
}) {
|
|
59
|
+
// Can use async/await directly
|
|
60
|
+
const locale = await getUserLocale()
|
|
61
|
+
const messages = await getMessages({ locale })
|
|
62
|
+
const defaultTheme = await getDefaultThemeMode()
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<html lang={locale} suppressHydrationWarning>
|
|
66
|
+
<body>
|
|
67
|
+
{/* Wrap client components in providers */}
|
|
68
|
+
<NextIntlClientProvider messages={messages}>
|
|
69
|
+
<NextThemeProvider defaultTheme={defaultTheme}>
|
|
70
|
+
<QueryProvider>
|
|
71
|
+
<TeamProvider>
|
|
72
|
+
{children}
|
|
73
|
+
</TeamProvider>
|
|
74
|
+
</QueryProvider>
|
|
75
|
+
</NextThemeProvider>
|
|
76
|
+
</NextIntlClientProvider>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**When to use Server Components:**
|
|
84
|
+
- Fetching data from database
|
|
85
|
+
- Accessing backend resources
|
|
86
|
+
- Keeping sensitive data on server
|
|
87
|
+
- Reducing client JavaScript bundle
|
|
88
|
+
- Pages that don't need interactivity
|
|
89
|
+
|
|
90
|
+
### Client Components
|
|
91
|
+
|
|
92
|
+
Mark with `'use client'` directive at the top of the file.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
'use client'
|
|
96
|
+
|
|
97
|
+
import { useState, useCallback } from 'react'
|
|
98
|
+
import { useRouter } from 'next/navigation'
|
|
99
|
+
import { useAuth } from '@/core/hooks/useAuth'
|
|
100
|
+
|
|
101
|
+
export function LoginForm() {
|
|
102
|
+
const [error, setError] = useState<string | null>(null)
|
|
103
|
+
const { signIn } = useAuth()
|
|
104
|
+
const router = useRouter()
|
|
105
|
+
|
|
106
|
+
const handleSubmit = useCallback(async (data: LoginData) => {
|
|
107
|
+
try {
|
|
108
|
+
await signIn(data)
|
|
109
|
+
router.push('/dashboard')
|
|
110
|
+
} catch (err) {
|
|
111
|
+
setError(err instanceof Error ? err.message : 'Sign in failed')
|
|
112
|
+
}
|
|
113
|
+
}, [signIn, router])
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<form onSubmit={handleSubmit}>
|
|
117
|
+
{/* Form content */}
|
|
118
|
+
</form>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**When to use Client Components:**
|
|
124
|
+
- Using React hooks (useState, useEffect, etc.)
|
|
125
|
+
- Adding event listeners (onClick, onChange, etc.)
|
|
126
|
+
- Using browser APIs
|
|
127
|
+
- Using third-party libraries that use hooks
|
|
128
|
+
- Components that need interactivity
|
|
129
|
+
|
|
130
|
+
## Context API Pattern
|
|
131
|
+
|
|
132
|
+
### Creating Context with Custom Hook
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// core/contexts/TeamContext.tsx
|
|
136
|
+
'use client'
|
|
137
|
+
|
|
138
|
+
import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
|
|
139
|
+
|
|
140
|
+
interface TeamContextValue {
|
|
141
|
+
currentTeam: Team | null
|
|
142
|
+
userTeams: UserTeamMembership[]
|
|
143
|
+
isLoading: boolean
|
|
144
|
+
switchTeam: (teamId: string) => Promise<void>
|
|
145
|
+
refreshTeams: () => Promise<void>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const TeamContext = createContext<TeamContextValue | undefined>(undefined)
|
|
149
|
+
|
|
150
|
+
export function TeamProvider({ children }: { children: ReactNode }) {
|
|
151
|
+
const [currentTeam, setCurrentTeam] = useState<Team | null>(null)
|
|
152
|
+
const [userTeams, setUserTeams] = useState<UserTeamMembership[]>([])
|
|
153
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
154
|
+
|
|
155
|
+
const switchTeam = useCallback(async (teamId: string) => {
|
|
156
|
+
// Implementation
|
|
157
|
+
}, [])
|
|
158
|
+
|
|
159
|
+
const refreshTeams = useCallback(async () => {
|
|
160
|
+
// Implementation
|
|
161
|
+
}, [])
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<TeamContext.Provider value={{
|
|
165
|
+
currentTeam,
|
|
166
|
+
userTeams,
|
|
167
|
+
isLoading,
|
|
168
|
+
switchTeam,
|
|
169
|
+
refreshTeams,
|
|
170
|
+
}}>
|
|
171
|
+
{children}
|
|
172
|
+
</TeamContext.Provider>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Custom hook with error boundary
|
|
177
|
+
export function useTeamContext() {
|
|
178
|
+
const context = useContext(TeamContext)
|
|
179
|
+
if (context === undefined) {
|
|
180
|
+
throw new Error('useTeamContext must be used within TeamProvider')
|
|
181
|
+
}
|
|
182
|
+
return context
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Provider Nesting Pattern
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// app/layout.tsx
|
|
190
|
+
<NextIntlClientProvider messages={messages}>
|
|
191
|
+
<NextThemeProvider>
|
|
192
|
+
<QueryProvider>
|
|
193
|
+
<TeamProvider>
|
|
194
|
+
<SubscriptionProvider>
|
|
195
|
+
{children}
|
|
196
|
+
</SubscriptionProvider>
|
|
197
|
+
</TeamProvider>
|
|
198
|
+
</QueryProvider>
|
|
199
|
+
</NextThemeProvider>
|
|
200
|
+
</NextIntlClientProvider>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Order matters:** Providers that depend on others must be nested inside them.
|
|
204
|
+
|
|
205
|
+
## TanStack Query for Data Fetching
|
|
206
|
+
|
|
207
|
+
### Provider Setup
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// core/providers/query-provider.tsx
|
|
211
|
+
'use client'
|
|
212
|
+
|
|
213
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
214
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
215
|
+
import { useState } from 'react'
|
|
216
|
+
|
|
217
|
+
export function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
218
|
+
const [queryClient] = useState(
|
|
219
|
+
() => new QueryClient({
|
|
220
|
+
defaultOptions: {
|
|
221
|
+
queries: {
|
|
222
|
+
staleTime: 60 * 1000, // 1 minute
|
|
223
|
+
refetchOnWindowFocus: false,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<QueryClientProvider client={queryClient}>
|
|
231
|
+
{children}
|
|
232
|
+
{process.env.NEXT_PUBLIC_RQ_DEVTOOLS === 'true' && (
|
|
233
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
234
|
+
)}
|
|
235
|
+
</QueryClientProvider>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Query Pattern
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// ✅ CORRECT - Use TanStack Query for data fetching
|
|
244
|
+
import { useQuery } from '@tanstack/react-query'
|
|
245
|
+
|
|
246
|
+
function CustomerList() {
|
|
247
|
+
const { data, isLoading, error } = useQuery({
|
|
248
|
+
queryKey: ['customers'],
|
|
249
|
+
queryFn: () => fetch('/api/v1/customers').then(res => res.json()),
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if (isLoading) return <Skeleton />
|
|
253
|
+
if (error) return <ErrorMessage error={error} />
|
|
254
|
+
|
|
255
|
+
return <DataTable data={data} />
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ❌ WRONG - Don't use useEffect for data fetching
|
|
259
|
+
function CustomerList() {
|
|
260
|
+
const [data, setData] = useState(null)
|
|
261
|
+
const [loading, setLoading] = useState(true)
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
fetch('/api/v1/customers')
|
|
265
|
+
.then(res => res.json())
|
|
266
|
+
.then(setData)
|
|
267
|
+
.finally(() => setLoading(false))
|
|
268
|
+
}, [])
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Mutation Pattern
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
276
|
+
|
|
277
|
+
function CreateCustomerForm() {
|
|
278
|
+
const queryClient = useQueryClient()
|
|
279
|
+
|
|
280
|
+
const mutation = useMutation({
|
|
281
|
+
mutationFn: (data: CreateCustomer) =>
|
|
282
|
+
fetch('/api/v1/customers', {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: { 'Content-Type': 'application/json' },
|
|
285
|
+
body: JSON.stringify(data),
|
|
286
|
+
}).then(res => res.json()),
|
|
287
|
+
onSuccess: () => {
|
|
288
|
+
// Invalidate queries to refetch
|
|
289
|
+
queryClient.invalidateQueries({ queryKey: ['customers'] })
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<form onSubmit={(e) => {
|
|
295
|
+
e.preventDefault()
|
|
296
|
+
mutation.mutate(formData)
|
|
297
|
+
}}>
|
|
298
|
+
{/* Form fields */}
|
|
299
|
+
</form>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Suspense Boundaries
|
|
305
|
+
|
|
306
|
+
### Basic Usage
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { Suspense } from 'react'
|
|
310
|
+
|
|
311
|
+
function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
312
|
+
return (
|
|
313
|
+
<div>
|
|
314
|
+
<Suspense fallback={<NavSkeleton />}>
|
|
315
|
+
<Navigation />
|
|
316
|
+
</Suspense>
|
|
317
|
+
|
|
318
|
+
<main>
|
|
319
|
+
<Suspense fallback={<ContentSkeleton />}>
|
|
320
|
+
{children}
|
|
321
|
+
</Suspense>
|
|
322
|
+
</main>
|
|
323
|
+
</div>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### With Async Components
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// app/dashboard/layout.tsx
|
|
332
|
+
'use client'
|
|
333
|
+
|
|
334
|
+
import { Suspense } from 'react'
|
|
335
|
+
|
|
336
|
+
function AuthMethodDetectorWrapper() {
|
|
337
|
+
useAuthMethodDetector() // Hook that reads URL params
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
342
|
+
return (
|
|
343
|
+
<>
|
|
344
|
+
{/* Wrap async detection in Suspense */}
|
|
345
|
+
<Suspense fallback={null}>
|
|
346
|
+
<AuthMethodDetectorWrapper />
|
|
347
|
+
</Suspense>
|
|
348
|
+
|
|
349
|
+
<div data-cy="dashboard-container">
|
|
350
|
+
{children}
|
|
351
|
+
</div>
|
|
352
|
+
</>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Block Renderer with Suspense
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// app/components/page-renderer.tsx
|
|
361
|
+
import { Suspense } from 'react'
|
|
362
|
+
|
|
363
|
+
function BlockSkeleton() {
|
|
364
|
+
return (
|
|
365
|
+
<div className="w-full py-12 px-4 animate-pulse">
|
|
366
|
+
<div className="h-8 bg-muted rounded w-1/3 mb-4" />
|
|
367
|
+
<div className="h-4 bg-muted rounded w-2/3" />
|
|
368
|
+
</div>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function BlockRenderer({ block }: { block: BlockInstance }) {
|
|
373
|
+
const BlockComponent = getBlockComponent(block.blockSlug)
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<Suspense fallback={<BlockSkeleton />}>
|
|
377
|
+
<BlockComponent {...block.props} />
|
|
378
|
+
</Suspense>
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Error Boundaries
|
|
384
|
+
|
|
385
|
+
### Next.js Error Boundary
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// app/dashboard/(main)/[entity]/error.tsx
|
|
389
|
+
'use client'
|
|
390
|
+
|
|
391
|
+
import { useEffect } from 'react'
|
|
392
|
+
import { Button } from '@/core/components/ui/button'
|
|
393
|
+
import { AlertCircle } from 'lucide-react'
|
|
394
|
+
|
|
395
|
+
export default function EntityError({
|
|
396
|
+
error,
|
|
397
|
+
reset,
|
|
398
|
+
}: {
|
|
399
|
+
error: Error & { digest?: string }
|
|
400
|
+
reset: () => void
|
|
401
|
+
}) {
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
console.error('Entity page error:', error)
|
|
404
|
+
}, [error])
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4 p-6">
|
|
408
|
+
<AlertCircle className="h-12 w-12 text-destructive" />
|
|
409
|
+
<h2 className="text-xl font-semibold">Something went wrong!</h2>
|
|
410
|
+
<p className="text-sm text-muted-foreground max-w-md text-center">
|
|
411
|
+
{error.message || 'An error occurred while loading this page.'}
|
|
412
|
+
</p>
|
|
413
|
+
|
|
414
|
+
<div className="flex items-center gap-2">
|
|
415
|
+
<Button variant="outline" onClick={() => window.history.back()}>
|
|
416
|
+
Go Back
|
|
417
|
+
</Button>
|
|
418
|
+
<Button onClick={() => reset()}>
|
|
419
|
+
Try Again
|
|
420
|
+
</Button>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
{/* Development error details */}
|
|
424
|
+
{process.env.NODE_ENV === 'development' && error.stack && (
|
|
425
|
+
<details className="mt-4 p-4 bg-muted rounded-lg text-xs max-w-2xl w-full">
|
|
426
|
+
<summary className="cursor-pointer font-medium">Error Details</summary>
|
|
427
|
+
<pre className="mt-2 overflow-auto">{error.stack}</pre>
|
|
428
|
+
</details>
|
|
429
|
+
)}
|
|
430
|
+
</div>
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Custom Error Component
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// Block error fallback
|
|
439
|
+
function BlockError({ blockSlug }: { blockSlug: string }) {
|
|
440
|
+
return (
|
|
441
|
+
<div className="w-full py-12 px-4 bg-destructive/10 border border-destructive/20 rounded">
|
|
442
|
+
<div className="max-w-7xl mx-auto text-center">
|
|
443
|
+
<p className="text-destructive">
|
|
444
|
+
Failed to load block: <code className="font-mono">{blockSlug}</code>
|
|
445
|
+
</p>
|
|
446
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
447
|
+
This block may not be available or there was an error rendering it.
|
|
448
|
+
</p>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Performance Patterns
|
|
456
|
+
|
|
457
|
+
### useCallback for Event Handlers
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
'use client'
|
|
461
|
+
|
|
462
|
+
import { useCallback, useState } from 'react'
|
|
463
|
+
|
|
464
|
+
function ThemeToggle() {
|
|
465
|
+
const { setTheme, theme } = useTheme()
|
|
466
|
+
const { user } = useAuth()
|
|
467
|
+
|
|
468
|
+
// Memoize callback to prevent unnecessary re-renders
|
|
469
|
+
const handleThemeChange = useCallback(async (newTheme: string) => {
|
|
470
|
+
setTheme(newTheme)
|
|
471
|
+
|
|
472
|
+
// Persist preference if logged in
|
|
473
|
+
if (user?.id) {
|
|
474
|
+
try {
|
|
475
|
+
await fetch('/api/user/profile', {
|
|
476
|
+
method: 'PATCH',
|
|
477
|
+
headers: { 'Content-Type': 'application/json' },
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
meta: { uiPreferences: { theme: newTheme } }
|
|
480
|
+
}),
|
|
481
|
+
})
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('Failed to save theme preference:', error)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}, [setTheme, user])
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<DropdownMenu>
|
|
490
|
+
<DropdownMenuItem onClick={() => handleThemeChange('light')}>
|
|
491
|
+
Light
|
|
492
|
+
</DropdownMenuItem>
|
|
493
|
+
<DropdownMenuItem onClick={() => handleThemeChange('dark')}>
|
|
494
|
+
Dark
|
|
495
|
+
</DropdownMenuItem>
|
|
496
|
+
</DropdownMenu>
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Avoiding Unnecessary Re-renders
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// ✅ CORRECT - useState with initializer function
|
|
505
|
+
const [queryClient] = useState(() => new QueryClient())
|
|
506
|
+
|
|
507
|
+
// ❌ WRONG - Creates new instance on every render
|
|
508
|
+
const queryClient = new QueryClient()
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Custom Hooks Pattern
|
|
512
|
+
|
|
513
|
+
### useAuth Hook
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// core/hooks/useAuth.ts
|
|
517
|
+
'use client'
|
|
518
|
+
|
|
519
|
+
import { useRouter } from 'next/navigation'
|
|
520
|
+
import { authClient } from '@/core/lib/auth-client'
|
|
521
|
+
|
|
522
|
+
export function useAuth() {
|
|
523
|
+
const router = useRouter()
|
|
524
|
+
const session = authClient.useSession()
|
|
525
|
+
|
|
526
|
+
const handleSignIn = async ({ email, password, redirectTo }: SignInParams) => {
|
|
527
|
+
const { data, error } = await authClient.signIn.email({ email, password })
|
|
528
|
+
|
|
529
|
+
if (error) {
|
|
530
|
+
throw new Error(error.message || 'Error signing in')
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (data) {
|
|
534
|
+
router.push(redirectTo || '/dashboard')
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return data
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const handleSignOut = async () => {
|
|
541
|
+
if (typeof window !== 'undefined') {
|
|
542
|
+
localStorage.removeItem('activeTeamId')
|
|
543
|
+
}
|
|
544
|
+
await authClient.signOut()
|
|
545
|
+
router.push('/login')
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
user: session.data?.user,
|
|
550
|
+
session: session.data,
|
|
551
|
+
isLoading: session.isPending,
|
|
552
|
+
signIn: handleSignIn,
|
|
553
|
+
signOut: handleSignOut,
|
|
554
|
+
// ... other auth methods
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## React 19 Patterns (Available but Not Used)
|
|
560
|
+
|
|
561
|
+
These patterns are documented in `.rules/components.md` but not currently used in the codebase:
|
|
562
|
+
|
|
563
|
+
### use() Hook
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// Pattern available in React 19
|
|
567
|
+
import { use } from 'react'
|
|
568
|
+
|
|
569
|
+
function TodoList({ todosPromise }: { todosPromise: Promise<Todo[]> }) {
|
|
570
|
+
const todos = use(todosPromise) // Suspends until resolved
|
|
571
|
+
return (
|
|
572
|
+
<ul>
|
|
573
|
+
{todos.map(todo => <li key={todo.id}>{todo.title}</li>)}
|
|
574
|
+
</ul>
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**Current approach:** Use TanStack Query instead.
|
|
580
|
+
|
|
581
|
+
### useActionState
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// Pattern available in React 19
|
|
585
|
+
import { useActionState } from 'react'
|
|
586
|
+
|
|
587
|
+
function LoginForm() {
|
|
588
|
+
const [state, formAction, isPending] = useActionState(loginAction, null)
|
|
589
|
+
|
|
590
|
+
return (
|
|
591
|
+
<form action={formAction}>
|
|
592
|
+
{state?.error && <p className="text-destructive">{state.error}</p>}
|
|
593
|
+
<input name="email" type="email" />
|
|
594
|
+
<input name="password" type="password" />
|
|
595
|
+
<button disabled={isPending}>Sign In</button>
|
|
596
|
+
</form>
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Current approach:** Use React Hook Form with Zod validation.
|
|
602
|
+
|
|
603
|
+
## Anti-Patterns
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
// ❌ NEVER: Use namespace import for React
|
|
607
|
+
import * as React from 'react'
|
|
608
|
+
React.useState()
|
|
609
|
+
React.useCallback()
|
|
610
|
+
React.memo()
|
|
611
|
+
|
|
612
|
+
// ✅ CORRECT: Use named imports (project convention)
|
|
613
|
+
import { useState, useCallback, memo } from 'react'
|
|
614
|
+
// For types: import { type MouseEvent, type DragEvent } from 'react'
|
|
615
|
+
|
|
616
|
+
// ❌ NEVER: Use useEffect for data fetching
|
|
617
|
+
useEffect(() => {
|
|
618
|
+
fetch('/api/data').then(setData)
|
|
619
|
+
}, [])
|
|
620
|
+
|
|
621
|
+
// ✅ CORRECT: Use TanStack Query
|
|
622
|
+
const { data } = useQuery({ queryKey: ['data'], queryFn: fetchData })
|
|
623
|
+
|
|
624
|
+
// ❌ NEVER: Create objects/functions in render without memoization
|
|
625
|
+
<Button onClick={() => handleClick(id)} /> // New function each render
|
|
626
|
+
|
|
627
|
+
// ✅ CORRECT: Use useCallback for stable references
|
|
628
|
+
const handleButtonClick = useCallback(() => handleClick(id), [id])
|
|
629
|
+
<Button onClick={handleButtonClick} />
|
|
630
|
+
|
|
631
|
+
// ❌ NEVER: Access hooks conditionally
|
|
632
|
+
if (condition) {
|
|
633
|
+
const value = useContext(MyContext) // Breaks Rules of Hooks
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ✅ CORRECT: Always call hooks at top level
|
|
637
|
+
const value = useContext(MyContext)
|
|
638
|
+
if (condition) {
|
|
639
|
+
// use value
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ❌ NEVER: Forget 'use client' directive for client components
|
|
643
|
+
export function InteractiveComponent() {
|
|
644
|
+
const [state, setState] = useState() // Error: useState is not defined
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ✅ CORRECT: Add directive at top of file
|
|
648
|
+
'use client'
|
|
649
|
+
export function InteractiveComponent() {
|
|
650
|
+
const [state, setState] = useState()
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ❌ NEVER: Use client-side hooks in Server Components
|
|
654
|
+
// app/page.tsx (Server Component)
|
|
655
|
+
export default function Page() {
|
|
656
|
+
const [count, setCount] = useState(0) // Error!
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ✅ CORRECT: Extract to Client Component
|
|
660
|
+
// app/page.tsx
|
|
661
|
+
import { Counter } from './Counter'
|
|
662
|
+
export default function Page() {
|
|
663
|
+
return <Counter /> // Client Component handles state
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## Checklist
|
|
668
|
+
|
|
669
|
+
Before finalizing React component implementation:
|
|
670
|
+
|
|
671
|
+
- [ ] Correct component type (Server vs Client)
|
|
672
|
+
- [ ] Named imports from 'react' (never `import * as React`)
|
|
673
|
+
- [ ] 'use client' directive if using hooks
|
|
674
|
+
- [ ] TanStack Query for data fetching (not useEffect)
|
|
675
|
+
- [ ] Context with custom hook for state sharing
|
|
676
|
+
- [ ] Suspense boundaries for async operations
|
|
677
|
+
- [ ] Error boundaries for error handling
|
|
678
|
+
- [ ] useCallback for event handlers passed as props
|
|
679
|
+
- [ ] data-cy attributes on interactive elements
|
|
680
|
+
- [ ] Proper TypeScript types for props
|
|
681
|
+
- [ ] Loading states handled
|
|
682
|
+
|
|
683
|
+
## Related Skills
|
|
684
|
+
|
|
685
|
+
- `tanstack-query` - Data fetching patterns
|
|
686
|
+
- `shadcn-components` - UI component patterns
|
|
687
|
+
- `accessibility` - ARIA and keyboard patterns
|
|
688
|
+
- `cypress-selectors` - Testing selectors
|