create-mantiq 0.4.0 → 0.5.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 (346) hide show
  1. package/package.json +2 -1
  2. package/src/index.ts +44 -18
  3. package/src/templates.ts +14 -326
  4. package/src/ui/shadcn.ts +450 -192
  5. package/stubs/manifest.json +1415 -0
  6. package/stubs/react/components.json.stub +19 -0
  7. package/stubs/react/src/App.tsx.stub +64 -0
  8. package/stubs/react/src/components/data-table.tsx.stub +548 -0
  9. package/stubs/react/src/components/layout/app-sidebar.tsx.stub +74 -0
  10. package/stubs/react/src/components/layout/authenticated-layout.tsx.stub +42 -0
  11. package/stubs/react/src/components/layout/header.tsx.stub +88 -0
  12. package/stubs/react/src/components/layout/index.ts.stub +10 -0
  13. package/stubs/react/src/components/layout/main.tsx.stub +22 -0
  14. package/stubs/react/src/components/layout/nav-group.tsx.stub +193 -0
  15. package/stubs/react/src/components/layout/nav-user.tsx.stub +111 -0
  16. package/stubs/react/src/components/layout/search-dialog.tsx.stub +121 -0
  17. package/stubs/react/src/components/layout/sidebar-data.ts.stub +57 -0
  18. package/stubs/react/src/components/layout/theme-toggle.tsx.stub +43 -0
  19. package/stubs/react/src/components/layout/top-nav.tsx.stub +94 -0
  20. package/stubs/react/src/components/ui/avatar.tsx.stub +107 -0
  21. package/stubs/react/src/components/ui/badge.tsx.stub +48 -0
  22. package/stubs/react/src/components/ui/button.tsx.stub +64 -0
  23. package/stubs/react/src/components/ui/card.tsx.stub +92 -0
  24. package/stubs/react/src/components/ui/dialog.tsx.stub +156 -0
  25. package/stubs/react/src/components/ui/dropdown-menu.tsx.stub +255 -0
  26. package/stubs/react/src/components/ui/input.tsx.stub +21 -0
  27. package/stubs/react/src/components/ui/label.tsx.stub +22 -0
  28. package/stubs/react/src/components/ui/separator.tsx.stub +28 -0
  29. package/stubs/react/src/components/ui/sheet.tsx.stub +143 -0
  30. package/stubs/react/src/components/ui/sidebar.tsx.stub +726 -0
  31. package/stubs/react/src/components/ui/skeleton.tsx.stub +13 -0
  32. package/stubs/react/src/components/ui/table.tsx.stub +116 -0
  33. package/stubs/react/src/components/ui/tabs.tsx.stub +69 -0
  34. package/stubs/react/src/components/ui/tooltip.tsx.stub +55 -0
  35. package/stubs/react/src/hooks/use-mobile.ts.stub +19 -0
  36. package/stubs/react/src/lib/api.ts.stub +17 -0
  37. package/stubs/react/src/lib/utils.ts.stub +6 -0
  38. package/stubs/react/src/main.tsx.stub +10 -0
  39. package/stubs/react/src/pages/Dashboard.tsx.stub +283 -0
  40. package/stubs/react/src/pages/Login.tsx.stub +119 -0
  41. package/stubs/react/src/pages/Register.tsx.stub +133 -0
  42. package/stubs/react/src/pages/Users.tsx.stub +411 -0
  43. package/stubs/react/src/pages/account/layout.tsx.stub +59 -0
  44. package/stubs/react/src/pages/account/preferences.tsx.stub +80 -0
  45. package/stubs/react/src/pages/account/profile.tsx.stub +65 -0
  46. package/stubs/react/src/pages/account/security.tsx.stub +105 -0
  47. package/stubs/react/src/pages/users/dialogs.tsx.stub +309 -0
  48. package/stubs/react/src/pages.ts.stub +17 -0
  49. package/stubs/react/src/ssr.tsx.stub +7 -0
  50. package/stubs/react/src/style.css.stub +138 -0
  51. package/stubs/react/tsconfig.json.stub +36 -0
  52. package/stubs/react/vite.config.ts.stub +22 -0
  53. package/stubs/shared/app/Http/Controllers/AuthController.ts.stub +67 -0
  54. package/stubs/shared/app/Http/Controllers/HomeController.ts.stub +62 -0
  55. package/stubs/shared/app/Http/Controllers/PageController.ts.stub +66 -0
  56. package/stubs/shared/database/factories/UserFactory.ts.stub +16 -0
  57. package/stubs/shared/database/seeders/DatabaseSeeder.ts.stub +42 -0
  58. package/stubs/shared/routes/api.ts.stub +106 -0
  59. package/stubs/shared/routes/web.ts.stub +23 -0
  60. package/stubs/svelte/components.json.stub +15 -0
  61. package/stubs/svelte/src/App.svelte.stub +77 -0
  62. package/stubs/svelte/src/lib/api.ts.stub +17 -0
  63. package/stubs/svelte/src/lib/components/DataTable.svelte.stub +399 -0
  64. package/stubs/svelte/src/lib/components/layout/AppSidebar.svelte.stub +62 -0
  65. package/stubs/svelte/src/lib/components/layout/AuthenticatedLayout.svelte.stub +40 -0
  66. package/stubs/svelte/src/lib/components/layout/Header.svelte.stub +98 -0
  67. package/stubs/svelte/src/lib/components/layout/Main.svelte.stub +26 -0
  68. package/stubs/svelte/src/lib/components/layout/NavGroup.svelte.stub +142 -0
  69. package/stubs/svelte/src/lib/components/layout/NavUser.svelte.stub +100 -0
  70. package/stubs/svelte/src/lib/components/layout/SearchDialog.svelte.stub +124 -0
  71. package/stubs/svelte/src/lib/components/layout/ThemeToggle.svelte.stub +30 -0
  72. package/stubs/svelte/src/lib/components/layout/TopNav.svelte.stub +81 -0
  73. package/stubs/svelte/src/lib/components/layout/sidebar-data.ts.stub +57 -0
  74. package/stubs/svelte/src/lib/components/ui/avatar/avatar-badge.svelte.stub +26 -0
  75. package/stubs/svelte/src/lib/components/ui/avatar/avatar-fallback.svelte.stub +20 -0
  76. package/stubs/svelte/src/lib/components/ui/avatar/avatar-group-count.svelte.stub +23 -0
  77. package/stubs/svelte/src/lib/components/ui/avatar/avatar-group.svelte.stub +23 -0
  78. package/stubs/svelte/src/lib/components/ui/avatar/avatar-image.svelte.stub +17 -0
  79. package/stubs/svelte/src/lib/components/ui/avatar/avatar.svelte.stub +26 -0
  80. package/stubs/svelte/src/lib/components/ui/avatar/index.ts.stub +22 -0
  81. package/stubs/svelte/src/lib/components/ui/badge/badge.svelte.stub +49 -0
  82. package/stubs/svelte/src/lib/components/ui/badge/index.ts.stub +2 -0
  83. package/stubs/svelte/src/lib/components/ui/button/button.svelte.stub +82 -0
  84. package/stubs/svelte/src/lib/components/ui/button/index.ts.stub +17 -0
  85. package/stubs/svelte/src/lib/components/ui/card/card-action.svelte.stub +23 -0
  86. package/stubs/svelte/src/lib/components/ui/card/card-content.svelte.stub +20 -0
  87. package/stubs/svelte/src/lib/components/ui/card/card-description.svelte.stub +20 -0
  88. package/stubs/svelte/src/lib/components/ui/card/card-footer.svelte.stub +20 -0
  89. package/stubs/svelte/src/lib/components/ui/card/card-header.svelte.stub +23 -0
  90. package/stubs/svelte/src/lib/components/ui/card/card-title.svelte.stub +15 -0
  91. package/stubs/svelte/src/lib/components/ui/card/card.svelte.stub +22 -0
  92. package/stubs/svelte/src/lib/components/ui/card/index.ts.stub +25 -0
  93. package/stubs/svelte/src/lib/components/ui/dialog/dialog-close.svelte.stub +11 -0
  94. package/stubs/svelte/src/lib/components/ui/dialog/dialog-content.svelte.stub +48 -0
  95. package/stubs/svelte/src/lib/components/ui/dialog/dialog-description.svelte.stub +17 -0
  96. package/stubs/svelte/src/lib/components/ui/dialog/dialog-footer.svelte.stub +32 -0
  97. package/stubs/svelte/src/lib/components/ui/dialog/dialog-header.svelte.stub +20 -0
  98. package/stubs/svelte/src/lib/components/ui/dialog/dialog-overlay.svelte.stub +17 -0
  99. package/stubs/svelte/src/lib/components/ui/dialog/dialog-portal.svelte.stub +7 -0
  100. package/stubs/svelte/src/lib/components/ui/dialog/dialog-title.svelte.stub +17 -0
  101. package/stubs/svelte/src/lib/components/ui/dialog/dialog-trigger.svelte.stub +11 -0
  102. package/stubs/svelte/src/lib/components/ui/dialog/dialog.svelte.stub +7 -0
  103. package/stubs/svelte/src/lib/components/ui/dialog/index.ts.stub +34 -0
  104. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.stub +16 -0
  105. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.stub +44 -0
  106. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte.stub +31 -0
  107. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.stub +22 -0
  108. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte.stub +7 -0
  109. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte.stub +27 -0
  110. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte.stub +24 -0
  111. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte.stub +7 -0
  112. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.stub +16 -0
  113. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.stub +34 -0
  114. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte.stub +17 -0
  115. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.stub +20 -0
  116. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.stub +17 -0
  117. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.stub +29 -0
  118. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte.stub +7 -0
  119. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.stub +7 -0
  120. package/stubs/svelte/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte.stub +7 -0
  121. package/stubs/svelte/src/lib/components/ui/dropdown-menu/index.ts.stub +54 -0
  122. package/stubs/svelte/src/lib/components/ui/input/index.ts.stub +7 -0
  123. package/stubs/svelte/src/lib/components/ui/input/input.svelte.stub +48 -0
  124. package/stubs/svelte/src/lib/components/ui/label/index.ts.stub +7 -0
  125. package/stubs/svelte/src/lib/components/ui/label/label.svelte.stub +20 -0
  126. package/stubs/svelte/src/lib/components/ui/separator/index.ts.stub +7 -0
  127. package/stubs/svelte/src/lib/components/ui/separator/separator.svelte.stub +23 -0
  128. package/stubs/svelte/src/lib/components/ui/sheet/index.ts.stub +34 -0
  129. package/stubs/svelte/src/lib/components/ui/sheet/sheet-close.svelte.stub +7 -0
  130. package/stubs/svelte/src/lib/components/ui/sheet/sheet-content.svelte.stub +55 -0
  131. package/stubs/svelte/src/lib/components/ui/sheet/sheet-description.svelte.stub +17 -0
  132. package/stubs/svelte/src/lib/components/ui/sheet/sheet-footer.svelte.stub +20 -0
  133. package/stubs/svelte/src/lib/components/ui/sheet/sheet-header.svelte.stub +20 -0
  134. package/stubs/svelte/src/lib/components/ui/sheet/sheet-overlay.svelte.stub +17 -0
  135. package/stubs/svelte/src/lib/components/ui/sheet/sheet-portal.svelte.stub +7 -0
  136. package/stubs/svelte/src/lib/components/ui/sheet/sheet-title.svelte.stub +17 -0
  137. package/stubs/svelte/src/lib/components/ui/sheet/sheet-trigger.svelte.stub +7 -0
  138. package/stubs/svelte/src/lib/components/ui/sheet/sheet.svelte.stub +7 -0
  139. package/stubs/svelte/src/lib/components/ui/sidebar/constants.ts.stub +6 -0
  140. package/stubs/svelte/src/lib/components/ui/sidebar/context.svelte.ts.stub +81 -0
  141. package/stubs/svelte/src/lib/components/ui/sidebar/index.ts.stub +75 -0
  142. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-content.svelte.stub +24 -0
  143. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-footer.svelte.stub +21 -0
  144. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-group-action.svelte.stub +33 -0
  145. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-group-content.svelte.stub +21 -0
  146. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-group-label.svelte.stub +33 -0
  147. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-group.svelte.stub +21 -0
  148. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-header.svelte.stub +21 -0
  149. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-input.svelte.stub +21 -0
  150. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-inset.svelte.stub +20 -0
  151. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-action.svelte.stub +37 -0
  152. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte.stub +24 -0
  153. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-button.svelte.stub +102 -0
  154. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-item.svelte.stub +21 -0
  155. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte.stub +36 -0
  156. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte.stub +39 -0
  157. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte.stub +21 -0
  158. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte.stub +21 -0
  159. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-menu.svelte.stub +21 -0
  160. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-provider.svelte.stub +53 -0
  161. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-rail.svelte.stub +36 -0
  162. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-separator.svelte.stub +19 -0
  163. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar-trigger.svelte.stub +36 -0
  164. package/stubs/svelte/src/lib/components/ui/sidebar/sidebar.svelte.stub +108 -0
  165. package/stubs/svelte/src/lib/components/ui/skeleton/index.ts.stub +7 -0
  166. package/stubs/svelte/src/lib/components/ui/skeleton/skeleton.svelte.stub +17 -0
  167. package/stubs/svelte/src/lib/components/ui/table/index.ts.stub +28 -0
  168. package/stubs/svelte/src/lib/components/ui/table/table-body.svelte.stub +15 -0
  169. package/stubs/svelte/src/lib/components/ui/table/table-caption.svelte.stub +20 -0
  170. package/stubs/svelte/src/lib/components/ui/table/table-cell.svelte.stub +15 -0
  171. package/stubs/svelte/src/lib/components/ui/table/table-footer.svelte.stub +20 -0
  172. package/stubs/svelte/src/lib/components/ui/table/table-head.svelte.stub +15 -0
  173. package/stubs/svelte/src/lib/components/ui/table/table-header.svelte.stub +20 -0
  174. package/stubs/svelte/src/lib/components/ui/table/table-row.svelte.stub +15 -0
  175. package/stubs/svelte/src/lib/components/ui/table/table.svelte.stub +17 -0
  176. package/stubs/svelte/src/lib/components/ui/tabs/index.ts.stub +18 -0
  177. package/stubs/svelte/src/lib/components/ui/tabs/tabs-content.svelte.stub +17 -0
  178. package/stubs/svelte/src/lib/components/ui/tabs/tabs-list.svelte.stub +40 -0
  179. package/stubs/svelte/src/lib/components/ui/tabs/tabs-trigger.svelte.stub +23 -0
  180. package/stubs/svelte/src/lib/components/ui/tabs/tabs.svelte.stub +19 -0
  181. package/stubs/svelte/src/lib/components/ui/tooltip/index.ts.stub +19 -0
  182. package/stubs/svelte/src/lib/components/ui/tooltip/tooltip-content.svelte.stub +52 -0
  183. package/stubs/svelte/src/lib/components/ui/tooltip/tooltip-portal.svelte.stub +7 -0
  184. package/stubs/svelte/src/lib/components/ui/tooltip/tooltip-provider.svelte.stub +7 -0
  185. package/stubs/svelte/src/lib/components/ui/tooltip/tooltip-trigger.svelte.stub +7 -0
  186. package/stubs/svelte/src/lib/components/ui/tooltip/tooltip.svelte.stub +10 -0
  187. package/stubs/svelte/src/lib/hooks/is-mobile.svelte.ts.stub +9 -0
  188. package/stubs/svelte/src/lib/utils.ts.stub +6 -0
  189. package/stubs/svelte/src/main.ts.stub +12 -0
  190. package/stubs/svelte/src/pages/Dashboard.svelte.stub +201 -0
  191. package/stubs/svelte/src/pages/Login.svelte.stub +117 -0
  192. package/stubs/svelte/src/pages/Register.svelte.stub +130 -0
  193. package/stubs/svelte/src/pages/Users.svelte.stub +347 -0
  194. package/stubs/svelte/src/pages/account/layout.svelte.stub +62 -0
  195. package/stubs/svelte/src/pages/account/preferences.svelte.stub +79 -0
  196. package/stubs/svelte/src/pages/account/profile.svelte.stub +66 -0
  197. package/stubs/svelte/src/pages/account/security.svelte.stub +110 -0
  198. package/stubs/svelte/src/pages/users/AddUserDialog.svelte.stub +103 -0
  199. package/stubs/svelte/src/pages/users/DeleteUserDialog.svelte.stub +78 -0
  200. package/stubs/svelte/src/pages/users/EditUserDialog.svelte.stub +104 -0
  201. package/stubs/svelte/src/pages.ts.stub +17 -0
  202. package/stubs/svelte/src/ssr.ts.stub +10 -0
  203. package/stubs/svelte/src/style.css.stub +127 -0
  204. package/stubs/svelte/svelte.config.js.stub +5 -0
  205. package/stubs/svelte/tsconfig.json.stub +25 -0
  206. package/stubs/svelte/vite.config.ts.stub +23 -0
  207. package/stubs/vue/components.json.stub +21 -0
  208. package/stubs/vue/src/App.vue.stub +73 -0
  209. package/stubs/vue/src/components/DataTable.vue.stub +68 -0
  210. package/stubs/vue/src/components/layout/AppSidebar.vue.stub +68 -0
  211. package/stubs/vue/src/components/layout/AuthenticatedLayout.vue.stub +36 -0
  212. package/stubs/vue/src/components/layout/Header.vue.stub +89 -0
  213. package/stubs/vue/src/components/layout/Main.vue.stub +22 -0
  214. package/stubs/vue/src/components/layout/NavGroup.vue.stub +152 -0
  215. package/stubs/vue/src/components/layout/NavUser.vue.stub +113 -0
  216. package/stubs/vue/src/components/layout/SearchDialog.vue.stub +121 -0
  217. package/stubs/vue/src/components/layout/ThemeToggle.vue.stub +40 -0
  218. package/stubs/vue/src/components/layout/TopNav.vue.stub +77 -0
  219. package/stubs/vue/src/components/layout/index.ts.stub +9 -0
  220. package/stubs/vue/src/components/layout/sidebar-data.ts.stub +57 -0
  221. package/stubs/vue/src/components/ui/avatar/Avatar.vue.stub +18 -0
  222. package/stubs/vue/src/components/ui/avatar/AvatarFallback.vue.stub +21 -0
  223. package/stubs/vue/src/components/ui/avatar/AvatarImage.vue.stub +16 -0
  224. package/stubs/vue/src/components/ui/avatar/index.ts.stub +3 -0
  225. package/stubs/vue/src/components/ui/badge/Badge.vue.stub +26 -0
  226. package/stubs/vue/src/components/ui/badge/index.ts.stub +26 -0
  227. package/stubs/vue/src/components/ui/button/Button.vue.stub +31 -0
  228. package/stubs/vue/src/components/ui/button/index.ts.stub +38 -0
  229. package/stubs/vue/src/components/ui/card/Card.vue.stub +22 -0
  230. package/stubs/vue/src/components/ui/card/CardAction.vue.stub +17 -0
  231. package/stubs/vue/src/components/ui/card/CardContent.vue.stub +17 -0
  232. package/stubs/vue/src/components/ui/card/CardDescription.vue.stub +17 -0
  233. package/stubs/vue/src/components/ui/card/CardFooter.vue.stub +17 -0
  234. package/stubs/vue/src/components/ui/card/CardHeader.vue.stub +17 -0
  235. package/stubs/vue/src/components/ui/card/CardTitle.vue.stub +17 -0
  236. package/stubs/vue/src/components/ui/card/index.ts.stub +7 -0
  237. package/stubs/vue/src/components/ui/dialog/Dialog.vue.stub +19 -0
  238. package/stubs/vue/src/components/ui/dialog/DialogClose.vue.stub +15 -0
  239. package/stubs/vue/src/components/ui/dialog/DialogContent.vue.stub +53 -0
  240. package/stubs/vue/src/components/ui/dialog/DialogDescription.vue.stub +23 -0
  241. package/stubs/vue/src/components/ui/dialog/DialogFooter.vue.stub +27 -0
  242. package/stubs/vue/src/components/ui/dialog/DialogHeader.vue.stub +17 -0
  243. package/stubs/vue/src/components/ui/dialog/DialogOverlay.vue.stub +21 -0
  244. package/stubs/vue/src/components/ui/dialog/DialogScrollContent.vue.stub +59 -0
  245. package/stubs/vue/src/components/ui/dialog/DialogTitle.vue.stub +23 -0
  246. package/stubs/vue/src/components/ui/dialog/DialogTrigger.vue.stub +15 -0
  247. package/stubs/vue/src/components/ui/dialog/index.ts.stub +10 -0
  248. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenu.vue.stub +19 -0
  249. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.stub +39 -0
  250. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuContent.vue.stub +39 -0
  251. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuGroup.vue.stub +15 -0
  252. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuItem.vue.stub +31 -0
  253. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuLabel.vue.stub +23 -0
  254. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.stub +21 -0
  255. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.stub +40 -0
  256. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue.stub +23 -0
  257. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue.stub +17 -0
  258. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuSub.vue.stub +18 -0
  259. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue.stub +27 -0
  260. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.stub +31 -0
  261. package/stubs/vue/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue.stub +17 -0
  262. package/stubs/vue/src/components/ui/dropdown-menu/index.ts.stub +16 -0
  263. package/stubs/vue/src/components/ui/input/Input.vue.stub +33 -0
  264. package/stubs/vue/src/components/ui/input/index.ts.stub +1 -0
  265. package/stubs/vue/src/components/ui/label/Label.vue.stub +26 -0
  266. package/stubs/vue/src/components/ui/label/index.ts.stub +1 -0
  267. package/stubs/vue/src/components/ui/separator/Separator.vue.stub +29 -0
  268. package/stubs/vue/src/components/ui/separator/index.ts.stub +1 -0
  269. package/stubs/vue/src/components/ui/sheet/Sheet.vue.stub +19 -0
  270. package/stubs/vue/src/components/ui/sheet/SheetClose.vue.stub +15 -0
  271. package/stubs/vue/src/components/ui/sheet/SheetContent.vue.stub +62 -0
  272. package/stubs/vue/src/components/ui/sheet/SheetDescription.vue.stub +21 -0
  273. package/stubs/vue/src/components/ui/sheet/SheetFooter.vue.stub +16 -0
  274. package/stubs/vue/src/components/ui/sheet/SheetHeader.vue.stub +15 -0
  275. package/stubs/vue/src/components/ui/sheet/SheetOverlay.vue.stub +21 -0
  276. package/stubs/vue/src/components/ui/sheet/SheetTitle.vue.stub +21 -0
  277. package/stubs/vue/src/components/ui/sheet/SheetTrigger.vue.stub +15 -0
  278. package/stubs/vue/src/components/ui/sheet/index.ts.stub +8 -0
  279. package/stubs/vue/src/components/ui/sidebar/Sidebar.vue.stub +96 -0
  280. package/stubs/vue/src/components/ui/sidebar/SidebarContent.vue.stub +18 -0
  281. package/stubs/vue/src/components/ui/sidebar/SidebarFooter.vue.stub +18 -0
  282. package/stubs/vue/src/components/ui/sidebar/SidebarGroup.vue.stub +18 -0
  283. package/stubs/vue/src/components/ui/sidebar/SidebarGroupAction.vue.stub +27 -0
  284. package/stubs/vue/src/components/ui/sidebar/SidebarGroupContent.vue.stub +18 -0
  285. package/stubs/vue/src/components/ui/sidebar/SidebarGroupLabel.vue.stub +25 -0
  286. package/stubs/vue/src/components/ui/sidebar/SidebarHeader.vue.stub +18 -0
  287. package/stubs/vue/src/components/ui/sidebar/SidebarInput.vue.stub +22 -0
  288. package/stubs/vue/src/components/ui/sidebar/SidebarInset.vue.stub +21 -0
  289. package/stubs/vue/src/components/ui/sidebar/SidebarMenu.vue.stub +18 -0
  290. package/stubs/vue/src/components/ui/sidebar/SidebarMenuAction.vue.stub +35 -0
  291. package/stubs/vue/src/components/ui/sidebar/SidebarMenuBadge.vue.stub +26 -0
  292. package/stubs/vue/src/components/ui/sidebar/SidebarMenuButton.vue.stub +48 -0
  293. package/stubs/vue/src/components/ui/sidebar/SidebarMenuButtonChild.vue.stub +36 -0
  294. package/stubs/vue/src/components/ui/sidebar/SidebarMenuItem.vue.stub +18 -0
  295. package/stubs/vue/src/components/ui/sidebar/SidebarMenuSkeleton.vue.stub +35 -0
  296. package/stubs/vue/src/components/ui/sidebar/SidebarMenuSub.vue.stub +22 -0
  297. package/stubs/vue/src/components/ui/sidebar/SidebarMenuSubButton.vue.stub +36 -0
  298. package/stubs/vue/src/components/ui/sidebar/SidebarMenuSubItem.vue.stub +18 -0
  299. package/stubs/vue/src/components/ui/sidebar/SidebarProvider.vue.stub +82 -0
  300. package/stubs/vue/src/components/ui/sidebar/SidebarRail.vue.stub +33 -0
  301. package/stubs/vue/src/components/ui/sidebar/SidebarSeparator.vue.stub +19 -0
  302. package/stubs/vue/src/components/ui/sidebar/SidebarTrigger.vue.stub +27 -0
  303. package/stubs/vue/src/components/ui/sidebar/index.ts.stub +60 -0
  304. package/stubs/vue/src/components/ui/sidebar/utils.ts.stub +19 -0
  305. package/stubs/vue/src/components/ui/skeleton/Skeleton.vue.stub +17 -0
  306. package/stubs/vue/src/components/ui/skeleton/index.ts.stub +1 -0
  307. package/stubs/vue/src/components/ui/table/Table.vue.stub +16 -0
  308. package/stubs/vue/src/components/ui/table/TableBody.vue.stub +17 -0
  309. package/stubs/vue/src/components/ui/table/TableCaption.vue.stub +17 -0
  310. package/stubs/vue/src/components/ui/table/TableCell.vue.stub +22 -0
  311. package/stubs/vue/src/components/ui/table/TableEmpty.vue.stub +34 -0
  312. package/stubs/vue/src/components/ui/table/TableFooter.vue.stub +17 -0
  313. package/stubs/vue/src/components/ui/table/TableHead.vue.stub +17 -0
  314. package/stubs/vue/src/components/ui/table/TableHeader.vue.stub +17 -0
  315. package/stubs/vue/src/components/ui/table/TableRow.vue.stub +17 -0
  316. package/stubs/vue/src/components/ui/table/index.ts.stub +9 -0
  317. package/stubs/vue/src/components/ui/table/utils.ts.stub +10 -0
  318. package/stubs/vue/src/components/ui/tabs/Tabs.vue.stub +24 -0
  319. package/stubs/vue/src/components/ui/tabs/TabsContent.vue.stub +21 -0
  320. package/stubs/vue/src/components/ui/tabs/TabsList.vue.stub +24 -0
  321. package/stubs/vue/src/components/ui/tabs/TabsTrigger.vue.stub +26 -0
  322. package/stubs/vue/src/components/ui/tabs/index.ts.stub +4 -0
  323. package/stubs/vue/src/components/ui/tooltip/Tooltip.vue.stub +19 -0
  324. package/stubs/vue/src/components/ui/tooltip/TooltipContent.vue.stub +34 -0
  325. package/stubs/vue/src/components/ui/tooltip/TooltipProvider.vue.stub +14 -0
  326. package/stubs/vue/src/components/ui/tooltip/TooltipTrigger.vue.stub +15 -0
  327. package/stubs/vue/src/components/ui/tooltip/index.ts.stub +4 -0
  328. package/stubs/vue/src/lib/api.ts.stub +17 -0
  329. package/stubs/vue/src/lib/utils.ts.stub +7 -0
  330. package/stubs/vue/src/main.ts.stub +13 -0
  331. package/stubs/vue/src/pages/Dashboard.vue.stub +211 -0
  332. package/stubs/vue/src/pages/Login.vue.stub +118 -0
  333. package/stubs/vue/src/pages/Register.vue.stub +133 -0
  334. package/stubs/vue/src/pages/Users.vue.stub +209 -0
  335. package/stubs/vue/src/pages/account/layout.vue.stub +57 -0
  336. package/stubs/vue/src/pages/account/preferences.vue.stub +88 -0
  337. package/stubs/vue/src/pages/account/profile.vue.stub +78 -0
  338. package/stubs/vue/src/pages/account/security.vue.stub +93 -0
  339. package/stubs/vue/src/pages/users/dialogs/AddUserDialog.vue.stub +86 -0
  340. package/stubs/vue/src/pages/users/dialogs/DeleteUserDialog.vue.stub +62 -0
  341. package/stubs/vue/src/pages/users/dialogs/EditUserDialog.vue.stub +94 -0
  342. package/stubs/vue/src/pages.ts.stub +17 -0
  343. package/stubs/vue/src/ssr.ts.stub +9 -0
  344. package/stubs/vue/src/style.css.stub +27 -0
  345. package/stubs/vue/tsconfig.json.stub +23 -0
  346. package/stubs/vue/vite.config.ts.stub +22 -0
