create-fluxstack 1.0.13 → 1.0.15

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.
Files changed (214) hide show
  1. package/.env.example +29 -29
  2. package/app/client/README.md +69 -69
  3. package/app/client/index.html +14 -13
  4. package/app/client/src/App.tsx +157 -524
  5. package/app/client/src/components/ErrorBoundary.tsx +107 -0
  6. package/app/client/src/components/ErrorDisplay.css +365 -0
  7. package/app/client/src/components/ErrorDisplay.tsx +258 -0
  8. package/app/client/src/components/FluxStackConfig.tsx +1321 -0
  9. package/app/client/src/components/HybridLiveCounter.tsx +140 -0
  10. package/app/client/src/components/LiveClock.tsx +286 -0
  11. package/app/client/src/components/MainLayout.tsx +390 -0
  12. package/app/client/src/components/SidebarNavigation.tsx +391 -0
  13. package/app/client/src/components/StateDemo.tsx +178 -0
  14. package/app/client/src/components/SystemMonitor.tsx +1038 -0
  15. package/app/client/src/components/Teste.tsx +104 -0
  16. package/app/client/src/components/UserProfile.tsx +809 -0
  17. package/app/client/src/hooks/useAuth.ts +39 -0
  18. package/app/client/src/hooks/useNotifications.ts +56 -0
  19. package/app/client/src/lib/eden-api.ts +189 -53
  20. package/app/client/src/lib/errors.ts +340 -0
  21. package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
  22. package/app/client/src/lib/index.ts +45 -0
  23. package/app/client/src/main.tsx +3 -2
  24. package/app/client/src/pages/ApiDocs.tsx +182 -0
  25. package/app/client/src/pages/Demo.tsx +174 -0
  26. package/app/client/src/pages/HybridLive.tsx +263 -0
  27. package/app/client/src/pages/Overview.tsx +155 -0
  28. package/app/client/src/store/README.md +43 -0
  29. package/app/client/src/store/index.ts +16 -0
  30. package/app/client/src/store/slices/uiSlice.ts +151 -0
  31. package/app/client/src/store/slices/userSlice.ts +161 -0
  32. package/app/client/src/test/README.md +257 -0
  33. package/app/client/src/test/setup.ts +70 -0
  34. package/app/client/src/test/types.ts +12 -0
  35. package/app/client/src/vite-env.d.ts +1 -1
  36. package/app/client/tsconfig.app.json +44 -43
  37. package/app/client/tsconfig.json +7 -7
  38. package/app/client/tsconfig.node.json +25 -25
  39. package/app/client/zustand-setup.md +65 -0
  40. package/app/server/controllers/users.controller.ts +68 -68
  41. package/app/server/index.ts +9 -1
  42. package/app/server/live/CounterComponent.ts +191 -0
  43. package/app/server/live/FluxStackConfig.ts +529 -0
  44. package/app/server/live/LiveClockComponent.ts +214 -0
  45. package/app/server/live/SidebarNavigation.ts +156 -0
  46. package/app/server/live/SystemMonitor.ts +594 -0
  47. package/app/server/live/SystemMonitorIntegration.ts +151 -0
  48. package/app/server/live/TesteComponent.ts +87 -0
  49. package/app/server/live/UserProfileComponent.ts +135 -0
  50. package/app/server/live/register-components.ts +28 -0
  51. package/app/server/middleware/auth.ts +136 -0
  52. package/app/server/middleware/errorHandling.ts +250 -0
  53. package/app/server/middleware/index.ts +10 -0
  54. package/app/server/middleware/rateLimit.ts +193 -0
  55. package/app/server/middleware/requestLogging.ts +215 -0
  56. package/app/server/middleware/validation.ts +270 -0
  57. package/app/server/routes/index.ts +14 -2
  58. package/app/server/routes/upload.ts +92 -0
  59. package/app/server/routes/users.routes.ts +2 -9
  60. package/app/server/services/NotificationService.ts +302 -0
  61. package/app/server/services/UserService.ts +222 -0
  62. package/app/server/services/index.ts +46 -0
  63. package/core/cli/commands/plugin-deps.ts +263 -0
  64. package/core/cli/generators/README.md +339 -0
  65. package/core/cli/generators/component.ts +770 -0
  66. package/core/cli/generators/controller.ts +299 -0
  67. package/core/cli/generators/index.ts +144 -0
  68. package/core/cli/generators/interactive.ts +228 -0
  69. package/core/cli/generators/prompts.ts +83 -0
  70. package/core/cli/generators/route.ts +513 -0
  71. package/core/cli/generators/service.ts +465 -0
  72. package/core/cli/generators/template-engine.ts +154 -0
  73. package/core/cli/generators/types.ts +71 -0
  74. package/core/cli/generators/utils.ts +192 -0
  75. package/core/cli/index.ts +69 -0
  76. package/core/cli/plugin-discovery.ts +16 -85
  77. package/core/client/fluxstack.ts +17 -0
  78. package/core/client/hooks/index.ts +7 -0
  79. package/core/client/hooks/state-validator.ts +130 -0
  80. package/core/client/hooks/useAuth.ts +49 -0
  81. package/core/client/hooks/useChunkedUpload.ts +258 -0
  82. package/core/client/hooks/useHybridLiveComponent.ts +967 -0
  83. package/core/client/hooks/useWebSocket.ts +373 -0
  84. package/core/client/index.ts +47 -0
  85. package/core/client/state/createStore.ts +193 -0
  86. package/core/client/state/index.ts +15 -0
  87. package/core/config/env-dynamic.ts +1 -1
  88. package/core/config/env.ts +2 -1
  89. package/core/config/runtime-config.ts +3 -3
  90. package/core/config/schema.ts +84 -49
  91. package/core/framework/server.ts +30 -0
  92. package/core/index.ts +25 -0
  93. package/core/live/ComponentRegistry.ts +399 -0
  94. package/core/live/types.ts +164 -0
  95. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  96. package/core/plugins/built-in/live-components/index.ts +27 -0
  97. package/core/plugins/built-in/logger/index.ts +1 -1
  98. package/core/plugins/built-in/monitoring/index.ts +1 -1
  99. package/core/plugins/built-in/static/index.ts +1 -1
  100. package/core/plugins/built-in/swagger/index.ts +1 -1
  101. package/core/plugins/built-in/vite/index.ts +1 -1
  102. package/core/plugins/dependency-manager.ts +384 -0
  103. package/core/plugins/index.ts +5 -1
  104. package/core/plugins/manager.ts +7 -3
  105. package/core/plugins/registry.ts +88 -10
  106. package/core/plugins/types.ts +11 -11
  107. package/core/server/framework.ts +43 -0
  108. package/core/server/index.ts +11 -1
  109. package/core/server/live/ComponentRegistry.ts +1017 -0
  110. package/core/server/live/FileUploadManager.ts +272 -0
  111. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  112. package/core/server/live/SingleConnectionManager.ts +0 -0
  113. package/core/server/live/StateSignature.ts +644 -0
  114. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  115. package/core/server/live/websocket-plugin.ts +435 -0
  116. package/core/server/middleware/errorHandling.ts +141 -0
  117. package/core/server/middleware/index.ts +16 -0
  118. package/core/server/plugins/static-files-plugin.ts +232 -0
  119. package/core/server/services/BaseService.ts +95 -0
  120. package/core/server/services/ServiceContainer.ts +144 -0
  121. package/core/server/services/index.ts +9 -0
  122. package/core/templates/create-project.ts +196 -33
  123. package/core/testing/index.ts +10 -0
  124. package/core/testing/setup.ts +74 -0
  125. package/core/types/build.ts +38 -14
  126. package/core/types/types.ts +319 -0
  127. package/core/utils/env-runtime.ts +7 -0
  128. package/core/utils/errors/handlers.ts +264 -39
  129. package/core/utils/errors/index.ts +528 -18
  130. package/core/utils/errors/middleware.ts +114 -0
  131. package/core/utils/logger/formatters.ts +222 -0
  132. package/core/utils/logger/index.ts +167 -48
  133. package/core/utils/logger/middleware.ts +253 -0
  134. package/core/utils/logger/performance.ts +384 -0
  135. package/core/utils/logger/transports.ts +365 -0
  136. package/create-fluxstack.ts +296 -296
  137. package/fluxstack.config.ts +17 -1
  138. package/package-template.json +66 -66
  139. package/package.json +31 -6
  140. package/public/README.md +16 -0
  141. package/vite.config.ts +29 -14
  142. package/.claude/settings.local.json +0 -74
  143. package/.github/workflows/ci-build-tests.yml +0 -480
  144. package/.github/workflows/dependency-management.yml +0 -324
  145. package/.github/workflows/release-validation.yml +0 -355
  146. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  147. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  148. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  149. package/CLAUDE.md +0 -200
  150. package/Dockerfile +0 -58
  151. package/Dockerfile.backend +0 -52
  152. package/Dockerfile.frontend +0 -54
  153. package/README-Docker.md +0 -85
  154. package/ai-context/00-QUICK-START.md +0 -86
  155. package/ai-context/README.md +0 -88
  156. package/ai-context/development/eden-treaty-guide.md +0 -362
  157. package/ai-context/development/patterns.md +0 -382
  158. package/ai-context/development/plugins-guide.md +0 -572
  159. package/ai-context/examples/crud-complete.md +0 -626
  160. package/ai-context/project/architecture.md +0 -399
  161. package/ai-context/project/overview.md +0 -213
  162. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  163. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  164. package/ai-context/reference/environment-vars.md +0 -384
  165. package/ai-context/reference/troubleshooting.md +0 -407
  166. package/app/client/src/components/TestPage.tsx +0 -453
  167. package/bun.lock +0 -1063
  168. package/bunfig.toml +0 -16
  169. package/core/__tests__/integration.test.ts +0 -227
  170. package/core/build/index.ts +0 -186
  171. package/core/config/__tests__/config-loader.test.ts +0 -554
  172. package/core/config/__tests__/config-merger.test.ts +0 -657
  173. package/core/config/__tests__/env-converter.test.ts +0 -372
  174. package/core/config/__tests__/env-processor.test.ts +0 -431
  175. package/core/config/__tests__/env.test.ts +0 -452
  176. package/core/config/__tests__/integration.test.ts +0 -418
  177. package/core/config/__tests__/loader.test.ts +0 -331
  178. package/core/config/__tests__/schema.test.ts +0 -129
  179. package/core/config/__tests__/validator.test.ts +0 -318
  180. package/core/framework/__tests__/server.test.ts +0 -233
  181. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  182. package/core/plugins/__tests__/manager.test.ts +0 -398
  183. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  184. package/core/plugins/__tests__/registry.test.ts +0 -335
  185. package/core/utils/__tests__/errors.test.ts +0 -139
  186. package/core/utils/__tests__/helpers.test.ts +0 -297
  187. package/core/utils/__tests__/logger.test.ts +0 -141
  188. package/create-test-app.ts +0 -156
  189. package/docker-compose.microservices.yml +0 -75
  190. package/docker-compose.simple.yml +0 -57
  191. package/docker-compose.yml +0 -71
  192. package/eslint.config.js +0 -23
  193. package/flux-cli.ts +0 -214
  194. package/nginx-lb.conf +0 -37
  195. package/publish.sh +0 -63
  196. package/run-clean.ts +0 -26
  197. package/run-env-tests.ts +0 -313
  198. package/tailwind.config.js +0 -34
  199. package/tests/__mocks__/api.ts +0 -56
  200. package/tests/fixtures/users.ts +0 -69
  201. package/tests/integration/api/users.routes.test.ts +0 -221
  202. package/tests/setup.ts +0 -29
  203. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  204. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  205. package/tests/unit/app/client/eden-api.test.ts +0 -186
  206. package/tests/unit/app/client/simple.test.tsx +0 -23
  207. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  208. package/tests/unit/core/create-project.test.ts.skip +0 -95
  209. package/tests/unit/core/framework.test.ts +0 -144
  210. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  211. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  212. package/tests/utils/test-helpers.ts +0 -61
  213. package/vitest.config.ts +0 -50
  214. package/workspace.json +0 -6
