specweave 0.23.18 → 0.24.1
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/marketplace.json +144 -45
- package/CLAUDE.md +137 -4
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts +89 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js +213 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts +29 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +109 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +2 -0
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/smart-filter.d.ts +83 -0
- package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -0
- package/dist/src/cli/helpers/smart-filter.js +265 -0
- package/dist/src/cli/helpers/smart-filter.js.map +1 -0
- package/dist/src/core/qa/quality-gate-decider.d.ts +1 -1
- package/dist/src/core/qa/quality-gate-decider.js +2 -2
- package/dist/src/core/qa/quality-gate-decider.js.map +1 -1
- package/dist/src/core/qa/risk-calculator.d.ts +2 -2
- package/dist/src/core/qa/risk-calculator.js +2 -2
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +76 -43
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/validators/ac-presence-validator.d.ts +56 -0
- package/dist/src/core/validators/ac-presence-validator.d.ts.map +1 -0
- package/dist/src/core/validators/ac-presence-validator.js +149 -0
- package/dist/src/core/validators/ac-presence-validator.js.map +1 -0
- package/dist/src/integrations/ado/area-path-mapper.d.ts +137 -0
- package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -0
- package/dist/src/integrations/ado/area-path-mapper.js +267 -0
- package/dist/src/integrations/ado/area-path-mapper.js.map +1 -0
- package/dist/src/integrations/jira/filter-processor.d.ts +126 -0
- package/dist/src/integrations/jira/filter-processor.d.ts.map +1 -0
- package/dist/src/integrations/jira/filter-processor.js +207 -0
- package/dist/src/integrations/jira/filter-processor.js.map +1 -0
- package/dist/src/integrations/jira/jira-client.d.ts +13 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +33 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/utils/ac-embedder.d.ts +63 -0
- package/dist/src/utils/ac-embedder.d.ts.map +1 -0
- package/dist/src/utils/ac-embedder.js +217 -0
- package/dist/src/utils/ac-embedder.js.map +1 -0
- package/dist/src/utils/env-manager.d.ts +86 -0
- package/dist/src/utils/env-manager.d.ts.map +1 -0
- package/dist/src/utils/env-manager.js +188 -0
- package/dist/src/utils/env-manager.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/agents/AGENTS-INDEX.md +1 -1
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +9 -9
- package/plugins/specweave/commands/specweave-do.md +37 -0
- package/plugins/specweave/commands/specweave-done.md +159 -0
- package/plugins/specweave/commands/specweave-embed-acs.md +446 -0
- package/plugins/specweave/commands/specweave-next.md +148 -3
- package/plugins/specweave/commands/specweave-qa.md +2 -2
- package/plugins/specweave/hooks/pre-increment-start.sh +168 -0
- package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-import-projects.md +331 -0
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-alternatives/commands/alternatives-analyze.md +336 -0
- package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +651 -0
- package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +420 -0
- package/plugins/specweave-alternatives/skills/spec-kit-expert/SKILL.md +487 -0
- package/plugins/specweave-backend/commands/api-scaffold.md +80 -0
- package/plugins/specweave-backend/commands/crud-generate.md +109 -0
- package/plugins/specweave-backend/commands/migration-generate.md +139 -0
- package/plugins/specweave-confluent/commands/connector-deploy.md +154 -0
- package/plugins/specweave-confluent/commands/ksqldb-query.md +179 -0
- package/plugins/specweave-confluent/commands/schema-register.md +123 -0
- package/plugins/specweave-core/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-core/commands/architecture-review.md +288 -0
- package/plugins/specweave-core/commands/code-review.md +213 -0
- package/plugins/specweave-core/commands/refactor-plan.md +249 -0
- package/plugins/specweave-core/skills/code-quality/SKILL.md +157 -0
- package/plugins/specweave-core/skills/design-patterns/SKILL.md +244 -0
- package/plugins/specweave-core/skills/software-architecture/SKILL.md +83 -0
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-cost-optimizer/commands/cost-analyze.md +360 -0
- package/plugins/specweave-cost-optimizer/commands/cost-optimize.md +480 -0
- package/plugins/specweave-cost-optimizer/skills/aws-cost-expert/SKILL.md +416 -0
- package/plugins/specweave-cost-optimizer/skills/cloud-pricing/SKILL.md +325 -0
- package/plugins/specweave-cost-optimizer/skills/cost-optimization/SKILL.md +337 -0
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/commands/diagrams-generate.md +168 -0
- package/plugins/specweave-docs/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-docs/commands/docs-generate.md +441 -0
- package/plugins/specweave-docs/commands/docs-init.md +334 -0
- package/plugins/specweave-docs/skills/docusaurus/SKILL.md +581 -0
- package/plugins/specweave-docs/skills/spec-driven-brainstorming/SKILL.md +689 -0
- package/plugins/specweave-docs/skills/technical-writing/SKILL.md +1039 -0
- package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-figma/commands/figma-import.md +690 -0
- package/plugins/specweave-figma/commands/figma-to-react.md +834 -0
- package/plugins/specweave-figma/commands/figma-tokens.md +815 -0
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +408 -0
- package/plugins/specweave-frontend/agents/frontend-architect/README.md +385 -0
- package/plugins/specweave-frontend/agents/frontend-architect/examples.md +590 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/component-template.tsx +152 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/hook-template.ts +311 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/page-template.tsx +228 -0
- package/plugins/specweave-frontend/commands/component-generate.md +510 -0
- package/plugins/specweave-frontend/commands/design-system-init.md +494 -0
- package/plugins/specweave-frontend/commands/frontend-scaffold.md +207 -0
- package/plugins/specweave-frontend/commands/nextjs-setup.md +396 -0
- package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +278 -0
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +420 -0
- package/plugins/specweave-frontend/skills/nextjs/SKILL.md +546 -0
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +212 -0
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/commands/import-projects.js +183 -0
- package/plugins/specweave-jira/commands/import-projects.md +97 -0
- package/plugins/specweave-jira/commands/import-projects.ts +288 -0
- package/plugins/specweave-jira/commands/specweave-jira-import-projects.md +298 -0
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/commands/cluster-setup.md +262 -0
- package/plugins/specweave-kubernetes/commands/deployment-generate.md +242 -0
- package/plugins/specweave-kubernetes/commands/helm-scaffold.md +333 -0
- package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/commands/app-scaffold.md +233 -0
- package/plugins/specweave-mobile/commands/build-config.md +256 -0
- package/plugins/specweave-mobile/commands/screen-generate.md +289 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-payments/commands/stripe-setup.md +931 -0
- package/plugins/specweave-payments/commands/subscription-flow.md +1193 -0
- package/plugins/specweave-payments/commands/subscription-manage.md +386 -0
- package/plugins/specweave-payments/commands/webhook-setup.md +295 -0
- package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +13 -12
- package/plugins/specweave-plugin-dev/commands/plugin-create.md +333 -0
- package/plugins/specweave-plugin-dev/commands/plugin-publish.md +339 -0
- package/plugins/specweave-plugin-dev/commands/plugin-test.md +293 -0
- package/plugins/specweave-plugin-dev/skills/claude-sdk/SKILL.md +162 -0
- package/plugins/specweave-plugin-dev/skills/marketplace-publishing/SKILL.md +263 -0
- package/plugins/specweave-plugin-dev/skills/plugin-development/SKILL.md +316 -0
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/commands/specweave-release-npm.md +110 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +168 -0
- package/plugins/specweave-testing/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +818 -0
- package/plugins/specweave-testing/agents/qa-engineer/README.md +443 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/playwright-e2e-test.ts +470 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/test-data-factory.ts +507 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/vitest-unit-test.ts +400 -0
- package/plugins/specweave-testing/agents/qa-engineer/test-strategies.md +726 -0
- package/plugins/specweave-testing/commands/e2e-setup.md +1081 -0
- package/plugins/specweave-testing/commands/test-coverage.md +979 -0
- package/plugins/specweave-testing/commands/test-generate.md +1156 -0
- package/plugins/specweave-testing/commands/test-init.md +409 -0
- package/plugins/specweave-testing/skills/e2e-playwright/SKILL.md +769 -0
- package/plugins/specweave-testing/skills/tdd-expert/SKILL.md +934 -0
- package/plugins/specweave-testing/skills/unit-testing-expert/SKILL.md +1011 -0
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-create.md +691 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-package.md +751 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-validate.md +858 -0
- package/plugins/specweave-ui/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-ui/commands/ui-automate.md +199 -0
- package/plugins/specweave-ui/commands/ui-inspect.md +70 -0
- package/plugins/specweave-ui/skills/browser-automation/SKILL.md +314 -0
- package/plugins/specweave-ui/skills/ui-testing/SKILL.md +716 -0
- package/plugins/specweave-ui/skills/visual-regression/SKILL.md +728 -0
- package/plugins/specweave/commands/check-hooks.md +0 -257
- package/plugins/specweave/commands/specweave-archive-increments.md +0 -82
- package/plugins/specweave-plugin-dev/skills/plugin-expert/SKILL.md +0 -1231
- /package/plugins/specweave/{agents/code-reviewer.md → skills/code-reviewer/SKILL.md} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Component Template
|
|
3
|
+
*
|
|
4
|
+
* This template provides a starting point for creating well-structured,
|
|
5
|
+
* type-safe React components following best practices.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* 1. Copy this template to your components directory
|
|
9
|
+
* 2. Rename file and component
|
|
10
|
+
* 3. Define props interface
|
|
11
|
+
* 4. Implement component logic
|
|
12
|
+
* 5. Add tests and Storybook stories
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { cn } from '@/lib/utils'; // Utility for className merging (clsx + tailwind-merge)
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// TYPES
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for the ComponentName component
|
|
24
|
+
*
|
|
25
|
+
* @property prop1 - Description of prop1
|
|
26
|
+
* @property prop2 - Description of prop2 (optional)
|
|
27
|
+
* @property className - Additional CSS classes (optional)
|
|
28
|
+
* @property children - React children (optional)
|
|
29
|
+
*/
|
|
30
|
+
export interface ComponentNameProps {
|
|
31
|
+
/** Required prop with specific type */
|
|
32
|
+
prop1: string;
|
|
33
|
+
|
|
34
|
+
/** Optional prop with default value */
|
|
35
|
+
prop2?: number;
|
|
36
|
+
|
|
37
|
+
/** Callback function */
|
|
38
|
+
onAction?: (value: string) => void;
|
|
39
|
+
|
|
40
|
+
/** Additional CSS classes for customization */
|
|
41
|
+
className?: string;
|
|
42
|
+
|
|
43
|
+
/** React children */
|
|
44
|
+
children?: React.ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// CONSTANTS
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
const DEFAULT_PROP2 = 42;
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// COMPONENT
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* ComponentName - Brief description of what this component does
|
|
59
|
+
*
|
|
60
|
+
* More detailed description of the component's purpose, use cases,
|
|
61
|
+
* and any important implementation notes.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* <ComponentName
|
|
66
|
+
* prop1="value"
|
|
67
|
+
* prop2={100}
|
|
68
|
+
* onAction={(value) => console.log(value)}
|
|
69
|
+
* >
|
|
70
|
+
* Content
|
|
71
|
+
* </ComponentName>
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export const ComponentName = React.forwardRef<
|
|
75
|
+
HTMLDivElement,
|
|
76
|
+
ComponentNameProps
|
|
77
|
+
>(
|
|
78
|
+
(
|
|
79
|
+
{
|
|
80
|
+
prop1,
|
|
81
|
+
prop2 = DEFAULT_PROP2,
|
|
82
|
+
onAction,
|
|
83
|
+
className,
|
|
84
|
+
children,
|
|
85
|
+
...restProps
|
|
86
|
+
},
|
|
87
|
+
ref
|
|
88
|
+
) => {
|
|
89
|
+
// ========================================================================
|
|
90
|
+
// STATE
|
|
91
|
+
// ========================================================================
|
|
92
|
+
|
|
93
|
+
const [internalState, setInternalState] = React.useState<string>('');
|
|
94
|
+
|
|
95
|
+
// ========================================================================
|
|
96
|
+
// EFFECTS
|
|
97
|
+
// ========================================================================
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
// Side effects here
|
|
101
|
+
}, [prop1]);
|
|
102
|
+
|
|
103
|
+
// ========================================================================
|
|
104
|
+
// HANDLERS
|
|
105
|
+
// ========================================================================
|
|
106
|
+
|
|
107
|
+
const handleClick = React.useCallback(() => {
|
|
108
|
+
if (onAction) {
|
|
109
|
+
onAction(internalState);
|
|
110
|
+
}
|
|
111
|
+
}, [internalState, onAction]);
|
|
112
|
+
|
|
113
|
+
// ========================================================================
|
|
114
|
+
// RENDER
|
|
115
|
+
// ========================================================================
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
// Base styles
|
|
122
|
+
'rounded-lg border bg-white p-4 shadow-sm',
|
|
123
|
+
// Conditional styles
|
|
124
|
+
prop2 > 50 && 'border-blue-500',
|
|
125
|
+
// Custom className
|
|
126
|
+
className
|
|
127
|
+
)}
|
|
128
|
+
{...restProps}
|
|
129
|
+
>
|
|
130
|
+
<h2 className="text-lg font-semibold">{prop1}</h2>
|
|
131
|
+
<p className="text-sm text-gray-600">Value: {prop2}</p>
|
|
132
|
+
|
|
133
|
+
{children && <div className="mt-4">{children}</div>}
|
|
134
|
+
|
|
135
|
+
<button
|
|
136
|
+
onClick={handleClick}
|
|
137
|
+
className="mt-4 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
|
138
|
+
>
|
|
139
|
+
Click Me
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
ComponentName.displayName = 'ComponentName';
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// EXPORTS
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
export default ComponentName;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom React Hook Template
|
|
3
|
+
*
|
|
4
|
+
* This template provides a starting point for creating reusable
|
|
5
|
+
* custom hooks following React best practices.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* 1. Copy this template to your hooks directory
|
|
9
|
+
* 2. Rename file and hook
|
|
10
|
+
* 3. Define input parameters and return type
|
|
11
|
+
* 4. Implement hook logic
|
|
12
|
+
* 5. Add tests and documentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// TYPES
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for the useCustomHook
|
|
23
|
+
*/
|
|
24
|
+
export interface UseCustomHookOptions {
|
|
25
|
+
/** Enable/disable the hook */
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
|
|
28
|
+
/** Callback when data is loaded */
|
|
29
|
+
onSuccess?: (data: string) => void;
|
|
30
|
+
|
|
31
|
+
/** Callback when error occurs */
|
|
32
|
+
onError?: (error: Error) => void;
|
|
33
|
+
|
|
34
|
+
/** Retry count */
|
|
35
|
+
retryCount?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return type of useCustomHook
|
|
40
|
+
*/
|
|
41
|
+
export interface UseCustomHookResult {
|
|
42
|
+
/** Current data */
|
|
43
|
+
data: string | null;
|
|
44
|
+
|
|
45
|
+
/** Loading state */
|
|
46
|
+
isLoading: boolean;
|
|
47
|
+
|
|
48
|
+
/** Error state */
|
|
49
|
+
error: Error | null;
|
|
50
|
+
|
|
51
|
+
/** Refetch function */
|
|
52
|
+
refetch: () => void;
|
|
53
|
+
|
|
54
|
+
/** Reset function */
|
|
55
|
+
reset: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// CONSTANTS
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
const DEFAULT_OPTIONS: Required<UseCustomHookOptions> = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
onSuccess: () => {},
|
|
65
|
+
onError: () => {},
|
|
66
|
+
retryCount: 3,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// HOOK
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* useCustomHook - Brief description of what this hook does
|
|
75
|
+
*
|
|
76
|
+
* More detailed description of the hook's purpose, use cases,
|
|
77
|
+
* and any important implementation notes.
|
|
78
|
+
*
|
|
79
|
+
* @param param1 - Description of param1
|
|
80
|
+
* @param options - Configuration options
|
|
81
|
+
* @returns Hook result with data, loading, error states and utility functions
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* function MyComponent() {
|
|
86
|
+
* const { data, isLoading, error, refetch } = useCustomHook('value', {
|
|
87
|
+
* enabled: true,
|
|
88
|
+
* onSuccess: (data) => console.log('Success:', data),
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* if (isLoading) return <Spinner />;
|
|
92
|
+
* if (error) return <Error message={error.message} />;
|
|
93
|
+
*
|
|
94
|
+
* return <div>{data}</div>;
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function useCustomHook(
|
|
99
|
+
param1: string,
|
|
100
|
+
options: UseCustomHookOptions = {}
|
|
101
|
+
): UseCustomHookResult {
|
|
102
|
+
// ========================================================================
|
|
103
|
+
// OPTIONS
|
|
104
|
+
// ========================================================================
|
|
105
|
+
|
|
106
|
+
const {
|
|
107
|
+
enabled = DEFAULT_OPTIONS.enabled,
|
|
108
|
+
onSuccess = DEFAULT_OPTIONS.onSuccess,
|
|
109
|
+
onError = DEFAULT_OPTIONS.onError,
|
|
110
|
+
retryCount = DEFAULT_OPTIONS.retryCount,
|
|
111
|
+
} = options;
|
|
112
|
+
|
|
113
|
+
// ========================================================================
|
|
114
|
+
// STATE
|
|
115
|
+
// ========================================================================
|
|
116
|
+
|
|
117
|
+
const [data, setData] = useState<string | null>(null);
|
|
118
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
119
|
+
const [error, setError] = useState<Error | null>(null);
|
|
120
|
+
|
|
121
|
+
// ========================================================================
|
|
122
|
+
// REFS
|
|
123
|
+
// ========================================================================
|
|
124
|
+
|
|
125
|
+
// Use refs for values that shouldn't trigger re-renders
|
|
126
|
+
const retryCountRef = useRef(0);
|
|
127
|
+
const mountedRef = useRef(true);
|
|
128
|
+
|
|
129
|
+
// ========================================================================
|
|
130
|
+
// CLEANUP
|
|
131
|
+
// ========================================================================
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
mountedRef.current = true;
|
|
135
|
+
|
|
136
|
+
return () => {
|
|
137
|
+
mountedRef.current = false;
|
|
138
|
+
};
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
// ========================================================================
|
|
142
|
+
// FETCH LOGIC
|
|
143
|
+
// ========================================================================
|
|
144
|
+
|
|
145
|
+
const fetchData = useCallback(async () => {
|
|
146
|
+
if (!enabled) return;
|
|
147
|
+
|
|
148
|
+
setIsLoading(true);
|
|
149
|
+
setError(null);
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Simulate async operation
|
|
153
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
if (Math.random() > 0.8) {
|
|
156
|
+
reject(new Error('Random error'));
|
|
157
|
+
} else {
|
|
158
|
+
resolve(`Data for ${param1}`);
|
|
159
|
+
}
|
|
160
|
+
}, 1000);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Only update state if component is still mounted
|
|
164
|
+
if (mountedRef.current) {
|
|
165
|
+
setData(result);
|
|
166
|
+
setIsLoading(false);
|
|
167
|
+
onSuccess(result);
|
|
168
|
+
retryCountRef.current = 0; // Reset retry count on success
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (mountedRef.current) {
|
|
172
|
+
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
173
|
+
|
|
174
|
+
// Retry logic
|
|
175
|
+
if (retryCountRef.current < retryCount) {
|
|
176
|
+
retryCountRef.current++;
|
|
177
|
+
setTimeout(() => fetchData(), 1000 * retryCountRef.current); // Exponential backoff
|
|
178
|
+
} else {
|
|
179
|
+
setError(error);
|
|
180
|
+
setIsLoading(false);
|
|
181
|
+
onError(error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}, [enabled, param1, onSuccess, onError, retryCount]);
|
|
186
|
+
|
|
187
|
+
// ========================================================================
|
|
188
|
+
// EFFECTS
|
|
189
|
+
// ========================================================================
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (enabled) {
|
|
193
|
+
fetchData();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Cleanup function
|
|
197
|
+
return () => {
|
|
198
|
+
// Cancel any pending operations
|
|
199
|
+
};
|
|
200
|
+
}, [enabled, fetchData]);
|
|
201
|
+
|
|
202
|
+
// ========================================================================
|
|
203
|
+
// HANDLERS
|
|
204
|
+
// ========================================================================
|
|
205
|
+
|
|
206
|
+
const refetch = useCallback(() => {
|
|
207
|
+
retryCountRef.current = 0; // Reset retry count
|
|
208
|
+
fetchData();
|
|
209
|
+
}, [fetchData]);
|
|
210
|
+
|
|
211
|
+
const reset = useCallback(() => {
|
|
212
|
+
setData(null);
|
|
213
|
+
setError(null);
|
|
214
|
+
setIsLoading(false);
|
|
215
|
+
retryCountRef.current = 0;
|
|
216
|
+
}, []);
|
|
217
|
+
|
|
218
|
+
// ========================================================================
|
|
219
|
+
// RETURN
|
|
220
|
+
// ========================================================================
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
data,
|
|
224
|
+
isLoading,
|
|
225
|
+
error,
|
|
226
|
+
refetch,
|
|
227
|
+
reset,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// HELPER HOOKS (Optional)
|
|
233
|
+
// ============================================================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* useDebounce - Debounce a value
|
|
237
|
+
*
|
|
238
|
+
* Useful for search inputs, window resize handlers, etc.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```tsx
|
|
242
|
+
* const [searchTerm, setSearchTerm] = useState('');
|
|
243
|
+
* const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
244
|
+
*
|
|
245
|
+
* useEffect(() => {
|
|
246
|
+
* // API call with debounced value
|
|
247
|
+
* searchAPI(debouncedSearchTerm);
|
|
248
|
+
* }, [debouncedSearchTerm]);
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
252
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
const handler = setTimeout(() => {
|
|
256
|
+
setDebouncedValue(value);
|
|
257
|
+
}, delay);
|
|
258
|
+
|
|
259
|
+
return () => {
|
|
260
|
+
clearTimeout(handler);
|
|
261
|
+
};
|
|
262
|
+
}, [value, delay]);
|
|
263
|
+
|
|
264
|
+
return debouncedValue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* useLocalStorage - Sync state with localStorage
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```tsx
|
|
272
|
+
* const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export function useLocalStorage<T>(
|
|
276
|
+
key: string,
|
|
277
|
+
initialValue: T
|
|
278
|
+
): [T, (value: T | ((val: T) => T)) => void] {
|
|
279
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
280
|
+
try {
|
|
281
|
+
const item = window.localStorage.getItem(key);
|
|
282
|
+
return item ? JSON.parse(item) : initialValue;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
285
|
+
return initialValue;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const setValue = useCallback(
|
|
290
|
+
(value: T | ((val: T) => T)) => {
|
|
291
|
+
try {
|
|
292
|
+
const valueToStore =
|
|
293
|
+
value instanceof Function ? value(storedValue) : value;
|
|
294
|
+
|
|
295
|
+
setStoredValue(valueToStore);
|
|
296
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
[key, storedValue]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return [storedValue, setValue];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// EXPORTS
|
|
309
|
+
// ============================================================================
|
|
310
|
+
|
|
311
|
+
export default useCustomHook;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js App Router Page Template
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates best practices for creating pages
|
|
5
|
+
* in Next.js 14+ App Router with Server Components.
|
|
6
|
+
*
|
|
7
|
+
* Key Features:
|
|
8
|
+
* - Server Component by default (no 'use client' needed)
|
|
9
|
+
* - SEO metadata generation
|
|
10
|
+
* - Suspense boundaries for streaming
|
|
11
|
+
* - Error handling
|
|
12
|
+
* - TypeScript types
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Metadata } from 'next';
|
|
16
|
+
import { Suspense } from 'react';
|
|
17
|
+
import { notFound } from 'next/navigation';
|
|
18
|
+
|
|
19
|
+
// Components
|
|
20
|
+
import { PageHeader } from '@/components/organisms/PageHeader';
|
|
21
|
+
import { DataTable } from '@/components/organisms/DataTable';
|
|
22
|
+
import { Skeleton } from '@/components/atoms/Skeleton';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// TYPES
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
interface PageProps {
|
|
29
|
+
params: {
|
|
30
|
+
id: string;
|
|
31
|
+
};
|
|
32
|
+
searchParams: {
|
|
33
|
+
page?: string;
|
|
34
|
+
sort?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// METADATA (SEO)
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate static metadata for SEO
|
|
44
|
+
*
|
|
45
|
+
* This function runs on the server and generates metadata
|
|
46
|
+
* for search engines and social media.
|
|
47
|
+
*/
|
|
48
|
+
export async function generateMetadata({
|
|
49
|
+
params,
|
|
50
|
+
}: PageProps): Promise<Metadata> {
|
|
51
|
+
// Fetch data needed for metadata
|
|
52
|
+
const data = await fetchData(params.id);
|
|
53
|
+
|
|
54
|
+
if (!data) {
|
|
55
|
+
return {
|
|
56
|
+
title: 'Not Found',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
title: `${data.title} | Your App Name`,
|
|
62
|
+
description: data.description,
|
|
63
|
+
openGraph: {
|
|
64
|
+
title: data.title,
|
|
65
|
+
description: data.description,
|
|
66
|
+
images: [data.imageUrl],
|
|
67
|
+
},
|
|
68
|
+
twitter: {
|
|
69
|
+
card: 'summary_large_image',
|
|
70
|
+
title: data.title,
|
|
71
|
+
description: data.description,
|
|
72
|
+
images: [data.imageUrl],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Optional: Generate static paths for Static Site Generation (SSG)
|
|
79
|
+
*
|
|
80
|
+
* Uncomment this function if you want to pre-render pages at build time.
|
|
81
|
+
*/
|
|
82
|
+
// export async function generateStaticParams() {
|
|
83
|
+
// const items = await fetchAllItems();
|
|
84
|
+
//
|
|
85
|
+
// return items.map((item) => ({
|
|
86
|
+
// id: item.id,
|
|
87
|
+
// }));
|
|
88
|
+
// }
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// DATA FETCHING
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Fetch data on the server
|
|
96
|
+
*
|
|
97
|
+
* This runs on the server, so you can safely access databases,
|
|
98
|
+
* private APIs, etc.
|
|
99
|
+
*/
|
|
100
|
+
async function fetchData(id: string) {
|
|
101
|
+
const response = await fetch(`https://api.example.com/items/${id}`, {
|
|
102
|
+
next: {
|
|
103
|
+
revalidate: 3600, // ISR: Revalidate every hour
|
|
104
|
+
// OR use 'force-cache' for static generation
|
|
105
|
+
// OR use 'no-store' for dynamic rendering
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return response.json();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function fetchRelatedData(filters: { page: number; sort: string }) {
|
|
117
|
+
const response = await fetch(
|
|
118
|
+
`https://api.example.com/related?page=${filters.page}&sort=${filters.sort}`,
|
|
119
|
+
{
|
|
120
|
+
next: { revalidate: 60 }, // Revalidate every minute
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return response.json();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// PAGE COMPONENT
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Page Component (Server Component by default)
|
|
133
|
+
*
|
|
134
|
+
* Server Components:
|
|
135
|
+
* - Run on the server only
|
|
136
|
+
* - Can access backend resources directly
|
|
137
|
+
* - Don't add JavaScript to the client bundle
|
|
138
|
+
* - Can't use hooks like useState, useEffect
|
|
139
|
+
* - Can't add event handlers
|
|
140
|
+
*/
|
|
141
|
+
export default async function Page({ params, searchParams }: PageProps) {
|
|
142
|
+
// ========================================================================
|
|
143
|
+
// DATA FETCHING (Server-side)
|
|
144
|
+
// ========================================================================
|
|
145
|
+
|
|
146
|
+
// Parse search params
|
|
147
|
+
const page = searchParams.page ? parseInt(searchParams.page) : 1;
|
|
148
|
+
const sort = searchParams.sort || 'desc';
|
|
149
|
+
|
|
150
|
+
// Fetch data in parallel for performance
|
|
151
|
+
const [mainData, relatedData] = await Promise.all([
|
|
152
|
+
fetchData(params.id),
|
|
153
|
+
fetchRelatedData({ page, sort }),
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
// Handle not found
|
|
157
|
+
if (!mainData) {
|
|
158
|
+
notFound(); // Renders app/not-found.tsx
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ========================================================================
|
|
162
|
+
// RENDER
|
|
163
|
+
// ========================================================================
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="container mx-auto px-4 py-8">
|
|
167
|
+
{/* Page Header */}
|
|
168
|
+
<PageHeader
|
|
169
|
+
title={mainData.title}
|
|
170
|
+
description={mainData.description}
|
|
171
|
+
breadcrumbs={[
|
|
172
|
+
{ label: 'Home', href: '/' },
|
|
173
|
+
{ label: 'Items', href: '/items' },
|
|
174
|
+
{ label: mainData.title, href: `/items/${params.id}` },
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
|
|
178
|
+
{/* Main Content */}
|
|
179
|
+
<section className="mt-8">
|
|
180
|
+
<h2 className="text-2xl font-bold">Details</h2>
|
|
181
|
+
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
182
|
+
<div>
|
|
183
|
+
<p className="text-gray-600">ID: {mainData.id}</p>
|
|
184
|
+
<p className="text-gray-600">Created: {mainData.createdAt}</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</section>
|
|
188
|
+
|
|
189
|
+
{/* Related Data with Suspense */}
|
|
190
|
+
<section className="mt-12">
|
|
191
|
+
<h2 className="text-2xl font-bold">Related Items</h2>
|
|
192
|
+
<Suspense fallback={<Skeleton className="mt-4 h-96" />}>
|
|
193
|
+
<DataTable
|
|
194
|
+
data={relatedData.items}
|
|
195
|
+
columns={['id', 'name', 'status']}
|
|
196
|
+
pagination={{
|
|
197
|
+
currentPage: page,
|
|
198
|
+
totalPages: relatedData.totalPages,
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
</Suspense>
|
|
202
|
+
</section>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// CLIENT COMPONENT EXAMPLE (if needed)
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* If you need interactivity, create a separate Client Component:
|
|
213
|
+
*
|
|
214
|
+
* 'use client';
|
|
215
|
+
*
|
|
216
|
+
* export function InteractiveSection({ initialData }) {
|
|
217
|
+
* const [state, setState] = useState(initialData);
|
|
218
|
+
*
|
|
219
|
+
* return (
|
|
220
|
+
* <div>
|
|
221
|
+
* <button onClick={() => setState(...)}>Click me</button>
|
|
222
|
+
* </div>
|
|
223
|
+
* );
|
|
224
|
+
* }
|
|
225
|
+
*
|
|
226
|
+
* Then use it in the Server Component:
|
|
227
|
+
* <InteractiveSection initialData={mainData} />
|
|
228
|
+
*/
|