package/src/ui/shadcn.ts CHANGED
@@ -227,8 +227,8 @@ export function cn(...inputs: ClassValue[]) {
227
227
  `,
228
228
 
229
229
  // Components (button, input, label, card, badge, table, avatar,
230
- // separator, sidebar, etc.) are installed by the shadcn CLI during
231
- // scaffold — no hand-rolled templates needed.
230
+ // separator, dropdown-menu, sheet, tooltip, sidebar) are installed
231
+ // by the shadcn CLI during scaffold — no hand-rolled templates needed.
232
232
 
233
233
  // ── Login page (shadcn) ─────────────────────────────────────────────────
234
234
  'src/pages/Login.tsx': `import { useState } from 'react'
@@ -236,7 +236,8 @@ import { post } from '../lib/api.ts'
236
236
  import { Button } from '@/components/ui/button'
237
237
  import { Input } from '@/components/ui/input'
238
238
  import { Label } from '@/components/ui/label'
239
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
239
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
240
+ import { LogIn } from 'lucide-react'
240
241
 
241
242
  interface LoginProps {
242
243
  appName?: string
@@ -267,52 +268,61 @@ export default function Login({ appName = '${ctx.name}', navigate }: LoginProps)
267
268
  <h2 className="text-lg font-semibold text-foreground">{appName}</h2>
268
269
  </div>
269
270
  <Card>
270
- <CardHeader>
271
+ <CardHeader className="text-center">
271
272
  <CardTitle className="text-xl">Welcome back</CardTitle>
272
- <CardDescription>Sign in to your account</CardDescription>
273
+ <CardDescription>Sign in to your account to continue</CardDescription>
273
274
  </CardHeader>
274
275
  <CardContent>
275
- <div className="space-y-4">
276
- {error && (
277
- <div className="bg-destructive/10 border border-destructive/30 text-destructive rounded-lg px-3.5 py-2.5 text-sm">
278
- {error}
279
- </div>
280
- )}
281
- <form onSubmit={handleSubmit} className="space-y-4">
282
- <div className="space-y-2">
283
- <Label htmlFor="email">Email</Label>
284
- <Input
285
- id="email"
286
- type="email"
287
- value={email}
288
- onChange={(e) => setEmail(e.target.value)}
289
- required
290
- placeholder="admin@example.com"
291
- />
292
- </div>
293
- <div className="space-y-2">
294
- <Label htmlFor="password">Password</Label>
295
- <Input
296
- id="password"
297
- type="password"
298
- value={password}
299
- onChange={(e) => setPassword(e.target.value)}
300
- required
301
- placeholder="Enter your password"
302
- />
303
- </div>
304
- <Button type="submit" className="w-full" disabled={loading}>
305
- {loading ? 'Signing in...' : 'Sign in'}
306
- </Button>
307
- </form>
308
- <p className="text-sm text-muted-foreground text-center">
309
- Don't have an account?{' '}
310
- <a href="/register" className="text-primary hover:text-primary/80 font-medium">
311
- Register
312
- </a>
313
- </p>
314
- </div>
276
+ {error && (
277
+ <div className="mb-4 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
278
+ {error}
279
+ </div>
280
+ )}
281
+ <form onSubmit={handleSubmit} className="space-y-4">
282
+ <div className="space-y-2">
283
+ <Label htmlFor="email">Email</Label>
284
+ <Input
285
+ id="email"
286
+ type="email"
287
+ value={email}
288
+ onChange={(e) => setEmail(e.target.value)}
289
+ required
290
+ placeholder="admin@example.com"
291
+ autoComplete="email"
292
+ />
293
+ </div>
294
+ <div className="space-y-2">
295
+ <Label htmlFor="password">Password</Label>
296
+ <Input
297
+ id="password"
298
+ type="password"
299
+ value={password}
300
+ onChange={(e) => setPassword(e.target.value)}
301
+ required
302
+ placeholder="Enter your password"
303
+ autoComplete="current-password"
304
+ />
305
+ </div>
306
+ <Button type="submit" className="w-full" disabled={loading}>
307
+ {loading ? (
308
+ 'Signing in\u2026'
309
+ ) : (
310
+ <>
311
+ <LogIn className="mr-2 h-4 w-4" />
312
+ Sign in
313
+ </>
314
+ )}
315
+ </Button>
316
+ </form>
315
317
  </CardContent>
318
+ <CardFooter className="justify-center">
319
+ <p className="text-sm text-muted-foreground">
320
+ Don't have an account?{' '}
321
+ <Button variant="link" className="h-auto p-0 text-sm" onClick={() => navigate('/register')}>
322
+ Register
323
+ </Button>
324
+ </p>
325
+ </CardFooter>
316
326
  </Card>
317
327
  </div>
318
328
  </div>
@@ -326,7 +336,8 @@ import { post } from '../lib/api.ts'
326
336
  import { Button } from '@/components/ui/button'
327
337
  import { Input } from '@/components/ui/input'
328
338
  import { Label } from '@/components/ui/label'
329
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
339
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
340
+ import { UserPlus } from 'lucide-react'
330
341
 
331
342
  interface RegisterProps {
332
343
  appName?: string
@@ -358,62 +369,72 @@ export default function Register({ appName = '${ctx.name}', navigate }: Register
358
369
  <h2 className="text-lg font-semibold text-foreground">{appName}</h2>
359
370
  </div>
360
371
  <Card>
361
- <CardHeader>
372
+ <CardHeader className="text-center">
362
373
  <CardTitle className="text-xl">Create an account</CardTitle>
363
374
  <CardDescription>Get started with {appName}</CardDescription>
364
375
  </CardHeader>
365
376
  <CardContent>
366
- <div className="space-y-4">
367
- {error && (
368
- <div className="bg-destructive/10 border border-destructive/30 text-destructive rounded-lg px-3.5 py-2.5 text-sm">
369
- {error}
370
- </div>
371
- )}
372
- <form onSubmit={handleSubmit} className="space-y-4">
373
- <div className="space-y-2">
374
- <Label htmlFor="name">Name</Label>
375
- <Input
376
- id="name"
377
- value={name}
378
- onChange={(e) => setName(e.target.value)}
379
- required
380
- placeholder="Your name"
381
- />
382
- </div>
383
- <div className="space-y-2">
384
- <Label htmlFor="email">Email</Label>
385
- <Input
386
- id="email"
387
- type="email"
388
- value={email}
389
- onChange={(e) => setEmail(e.target.value)}
390
- required
391
- placeholder="you@example.com"
392
- />
393
- </div>
394
- <div className="space-y-2">
395
- <Label htmlFor="password">Password</Label>
396
- <Input
397
- id="password"
398
- type="password"
399
- value={password}
400
- onChange={(e) => setPassword(e.target.value)}
401
- required
402
- placeholder="Create a password"
403
- />
404
- </div>
405
- <Button type="submit" className="w-full" disabled={loading}>
406
- {loading ? 'Creating account...' : 'Create account'}
407
- </Button>
408
- </form>
409
- <p className="text-sm text-muted-foreground text-center">
410
- Already have an account?{' '}
411
- <a href="/login" className="text-primary hover:text-primary/80 font-medium">
412
- Sign in
413
- </a>
414
- </p>
415
- </div>
377
+ {error && (
378
+ <div className="mb-4 rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
379
+ {error}
380
+ </div>
381
+ )}
382
+ <form onSubmit={handleSubmit} className="space-y-4">
383
+ <div className="space-y-2">
384
+ <Label htmlFor="name">Name</Label>
385
+ <Input
386
+ id="name"
387
+ value={name}
388
+ onChange={(e) => setName(e.target.value)}
389
+ required
390
+ placeholder="Your name"
391
+ autoComplete="name"
392
+ />
393
+ </div>
394
+ <div className="space-y-2">
395
+ <Label htmlFor="email">Email</Label>
396
+ <Input
397
+ id="email"
398
+ type="email"
399
+ value={email}
400
+ onChange={(e) => setEmail(e.target.value)}
401
+ required
402
+ placeholder="you@example.com"
403
+ autoComplete="email"
404
+ />
405
+ </div>
406
+ <div className="space-y-2">
407
+ <Label htmlFor="password">Password</Label>
408
+ <Input
409
+ id="password"
410
+ type="password"
411
+ value={password}
412
+ onChange={(e) => setPassword(e.target.value)}
413
+ required
414
+ placeholder="Create a password"
415
+ autoComplete="new-password"
416
+ />
417
+ </div>
418
+ <Button type="submit" className="w-full" disabled={loading}>
419
+ {loading ? (
420
+ 'Creating account\u2026'
421
+ ) : (
422
+ <>
423
+ <UserPlus className="mr-2 h-4 w-4" />
424
+ Create account
425
+ </>
426
+ )}
427
+ </Button>
428
+ </form>
416
429
  </CardContent>
430
+ <CardFooter className="justify-center">
431
+ <p className="text-sm text-muted-foreground">
432
+ Already have an account?{' '}
433
+ <Button variant="link" className="h-auto p-0 text-sm" onClick={() => navigate('/login')}>
434
+ Sign in
435
+ </Button>
436
+ </p>
437
+ </CardFooter>
417
438
  </Card>
418
439
  </div>
419
440
  </div>
@@ -437,13 +458,53 @@ import {
437
458
  TableHeader,
438
459
  TableRow,
439
460
  } from '@/components/ui/table'
440
-
441
- interface User { id: number; name: string; email: string; role: string }
461
+ import {
462
+ DropdownMenu,
463
+ DropdownMenuContent,
464
+ DropdownMenuItem,
465
+ DropdownMenuLabel,
466
+ DropdownMenuSeparator,
467
+ DropdownMenuTrigger,
468
+ } from '@/components/ui/dropdown-menu'
469
+ import {
470
+ Tooltip,
471
+ TooltipContent,
472
+ TooltipProvider,
473
+ TooltipTrigger,
474
+ } from '@/components/ui/tooltip'
475
+ import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
476
+ import {
477
+ Home,
478
+ Users,
479
+ PanelLeftClose,
480
+ PanelLeft,
481
+ Sun,
482
+ Moon,
483
+ Menu,
484
+ Github,
485
+ ChevronDown,
486
+ LogOut,
487
+ Settings,
488
+ User,
489
+ Activity,
490
+ Zap,
491
+ TrendingUp,
492
+ UserCheck,
493
+ UserPlus,
494
+ Shield,
495
+ } from 'lucide-react'
496
+
497
+ interface UserRecord {
498
+ id: number
499
+ name: string
500
+ email: string
501
+ role: string
502
+ }
442
503
 
443
504
  interface DashboardProps {
444
505
  appName?: string
445
- currentUser?: User | null
446
- users?: User[]
506
+ currentUser?: UserRecord | null
507
+ users?: UserRecord[]
447
508
  navigate: (href: string) => void
448
509
  [key: string]: any
449
510
  }
@@ -457,12 +518,166 @@ function getInitials(name: string) {
457
518
  .slice(0, 2)
458
519
  }
459
520
 
460
- export default function Dashboard({ appName = '${ctx.name}', currentUser, users: initialUsers, navigate }: DashboardProps) {
461
- const [users, setUsers] = useState<User[]>(initialUsers ?? [])
521
+ /* ── Sidebar nav items ────────────────────────────────────────────────────── */
522
+ const mainNav = [
523
+ { label: 'Dashboard', icon: Home, href: '/dashboard', active: true },
524
+ { label: 'Users', icon: Users, href: '#users-section', scroll: true },
525
+ ]
526
+ const secondaryNav = [
527
+ { label: 'Heartbeat', icon: Activity, href: '/_heartbeat' },
528
+ { label: 'API Ping', icon: Zap, href: '/api/ping' },
529
+ ]
530
+
531
+ /* ── Sidebar content (shared between desktop aside & mobile Sheet) ──────── */
532
+ function SidebarNav({
533
+ appName,
534
+ collapsed,
535
+ onToggle,
536
+ onNavigate,
537
+ }: {
538
+ appName: string
539
+ collapsed: boolean
540
+ onToggle: () => void
541
+ onNavigate?: () => void
542
+ }) {
543
+ return (
544
+ <TooltipProvider delayDuration={0}>
545
+ <div className="flex h-full flex-col bg-sidebar text-sidebar-foreground">
546
+ {/* Brand */}
547
+ <div className="flex h-14 items-center border-b border-sidebar-border px-3">
548
+ {!collapsed && (
549
+ <span className="flex-1 truncate pl-2 text-sm font-semibold">{appName}</span>
550
+ )}
551
+ <Tooltip>
552
+ <TooltipTrigger asChild>
553
+ <Button
554
+ variant="ghost"
555
+ size="icon"
556
+ className="ml-auto h-8 w-8 text-sidebar-foreground/60 hover:text-sidebar-foreground"
557
+ onClick={onToggle}
558
+ >
559
+ {collapsed ? <PanelLeft className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
560
+ </Button>
561
+ </TooltipTrigger>
562
+ <TooltipContent side="right">{collapsed ? 'Expand sidebar' : 'Collapse sidebar'}</TooltipContent>
563
+ </Tooltip>
564
+ </div>
565
+
566
+ {/* Main nav */}
567
+ <nav className="flex-1 space-y-1 px-2 py-3">
568
+ {mainNav.map((item) => {
569
+ const Icon = item.icon
570
+ const btn = (
571
+ <Button
572
+ key={item.label}
573
+ variant="ghost"
574
+ className={\`w-full \${collapsed ? 'justify-center px-0' : 'justify-start gap-3'} \${
575
+ item.active
576
+ ? 'bg-sidebar-accent text-sidebar-accent-foreground'
577
+ : 'text-sidebar-foreground/60 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'
578
+ }\`}
579
+ asChild
580
+ >
581
+ <a
582
+ href={item.href}
583
+ onClick={
584
+ item.scroll
585
+ ? (e: React.MouseEvent) => {
586
+ e.preventDefault()
587
+ document.getElementById('users-section')?.scrollIntoView({ behavior: 'smooth' })
588
+ onNavigate?.()
589
+ }
590
+ : onNavigate
591
+ ? (e: React.MouseEvent) => { onNavigate() }
592
+ : undefined
593
+ }
594
+ >
595
+ <Icon className="h-4 w-4 shrink-0" />
596
+ {!collapsed && item.label}
597
+ </a>
598
+ </Button>
599
+ )
600
+ if (collapsed) {
601
+ return (
602
+ <Tooltip key={item.label}>
603
+ <TooltipTrigger asChild>{btn}</TooltipTrigger>
604
+ <TooltipContent side="right">{item.label}</TooltipContent>
605
+ </Tooltip>
606
+ )
607
+ }
608
+ return btn
609
+ })}
610
+ </nav>
611
+
612
+ <Separator className="bg-sidebar-border" />
613
+
614
+ {/* Secondary nav */}
615
+ <div className="space-y-1 px-2 py-3">
616
+ {secondaryNav.map((item) => {
617
+ const Icon = item.icon
618
+ const btn = (
619
+ <Button
620
+ key={item.label}
621
+ variant="ghost"
622
+ className={\`w-full \${collapsed ? 'justify-center px-0' : 'justify-start gap-3'} text-sidebar-foreground/60 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground\`}
623
+ asChild
624
+ >
625
+ <a href={item.href} onClick={onNavigate ? () => onNavigate() : undefined}>
626
+ <Icon className="h-4 w-4 shrink-0" />
627
+ {!collapsed && item.label}
628
+ </a>
629
+ </Button>
630
+ )
631
+ if (collapsed) {
632
+ return (
633
+ <Tooltip key={item.label}>
634
+ <TooltipTrigger asChild>{btn}</TooltipTrigger>
635
+ <TooltipContent side="right">{item.label}</TooltipContent>
636
+ </Tooltip>
637
+ )
638
+ }
639
+ return btn
640
+ })}
641
+ </div>
642
+
643
+ {/* GitHub link */}
644
+ <div className="border-t border-sidebar-border px-2 py-3">
645
+ {collapsed ? (
646
+ <Tooltip>
647
+ <TooltipTrigger asChild>
648
+ <Button variant="ghost" size="icon" className="w-full text-sidebar-foreground/60 hover:text-sidebar-foreground" asChild>
649
+ <a href="https://github.com" target="_blank" rel="noreferrer">
650
+ <Github className="h-4 w-4" />
651
+ </a>
652
+ </Button>
653
+ </TooltipTrigger>
654
+ <TooltipContent side="right">GitHub</TooltipContent>
655
+ </Tooltip>
656
+ ) : (
657
+ <Button variant="ghost" className="w-full justify-start gap-3 text-sidebar-foreground/60 hover:text-sidebar-foreground" asChild>
658
+ <a href="https://github.com" target="_blank" rel="noreferrer">
659
+ <Github className="h-4 w-4 shrink-0" />
660
+ GitHub
661
+ </a>
662
+ </Button>
663
+ )}
664
+ </div>
665
+ </div>
666
+ </TooltipProvider>
667
+ )
668
+ }
669
+
670
+ export default function Dashboard({
671
+ appName = '${ctx.name}',
672
+ currentUser,
673
+ users: initialUsers,
674
+ navigate,
675
+ }: DashboardProps) {
676
+ const [users, setUsers] = useState<UserRecord[]>(initialUsers ?? [])
462
677
  const [loading, setLoading] = useState(!initialUsers?.length)
463
- const [sidebarOpen, setSidebarOpen] = useState(false)
678
+ const [collapsed, setCollapsed] = useState(false)
464
679
  const [isDark, setIsDark] = useState(() =>
465
- typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true
680
+ typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true,
466
681
  )
467
682
 
468
683
  const toggleTheme = () => {
@@ -487,117 +702,160 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
487
702
  navigate('/login')
488
703
  }
489
704
 
705
+ /* Stats — derived from real user data + mock extras */
706
+ const stats = [
707
+ { label: 'Total Users', value: users.length, icon: Users, change: '+12%' },
708
+ { label: 'Active Now', value: Math.max(1, Math.ceil(users.length * 0.6)), icon: UserCheck, change: '+3%' },
709
+ { label: 'New Today', value: Math.min(users.length, 2), icon: UserPlus, change: '+18%' },
710
+ { label: 'Admin Users', value: users.filter((u) => u.role === 'admin').length, icon: Shield, change: '0%' },
711
+ ]
712
+
713
+ const sidebarWidth = collapsed ? 'w-16' : 'w-60'
714
+
490
715
  return (
491
- <div className="min-h-screen flex bg-background">
492
- {/* Mobile overlay */}
493
- {sidebarOpen && (
494
- <div
495
- className="fixed inset-0 z-40 bg-black/50 lg:hidden"
496
- onClick={() => setSidebarOpen(false)}
716
+ <div className="min-h-screen bg-background">
717
+ {/* ── Desktop sidebar ──────────────────────────────────────────────── */}
718
+ <aside
719
+ className={\`fixed inset-y-0 left-0 z-30 hidden border-r border-sidebar-border transition-all duration-200 lg:block \${sidebarWidth}\`}
720
+ >
721
+ <SidebarNav
722
+ appName={appName}
723
+ collapsed={collapsed}
724
+ onToggle={() => setCollapsed((c) => !c)}
497
725
  />
498
- )}
499
-
500
- {/* Sidebar */}
501
- <aside className={\`fixed inset-y-0 left-0 z-50 w-60 bg-sidebar border-r border-sidebar-border flex flex-col transition-transform lg:translate-x-0 \${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}\`}>
502
- <div className="h-14 flex items-center px-5 border-b border-sidebar-border">
503
- <span className="text-sm font-semibold text-sidebar-foreground">{appName}</span>
504
- </div>
505
- <nav className="flex-1 px-3 py-3 space-y-1">
506
- <Button variant="ghost" className="w-full justify-start gap-2.5 bg-sidebar-accent text-sidebar-accent-foreground" asChild>
507
- <a href="/dashboard">
508
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>
509
- Dashboard
510
- </a>
511
- </Button>
512
- <Button variant="ghost" className="w-full justify-start gap-2.5 text-sidebar-foreground/60 hover:text-sidebar-foreground" asChild>
513
- <a href="#users-section" onClick={(e) => { e.preventDefault(); document.getElementById('users-section')?.scrollIntoView({ behavior: 'smooth' }); setSidebarOpen(false) }}>
514
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
515
- Users
516
- </a>
517
- </Button>
518
- </nav>
519
- <Separator />
520
- <div className="px-3 py-3 space-y-1">
521
- <Button variant="ghost" className="w-full justify-start gap-2.5 text-sidebar-foreground/60 hover:text-sidebar-foreground" asChild>
522
- <a href="/_heartbeat">
523
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064" /></svg>
524
- Heartbeat
525
- </a>
526
- </Button>
527
- <Button variant="ghost" className="w-full justify-start gap-2.5 text-sidebar-foreground/60 hover:text-sidebar-foreground" asChild>
528
- <a href="/api/ping">
529
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
530
- API Ping
531
- </a>
532
- </Button>
533
- </div>
534
726
  </aside>
535
727
 
536
- {/* Main */}
537
- <div className="flex-1 lg:ml-60">
728
+ {/* ── Main area ────────────────────────────────────────────────────── */}
729
+ <div className={\`transition-all duration-200 \${collapsed ? 'lg:ml-16' : 'lg:ml-60'}\`}>
538
730
  {/* Top bar */}
539
- <header className="h-14 border-b border-border bg-background/90 backdrop-blur-md sticky top-0 z-20 flex items-center justify-between px-4 lg:px-6">
731
+ <header className="sticky top-0 z-20 flex h-14 items-center justify-between border-b border-border bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60 lg:px-6">
540
732
  <div className="flex items-center gap-3">
541
- {/* Mobile hamburger */}
542
- <Button
543
- variant="ghost"
544
- size="icon"
545
- className="lg:hidden"
546
- onClick={() => setSidebarOpen(!sidebarOpen)}
547
- >
548
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
549
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
550
- </svg>
551
- </Button>
733
+ {/* Mobile hamburger via Sheet */}
734
+ <Sheet>
735
+ <SheetTrigger asChild>
736
+ <Button variant="ghost" size="icon" className="lg:hidden">
737
+ <Menu className="h-5 w-5" />
738
+ <span className="sr-only">Toggle sidebar</span>
739
+ </Button>
740
+ </SheetTrigger>
741
+ <SheetContent side="left" className="w-60 p-0">
742
+ <SidebarNav
743
+ appName={appName}
744
+ collapsed={false}
745
+ onToggle={() => {}}
746
+ />
747
+ </SheetContent>
748
+ </Sheet>
552
749
  <h1 className="text-sm font-medium text-foreground">Dashboard</h1>
553
750
  </div>
554
- <div className="flex items-center gap-3">
555
- <Button variant="ghost" size="icon" onClick={toggleTheme} title="Toggle theme">
556
- {isDark ? (
557
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
558
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
559
- </svg>
560
- ) : (
561
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
562
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
563
- </svg>
564
- )}
565
- </Button>
566
- <div className="flex items-center gap-2">
567
- <Avatar className="h-7 w-7">
568
- <AvatarFallback className="text-[10px] bg-primary/10 text-primary">
569
- {currentUser?.name ? getInitials(currentUser.name) : '?'}
570
- </AvatarFallback>
571
- </Avatar>
572
- <span className="text-xs text-muted-foreground hidden sm:inline">{currentUser?.name}</span>
573
- </div>
574
- <Button variant="outline" size="sm" onClick={handleLogout}>
575
- Logout
576
- </Button>
751
+
752
+ <div className="flex items-center gap-2">
753
+ {/* Theme toggle */}
754
+ <TooltipProvider>
755
+ <Tooltip>
756
+ <TooltipTrigger asChild>
757
+ <Button variant="ghost" size="icon" onClick={toggleTheme}>
758
+ {isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
759
+ <span className="sr-only">Toggle theme</span>
760
+ </Button>
761
+ </TooltipTrigger>
762
+ <TooltipContent>{isDark ? 'Light mode' : 'Dark mode'}</TooltipContent>
763
+ </Tooltip>
764
+ </TooltipProvider>
765
+
766
+ {/* Account dropdown */}
767
+ <DropdownMenu>
768
+ <DropdownMenuTrigger asChild>
769
+ <Button variant="ghost" className="gap-2 px-2">
770
+ <Avatar className="h-7 w-7">
771
+ <AvatarFallback className="bg-primary/10 text-[10px] text-primary">
772
+ {currentUser?.name ? getInitials(currentUser.name) : '?'}
773
+ </AvatarFallback>
774
+ </Avatar>
775
+ <span className="hidden text-sm font-medium sm:inline-block">
776
+ {currentUser?.name}
777
+ </span>
778
+ <ChevronDown className="hidden h-4 w-4 text-muted-foreground sm:inline-block" />
779
+ </Button>
780
+ </DropdownMenuTrigger>
781
+ <DropdownMenuContent align="end" className="w-56">
782
+ <DropdownMenuLabel className="font-normal">
783
+ <div className="flex flex-col space-y-1">
784
+ <p className="text-sm font-medium leading-none">{currentUser?.name}</p>
785
+ <p className="text-xs leading-none text-muted-foreground">{currentUser?.email}</p>
786
+ </div>
787
+ </DropdownMenuLabel>
788
+ <DropdownMenuSeparator />
789
+ <DropdownMenuItem>
790
+ <User className="mr-2 h-4 w-4" />
791
+ Profile
792
+ </DropdownMenuItem>
793
+ <DropdownMenuItem>
794
+ <Settings className="mr-2 h-4 w-4" />
795
+ Settings
796
+ </DropdownMenuItem>
797
+ <DropdownMenuSeparator />
798
+ <DropdownMenuItem onClick={handleLogout}>
799
+ <LogOut className="mr-2 h-4 w-4" />
800
+ Sign out
801
+ </DropdownMenuItem>
802
+ </DropdownMenuContent>
803
+ </DropdownMenu>
577
804
  </div>
578
805
  </header>
579
806
 
580
807
  {/* Content */}
581
- <main className="p-4 lg:p-6 space-y-6 animate-fade-up">
808
+ <main className="animate-fade-up space-y-6 p-4 lg:p-6">
809
+ {/* Welcome card */}
582
810
  <Card>
583
811
  <CardHeader>
584
812
  <CardTitle>Welcome back, {currentUser?.name}</CardTitle>
585
- <CardDescription>Here's what's happening with your application.</CardDescription>
813
+ <CardDescription>
814
+ Here's what's happening with your application today.
815
+ </CardDescription>
586
816
  </CardHeader>
587
817
  </Card>
588
818
 
819
+ {/* Stats row */}
820
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
821
+ {stats.map((stat) => {
822
+ const Icon = stat.icon
823
+ return (
824
+ <Card key={stat.label}>
825
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
826
+ <CardDescription className="text-sm font-medium">{stat.label}</CardDescription>
827
+ <Icon className="h-4 w-4 text-muted-foreground" />
828
+ </CardHeader>
829
+ <CardContent>
830
+ <div className="text-2xl font-bold">{stat.value}</div>
831
+ <p className="flex items-center gap-1 text-xs text-muted-foreground">
832
+ <TrendingUp className="h-3 w-3" />
833
+ {stat.change} from last month
834
+ </p>
835
+ </CardContent>
836
+ </Card>
837
+ )
838
+ })}
839
+ </div>
840
+
841
+ {/* Users table */}
589
842
  <Card id="users-section">
590
843
  <CardHeader className="flex flex-row items-center justify-between">
591
844
  <div className="space-y-1">
592
845
  <CardTitle className="text-base">Users</CardTitle>
593
- <CardDescription>{loading ? 'Loading...' : \`\${users.length} total\`}</CardDescription>
846
+ <CardDescription>
847
+ {loading ? 'Loading\u2026' : \`\${users.length} registered user\${users.length === 1 ? '' : 's'}\`}
848
+ </CardDescription>
594
849
  </div>
850
+ <Button variant="outline" size="sm" onClick={fetchUsers} disabled={loading}>
851
+ Refresh
852
+ </Button>
595
853
  </CardHeader>
596
854
  <CardContent>
597
855
  <Table>
598
856
  <TableHeader>
599
857
  <TableRow>
600
- <TableHead className="w-[200px]">Name</TableHead>
858
+ <TableHead className="w-[240px]">Name</TableHead>
601
859
  <TableHead>Email</TableHead>
602
860
  <TableHead className="w-[100px]">Role</TableHead>
603
861
  </TableRow>
@@ -606,9 +864,9 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
606
864
  {users.map((u) => (
607
865
  <TableRow key={u.id}>
608
866
  <TableCell>
609
- <div className="flex items-center gap-2.5">
610
- <Avatar className="h-7 w-7">
611
- <AvatarFallback className="text-[10px] bg-muted">
867
+ <div className="flex items-center gap-3">
868
+ <Avatar className="h-8 w-8">
869
+ <AvatarFallback className="bg-muted text-xs">
612
870
  {getInitials(u.name)}
613
871
  </AvatarFallback>
614
872
  </Avatar>
@@ -626,7 +884,7 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
626
884
  {users.length === 0 && !loading && (
627
885
  <TableRow>
628
886
  <TableCell colSpan={3} className="h-24 text-center text-muted-foreground">
629
- No users found
887
+ No users found.
630
888
  </TableCell>
631
889
  </TableRow>
632
890
  )}