@prmvx/frontend-forge 0.1.0

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/dist/cli.js +662 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/templates/architectures/next/enterprise/src/components/common/Button.tsx +17 -0
  6. package/dist/templates/architectures/next/enterprise/src/components/home/HomeView.tsx +41 -0
  7. package/dist/templates/architectures/next/enterprise/src/components/layout/Footer.tsx +9 -0
  8. package/dist/templates/architectures/next/enterprise/src/components/layout/Header.tsx +19 -0
  9. package/dist/templates/architectures/next/enterprise/src/config/env.ts +3 -0
  10. package/dist/templates/architectures/next/enterprise/src/constants/routes.ts +4 -0
  11. package/dist/templates/architectures/next/enterprise/src/features/dashboard/components/DashboardSummary.tsx +22 -0
  12. package/dist/templates/architectures/next/enterprise/src/features/dashboard/hooks/useDashboard.ts +18 -0
  13. package/dist/templates/architectures/next/enterprise/src/features/dashboard/services/dashboard-api.ts +22 -0
  14. package/dist/templates/architectures/next/enterprise/src/features/dashboard/types/index.ts +4 -0
  15. package/dist/templates/architectures/next/enterprise/src/hooks/useDebounce.ts +14 -0
  16. package/dist/templates/architectures/next/enterprise/src/hooks/useToggle.ts +15 -0
  17. package/dist/templates/architectures/next/enterprise/src/providers/index.tsx +17 -0
  18. package/dist/templates/architectures/next/enterprise/src/providers/theme-provider.tsx +11 -0
  19. package/dist/templates/architectures/next/enterprise/src/services/api.ts +22 -0
  20. package/dist/templates/architectures/next/enterprise/src/store/app-store.tsx +33 -0
  21. package/dist/templates/architectures/next/enterprise/src/types/user.ts +5 -0
  22. package/dist/templates/architectures/next/enterprise/src/utils/cn.ts +3 -0
  23. package/dist/templates/architectures/next/enterprise/src/utils/formatDate.ts +7 -0
  24. package/dist/templates/architectures/next/feature/src/config/env.ts +3 -0
  25. package/dist/templates/architectures/next/feature/src/features/home/components/HomeHero.tsx +38 -0
  26. package/dist/templates/architectures/next/feature/src/features/home/components/HomePage.tsx +19 -0
  27. package/dist/templates/architectures/next/feature/src/features/home/constants/routes.ts +4 -0
  28. package/dist/templates/architectures/next/feature/src/features/home/hooks/useHome.ts +18 -0
  29. package/dist/templates/architectures/next/feature/src/features/home/services/home-api.ts +22 -0
  30. package/dist/templates/architectures/next/feature/src/features/home/types/index.ts +4 -0
  31. package/dist/templates/architectures/next/feature/src/features/profile/components/ProfileCard.tsx +19 -0
  32. package/dist/templates/architectures/next/feature/src/features/profile/hooks/useProfile.ts +18 -0
  33. package/dist/templates/architectures/next/feature/src/features/profile/services/profile-api.ts +22 -0
  34. package/dist/templates/architectures/next/feature/src/features/profile/types/index.ts +5 -0
  35. package/dist/templates/architectures/next/feature/src/shared/components/common/Button.tsx +17 -0
  36. package/dist/templates/architectures/next/feature/src/shared/components/layout/Footer.tsx +9 -0
  37. package/dist/templates/architectures/next/feature/src/shared/components/layout/Header.tsx +19 -0
  38. package/dist/templates/architectures/next/feature/src/shared/hooks/useDebounce.ts +14 -0
  39. package/dist/templates/architectures/next/feature/src/shared/hooks/useToggle.ts +15 -0
  40. package/dist/templates/architectures/next/feature/src/shared/providers/index.tsx +8 -0
  41. package/dist/templates/architectures/next/feature/src/shared/providers/theme-provider.tsx +7 -0
  42. package/dist/templates/architectures/next/feature/src/shared/utils/cn.ts +3 -0
  43. package/dist/templates/architectures/next/feature/src/shared/utils/formatDate.ts +7 -0
  44. package/dist/templates/architectures/next/standard/src/components/common/Button.tsx +17 -0
  45. package/dist/templates/architectures/next/standard/src/components/home/HomeView.tsx +53 -0
  46. package/dist/templates/architectures/next/standard/src/components/layout/Footer.tsx +9 -0
  47. package/dist/templates/architectures/next/standard/src/components/layout/Header.tsx +19 -0
  48. package/dist/templates/architectures/next/standard/src/config/env.ts +3 -0
  49. package/dist/templates/architectures/next/standard/src/constants/routes.ts +4 -0
  50. package/dist/templates/architectures/next/standard/src/hooks/useDebounce.ts +14 -0
  51. package/dist/templates/architectures/next/standard/src/hooks/useToggle.ts +15 -0
  52. package/dist/templates/architectures/next/standard/src/providers/index.tsx +12 -0
  53. package/dist/templates/architectures/next/standard/src/providers/theme-provider.tsx +11 -0
  54. package/dist/templates/architectures/next/standard/src/services/api.ts +22 -0
  55. package/dist/templates/architectures/next/standard/src/types/user.ts +5 -0
  56. package/dist/templates/architectures/next/standard/src/utils/cn.ts +3 -0
  57. package/dist/templates/architectures/next/standard/src/utils/formatDate.ts +7 -0
  58. package/dist/templates/modules/axios/module.ts +7 -0
  59. package/dist/templates/modules/core/module.ts +5 -0
  60. package/dist/templates/modules/core/src/components/common/Button.tsx +12 -0
  61. package/dist/templates/modules/core/src/components/layout/Header.tsx +7 -0
  62. package/dist/templates/modules/core/src/config/env.ts +3 -0
  63. package/dist/templates/modules/core/src/constants/index.ts +1 -0
  64. package/dist/templates/modules/core/src/constants/routes.ts +3 -0
  65. package/dist/templates/modules/core/src/hooks/index.ts +1 -0
  66. package/dist/templates/modules/core/src/hooks/useToggle.ts +13 -0
  67. package/dist/templates/modules/core/src/providers/index.tsx +9 -0
  68. package/dist/templates/modules/core/src/services/api.ts +5 -0
  69. package/dist/templates/modules/core/src/utils/cn.ts +3 -0
  70. package/dist/templates/modules/core/src/utils/index.ts +1 -0
  71. package/dist/templates/modules/zustand/module.ts +7 -0
  72. package/package.json +70 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/init.ts","../src/utils/detectProject.ts","../src/utils/detectProjectStructure.ts","../src/utils/copyArchitecture.ts","../src/utils/copyTemplate.ts","../src/utils/resolveTemplatePath.ts","../src/utils/copyModules.ts","../src/utils/extendProjectFiles.ts","../src/utils/readModule.ts","../src/utils/installDependencies.ts","../src/utils/detectPackageManager.ts","../src/utils/logger.ts","../src/prompts/architecture.ts","../src/config/architectures.ts","../src/prompts/modules.ts","../src/config/modules.ts","../src/prompts/packageManager.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\nimport { Command } from \"commander\";\r\nimport { initCommand } from \"./commands/init.js\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"frontend-init\")\r\n .description(\"Initialize frontend architecture\")\r\n .version(\"1.0.0\");\r\n\r\nprogram.command(\"init\").action(initCommand);\r\n\r\nprogram.parse();","import fs from \"fs\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport { intro, isCancel, cancel, spinner } from \"@clack/prompts\";\r\nimport { validateProject } from \"../utils/detectProject.js\";\r\nimport { detectProjectStructure } from \"../utils/detectProjectStructure.js\";\r\nimport { copyArchitecture } from \"../utils/copyArchitecture.js\";\r\nimport { copyModules } from \"../utils/copyModules.js\";\r\nimport { extendProjectFiles } from \"../utils/extendProjectFiles.js\";\r\nimport { readModule } from \"../utils/readModule.js\";\r\nimport { installDependencies } from \"../utils/installDependencies.js\";\r\nimport {\r\n detectPackageManager,\r\n validatePackageManager,\r\n} from \"../utils/detectPackageManager.js\";\r\nimport { logger } from \"../utils/logger.js\";\r\nimport { selectArchitecture } from \"../prompts/architecture.js\";\r\nimport { selectModules } from \"../prompts/modules.js\";\r\nimport { selectPackageManager } from \"../prompts/packageManager.js\";\r\nimport { architectures } from \"../config/architectures.js\";\r\nimport { modules } from \"../config/modules.js\";\r\nimport type { PackageManager } from \"../types/package-manager.js\";\r\n\r\nfunction getVersion(): string {\r\n const dir = path.dirname(fileURLToPath(import.meta.url));\r\n const pkg = JSON.parse(\r\n fs.readFileSync(path.join(dir, \"../package.json\"), \"utf-8\")\r\n );\r\n return pkg.version;\r\n}\r\n\r\nfunction getArchitectureLabel(value: string): string {\r\n return architectures.find((item) => item.value === value)?.label ?? value;\r\n}\r\n\r\nfunction getModuleLabel(value: string): string {\r\n return modules.find((item) => item.value === value)?.label ?? value;\r\n}\r\n\r\nfunction printSummary(options: {\r\n architecture: string;\r\n selectedModules: string[];\r\n packageManager: PackageManager;\r\n dependenciesInstalled: number;\r\n filesCreated: number;\r\n filesSkipped: number;\r\n}) {\r\n console.log(\"\");\r\n logger.success(\"Project initialized successfully.\");\r\n console.log(\"\");\r\n console.log(\"Framework:\");\r\n console.log(\"Next.js\");\r\n console.log(\"\");\r\n console.log(\"Architecture:\");\r\n console.log(getArchitectureLabel(options.architecture));\r\n console.log(\"\");\r\n console.log(\"Modules:\");\r\n for (const module of options.selectedModules) {\r\n console.log(getModuleLabel(module));\r\n }\r\n console.log(\"\");\r\n console.log(\"Package Manager:\");\r\n console.log(options.packageManager);\r\n console.log(\"\");\r\n console.log(\"Dependencies Installed:\");\r\n console.log(options.dependenciesInstalled);\r\n console.log(\"\");\r\n console.log(\"Files Created:\");\r\n console.log(options.filesCreated);\r\n console.log(\"\");\r\n console.log(\"Files Skipped:\");\r\n console.log(options.filesSkipped);\r\n console.log(\"\");\r\n console.log(\"Done.\");\r\n console.log(\"\");\r\n logger.success(\"Happy Coding!\");\r\n}\r\n\r\nexport async function initCommand() {\r\n intro(\"Frontend Init\");\r\n logger.info(\"Production Ready Frontend Starter\");\r\n logger.info(`Version ${getVersion()}`);\r\n\r\n const detectSpinner = spinner();\r\n detectSpinner.start(\"Detecting project...\");\r\n\r\n try {\r\n validateProject();\r\n detectSpinner.stop(\"Success\");\r\n } catch (error) {\r\n detectSpinner.stop(\"Failed\");\r\n logger.error(\r\n error instanceof Error ? error.message : \"Project validation failed.\"\r\n );\r\n return;\r\n }\r\n\r\n const architectureSpinner = spinner();\r\n architectureSpinner.start(\"Selecting architecture...\");\r\n\r\n const architecture = await selectArchitecture();\r\n\r\n if (isCancel(architecture) || !architecture) {\r\n architectureSpinner.stop(\"Failed\");\r\n cancel(\"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n architectureSpinner.stop(\"Success\");\r\n\r\n const modulesSpinner = spinner();\r\n modulesSpinner.start(\"Selecting modules...\");\r\n\r\n const selectedModules = await selectModules();\r\n\r\n if (isCancel(selectedModules) || selectedModules.length === 0) {\r\n modulesSpinner.stop(\"Failed\");\r\n cancel(\"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n modulesSpinner.stop(\"Success\");\r\n\r\n const structure = detectProjectStructure();\r\n\r\n const moduleDefinitions = await Promise.all(\r\n selectedModules.map((module) => readModule(module))\r\n );\r\n\r\n const detected = detectPackageManager();\r\n let packageManager: PackageManager;\r\n\r\n if (detected) {\r\n packageManager = detected;\r\n logger.info(`Detected: ${packageManager}`);\r\n } else {\r\n const selected = await selectPackageManager();\r\n\r\n if (isCancel(selected) || !selected) {\r\n cancel(\"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n packageManager = selected;\r\n logger.info(`Using ${packageManager}`);\r\n }\r\n\r\n try {\r\n await validatePackageManager(packageManager);\r\n } catch (error) {\r\n logger.error(\r\n error instanceof Error ? error.message : \"Package manager not available.\"\r\n );\r\n return;\r\n }\r\n\r\n let dependenciesInstalled = 0;\r\n const installSpinner = spinner();\r\n installSpinner.start(\"Installing dependencies...\");\r\n\r\n try {\r\n dependenciesInstalled = await installDependencies(\r\n moduleDefinitions,\r\n packageManager\r\n );\r\n installSpinner.stop(\"Success\");\r\n } catch (error) {\r\n installSpinner.stop(\"Failed\");\r\n logger.error(\r\n error instanceof Error\r\n ? error.message\r\n : \"Failed to install dependencies.\\nPlease resolve the issue and run the command again.\"\r\n );\r\n return;\r\n }\r\n\r\n const architectureCopySpinner = spinner();\r\n architectureCopySpinner.start(\"Copying architecture...\");\r\n\r\n let architectureStats;\r\n\r\n try {\r\n architectureStats = await copyArchitecture(\r\n \"next\",\r\n architecture as string,\r\n structure\r\n );\r\n architectureCopySpinner.stop(\"Success\");\r\n } catch (error) {\r\n architectureCopySpinner.stop(\"Failed\");\r\n logger.error(\r\n error instanceof Error ? error.message : \"Failed to copy architecture.\"\r\n );\r\n return;\r\n }\r\n\r\n const modulesCopySpinner = spinner();\r\n modulesCopySpinner.start(\"Copying modules...\");\r\n\r\n let moduleStats;\r\n\r\n try {\r\n moduleStats = await copyModules(\r\n selectedModules as string[],\r\n structure,\r\n architecture as string\r\n );\r\n modulesCopySpinner.stop(\"Success\");\r\n } catch (error) {\r\n modulesCopySpinner.stop(\"Failed\");\r\n logger.error(\r\n error instanceof Error ? error.message : \"Failed to copy modules.\"\r\n );\r\n return;\r\n }\r\n\r\n try {\r\n await extendProjectFiles(architecture as string, structure);\r\n } catch (error) {\r\n logger.error(\r\n error instanceof Error ? error.message : \"Failed to extend project files.\"\r\n );\r\n return;\r\n }\r\n\r\n const finishingSpinner = spinner();\r\n finishingSpinner.start(\"Finishing...\");\r\n\r\n printSummary({\r\n architecture: architecture as string,\r\n selectedModules: selectedModules as string[],\r\n packageManager,\r\n dependenciesInstalled,\r\n filesCreated: architectureStats.created + moduleStats.created,\r\n filesSkipped: architectureStats.skipped + moduleStats.skipped,\r\n });\r\n\r\n finishingSpinner.stop(\"Success\");\r\n}\r\n","import fs from \"fs\";\r\n\r\nexport function validateProject() {\r\n if (!fs.existsSync(\"package.json\")) {\r\n throw new Error(\r\n \"Could not find package.json.\\nPlease run this command inside an existing Next.js project.\"\r\n );\r\n }\r\n\r\n const pkg = JSON.parse(\r\n fs.readFileSync(\"package.json\", \"utf-8\")\r\n );\r\n\r\n if (!pkg.dependencies?.next) {\r\n throw new Error(\r\n \"Next.js project not detected.\\nCurrent version only supports Next.js.\"\r\n );\r\n }\r\n}\r\n","import fs from \"fs\";\r\nimport path from \"path\";\r\n\r\nexport type ProjectStructure = {\r\n useSrc: boolean;\r\n root: string;\r\n appDir: string;\r\n};\r\n\r\nexport function detectProjectStructure(): ProjectStructure {\r\n const cwd = process.cwd();\r\n\r\n if (fs.existsSync(path.join(cwd, \"app\"))) {\r\n return {\r\n useSrc: false,\r\n root: \"\",\r\n appDir: \"app\",\r\n };\r\n }\r\n\r\n if (fs.existsSync(path.join(cwd, \"src\", \"app\"))) {\r\n return {\r\n useSrc: true,\r\n root: \"src\",\r\n appDir: \"src/app\",\r\n };\r\n }\r\n\r\n if (fs.existsSync(path.join(cwd, \"src\"))) {\r\n return {\r\n useSrc: true,\r\n root: \"src\",\r\n appDir: \"src/app\",\r\n };\r\n }\r\n\r\n return {\r\n useSrc: false,\r\n root: \"\",\r\n appDir: \"app\",\r\n };\r\n}\r\n","import path from \"path\";\r\nimport { copyTemplateDir, type CopyStats } from \"./copyTemplate.js\";\r\nimport type { ProjectStructure } from \"./detectProjectStructure.js\";\r\n\r\nexport async function copyArchitecture(\r\n framework: string,\r\n architecture: string,\r\n structure: ProjectStructure\r\n): Promise<CopyStats> {\r\n return copyTemplateDir(\r\n path.join(\"architectures\", framework, architecture),\r\n { structure, architecture }\r\n );\r\n}\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport type { ProjectStructure } from \"./detectProjectStructure.js\";\r\nimport { resolveTemplatePath, shouldSkipTemplatePath } from \"./resolveTemplatePath.js\";\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nexport type CopyStats = {\r\n created: number;\r\n skipped: number;\r\n failed: number;\r\n};\r\n\r\ntype CopyOptions = {\r\n exclude?: (filePath: string) => boolean;\r\n structure: ProjectStructure;\r\n architecture?: string;\r\n remapCore?: boolean;\r\n};\r\n\r\nasync function getAllFiles(dir: string): Promise<string[]> {\r\n const entries = await fs.readdir(dir, { withFileTypes: true });\r\n const files: string[] = [];\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n\r\n if (entry.isDirectory()) {\r\n files.push(...(await getAllFiles(fullPath)));\r\n } else if (entry.isFile()) {\r\n files.push(fullPath);\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\nexport async function copyTemplateDir(\r\n relativePath: string,\r\n options: CopyOptions\r\n): Promise<CopyStats> {\r\n const templatePath = path.resolve(__dirname, \"../templates\", relativePath);\r\n const targetPath = process.cwd();\r\n const files = await getAllFiles(templatePath);\r\n const createdDirs = new Set<string>();\r\n\r\n let created = 0;\r\n let skipped = 0;\r\n let failed = 0;\r\n\r\n for (const file of files) {\r\n if (options.exclude?.(file)) {\r\n continue;\r\n }\r\n\r\n const relativeFile = path.relative(templatePath, file);\r\n\r\n if (shouldSkipTemplatePath(relativeFile)) {\r\n continue;\r\n }\r\n\r\n const destRelative = resolveTemplatePath(relativeFile, {\r\n structure: options.structure,\r\n architecture: options.architecture,\r\n remapCore: options.remapCore,\r\n });\r\n\r\n if (!destRelative) {\r\n continue;\r\n }\r\n\r\n const dest = path.join(targetPath, destRelative);\r\n const destDir = path.dirname(dest);\r\n\r\n try {\r\n if (await fs.pathExists(dest)) {\r\n skipped++;\r\n continue;\r\n }\r\n\r\n if (!createdDirs.has(destDir)) {\r\n await fs.ensureDir(destDir);\r\n createdDirs.add(destDir);\r\n }\r\n\r\n await fs.copy(file, dest);\r\n created++;\r\n } catch {\r\n failed++;\r\n }\r\n }\r\n\r\n return { created, skipped, failed };\r\n}\r\n","import path from \"path\";\r\nimport type { ProjectStructure } from \"./detectProjectStructure.js\";\r\n\r\nfunction normalizePath(relativePath: string): string {\r\n return relativePath.split(path.sep).join(\"/\");\r\n}\r\n\r\nfunction remapCoreModulePath(\r\n normalizedPath: string,\r\n architecture: string\r\n): string {\r\n if (architecture === \"feature\") {\r\n if (normalizedPath.startsWith(\"src/components/\")) {\r\n return normalizedPath.replace(\r\n \"src/components/\",\r\n \"src/shared/components/\"\r\n );\r\n }\r\n\r\n if (normalizedPath.startsWith(\"src/hooks/\")) {\r\n return normalizedPath.replace(\"src/hooks/\", \"src/shared/hooks/\");\r\n }\r\n\r\n if (normalizedPath.startsWith(\"src/utils/\")) {\r\n return normalizedPath.replace(\"src/utils/\", \"src/shared/utils/\");\r\n }\r\n\r\n if (normalizedPath.startsWith(\"src/providers/\")) {\r\n return normalizedPath.replace(\"src/providers/\", \"src/shared/providers/\");\r\n }\r\n\r\n if (normalizedPath.startsWith(\"src/services/\")) {\r\n return normalizedPath.replace(\"src/services/\", \"src/features/home/services/\");\r\n }\r\n\r\n if (normalizedPath.startsWith(\"src/constants/\")) {\r\n return normalizedPath.replace(\"src/constants/\", \"src/features/home/constants/\");\r\n }\r\n }\r\n\r\n return normalizedPath;\r\n}\r\n\r\ntype ResolveOptions = {\r\n structure: ProjectStructure;\r\n architecture?: string;\r\n remapCore?: boolean;\r\n};\r\n\r\nexport function resolveTemplatePath(\r\n relativePath: string,\r\n options: ResolveOptions\r\n): string | null {\r\n let normalized = normalizePath(relativePath);\r\n\r\n if (normalized === \"src/app\" || normalized.startsWith(\"src/app/\")) {\r\n return null;\r\n }\r\n\r\n if (options.remapCore && options.architecture) {\r\n normalized = remapCoreModulePath(normalized, options.architecture);\r\n }\r\n\r\n if (!options.structure.useSrc && normalized.startsWith(\"src/\")) {\r\n normalized = normalized.slice(4);\r\n }\r\n\r\n return normalized.split(\"/\").join(path.sep);\r\n}\r\n\r\nexport function shouldSkipTemplatePath(relativePath: string): boolean {\r\n const normalized = normalizePath(relativePath);\r\n\r\n return normalized === \"src/app\" || normalized.startsWith(\"src/app/\");\r\n}\r\n","import path from \"path\";\r\nimport { copyTemplateDir, type CopyStats } from \"./copyTemplate.js\";\r\nimport type { ProjectStructure } from \"./detectProjectStructure.js\";\r\n\r\nexport async function copyModules(\r\n modules: string[],\r\n structure: ProjectStructure,\r\n architecture: string\r\n): Promise<CopyStats> {\r\n const stats: CopyStats = { created: 0, skipped: 0, failed: 0 };\r\n\r\n for (const module of modules) {\r\n const result = await copyTemplateDir(path.join(\"modules\", module), {\r\n structure,\r\n architecture,\r\n remapCore: module === \"core\",\r\n exclude: (filePath) => filePath.endsWith(\"module.ts\"),\r\n });\r\n\r\n stats.created += result.created;\r\n stats.skipped += result.skipped;\r\n stats.failed += result.failed;\r\n }\r\n\r\n return stats;\r\n}\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\nimport type { ProjectStructure } from \"./detectProjectStructure.js\";\r\n\r\nfunction getAppFilePath(\r\n structure: ProjectStructure,\r\n fileName: string\r\n): string {\r\n return path.join(process.cwd(), structure.appDir, fileName);\r\n}\r\n\r\nfunction getProvidersImport(architecture: string): string {\r\n if (architecture === \"feature\") {\r\n return \"@/shared/providers\";\r\n }\r\n\r\n return \"@/providers\";\r\n}\r\n\r\nfunction getPageImport(architecture: string): string {\r\n if (architecture === \"feature\") {\r\n return \"@/features/home/components/HomePage\";\r\n }\r\n\r\n return \"@/components/home/HomeView\";\r\n}\r\n\r\nfunction getPageComponent(architecture: string): string {\r\n if (architecture === \"feature\") {\r\n return \"HomePage\";\r\n }\r\n\r\n return \"HomeView\";\r\n}\r\n\r\nexport async function extendProjectFiles(\r\n architecture: string,\r\n structure: ProjectStructure\r\n) {\r\n await extendLayout(structure, architecture);\r\n await extendPage(structure, architecture);\r\n}\r\n\r\nasync function extendLayout(\r\n structure: ProjectStructure,\r\n architecture: string\r\n) {\r\n const layoutPath = getAppFilePath(structure, \"layout.tsx\");\r\n\r\n if (!(await fs.pathExists(layoutPath))) {\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(layoutPath, \"utf-8\");\r\n const providersImport = getProvidersImport(architecture);\r\n\r\n if (content.includes(\"<Providers>\")) {\r\n return;\r\n }\r\n\r\n let updated = content;\r\n\r\n if (\r\n !content.includes(providersImport) &&\r\n !content.includes('from \"@/providers\"') &&\r\n !content.includes(\"from '@/providers'\")\r\n ) {\r\n const importLine = `import { Providers } from \"${providersImport}\";\\n`;\r\n const lastImportIndex = updated.lastIndexOf(\"import \");\r\n\r\n if (lastImportIndex !== -1) {\r\n const insertAt = updated.indexOf(\"\\n\", lastImportIndex) + 1;\r\n updated = updated.slice(0, insertAt) + importLine + updated.slice(insertAt);\r\n } else {\r\n updated = importLine + updated;\r\n }\r\n }\r\n\r\n updated = updated.replace(/(<body[^>]*>)/, \"$1\\n <Providers>\");\r\n updated = updated.replace(\"</body>\", \" </Providers>\\n </body>\");\r\n\r\n if (updated !== content) {\r\n await fs.writeFile(layoutPath, updated);\r\n }\r\n}\r\n\r\nasync function extendPage(\r\n structure: ProjectStructure,\r\n architecture: string\r\n) {\r\n const pagePath = getAppFilePath(structure, \"page.tsx\");\r\n\r\n if (!(await fs.pathExists(pagePath))) {\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(pagePath, \"utf-8\");\r\n const pageImport = getPageImport(architecture);\r\n const pageComponent = getPageComponent(architecture);\r\n\r\n if (content.includes(pageComponent)) {\r\n return;\r\n }\r\n\r\n const isDefaultTemplate =\r\n content.includes(\"Get started by editing\") ||\r\n content.includes(\"next.svg\") ||\r\n content.includes(\"vercel.svg\");\r\n\r\n if (!isDefaultTemplate) {\r\n return;\r\n }\r\n\r\n const updated = `import { ${pageComponent} } from \"${pageImport}\";\r\n\r\nexport default function Home() {\r\n return <${pageComponent} />;\r\n}\r\n`;\r\n\r\n await fs.writeFile(pagePath, updated);\r\n}\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport type { ModuleDefinition } from \"../types/module.js\";\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nexport async function readModule(moduleName: string): Promise<ModuleDefinition> {\r\n const modulePath = path.resolve(\r\n __dirname,\r\n \"../templates/modules\",\r\n moduleName,\r\n \"module.ts\"\r\n );\r\n\r\n const content = await fs.readFile(modulePath, \"utf-8\");\r\n const dataUrl = `data:text/javascript;charset=utf-8,${encodeURIComponent(content)}`;\r\n const { default: moduleDef } = await import(dataUrl);\r\n\r\n return moduleDef as ModuleDefinition;\r\n}\r\n","import { execa } from \"execa\";\r\nimport type { ModuleDefinition } from \"../types/module.js\";\r\nimport type { PackageManager } from \"../types/package-manager.js\";\r\n\r\nconst installCommands: Record<\r\n PackageManager,\r\n { command: string; dependencies: string[]; devDependencies: string[] }\r\n> = {\r\n npm: { command: \"npm\", dependencies: [\"install\"], devDependencies: [\"install\", \"-D\"] },\r\n pnpm: { command: \"pnpm\", dependencies: [\"add\"], devDependencies: [\"add\", \"-D\"] },\r\n yarn: { command: \"yarn\", dependencies: [\"add\"], devDependencies: [\"add\", \"-D\"] },\r\n bun: { command: \"bun\", dependencies: [\"add\"], devDependencies: [\"add\", \"-d\"] },\r\n};\r\n\r\nexport async function installDependencies(\r\n modules: ModuleDefinition[],\r\n packageManager: PackageManager\r\n): Promise<number> {\r\n const dependencies = [\r\n ...new Set(modules.flatMap((module) => module.dependencies)),\r\n ];\r\n const devDependencies = [\r\n ...new Set(modules.flatMap((module) => module.devDependencies)),\r\n ];\r\n\r\n const totalCount = dependencies.length + devDependencies.length;\r\n\r\n if (totalCount === 0) {\r\n return 0;\r\n }\r\n\r\n const { command, dependencies: depsArgs, devDependencies: devArgs } =\r\n installCommands[packageManager];\r\n\r\n try {\r\n if (dependencies.length > 0) {\r\n await execa(command, [...depsArgs, ...dependencies], {\r\n stdio: \"inherit\",\r\n cwd: process.cwd(),\r\n });\r\n }\r\n\r\n if (devDependencies.length > 0) {\r\n await execa(command, [...devArgs, ...devDependencies], {\r\n stdio: \"inherit\",\r\n cwd: process.cwd(),\r\n });\r\n }\r\n\r\n return totalCount;\r\n } catch {\r\n throw new Error(\r\n \"Failed to install dependencies.\\nPlease resolve the issue and run the command again.\"\r\n );\r\n }\r\n}\r\n","import fs from \"fs\";\r\nimport path from \"path\";\r\nimport { execa } from \"execa\";\r\nimport type { PackageManager } from \"../types/package-manager.js\";\r\n\r\nexport function detectPackageManager(): PackageManager | null {\r\n const root = process.cwd();\r\n\r\n if (fs.existsSync(path.join(root, \"pnpm-lock.yaml\"))) {\r\n return \"pnpm\";\r\n }\r\n\r\n if (fs.existsSync(path.join(root, \"yarn.lock\"))) {\r\n return \"yarn\";\r\n }\r\n\r\n if (\r\n fs.existsSync(path.join(root, \"bun.lockb\")) ||\r\n fs.existsSync(path.join(root, \"bun.lock\"))\r\n ) {\r\n return \"bun\";\r\n }\r\n\r\n if (fs.existsSync(path.join(root, \"package-lock.json\"))) {\r\n return \"npm\";\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport async function validatePackageManager(packageManager: PackageManager) {\r\n const result = await execa(packageManager, [\"--version\"], {\r\n reject: false,\r\n });\r\n\r\n if (result.exitCode !== 0) {\r\n throw new Error(\r\n `${packageManager} is not available.\\nPlease install ${packageManager} and try again.`\r\n );\r\n }\r\n}\r\n","import pc from \"picocolors\";\r\n\r\nexport const logger = {\r\n success(message: string) {\r\n console.log(`${pc.green(\"✓\")} ${message}`);\r\n },\r\n\r\n info(message: string) {\r\n console.log(`${pc.cyan(\"ℹ\")} ${message}`);\r\n },\r\n\r\n warning(message: string) {\r\n console.log(`${pc.yellow(\"⚠\")} ${message}`);\r\n },\r\n\r\n error(message: string) {\r\n for (const line of message.split(\"\\n\")) {\r\n console.log(`${pc.red(\"✖\")} ${line}`);\r\n }\r\n },\r\n};\r\n","import { select } from \"@clack/prompts\";\r\nimport { architectures } from \"../config/architectures.js\";\r\n\r\nexport async function selectArchitecture() {\r\n return await select({\r\n message: \"Choose project architecture\",\r\n options: architectures.map((item) => ({\r\n value: item.value,\r\n label: item.label,\r\n hint: item.hint,\r\n })),\r\n });\r\n}","export const architectures = [\r\n {\r\n value: \"standard\",\r\n label: \"Standard\",\r\n hint: \"Simple folder structure\",\r\n },\r\n {\r\n value: \"enterprise\",\r\n label: \"Enterprise\",\r\n hint: \"Scalable enterprise architecture\",\r\n },\r\n {\r\n value: \"feature\",\r\n label: \"Feature Based\",\r\n hint: \"Organize code by feature\",\r\n },\r\n] as const;","import { multiselect } from \"@clack/prompts\";\r\nimport { modules } from \"../config/modules.js\";\r\n\r\nexport async function selectModules() {\r\n return await multiselect({\r\n message: \"Select modules\",\r\n options: modules.map((item) => ({\r\n value: item.value,\r\n label: item.label,\r\n hint: item.hint,\r\n })),\r\n required: true,\r\n });\r\n}\r\n","export const modules = [\r\n {\r\n value: \"core\",\r\n label: \"Core\",\r\n hint: \"Basic starter files\",\r\n },\r\n {\r\n value: \"axios\",\r\n label: \"Axios\",\r\n hint: \"API Client\",\r\n },\r\n {\r\n value: \"zustand\",\r\n label: \"Zustand\",\r\n hint: \"State Management\",\r\n },\r\n];\r\n","import { select } from \"@clack/prompts\";\r\nimport type { PackageManager } from \"../types/package-manager.js\";\r\n\r\nexport async function selectPackageManager() {\r\n return await select<PackageManager>({\r\n message: \"Choose package manager\",\r\n options: [\r\n { value: \"npm\", label: \"npm\" },\r\n { value: \"pnpm\", label: \"pnpm\" },\r\n { value: \"yarn\", label: \"yarn\" },\r\n { value: \"bun\", label: \"bun\" },\r\n ],\r\n initialValue: \"npm\",\r\n });\r\n}\r\n"],"mappings":"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","names":["fs","path","fileURLToPath","fs","path","fs","path","path","path","fs","path","path","modules","path","fs","path","fs","path","fileURLToPath","__filename","__dirname","modules","fs","path","execa","select","path","fileURLToPath","fs"]}
