create-fluxstack 1.0.12 → 1.0.14

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 (215) 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/loader.ts +8 -32
  90. package/core/config/runtime-config.ts +3 -3
  91. package/core/config/schema.ts +84 -49
  92. package/core/framework/server.ts +30 -0
  93. package/core/index.ts +25 -0
  94. package/core/live/ComponentRegistry.ts +399 -0
  95. package/core/live/types.ts +164 -0
  96. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  97. package/core/plugins/built-in/live-components/index.ts +27 -0
  98. package/core/plugins/built-in/logger/index.ts +1 -1
  99. package/core/plugins/built-in/monitoring/index.ts +1 -1
  100. package/core/plugins/built-in/static/index.ts +1 -1
  101. package/core/plugins/built-in/swagger/index.ts +1 -1
  102. package/core/plugins/built-in/vite/index.ts +1 -1
  103. package/core/plugins/dependency-manager.ts +384 -0
  104. package/core/plugins/index.ts +5 -1
  105. package/core/plugins/manager.ts +7 -3
  106. package/core/plugins/registry.ts +88 -10
  107. package/core/plugins/types.ts +11 -11
  108. package/core/server/framework.ts +43 -0
  109. package/core/server/index.ts +11 -1
  110. package/core/server/live/ComponentRegistry.ts +1017 -0
  111. package/core/server/live/FileUploadManager.ts +272 -0
  112. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  113. package/core/server/live/SingleConnectionManager.ts +0 -0
  114. package/core/server/live/StateSignature.ts +644 -0
  115. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  116. package/core/server/live/websocket-plugin.ts +435 -0
  117. package/core/server/middleware/errorHandling.ts +141 -0
  118. package/core/server/middleware/index.ts +16 -0
  119. package/core/server/plugins/static-files-plugin.ts +232 -0
  120. package/core/server/services/BaseService.ts +95 -0
  121. package/core/server/services/ServiceContainer.ts +144 -0
  122. package/core/server/services/index.ts +9 -0
  123. package/core/templates/create-project.ts +46 -2
  124. package/core/testing/index.ts +10 -0
  125. package/core/testing/setup.ts +74 -0
  126. package/core/types/build.ts +38 -14
  127. package/core/types/types.ts +319 -0
  128. package/core/utils/env-runtime.ts +7 -0
  129. package/core/utils/errors/handlers.ts +264 -39
  130. package/core/utils/errors/index.ts +528 -18
  131. package/core/utils/errors/middleware.ts +114 -0
  132. package/core/utils/logger/formatters.ts +222 -0
  133. package/core/utils/logger/index.ts +167 -48
  134. package/core/utils/logger/middleware.ts +253 -0
  135. package/core/utils/logger/performance.ts +384 -0
  136. package/core/utils/logger/transports.ts +365 -0
  137. package/create-fluxstack.ts +296 -296
  138. package/fluxstack.config.ts +17 -1
  139. package/package-template.json +66 -66
  140. package/package.json +31 -6
  141. package/public/README.md +16 -0
  142. package/vite.config.ts +29 -14
  143. package/.claude/settings.local.json +0 -74
  144. package/.github/workflows/ci-build-tests.yml +0 -480
  145. package/.github/workflows/dependency-management.yml +0 -324
  146. package/.github/workflows/release-validation.yml +0 -355
  147. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  148. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  149. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  150. package/CLAUDE.md +0 -200
  151. package/Dockerfile +0 -58
  152. package/Dockerfile.backend +0 -52
  153. package/Dockerfile.frontend +0 -54
  154. package/README-Docker.md +0 -85
  155. package/ai-context/00-QUICK-START.md +0 -86
  156. package/ai-context/README.md +0 -88
  157. package/ai-context/development/eden-treaty-guide.md +0 -362
  158. package/ai-context/development/patterns.md +0 -382
  159. package/ai-context/development/plugins-guide.md +0 -572
  160. package/ai-context/examples/crud-complete.md +0 -626
  161. package/ai-context/project/architecture.md +0 -399
  162. package/ai-context/project/overview.md +0 -213
  163. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  164. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  165. package/ai-context/reference/environment-vars.md +0 -384
  166. package/ai-context/reference/troubleshooting.md +0 -407
  167. package/app/client/src/components/TestPage.tsx +0 -453
  168. package/bun.lock +0 -1063
  169. package/bunfig.toml +0 -16
  170. package/core/__tests__/integration.test.ts +0 -227
  171. package/core/build/index.ts +0 -186
  172. package/core/config/__tests__/config-loader.test.ts +0 -591
  173. package/core/config/__tests__/config-merger.test.ts +0 -657
  174. package/core/config/__tests__/env-converter.test.ts +0 -372
  175. package/core/config/__tests__/env-processor.test.ts +0 -431
  176. package/core/config/__tests__/env.test.ts +0 -452
  177. package/core/config/__tests__/integration.test.ts +0 -418
  178. package/core/config/__tests__/loader.test.ts +0 -331
  179. package/core/config/__tests__/schema.test.ts +0 -129
  180. package/core/config/__tests__/validator.test.ts +0 -318
  181. package/core/framework/__tests__/server.test.ts +0 -233
  182. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  183. package/core/plugins/__tests__/manager.test.ts +0 -398
  184. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  185. package/core/plugins/__tests__/registry.test.ts +0 -335
  186. package/core/utils/__tests__/errors.test.ts +0 -139
  187. package/core/utils/__tests__/helpers.test.ts +0 -297
  188. package/core/utils/__tests__/logger.test.ts +0 -141
  189. package/create-test-app.ts +0 -156
  190. package/docker-compose.microservices.yml +0 -75
  191. package/docker-compose.simple.yml +0 -57
  192. package/docker-compose.yml +0 -71
  193. package/eslint.config.js +0 -23
  194. package/flux-cli.ts +0 -214
  195. package/nginx-lb.conf +0 -37
  196. package/publish.sh +0 -63
  197. package/run-clean.ts +0 -26
  198. package/run-env-tests.ts +0 -313
  199. package/tailwind.config.js +0 -34
  200. package/tests/__mocks__/api.ts +0 -56
  201. package/tests/fixtures/users.ts +0 -69
  202. package/tests/integration/api/users.routes.test.ts +0 -221
  203. package/tests/setup.ts +0 -29
  204. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  205. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  206. package/tests/unit/app/client/eden-api.test.ts +0 -186
  207. package/tests/unit/app/client/simple.test.tsx +0 -23
  208. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  209. package/tests/unit/core/create-project.test.ts.skip +0 -95
  210. package/tests/unit/core/framework.test.ts +0 -144
  211. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  212. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  213. package/tests/utils/test-helpers.ts +0 -61
  214. package/vitest.config.ts +0 -50
  215. 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
+ }