@@ -0,0 +1,770 @@
1
+ import type { Generator } from "./index.js"
2
+ import type { GeneratorContext, GeneratorOptions, Template } from "./types.js"
3
+ import { templateEngine } from "./template-engine.js"
4
+
5
+ export class ComponentGenerator implements Generator {
6
+ name = 'component'
7
+ description = 'Generate a new React component'
8
+
9
+ async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
10
+ const template = this.getTemplate(options.template)
11
+
12
+ if (template.hooks?.beforeGenerate) {
13
+ await template.hooks.beforeGenerate(context, options)
14
+ }
15
+
16
+ const files = await templateEngine.processTemplate(template, context, options)
17
+
18
+ if (options.dryRun) {
19
+ console.log(`\n📋 Would generate component '${options.name}':\n`)
20
+ for (const file of files) {
21
+ console.log(`${file.action === 'create' ? '📄' : '✏️'} ${file.path}`)
22
+ }
23
+ return
24
+ }
25
+
26
+ await templateEngine.generateFiles(files, options.dryRun)
27
+
28
+ if (template.hooks?.afterGenerate) {
29
+ const filePaths = files.map(f => f.path)
30
+ await template.hooks.afterGenerate(context, options, filePaths)
31
+ }
32
+
33
+ console.log(`\n✅ Generated component '${options.name}' with ${files.length} files`)
34
+ }
35
+
36
+ private getTemplate(templateName?: string): Template {
37
+ switch (templateName) {
38
+ case 'functional':
39
+ return this.getFunctionalTemplate()
40
+ case 'page':
41
+ return this.getPageTemplate()
42
+ case 'form':
43
+ return this.getFormTemplate()
44
+ case 'full':
45
+ return this.getFullTemplate()
46
+ default:
47
+ return this.getBasicTemplate()
48
+ }
49
+ }
50
+
51
+ private getBasicTemplate(): Template {
52
+ return {
53
+ name: 'basic-component',
54
+ description: 'Basic React component with TypeScript',
55
+ files: [
56
+ {
57
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.tsx',
58
+ content: `import React from 'react'
59
+ import './{{pascalName}}.css'
60
+
61
+ export interface {{pascalName}}Props {
62
+ className?: string
63
+ children?: React.ReactNode
64
+ }
65
+
66
+ export const {{pascalName}}: React.FC<{{pascalName}}Props> = ({
67
+ className = '',
68
+ children,
69
+ ...props
70
+ }) => {
71
+ return (
72
+ <div className={\`{{kebabName}} \${className}\`.trim()} {...props}>
73
+ <h2>{{pascalName}} Component</h2>
74
+ {children}
75
+ </div>
76
+ )
77
+ }
78
+
79
+ export default {{pascalName}}
80
+ `
81
+ },
82
+ {
83
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.css',
84
+ content: `.{{kebabName}} {
85
+ /* Add your styles here */
86
+ padding: 1rem;
87
+ border: 1px solid #e2e8f0;
88
+ border-radius: 0.5rem;
89
+ background-color: #ffffff;
90
+ }
91
+
92
+ .{{kebabName}} h2 {
93
+ margin: 0 0 1rem 0;
94
+ font-size: 1.25rem;
95
+ font-weight: 600;
96
+ color: #1a202c;
97
+ }
98
+
99
+ /* Responsive styles */
100
+ @media (max-width: 768px) {
101
+ .{{kebabName}} {
102
+ padding: 0.75rem;
103
+ }
104
+
105
+ .{{kebabName}} h2 {
106
+ font-size: 1.125rem;
107
+ }
108
+ }
109
+ `
110
+ },
111
+ {
112
+ path: 'app/client/src/components/{{pascalName}}/index.ts',
113
+ content: `export { {{pascalName}}, type {{pascalName}}Props } from './{{pascalName}}'
114
+ export { default } from './{{pascalName}}'
115
+ `
116
+ }
117
+ ],
118
+ hooks: {
119
+ afterGenerate: async (context, options, files) => {
120
+ context.logger.info(`Generated component files:`)
121
+ files.forEach(file => {
122
+ context.logger.info(` - ${file}`)
123
+ })
124
+ context.logger.info(`\nUsage example:`)
125
+ context.logger.info(`import { ${options.name} } from './components/${options.name}'`)
126
+ context.logger.info(`\n<${options.name}>Content here</${options.name}>`)
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ private getFunctionalTemplate(): Template {
133
+ return {
134
+ name: 'functional-component',
135
+ description: 'Functional component with hooks',
136
+ files: [
137
+ {
138
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.tsx',
139
+ content: `import React, { useState, useEffect } from 'react'
140
+ import './{{pascalName}}.css'
141
+
142
+ export interface {{pascalName}}Props {
143
+ className?: string
144
+ onAction?: (data: any) => void
145
+ }
146
+
147
+ export const {{pascalName}}: React.FC<{{pascalName}}Props> = ({
148
+ className = '',
149
+ onAction,
150
+ ...props
151
+ }) => {
152
+ const [loading, setLoading] = useState(false)
153
+ const [data, setData] = useState<any>(null)
154
+
155
+ useEffect(() => {
156
+ // Component initialization logic
157
+ console.log('{{pascalName}} mounted')
158
+
159
+ return () => {
160
+ // Cleanup logic
161
+ console.log('{{pascalName}} unmounted')
162
+ }
163
+ }, [])
164
+
165
+ const handleAction = async () => {
166
+ setLoading(true)
167
+ try {
168
+ // Simulate async operation
169
+ await new Promise(resolve => setTimeout(resolve, 1000))
170
+ const result = { message: 'Action completed', timestamp: new Date() }
171
+ setData(result)
172
+ onAction?.(result)
173
+ } catch (error) {
174
+ console.error('Action failed:', error)
175
+ } finally {
176
+ setLoading(false)
177
+ }
178
+ }
179
+
180
+ return (
181
+ <div className={\`{{kebabName}} \${className}\`.trim()} {...props}>
182
+ <h2>{{pascalName}}</h2>
183
+
184
+ <div className="{{kebabName}}__content">
185
+ {data && (
186
+ <div className="{{kebabName}}__data">
187
+ <p>Last action: {data.message}</p>
188
+ <small>{data.timestamp?.toLocaleString()}</small>
189
+ </div>
190
+ )}
191
+
192
+ <button
193
+ className="{{kebabName}}__button"
194
+ onClick={handleAction}
195
+ disabled={loading}
196
+ >
197
+ {loading ? 'Loading...' : 'Perform Action'}
198
+ </button>
199
+ </div>
200
+ </div>
201
+ )
202
+ }
203
+
204
+ export default {{pascalName}}
205
+ `
206
+ },
207
+ {
208
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.css',
209
+ content: `.{{kebabName}} {
210
+ padding: 1.5rem;
211
+ border: 1px solid #e2e8f0;
212
+ border-radius: 0.75rem;
213
+ background-color: #ffffff;
214
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
215
+ }
216
+
217
+ .{{kebabName}} h2 {
218
+ margin: 0 0 1rem 0;
219
+ font-size: 1.5rem;
220
+ font-weight: 600;
221
+ color: #2d3748;
222
+ }
223
+
224
+ .{{kebabName}}__content {
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 1rem;
228
+ }
229
+
230
+ .{{kebabName}}__data {
231
+ padding: 1rem;
232
+ background-color: #f7fafc;
233
+ border-radius: 0.5rem;
234
+ border-left: 4px solid #4299e1;
235
+ }
236
+
237
+ .{{kebabName}}__data p {
238
+ margin: 0 0 0.5rem 0;
239
+ font-weight: 500;
240
+ color: #2d3748;
241
+ }
242
+
243
+ .{{kebabName}}__data small {
244
+ color: #718096;
245
+ }
246
+
247
+ .{{kebabName}}__button {
248
+ padding: 0.75rem 1.5rem;
249
+ background-color: #4299e1;
250
+ color: white;
251
+ border: none;
252
+ border-radius: 0.5rem;
253
+ font-weight: 500;
254
+ cursor: pointer;
255
+ transition: background-color 0.2s;
256
+ }
257
+
258
+ .{{kebabName}}__button:hover:not(:disabled) {
259
+ background-color: #3182ce;
260
+ }
261
+
262
+ .{{kebabName}}__button:disabled {
263
+ background-color: #a0aec0;
264
+ cursor: not-allowed;
265
+ }
266
+
267
+ /* Responsive styles */
268
+ @media (max-width: 768px) {
269
+ .{{kebabName}} {
270
+ padding: 1rem;
271
+ }
272
+
273
+ .{{kebabName}} h2 {
274
+ font-size: 1.25rem;
275
+ }
276
+ }
277
+ `
278
+ },
279
+ {
280
+ path: 'app/client/src/components/{{pascalName}}/index.ts',
281
+ content: `export { {{pascalName}}, type {{pascalName}}Props } from './{{pascalName}}'
282
+ export { default } from './{{pascalName}}'
283
+ `
284
+ }
285
+ ]
286
+ }
287
+ }
288
+
289
+ private getPageTemplate(): Template {
290
+ return {
291
+ name: 'page-component',
292
+ description: 'Page component with layout and SEO',
293
+ files: [
294
+ {
295
+ path: 'app/client/src/pages/{{pascalName}}Page/{{pascalName}}Page.tsx',
296
+ content: `import React, { useEffect } from 'react'
297
+ import './{{pascalName}}Page.css'
298
+
299
+ export interface {{pascalName}}PageProps {
300
+ className?: string
301
+ }
302
+
303
+ export const {{pascalName}}Page: React.FC<{{pascalName}}PageProps> = ({
304
+ className = '',
305
+ ...props
306
+ }) => {
307
+ useEffect(() => {
308
+ // Set page title
309
+ document.title = '{{pascalName}} - FluxStack App'
310
+
311
+ // Set meta description
312
+ const metaDescription = document.querySelector('meta[name="description"]')
313
+ if (metaDescription) {
314
+ metaDescription.setAttribute('content', '{{pascalName}} page description')
315
+ }
316
+ }, [])
317
+
318
+ return (
319
+ <div className={\`{{kebabName}}-page \${className}\`.trim()} {...props}>
320
+ <header className="{{kebabName}}-page__header">
321
+ <h1>{{pascalName}}</h1>
322
+ <p className="{{kebabName}}-page__subtitle">
323
+ Welcome to the {{pascalName}} page
324
+ </p>
325
+ </header>
326
+
327
+ <main className="{{kebabName}}-page__main">
328
+ <section className="{{kebabName}}-page__section">
329
+ <h2>Section Title</h2>
330
+ <p>Add your page content here.</p>
331
+ </section>
332
+ </main>
333
+
334
+ <footer className="{{kebabName}}-page__footer">
335
+ <p>&copy; {new Date().getFullYear()} FluxStack App</p>
336
+ </footer>
337
+ </div>
338
+ )
339
+ }
340
+
341
+ export default {{pascalName}}Page
342
+ `
343
+ },
344
+ {
345
+ path: 'app/client/src/pages/{{pascalName}}Page/{{pascalName}}Page.css',
346
+ content: `.{{kebabName}}-page {
347
+ min-height: 100vh;
348
+ display: flex;
349
+ flex-direction: column;
350
+ }
351
+
352
+ .{{kebabName}}-page__header {
353
+ padding: 2rem;
354
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
355
+ color: white;
356
+ text-align: center;
357
+ }
358
+
359
+ .{{kebabName}}-page__header h1 {
360
+ margin: 0 0 0.5rem 0;
361
+ font-size: 2.5rem;
362
+ font-weight: 700;
363
+ }
364
+
365
+ .{{kebabName}}-page__subtitle {
366
+ margin: 0;
367
+ font-size: 1.125rem;
368
+ opacity: 0.9;
369
+ }
370
+
371
+ .{{kebabName}}-page__main {
372
+ flex: 1;
373
+ padding: 2rem;
374
+ max-width: 1200px;
375
+ margin: 0 auto;
376
+ width: 100%;
377
+ }
378
+
379
+ .{{kebabName}}-page__section {
380
+ margin-bottom: 2rem;
381
+ }
382
+
383
+ .{{kebabName}}-page__section h2 {
384
+ margin: 0 0 1rem 0;
385
+ font-size: 1.75rem;
386
+ font-weight: 600;
387
+ color: #2d3748;
388
+ }
389
+
390
+ .{{kebabName}}-page__section p {
391
+ margin: 0;
392
+ line-height: 1.6;
393
+ color: #4a5568;
394
+ }
395
+
396
+ .{{kebabName}}-page__footer {
397
+ padding: 1rem 2rem;
398
+ background-color: #f7fafc;
399
+ border-top: 1px solid #e2e8f0;
400
+ text-align: center;
401
+ color: #718096;
402
+ }
403
+
404
+ /* Responsive styles */
405
+ @media (max-width: 768px) {
406
+ .{{kebabName}}-page__header {
407
+ padding: 1.5rem 1rem;
408
+ }
409
+
410
+ .{{kebabName}}-page__header h1 {
411
+ font-size: 2rem;
412
+ }
413
+
414
+ .{{kebabName}}-page__main {
415
+ padding: 1.5rem 1rem;
416
+ }
417
+
418
+ .{{kebabName}}-page__section h2 {
419
+ font-size: 1.5rem;
420
+ }
421
+ }
422
+ `
423
+ },
424
+ {
425
+ path: 'app/client/src/pages/{{pascalName}}Page/index.ts',
426
+ content: `export { {{pascalName}}Page, type {{pascalName}}PageProps } from './{{pascalName}}Page'
427
+ export { default } from './{{pascalName}}Page'
428
+ `
429
+ }
430
+ ]
431
+ }
432
+ }
433
+
434
+ private getFormTemplate(): Template {
435
+ return {
436
+ name: 'form-component',
437
+ description: 'Form component with validation',
438
+ files: [
439
+ {
440
+ path: 'app/client/src/components/{{pascalName}}Form/{{pascalName}}Form.tsx',
441
+ content: `import React, { useState } from 'react'
442
+ import './{{pascalName}}Form.css'
443
+
444
+ export interface {{pascalName}}FormData {
445
+ name: string
446
+ email: string
447
+ message: string
448
+ }
449
+
450
+ export interface {{pascalName}}FormProps {
451
+ className?: string
452
+ onSubmit?: (data: {{pascalName}}FormData) => void | Promise<void>
453
+ initialData?: Partial<{{pascalName}}FormData>
454
+ }
455
+
456
+ export const {{pascalName}}Form: React.FC<{{pascalName}}FormProps> = ({
457
+ className = '',
458
+ onSubmit,
459
+ initialData = {},
460
+ ...props
461
+ }) => {
462
+ const [formData, setFormData] = useState<{{pascalName}}FormData>({
463
+ name: initialData.name || '',
464
+ email: initialData.email || '',
465
+ message: initialData.message || ''
466
+ })
467
+
468
+ const [errors, setErrors] = useState<Partial<{{pascalName}}FormData>>({})
469
+ const [loading, setLoading] = useState(false)
470
+
471
+ const validateForm = (): boolean => {
472
+ const newErrors: Partial<{{pascalName}}FormData> = {}
473
+
474
+ if (!formData.name.trim()) {
475
+ newErrors.name = 'Name is required'
476
+ }
477
+
478
+ if (!formData.email.trim()) {
479
+ newErrors.email = 'Email is required'
480
+ } else if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.email)) {
481
+ newErrors.email = 'Please enter a valid email'
482
+ }
483
+
484
+ if (!formData.message.trim()) {
485
+ newErrors.message = 'Message is required'
486
+ }
487
+
488
+ setErrors(newErrors)
489
+ return Object.keys(newErrors).length === 0
490
+ }
491
+
492
+ const handleSubmit = async (e: React.FormEvent) => {
493
+ e.preventDefault()
494
+
495
+ if (!validateForm()) {
496
+ return
497
+ }
498
+
499
+ setLoading(true)
500
+ try {
501
+ await onSubmit?.(formData)
502
+ // Reset form on successful submission
503
+ setFormData({ name: '', email: '', message: '' })
504
+ setErrors({})
505
+ } catch (error) {
506
+ console.error('Form submission failed:', error)
507
+ } finally {
508
+ setLoading(false)
509
+ }
510
+ }
511
+
512
+ const handleChange = (field: keyof {{pascalName}}FormData) => (
513
+ e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
514
+ ) => {
515
+ setFormData(prev => ({ ...prev, [field]: e.target.value }))
516
+ // Clear error when user starts typing
517
+ if (errors[field]) {
518
+ setErrors(prev => ({ ...prev, [field]: undefined }))
519
+ }
520
+ }
521
+
522
+ return (
523
+ <form
524
+ className={\`{{kebabName}}-form \${className}\`.trim()}
525
+ onSubmit={handleSubmit}
526
+ {...props}
527
+ >
528
+ <h2>{{pascalName}} Form</h2>
529
+
530
+ <div className="{{kebabName}}-form__field">
531
+ <label htmlFor="{{kebabName}}-name">Name *</label>
532
+ <input
533
+ id="{{kebabName}}-name"
534
+ type="text"
535
+ value={formData.name}
536
+ onChange={handleChange('name')}
537
+ className={errors.name ? 'error' : ''}
538
+ placeholder="Enter your name"
539
+ />
540
+ {errors.name && <span className="{{kebabName}}-form__error">{errors.name}</span>}
541
+ </div>
542
+
543
+ <div className="{{kebabName}}-form__field">
544
+ <label htmlFor="{{kebabName}}-email">Email *</label>
545
+ <input
546
+ id="{{kebabName}}-email"
547
+ type="email"
548
+ value={formData.email}
549
+ onChange={handleChange('email')}
550
+ className={errors.email ? 'error' : ''}
551
+ placeholder="Enter your email"
552
+ />
553
+ {errors.email && <span className="{{kebabName}}-form__error">{errors.email}</span>}
554
+ </div>
555
+
556
+ <div className="{{kebabName}}-form__field">
557
+ <label htmlFor="{{kebabName}}-message">Message *</label>
558
+ <textarea
559
+ id="{{kebabName}}-message"
560
+ value={formData.message}
561
+ onChange={handleChange('message')}
562
+ className={errors.message ? 'error' : ''}
563
+ placeholder="Enter your message"
564
+ rows={4}
565
+ />
566
+ {errors.message && <span className="{{kebabName}}-form__error">{errors.message}</span>}
567
+ </div>
568
+
569
+ <button
570
+ type="submit"
571
+ className="{{kebabName}}-form__submit"
572
+ disabled={loading}
573
+ >
574
+ {loading ? 'Submitting...' : 'Submit'}
575
+ </button>
576
+ </form>
577
+ )
578
+ }
579
+
580
+ export default {{pascalName}}Form
581
+ `
582
+ },
583
+ {
584
+ path: 'app/client/src/components/{{pascalName}}Form/{{pascalName}}Form.css',
585
+ content: `.{{kebabName}}-form {
586
+ max-width: 500px;
587
+ padding: 2rem;
588
+ border: 1px solid #e2e8f0;
589
+ border-radius: 0.75rem;
590
+ background-color: #ffffff;
591
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
592
+ }
593
+
594
+ .{{kebabName}}-form h2 {
595
+ margin: 0 0 1.5rem 0;
596
+ font-size: 1.5rem;
597
+ font-weight: 600;
598
+ color: #2d3748;
599
+ text-align: center;
600
+ }
601
+
602
+ .{{kebabName}}-form__field {
603
+ margin-bottom: 1.5rem;
604
+ }
605
+
606
+ .{{kebabName}}-form__field label {
607
+ display: block;
608
+ margin-bottom: 0.5rem;
609
+ font-weight: 500;
610
+ color: #374151;
611
+ }
612
+
613
+ .{{kebabName}}-form__field input,
614
+ .{{kebabName}}-form__field textarea {
615
+ width: 100%;
616
+ padding: 0.75rem;
617
+ border: 1px solid #d1d5db;
618
+ border-radius: 0.5rem;
619
+ font-size: 1rem;
620
+ transition: border-color 0.2s, box-shadow 0.2s;
621
+ box-sizing: border-box;
622
+ }
623
+
624
+ .{{kebabName}}-form__field input:focus,
625
+ .{{kebabName}}-form__field textarea:focus {
626
+ outline: none;
627
+ border-color: #4299e1;
628
+ box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
629
+ }
630
+
631
+ .{{kebabName}}-form__field input.error,
632
+ .{{kebabName}}-form__field textarea.error {
633
+ border-color: #e53e3e;
634
+ box-shadow: 0 0 0 3px rgba(229, 62, 62, 0.1);
635
+ }
636
+
637
+ .{{kebabName}}-form__error {
638
+ display: block;
639
+ margin-top: 0.25rem;
640
+ font-size: 0.875rem;
641
+ color: #e53e3e;
642
+ }
643
+
644
+ .{{kebabName}}-form__submit {
645
+ width: 100%;
646
+ padding: 0.75rem 1.5rem;
647
+ background-color: #4299e1;
648
+ color: white;
649
+ border: none;
650
+ border-radius: 0.5rem;
651
+ font-size: 1rem;
652
+ font-weight: 500;
653
+ cursor: pointer;
654
+ transition: background-color 0.2s;
655
+ }
656
+
657
+ .{{kebabName}}-form__submit:hover:not(:disabled) {
658
+ background-color: #3182ce;
659
+ }
660
+
661
+ .{{kebabName}}-form__submit:disabled {
662
+ background-color: #a0aec0;
663
+ cursor: not-allowed;
664
+ }
665
+
666
+ /* Responsive styles */
667
+ @media (max-width: 768px) {
668
+ .{{kebabName}}-form {
669
+ padding: 1.5rem;
670
+ }
671
+
672
+ .{{kebabName}}-form h2 {
673
+ font-size: 1.25rem;
674
+ }
675
+ }
676
+ `
677
+ },
678
+ {
679
+ path: 'app/client/src/components/{{pascalName}}Form/index.ts',
680
+ content: `export { {{pascalName}}Form, type {{pascalName}}FormProps, type {{pascalName}}FormData } from './{{pascalName}}Form'
681
+ export { default } from './{{pascalName}}Form'
682
+ `
683
+ }
684
+ ]
685
+ }
686
+ }
687
+
688
+ private getFullTemplate(): Template {
689
+ return {
690
+ name: 'full-component',
691
+ description: 'Complete component with tests and stories',
692
+ files: [
693
+ ...this.getBasicTemplate().files,
694
+ {
695
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.test.tsx',
696
+ content: `import React from 'react'
697
+ import { render, screen } from '@testing-library/react'
698
+ import { {{pascalName}} } from './{{pascalName}}'
699
+
700
+ describe('{{pascalName}}', () => {
701
+ it('renders without crashing', () => {
702
+ render(<{{pascalName}} />)
703
+ expect(screen.getByText('{{pascalName}} Component')).toBeInTheDocument()
704
+ })
705
+
706
+ it('applies custom className', () => {
707
+ const { container } = render(<{{pascalName}} className="custom-class" />)
708
+ expect(container.firstChild).toHaveClass('{{kebabName}}', 'custom-class')
709
+ })
710
+
711
+ it('renders children content', () => {
712
+ render(
713
+ <{{pascalName}}>
714
+ <p>Test content</p>
715
+ </{{pascalName}}>
716
+ )
717
+ expect(screen.getByText('Test content')).toBeInTheDocument()
718
+ })
719
+ })
720
+ `
721
+ },
722
+ {
723
+ path: 'app/client/src/components/{{pascalName}}/{{pascalName}}.stories.tsx',
724
+ content: `import type { Meta, StoryObj } from '@storybook/react'
725
+ import { {{pascalName}} } from './{{pascalName}}'
726
+
727
+ const meta: Meta<typeof {{pascalName}}> = {
728
+ title: 'Components/{{pascalName}}',
729
+ component: {{pascalName}},
730
+ parameters: {
731
+ layout: 'centered',
732
+ },
733
+ tags: ['autodocs'],
734
+ argTypes: {
735
+ className: {
736
+ control: 'text',
737
+ description: 'Additional CSS classes'
738
+ }
739
+ },
740
+ }
741
+
742
+ export default meta
743
+ type Story = StoryObj<typeof meta>
744
+
745
+ export const Default: Story = {
746
+ args: {},
747
+ }
748
+
749
+ export const WithCustomClass: Story = {
750
+ args: {
751
+ className: 'custom-styling',
752
+ },
753
+ }
754
+
755
+ export const WithChildren: Story = {
756
+ args: {
757
+ children: (
758
+ <div>
759
+ <p>This is custom content inside the {{pascalName}} component.</p>
760
+ <button>Click me</button>
761
+ </div>
762
+ ),
763
+ },
764
+ }
765
+ `
766
+ }
767
+ ]
768
+ }
769
+ }
770
+ }