@@ -0,0 +1,17 @@
1
+ import { cn } from "@/utils/cn";
2
+
3
+ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
4
+
5
+ export function Button({ children, className, ...props }: ButtonProps) {
6
+ return (
7
+ <button
8
+ {...props}
9
+ className={cn(
10
+ "rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-zinc-700",
11
+ className
12
+ )}
13
+ >
14
+ {children}
15
+ </button>
16
+ );
17
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import { Header } from "@/components/layout/Header";
4
+ import { Footer } from "@/components/layout/Footer";
5
+ import { Button } from "@/components/common/Button";
6
+ import { DashboardSummary } from "@/features/dashboard/components/DashboardSummary";
7
+ import { useAppStore } from "@/store/app-store";
8
+ import { formatDate } from "@/utils/formatDate";
9
+
10
+ export function HomeView() {
11
+ const { sidebarOpen, toggleSidebar } = useAppStore();
12
+
13
+ return (
14
+ <div className="flex min-h-screen flex-col">
15
+ <Header />
16
+ <main className="mx-auto flex w-full max-w-5xl flex-1 flex-col gap-6 px-6 py-10">
17
+ <section className="space-y-2">
18
+ <p className="text-sm uppercase tracking-wide text-zinc-500">
19
+ Enterprise Architecture
20
+ </p>
21
+ <h1 className="text-3xl font-bold">Layered, scalable structure</h1>
22
+ <p className="text-zinc-600">
23
+ Features, services, providers, and store work together.{" "}
24
+ {formatDate(new Date())}
25
+ </p>
26
+ </section>
27
+
28
+ <section className="rounded-lg border bg-white p-4">
29
+ <div className="mb-4 flex items-center justify-between">
30
+ <h2 className="font-medium">Global store</h2>
31
+ <Button onClick={toggleSidebar}>
32
+ Sidebar {sidebarOpen ? "Open" : "Closed"}
33
+ </Button>
34
+ </div>
35
+ <DashboardSummary />
36
+ </section>
37
+ </main>
38
+ <Footer />
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,9 @@
1
+ import { formatDate } from "@/utils/formatDate";
2
+
3
+ export function Footer() {
4
+ return (
5
+ <footer className="border-t bg-white px-6 py-4 text-sm text-zinc-500">
6
+ Enterprise starter · {formatDate(new Date())}
7
+ </footer>
8
+ );
9
+ }
@@ -0,0 +1,19 @@
1
+ import Link from "next/link";
2
+ import { Button } from "@/components/common/Button";
3
+ import { ROUTES } from "@/constants/routes";
4
+
5
+ export function Header() {
6
+ return (
7
+ <header className="flex items-center justify-between border-b bg-white px-6 py-4">
8
+ <Link href={ROUTES.HOME} className="text-lg font-semibold">
9
+ Enterprise App
10
+ </Link>
11
+ <nav className="flex items-center gap-3">
12
+ <Link href={ROUTES.DASHBOARD} className="text-sm text-zinc-600">
13
+ Dashboard
14
+ </Link>
15
+ <Button>Launch</Button>
16
+ </nav>
17
+ </header>
18
+ );
19
+ }
@@ -0,0 +1,3 @@
1
+ export const env = {
2
+ apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "https://api.example.com",
3
+ };
@@ -0,0 +1,4 @@
1
+ export const ROUTES = {
2
+ HOME: "/",
3
+ DASHBOARD: "/dashboard",
4
+ } as const;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { useDashboard } from "../hooks/useDashboard";
4
+
5
+ export function DashboardSummary() {
6
+ const { metrics, loading } = useDashboard();
7
+
8
+ if (loading) {
9
+ return <p className="text-sm text-zinc-500">Loading dashboard...</p>;
10
+ }
11
+
12
+ return (
13
+ <div className="grid gap-4 sm:grid-cols-3">
14
+ {metrics.map((metric) => (
15
+ <article key={metric.label} className="rounded-lg border bg-white p-4">
16
+ <p className="text-sm text-zinc-500">{metric.label}</p>
17
+ <p className="mt-1 text-2xl font-semibold">{metric.value}</p>
18
+ </article>
19
+ ))}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { fetchDashboardMetrics } from "../services/dashboard-api";
5
+ import type { DashboardMetric } from "../types";
6
+
7
+ export function useDashboard() {
8
+ const [metrics, setMetrics] = useState<DashboardMetric[]>([]);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ fetchDashboardMetrics()
13
+ .then(setMetrics)
14
+ .finally(() => setLoading(false));
15
+ }, []);
16
+
17
+ return { metrics, loading };
18
+ }
@@ -0,0 +1,22 @@
1
+ import { env } from "@/config/env";
2
+ import type { DashboardMetric } from "../types";
3
+
4
+ const fallbackMetrics: DashboardMetric[] = [
5
+ { label: "Active Users", value: "1,248" },
6
+ { label: "Revenue", value: "$42,300" },
7
+ { label: "Uptime", value: "99.9%" },
8
+ ];
9
+
10
+ export async function fetchDashboardMetrics(): Promise<DashboardMetric[]> {
11
+ try {
12
+ const response = await fetch(`${env.apiUrl}/dashboard/metrics`);
13
+
14
+ if (!response.ok) {
15
+ return fallbackMetrics;
16
+ }
17
+
18
+ return response.json();
19
+ } catch {
20
+ return fallbackMetrics;
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ export type DashboardMetric = {
2
+ label: string;
3
+ value: string;
4
+ };
@@ -0,0 +1,14 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ export function useDebounce<T>(value: T, delay = 300) {
6
+ const [debouncedValue, setDebouncedValue] = useState(value);
7
+
8
+ useEffect(() => {
9
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
10
+ return () => clearTimeout(timer);
11
+ }, [value, delay]);
12
+
13
+ return debouncedValue;
14
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ export function useToggle(defaultValue = false) {
6
+ const [value, setValue] = useState(defaultValue);
7
+
8
+ const toggle = () => setValue((prev) => !prev);
9
+
10
+ return {
11
+ value,
12
+ toggle,
13
+ setValue,
14
+ };
15
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { ReactNode } from "react";
4
+ import { ThemeProvider } from "./theme-provider";
5
+ import { AppStoreProvider } from "@/store/app-store";
6
+
7
+ type ProvidersProps = {
8
+ children: ReactNode;
9
+ };
10
+
11
+ export function Providers({ children }: ProvidersProps) {
12
+ return (
13
+ <ThemeProvider>
14
+ <AppStoreProvider>{children}</AppStoreProvider>
15
+ </ThemeProvider>
16
+ );
17
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ import { ReactNode } from "react";
4
+
5
+ type ThemeProviderProps = {
6
+ children: ReactNode;
7
+ };
8
+
9
+ export function ThemeProvider({ children }: ThemeProviderProps) {
10
+ return <div className="min-h-screen bg-zinc-50 text-zinc-900">{children}</div>;
11
+ }
@@ -0,0 +1,22 @@
1
+ import { env } from "@/config/env";
2
+ import type { User } from "@/types/user";
3
+
4
+ export async function fetchHealth() {
5
+ const response = await fetch(`${env.apiUrl}/health`);
6
+
7
+ if (!response.ok) {
8
+ throw new Error("Health check failed");
9
+ }
10
+
11
+ return response.json();
12
+ }
13
+
14
+ export async function fetchCurrentUser(): Promise<User> {
15
+ const response = await fetch(`${env.apiUrl}/users/me`);
16
+
17
+ if (!response.ok) {
18
+ throw new Error("Failed to fetch user");
19
+ }
20
+
21
+ return response.json();
22
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import { createContext, ReactNode, useContext, useState } from "react";
4
+
5
+ type AppStore = {
6
+ sidebarOpen: boolean;
7
+ toggleSidebar: () => void;
8
+ };
9
+
10
+ const AppStoreContext = createContext<AppStore | null>(null);
11
+
12
+ export function AppStoreProvider({ children }: { children: ReactNode }) {
13
+ const [sidebarOpen, setSidebarOpen] = useState(false);
14
+
15
+ const value: AppStore = {
16
+ sidebarOpen,
17
+ toggleSidebar: () => setSidebarOpen((prev) => !prev),
18
+ };
19
+
20
+ return (
21
+ <AppStoreContext.Provider value={value}>{children}</AppStoreContext.Provider>
22
+ );
23
+ }
24
+
25
+ export function useAppStore() {
26
+ const context = useContext(AppStoreContext);
27
+
28
+ if (!context) {
29
+ throw new Error("useAppStore must be used within AppStoreProvider");
30
+ }
31
+
32
+ return context;
33
+ }
@@ -0,0 +1,5 @@
1
+ export type User = {
2
+ id: string;
3
+ name: string;
4
+ email: string;
5
+ };
@@ -0,0 +1,3 @@
1
+ export function cn(...classes: (string | undefined | false | null)[]) {
2
+ return classes.filter(Boolean).join(" ");
3
+ }
@@ -0,0 +1,7 @@
1
+ export function formatDate(date: Date) {
2
+ return date.toLocaleDateString("en-US", {
3
+ year: "numeric",
4
+ month: "long",
5
+ day: "numeric",
6
+ });
7
+ }
@@ -0,0 +1,3 @@
1
+ export const env = {
2
+ apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "https://api.example.com",
3
+ };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/shared/components/common/Button";
4
+ import { useToggle } from "@/shared/hooks/useToggle";
5
+ import { useHome } from "../hooks/useHome";
6
+
7
+ export function HomeHero() {
8
+ const { value, toggle } = useToggle();
9
+ const { stats, loading } = useHome();
10
+
11
+ return (
12
+ <section className="space-y-4 rounded-lg border p-6">
13
+ <div>
14
+ <p className="text-sm uppercase tracking-wide text-zinc-500">
15
+ Home Feature
16
+ </p>
17
+ <h1 className="text-3xl font-bold">Organized by feature</h1>
18
+ <p className="mt-2 text-zinc-600">
19
+ This page lives inside the home feature with its own hooks and
20
+ services.
21
+ </p>
22
+ </div>
23
+
24
+ <Button onClick={toggle}>Feature toggle: {value ? "On" : "Off"}</Button>
25
+
26
+ {!loading && (
27
+ <div className="grid gap-3 sm:grid-cols-3">
28
+ {stats.map((stat) => (
29
+ <article key={stat.label} className="rounded-md border p-3">
30
+ <p className="text-sm text-zinc-500">{stat.label}</p>
31
+ <p className="text-xl font-semibold">{stat.value}</p>
32
+ </article>
33
+ ))}
34
+ </div>
35
+ )}
36
+ </section>
37
+ );
38
+ }
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { Header } from "@/shared/components/layout/Header";
4
+ import { Footer } from "@/shared/components/layout/Footer";
5
+ import { ProfileCard } from "@/features/profile/components/ProfileCard";
6
+ import { HomeHero } from "./HomeHero";
7
+
8
+ export function HomePage() {
9
+ return (
10
+ <div className="flex min-h-screen flex-col">
11
+ <Header />
12
+ <main className="mx-auto flex w-full max-w-4xl flex-1 flex-col gap-6 px-6 py-10">
13
+ <HomeHero />
14
+ <ProfileCard />
15
+ </main>
16
+ <Footer />
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,4 @@
1
+ export const HOME_ROUTES = {
2
+ HOME: "/",
3
+ PROFILE: "/profile",
4
+ } as const;
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { fetchHomeStats } from "../services/home-api";
5
+ import type { HomeStats } from "../types";
6
+
7
+ export function useHome() {
8
+ const [stats, setStats] = useState<HomeStats[]>([]);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ fetchHomeStats()
13
+ .then(setStats)
14
+ .finally(() => setLoading(false));
15
+ }, []);
16
+
17
+ return { stats, loading };
18
+ }
@@ -0,0 +1,22 @@
1
+ import { env } from "@/config/env";
2
+ import type { HomeStats } from "../types";
3
+
4
+ const fallbackStats: HomeStats[] = [
5
+ { label: "Features", value: "2" },
6
+ { label: "Shared Modules", value: "5" },
7
+ { label: "Ready", value: "Yes" },
8
+ ];
9
+
10
+ export async function fetchHomeStats(): Promise<HomeStats[]> {
11
+ try {
12
+ const response = await fetch(`${env.apiUrl}/home/stats`);
13
+
14
+ if (!response.ok) {
15
+ return fallbackStats;
16
+ }
17
+
18
+ return response.json();
19
+ } catch {
20
+ return fallbackStats;
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ export type HomeStats = {
2
+ label: string;
3
+ value: string;
4
+ };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { useProfile } from "../hooks/useProfile";
4
+
5
+ export function ProfileCard() {
6
+ const { profile, loading } = useProfile();
7
+
8
+ if (loading || !profile) {
9
+ return <p className="text-sm text-zinc-500">Loading profile...</p>;
10
+ }
11
+
12
+ return (
13
+ <article className="rounded-lg border p-4">
14
+ <p className="text-sm text-zinc-500">Profile Feature</p>
15
+ <h2 className="mt-1 text-xl font-semibold">{profile.name}</h2>
16
+ <p className="text-zinc-600">{profile.role}</p>
17
+ </article>
18
+ );
19
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { fetchProfile } from "../services/profile-api";
5
+ import type { Profile } from "../types";
6
+
7
+ export function useProfile() {
8
+ const [profile, setProfile] = useState<Profile | null>(null);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ fetchProfile()
13
+ .then(setProfile)
14
+ .finally(() => setLoading(false));
15
+ }, []);
16
+
17
+ return { profile, loading };
18
+ }
@@ -0,0 +1,22 @@
1
+ import { env } from "@/config/env";
2
+ import type { Profile } from "../types";
3
+
4
+ const fallbackProfile: Profile = {
5
+ id: "1",
6
+ name: "Alex Developer",
7
+ role: "Frontend Engineer",
8
+ };
9
+
10
+ export async function fetchProfile(): Promise<Profile> {
11
+ try {
12
+ const response = await fetch(`${env.apiUrl}/profile`);
13
+
14
+ if (!response.ok) {
15
+ return fallbackProfile;
16
+ }
17
+
18
+ return response.json();
19
+ } catch {
20
+ return fallbackProfile;
21
+ }
22
+ }
@@ -0,0 +1,5 @@
1
+ export type Profile = {
2
+ id: string;
3
+ name: string;
4
+ role: string;
5
+ };
@@ -0,0 +1,17 @@
1
+ import { cn } from "@/shared/utils/cn";
2
+
3
+ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
4
+
5
+ export function Button({ children, className, ...props }: ButtonProps) {
6
+ return (
7
+ <button
8
+ {...props}
9
+ className={cn(
10
+ "rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-zinc-700",
11
+ className
12
+ )}
13
+ >
14
+ {children}
15
+ </button>
16
+ );
17
+ }
@@ -0,0 +1,9 @@
1
+ import { formatDate } from "@/shared/utils/formatDate";
2
+
3
+ export function Footer() {
4
+ return (
5
+ <footer className="border-t px-6 py-4 text-sm text-zinc-500">
6
+ Feature-based starter · {formatDate(new Date())}
7
+ </footer>
8
+ );
9
+ }