create-zhx-monorepo 0.2.2 → 2.0.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 (349) hide show
  1. package/README.md +1 -1
  2. package/bin/index.js +0 -0
  3. package/package.json +3 -3
  4. package/templates/monorepo-starter/apps/dashboard/components.json +20 -0
  5. package/templates/monorepo-starter/apps/dashboard/eslint.config.js +1 -0
  6. package/templates/monorepo-starter/apps/dashboard/next-env.d.ts +6 -0
  7. package/templates/monorepo-starter/apps/dashboard/next.config.mjs +30 -0
  8. package/templates/monorepo-starter/apps/dashboard/package.json +43 -0
  9. package/templates/monorepo-starter/apps/dashboard/postcss.config.mjs +1 -0
  10. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/[id]/edit/page.tsx +8 -0
  11. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/[id]/page.tsx +104 -0
  12. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/new/page.tsx +7 -0
  13. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/admin/users/page.tsx +88 -0
  14. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/dashboard/data.json +614 -0
  15. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/dashboard/page.tsx +21 -0
  16. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/layout.tsx +40 -0
  17. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/media/page.tsx +7 -0
  18. package/templates/monorepo-starter/apps/dashboard/src/app/(root)/users/account/page.tsx +75 -0
  19. package/templates/monorepo-starter/apps/dashboard/src/app/auth/[type]/page.tsx +19 -0
  20. package/templates/monorepo-starter/apps/dashboard/src/app/auth/layout.tsx +11 -0
  21. package/templates/monorepo-starter/apps/dashboard/src/app/auth/verify/page.tsx +11 -0
  22. package/templates/monorepo-starter/apps/dashboard/src/app/favicon.ico +0 -0
  23. package/templates/monorepo-starter/apps/dashboard/src/app/layout.tsx +28 -0
  24. package/templates/monorepo-starter/apps/dashboard/src/components/forms/AuthForm.tsx +345 -0
  25. package/templates/monorepo-starter/apps/dashboard/src/components/forms/CUUserForm.tsx +223 -0
  26. package/templates/monorepo-starter/apps/dashboard/src/components/forms/MediaForm.tsx +35 -0
  27. package/templates/monorepo-starter/apps/dashboard/src/components/forms/SocialAuthField.tsx +56 -0
  28. package/templates/monorepo-starter/apps/dashboard/src/components/layout/AppSidebar.tsx +141 -0
  29. package/templates/monorepo-starter/apps/dashboard/src/components/layout/DashboardChart.tsx +293 -0
  30. package/templates/monorepo-starter/apps/dashboard/src/components/layout/DashboardStats.tsx +104 -0
  31. package/templates/monorepo-starter/apps/dashboard/src/components/layout/Header.tsx +21 -0
  32. package/templates/monorepo-starter/apps/dashboard/src/components/layout/NavMain.tsx +99 -0
  33. package/templates/monorepo-starter/apps/dashboard/src/components/media/LibraryPopup.tsx +25 -0
  34. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaCard.tsx +175 -0
  35. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaDetails.tsx +58 -0
  36. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaGrid.tsx +51 -0
  37. package/templates/monorepo-starter/apps/dashboard/src/components/media/MediaUploader.tsx +182 -0
  38. package/templates/monorepo-starter/apps/dashboard/src/components/media/index.tsx +96 -0
  39. package/templates/monorepo-starter/apps/dashboard/src/components/shared/DateWrapper.tsx +43 -0
  40. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericArrayField.tsx +189 -0
  41. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericCUForm.tsx +124 -0
  42. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericDetailsPage.tsx +291 -0
  43. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericFormTable.tsx +189 -0
  44. package/templates/monorepo-starter/apps/dashboard/src/components/shared/GenericTable.tsx +244 -0
  45. package/templates/monorepo-starter/apps/dashboard/src/components/shared/ListPage.tsx +134 -0
  46. package/templates/monorepo-starter/apps/dashboard/src/components/shared/Pagination.tsx +120 -0
  47. package/templates/monorepo-starter/apps/dashboard/src/components/shared/SearchToolbar.tsx +133 -0
  48. package/templates/monorepo-starter/apps/dashboard/src/components/shared/ThemeSwitch.tsx +41 -0
  49. package/templates/monorepo-starter/apps/dashboard/src/components/shared/VerifyAuthPage.tsx +109 -0
  50. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/CUFormSkeleton.tsx +31 -0
  51. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/DashboardSke1eton.tsx +135 -0
  52. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/DetailsPageSkeleton.tsx +64 -0
  53. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/RootLayoutSkeleton.tsx +106 -0
  54. package/templates/monorepo-starter/apps/dashboard/src/components/skeleton/TableSkeleton.tsx +77 -0
  55. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserAccountSection.tsx +530 -0
  56. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserCard.tsx +54 -0
  57. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserProfileSection.tsx +239 -0
  58. package/templates/monorepo-starter/apps/dashboard/src/components/user/UserSessions.tsx +154 -0
  59. package/templates/monorepo-starter/apps/dashboard/src/hooks/admin.ts +100 -0
  60. package/templates/monorepo-starter/apps/dashboard/src/hooks/auth.ts +59 -0
  61. package/templates/monorepo-starter/apps/dashboard/src/hooks/crud.ts +275 -0
  62. package/templates/monorepo-starter/apps/dashboard/src/hooks/lookup.ts +38 -0
  63. package/templates/monorepo-starter/apps/dashboard/src/hooks/media.ts +34 -0
  64. package/templates/monorepo-starter/apps/dashboard/src/hooks/user.ts +58 -0
  65. package/templates/monorepo-starter/apps/dashboard/src/lib/constants.ts +61 -0
  66. package/templates/monorepo-starter/apps/dashboard/src/providers/index.tsx +15 -0
  67. package/templates/monorepo-starter/apps/dashboard/src/providers/media-library.tsx +48 -0
  68. package/templates/monorepo-starter/apps/dashboard/tsconfig.json +20 -0
  69. package/templates/monorepo-starter/apps/web/components.json +20 -20
  70. package/templates/monorepo-starter/apps/web/eslint.config.js +1 -0
  71. package/templates/monorepo-starter/apps/web/next-env.d.ts +6 -6
  72. package/templates/monorepo-starter/apps/web/next.config.mjs +20 -6
  73. package/templates/monorepo-starter/apps/web/package.json +36 -31
  74. package/templates/monorepo-starter/apps/web/postcss.config.mjs +1 -1
  75. package/templates/monorepo-starter/apps/web/src/app/(root)/layout.tsx +2 -2
  76. package/templates/monorepo-starter/apps/web/src/app/favicon.ico +0 -0
  77. package/templates/monorepo-starter/apps/web/src/app/layout.tsx +13 -9
  78. package/templates/monorepo-starter/apps/web/src/components/Footer.tsx +5 -7
  79. package/templates/monorepo-starter/apps/web/src/components/Header.tsx +5 -9
  80. package/templates/monorepo-starter/apps/web/tsconfig.json +20 -20
  81. package/templates/monorepo-starter/eslint.config.mjs +1 -1
  82. package/templates/monorepo-starter/gitignore +13 -4
  83. package/templates/monorepo-starter/package.json +49 -23
  84. package/templates/monorepo-starter/packages/config/eslint/next.mjs +18 -0
  85. package/templates/monorepo-starter/packages/config/eslint/node.mjs +44 -0
  86. package/templates/monorepo-starter/packages/config/eslint/react.mjs +68 -0
  87. package/templates/monorepo-starter/packages/config/package.json +23 -32
  88. package/templates/monorepo-starter/packages/config/typescript/node.json +25 -0
  89. package/templates/monorepo-starter/packages/config/typescript/react.json +20 -3
  90. package/templates/monorepo-starter/packages/contracts/eslint.config.mjs +1 -0
  91. package/templates/monorepo-starter/packages/contracts/package.json +51 -0
  92. package/templates/monorepo-starter/packages/contracts/src/admin/dto.ts +6 -0
  93. package/templates/monorepo-starter/packages/contracts/src/admin/index.ts +2 -0
  94. package/templates/monorepo-starter/packages/contracts/src/admin/schema.ts +28 -0
  95. package/templates/monorepo-starter/packages/contracts/src/admin/types.d.ts +27 -0
  96. package/templates/monorepo-starter/packages/contracts/src/auth/dto.ts +24 -0
  97. package/templates/monorepo-starter/packages/contracts/src/auth/index.ts +2 -0
  98. package/templates/monorepo-starter/packages/contracts/src/auth/schema.ts +35 -0
  99. package/templates/monorepo-starter/packages/contracts/src/auth/types.d.ts +73 -0
  100. package/templates/monorepo-starter/packages/contracts/src/lib/enums.ts +55 -0
  101. package/templates/monorepo-starter/packages/contracts/src/lib/index.ts +4 -0
  102. package/templates/monorepo-starter/packages/contracts/src/lib/prisma.ts +2 -0
  103. package/templates/monorepo-starter/packages/contracts/src/lib/schema.ts +61 -0
  104. package/templates/monorepo-starter/packages/contracts/src/lib/types.d.ts +149 -0
  105. package/templates/monorepo-starter/packages/contracts/src/lib/utils.ts +8 -0
  106. package/templates/monorepo-starter/packages/contracts/src/media/dto.ts +12 -0
  107. package/templates/monorepo-starter/packages/contracts/src/media/index.ts +2 -0
  108. package/templates/monorepo-starter/packages/contracts/src/media/schema.ts +27 -0
  109. package/templates/monorepo-starter/packages/contracts/src/media/types.d.ts +36 -0
  110. package/templates/monorepo-starter/packages/contracts/src/notification/dto.ts +6 -0
  111. package/templates/monorepo-starter/packages/contracts/src/notification/index.ts +2 -0
  112. package/templates/monorepo-starter/packages/contracts/src/notification/schema.ts +7 -0
  113. package/templates/monorepo-starter/packages/contracts/src/notification/types.d.ts +22 -0
  114. package/templates/monorepo-starter/packages/contracts/src/user/dto.ts +4 -0
  115. package/templates/monorepo-starter/packages/contracts/src/user/index.ts +2 -0
  116. package/templates/monorepo-starter/packages/contracts/src/user/schema.ts +16 -0
  117. package/templates/monorepo-starter/packages/contracts/src/user/types.d.ts +36 -0
  118. package/templates/monorepo-starter/packages/contracts/tsconfig.json +12 -0
  119. package/templates/monorepo-starter/packages/contracts/tsup.config.ts +20 -0
  120. package/templates/monorepo-starter/packages/sdk/eslint.config.mjs +1 -0
  121. package/templates/monorepo-starter/packages/sdk/package.json +31 -0
  122. package/templates/monorepo-starter/packages/sdk/src/admin/index.ts +31 -0
  123. package/templates/monorepo-starter/packages/sdk/src/auth/index.ts +71 -0
  124. package/templates/monorepo-starter/packages/sdk/src/lib/api-client.ts +71 -0
  125. package/templates/monorepo-starter/packages/sdk/src/lib/index.ts +2 -0
  126. package/templates/monorepo-starter/packages/sdk/src/lib/types.ts +40 -0
  127. package/templates/monorepo-starter/packages/sdk/src/media/index.ts +27 -0
  128. package/templates/monorepo-starter/packages/sdk/src/notification/index.ts +10 -0
  129. package/templates/monorepo-starter/packages/sdk/src/public/index.ts +6 -0
  130. package/templates/monorepo-starter/packages/sdk/src/user/index.ts +7 -0
  131. package/templates/monorepo-starter/packages/sdk/tsconfig.json +12 -0
  132. package/templates/monorepo-starter/packages/shared/eslint.config.mjs +1 -0
  133. package/templates/monorepo-starter/packages/shared/package.json +36 -0
  134. package/templates/monorepo-starter/packages/shared/src/constants/app.ts +4 -0
  135. package/templates/monorepo-starter/packages/shared/src/constants/index.ts +1 -0
  136. package/templates/monorepo-starter/packages/shared/src/utils/date.ts +137 -0
  137. package/templates/monorepo-starter/packages/shared/src/utils/identifier.ts +11 -0
  138. package/templates/monorepo-starter/packages/shared/src/utils/index.ts +2 -0
  139. package/templates/monorepo-starter/packages/shared/tsconfig.json +12 -0
  140. package/templates/monorepo-starter/packages/shared/tsup.config.ts +16 -0
  141. package/templates/monorepo-starter/packages/templates/eslint.config.mjs +1 -0
  142. package/templates/monorepo-starter/packages/templates/package.json +38 -0
  143. package/templates/monorepo-starter/packages/templates/src/email/auth/disableMfa.tsx +40 -0
  144. package/templates/monorepo-starter/packages/templates/src/email/auth/index.ts +10 -0
  145. package/templates/monorepo-starter/packages/templates/src/email/auth/resetPassword.tsx +41 -0
  146. package/templates/monorepo-starter/packages/templates/src/email/auth/securityAlert.tsx +21 -0
  147. package/templates/monorepo-starter/packages/templates/src/email/auth/setPassword.tsx +42 -0
  148. package/templates/monorepo-starter/packages/templates/src/email/auth/signin.tsx +19 -0
  149. package/templates/monorepo-starter/packages/templates/src/email/auth/signup.tsx +24 -0
  150. package/templates/monorepo-starter/packages/templates/src/email/auth/updateIdentifier.tsx +79 -0
  151. package/templates/monorepo-starter/packages/templates/src/email/auth/updateMfa.tsx +40 -0
  152. package/templates/monorepo-starter/packages/templates/src/email/auth/verifyIdentifier.tsx +42 -0
  153. package/templates/monorepo-starter/packages/templates/src/email/auth/verifyMfa.tsx +23 -0
  154. package/templates/monorepo-starter/packages/templates/src/email/components/actionBlock.tsx +39 -0
  155. package/templates/monorepo-starter/packages/templates/src/email/components/footer.tsx +0 -0
  156. package/templates/monorepo-starter/packages/templates/src/email/components/greeting.tsx +11 -0
  157. package/templates/monorepo-starter/packages/templates/src/email/components/header.tsx +15 -0
  158. package/templates/monorepo-starter/packages/templates/src/email/components/layout.tsx +33 -0
  159. package/templates/monorepo-starter/packages/templates/src/email/components/numericCode.tsx +16 -0
  160. package/templates/monorepo-starter/packages/templates/src/email/index.ts +2 -0
  161. package/templates/monorepo-starter/packages/templates/src/email/service/accountReactivated.tsx +31 -0
  162. package/templates/monorepo-starter/packages/templates/src/email/service/accountSuspended.tsx +37 -0
  163. package/templates/monorepo-starter/packages/templates/src/email/service/index.ts +2 -0
  164. package/templates/monorepo-starter/packages/templates/src/email/test.tsx +141 -0
  165. package/templates/monorepo-starter/packages/templates/src/index.ts +1 -0
  166. package/templates/monorepo-starter/packages/templates/src/lib/constants.ts +22 -0
  167. package/templates/monorepo-starter/packages/templates/src/lib/utils.ts +19 -0
  168. package/templates/monorepo-starter/packages/templates/src/types/global.d.ts +37 -0
  169. package/templates/monorepo-starter/packages/templates/tsconfig.json +13 -0
  170. package/templates/monorepo-starter/packages/templates/tsup.config.ts +23 -0
  171. package/templates/monorepo-starter/packages/ui/components.json +1 -1
  172. package/templates/monorepo-starter/packages/ui/eslint.config.mjs +1 -4
  173. package/templates/monorepo-starter/packages/ui/package.json +81 -38
  174. package/templates/monorepo-starter/packages/ui/src/components/accordion.tsx +66 -0
  175. package/templates/monorepo-starter/packages/ui/src/components/airport-search-field.tsx +203 -0
  176. package/templates/monorepo-starter/packages/ui/src/components/alert-dialog.tsx +157 -0
  177. package/templates/monorepo-starter/packages/ui/src/components/alert.tsx +66 -0
  178. package/templates/monorepo-starter/packages/ui/src/components/aspect-ratio.tsx +11 -0
  179. package/templates/monorepo-starter/packages/ui/src/components/avatar.tsx +53 -0
  180. package/templates/monorepo-starter/packages/ui/src/components/badge.tsx +46 -0
  181. package/templates/monorepo-starter/packages/ui/src/components/breadcrumb.tsx +109 -0
  182. package/templates/monorepo-starter/packages/ui/src/components/button-group.tsx +83 -0
  183. package/templates/monorepo-starter/packages/ui/src/components/button.tsx +2 -1
  184. package/templates/monorepo-starter/packages/ui/src/components/calendar.tsx +220 -0
  185. package/templates/monorepo-starter/packages/ui/src/components/card.tsx +92 -0
  186. package/templates/monorepo-starter/packages/ui/src/components/carousel.tsx +241 -0
  187. package/templates/monorepo-starter/packages/ui/src/components/chart.tsx +357 -0
  188. package/templates/monorepo-starter/packages/ui/src/components/checkbox.tsx +32 -0
  189. package/templates/monorepo-starter/packages/ui/src/components/collapsible.tsx +33 -0
  190. package/templates/monorepo-starter/packages/ui/src/components/combobox-field.tsx +181 -0
  191. package/templates/monorepo-starter/packages/ui/src/components/combobox.tsx +309 -0
  192. package/templates/monorepo-starter/packages/ui/src/components/command.tsx +184 -0
  193. package/templates/monorepo-starter/packages/ui/src/components/context-menu.tsx +252 -0
  194. package/templates/monorepo-starter/packages/ui/src/components/date-field.tsx +65 -0
  195. package/templates/monorepo-starter/packages/ui/src/components/dialog.tsx +143 -0
  196. package/templates/monorepo-starter/packages/ui/src/components/direction.tsx +22 -0
  197. package/templates/monorepo-starter/packages/ui/src/components/drawer.tsx +135 -0
  198. package/templates/monorepo-starter/packages/ui/src/components/dropdown-menu.tsx +257 -0
  199. package/templates/monorepo-starter/packages/ui/src/components/empty.tsx +104 -0
  200. package/templates/monorepo-starter/packages/ui/src/components/field.tsx +248 -0
  201. package/templates/monorepo-starter/packages/ui/src/components/form.tsx +197 -0
  202. package/templates/monorepo-starter/packages/ui/src/components/hover-card.tsx +44 -0
  203. package/templates/monorepo-starter/packages/ui/src/components/input-field.tsx +116 -0
  204. package/templates/monorepo-starter/packages/ui/src/components/input-group.tsx +170 -0
  205. package/templates/monorepo-starter/packages/ui/src/components/input-otp.tsx +77 -0
  206. package/templates/monorepo-starter/packages/ui/src/components/input.tsx +44 -0
  207. package/templates/monorepo-starter/packages/ui/src/components/item.tsx +193 -0
  208. package/templates/monorepo-starter/packages/ui/src/components/kbd.tsx +28 -0
  209. package/templates/monorepo-starter/packages/ui/src/components/label.tsx +24 -0
  210. package/templates/monorepo-starter/packages/ui/src/components/menubar.tsx +276 -0
  211. package/templates/monorepo-starter/packages/ui/src/components/native-select.tsx +53 -0
  212. package/templates/monorepo-starter/packages/ui/src/components/navigation-menu.tsx +168 -0
  213. package/templates/monorepo-starter/packages/ui/src/components/otp-modal.tsx +125 -0
  214. package/templates/monorepo-starter/packages/ui/src/components/pagination.tsx +127 -0
  215. package/templates/monorepo-starter/packages/ui/src/components/popover.tsx +48 -0
  216. package/templates/monorepo-starter/packages/ui/src/components/progress.tsx +31 -0
  217. package/templates/monorepo-starter/packages/ui/src/components/radio-group.tsx +45 -0
  218. package/templates/monorepo-starter/packages/ui/src/components/resizable.tsx +53 -0
  219. package/templates/monorepo-starter/packages/ui/src/components/scroll-area.tsx +58 -0
  220. package/templates/monorepo-starter/packages/ui/src/components/select-field.tsx +165 -0
  221. package/templates/monorepo-starter/packages/ui/src/components/select.tsx +190 -0
  222. package/templates/monorepo-starter/packages/ui/src/components/separator.tsx +28 -0
  223. package/templates/monorepo-starter/packages/ui/src/components/sheet.tsx +139 -0
  224. package/templates/monorepo-starter/packages/ui/src/components/sidebar.tsx +728 -0
  225. package/templates/monorepo-starter/packages/ui/src/components/skeleton.tsx +13 -0
  226. package/templates/monorepo-starter/packages/ui/src/components/slider.tsx +63 -0
  227. package/templates/monorepo-starter/packages/ui/src/components/sonner.tsx +57 -0
  228. package/templates/monorepo-starter/packages/ui/src/components/spinner.tsx +16 -0
  229. package/templates/monorepo-starter/packages/ui/src/components/switch-field.tsx +22 -0
  230. package/templates/monorepo-starter/packages/ui/src/components/switch.tsx +35 -0
  231. package/templates/monorepo-starter/packages/ui/src/components/table.tsx +120 -0
  232. package/templates/monorepo-starter/packages/ui/src/components/tabs.tsx +66 -0
  233. package/templates/monorepo-starter/packages/ui/src/components/textarea.tsx +18 -0
  234. package/templates/monorepo-starter/packages/ui/src/components/toggle-group.tsx +83 -0
  235. package/templates/monorepo-starter/packages/ui/src/components/toggle.tsx +47 -0
  236. package/templates/monorepo-starter/packages/ui/src/components/tooltip.tsx +61 -0
  237. package/templates/monorepo-starter/packages/ui/src/hooks/use-confirm.ts +12 -0
  238. package/templates/monorepo-starter/packages/ui/src/hooks/use-dialog.ts +12 -0
  239. package/templates/monorepo-starter/packages/ui/src/hooks/use-mobile.ts +21 -0
  240. package/templates/monorepo-starter/packages/ui/src/lib/utils.ts +38 -3
  241. package/templates/monorepo-starter/packages/ui/src/provider/confirm-dialog.tsx +99 -0
  242. package/templates/monorepo-starter/packages/ui/src/provider/dialog.tsx +62 -0
  243. package/templates/monorepo-starter/packages/ui/src/provider/index.tsx +30 -0
  244. package/templates/monorepo-starter/packages/ui/src/provider/react-query.tsx +11 -0
  245. package/templates/monorepo-starter/{apps/web/src/providers → packages/ui/src/provider}/theme.tsx +5 -7
  246. package/templates/monorepo-starter/packages/ui/src/styles/globals.css +109 -61
  247. package/templates/monorepo-starter/packages/ui/src/types/airports-data.d.ts +21 -0
  248. package/templates/monorepo-starter/packages/ui/tsconfig.json +1 -3
  249. package/templates/monorepo-starter/pnpm-lock.yaml +17765 -12441
  250. package/templates/monorepo-starter/pnpm-workspace.yaml +3 -16
  251. package/templates/monorepo-starter/server/.env.example +7 -5
  252. package/templates/monorepo-starter/server/eslint.config.mjs +1 -4
  253. package/templates/monorepo-starter/server/package.json +129 -97
  254. package/templates/monorepo-starter/server/prisma/schema.prisma +170 -92
  255. package/templates/monorepo-starter/server/prisma/seed-functions.ts +0 -0
  256. package/templates/monorepo-starter/server/prisma/seed.ts +0 -0
  257. package/templates/monorepo-starter/server/prisma.config.ts +9 -9
  258. package/templates/monorepo-starter/server/scripts/admin-bootstrap.ts +61 -0
  259. package/templates/monorepo-starter/server/scripts/gen-module.sh +20 -0
  260. package/templates/monorepo-starter/server/scripts/gen-soft-delete.ts +52 -0
  261. package/templates/monorepo-starter/server/src/app.module.ts +35 -28
  262. package/templates/monorepo-starter/server/src/lib/constants/index.ts +59 -0
  263. package/templates/monorepo-starter/server/src/lib/decorators/boolean-query.decorator.ts +18 -0
  264. package/templates/monorepo-starter/server/src/lib/decorators/logger.decorator.ts +1 -1
  265. package/templates/monorepo-starter/server/src/lib/decorators/oauth.decorator.ts +7 -0
  266. package/templates/monorepo-starter/server/src/lib/decorators/user.decorator.ts +10 -0
  267. package/templates/monorepo-starter/server/src/lib/filters/exceptions.filter.ts +169 -24
  268. package/templates/monorepo-starter/server/src/lib/guards/auth.guard.ts +29 -65
  269. package/templates/monorepo-starter/server/src/lib/guards/oauth.guard.ts +28 -0
  270. package/templates/monorepo-starter/server/src/lib/interceptors/response.interceptor.ts +42 -5
  271. package/templates/monorepo-starter/server/src/lib/middleware/cache.middleware.ts +24 -0
  272. package/templates/monorepo-starter/server/src/lib/schemas/env.schema.ts +25 -5
  273. package/templates/monorepo-starter/server/src/lib/utils/client.utils.ts +95 -0
  274. package/templates/monorepo-starter/server/src/lib/utils/cookie.util.ts +4 -3
  275. package/templates/monorepo-starter/server/src/main.ts +20 -10
  276. package/templates/monorepo-starter/server/src/modules/admin/admin.controller.ts +54 -0
  277. package/templates/monorepo-starter/server/src/modules/admin/admin.module.ts +13 -0
  278. package/templates/monorepo-starter/server/src/modules/admin/admin.service.ts +134 -0
  279. package/templates/monorepo-starter/server/src/modules/auth/auth.controller.ts +60 -18
  280. package/templates/monorepo-starter/server/src/modules/auth/auth.module.ts +3 -2
  281. package/templates/monorepo-starter/server/src/modules/auth/auth.service.ts +354 -307
  282. package/templates/monorepo-starter/server/src/modules/auth/oauth.controller.ts +50 -28
  283. package/templates/monorepo-starter/server/src/modules/auth/oauth.service.ts +59 -34
  284. package/templates/monorepo-starter/server/src/modules/auth/otp.service.ts +29 -36
  285. package/templates/monorepo-starter/server/src/modules/cache/cache.module.ts +28 -0
  286. package/templates/monorepo-starter/server/src/modules/cache/cache.service.ts +33 -0
  287. package/templates/monorepo-starter/server/src/modules/env/env.service.ts +4 -3
  288. package/templates/monorepo-starter/server/src/modules/logger/logger.module.ts +1 -0
  289. package/templates/monorepo-starter/server/src/modules/logger/logger.service.ts +1 -1
  290. package/templates/monorepo-starter/server/src/modules/logger/winston.config.ts +15 -1
  291. package/templates/monorepo-starter/server/src/modules/media/cloudinary.provider.ts +14 -0
  292. package/templates/monorepo-starter/server/src/modules/media/cloudinary.service.ts +62 -0
  293. package/templates/monorepo-starter/server/src/modules/media/media.controller.ts +72 -0
  294. package/templates/monorepo-starter/server/src/modules/media/media.module.ts +13 -0
  295. package/templates/monorepo-starter/server/src/modules/media/media.service.ts +146 -0
  296. package/templates/monorepo-starter/server/src/modules/notification/email.service.ts +30 -0
  297. package/templates/monorepo-starter/server/src/modules/notification/messaging.service.ts +50 -0
  298. package/templates/monorepo-starter/server/src/modules/notification/notification.controller.ts +44 -0
  299. package/templates/monorepo-starter/server/src/modules/notification/notification.module.ts +7 -3
  300. package/templates/monorepo-starter/server/src/modules/notification/notification.service.ts +121 -78
  301. package/templates/monorepo-starter/server/src/modules/notification/push.service.ts +92 -0
  302. package/templates/monorepo-starter/server/src/modules/prisma/prisma.extension.ts +84 -56
  303. package/templates/monorepo-starter/server/src/modules/prisma/prisma.service.ts +10 -9
  304. package/templates/monorepo-starter/server/src/modules/prisma/soft-delete.models.ts +7 -0
  305. package/templates/monorepo-starter/server/src/modules/public/public.controller.ts +3 -2
  306. package/templates/monorepo-starter/server/src/modules/public/public.module.ts +5 -4
  307. package/templates/monorepo-starter/server/src/modules/public/public.service.ts +7 -4
  308. package/templates/monorepo-starter/server/src/modules/scheduler/cleanup.service.ts +15 -7
  309. package/templates/monorepo-starter/server/src/modules/scheduler/scheduler.module.ts +2 -0
  310. package/templates/monorepo-starter/server/src/modules/token/token.module.ts +3 -4
  311. package/templates/monorepo-starter/server/src/modules/token/token.service.ts +205 -76
  312. package/templates/monorepo-starter/server/src/modules/user/user.controller.ts +21 -0
  313. package/templates/monorepo-starter/server/src/modules/user/user.module.ts +12 -0
  314. package/templates/monorepo-starter/server/src/modules/user/user.service.ts +38 -0
  315. package/templates/monorepo-starter/server/src/types/express.d.ts +6 -2
  316. package/templates/monorepo-starter/server/src/types/index.ts +5 -0
  317. package/templates/monorepo-starter/server/tsconfig.json +17 -12
  318. package/templates/monorepo-starter/server/tsup.config.ts +7 -2
  319. package/templates/monorepo-starter/tsconfig.json +1 -1
  320. package/templates/monorepo-starter/turbo.json +1 -1
  321. package/templates/monorepo-starter/apps/web/eslint.config.mjs +0 -4
  322. package/templates/monorepo-starter/apps/web/src/app/(auth)/layout.tsx +0 -7
  323. package/templates/monorepo-starter/apps/web/src/app/globals.css +0 -1
  324. package/templates/monorepo-starter/apps/web/src/components/ThemeSwitch.tsx +0 -34
  325. package/templates/monorepo-starter/apps/web/src/providers/index.tsx +0 -10
  326. package/templates/monorepo-starter/apps/web/src/types/index.d.ts +0 -16
  327. package/templates/monorepo-starter/packages/config/eslint/base.js +0 -31
  328. package/templates/monorepo-starter/packages/config/eslint/nest.js +0 -26
  329. package/templates/monorepo-starter/packages/config/eslint/next.js +0 -39
  330. package/templates/monorepo-starter/packages/config/eslint/react.js +0 -30
  331. package/templates/monorepo-starter/packages/config/typescript/base.json +0 -31
  332. package/templates/monorepo-starter/packages/config/typescript/nest.json +0 -14
  333. package/templates/monorepo-starter/packages/config/typescript/next.json +0 -10
  334. package/templates/monorepo-starter/packages/ui/tsconfig.lint.json +0 -8
  335. package/templates/monorepo-starter/server/README.md +0 -63
  336. package/templates/monorepo-starter/server/scripts/generate.sh +0 -14
  337. package/templates/monorepo-starter/server/src/lib/dto/auth.dto.ts +0 -64
  338. package/templates/monorepo-starter/server/src/lib/dto/security-setting.dto.ts +0 -21
  339. package/templates/monorepo-starter/server/src/lib/pipes/validation.pipe.ts +0 -7
  340. package/templates/monorepo-starter/server/src/lib/templates/notification.templates.ts +0 -243
  341. package/templates/monorepo-starter/server/src/lib/utils/general.util.ts +0 -24
  342. package/templates/monorepo-starter/server/src/modules/auth/role.service.ts +0 -99
  343. package/templates/monorepo-starter/server/src/modules/auth/security-setting.service.ts +0 -102
  344. package/templates/monorepo-starter/server/src/modules/template/template.module.ts +0 -8
  345. package/templates/monorepo-starter/server/src/modules/template/template.service.ts +0 -33
  346. package/templates/monorepo-starter/server/src/types/index.d.ts +0 -13
  347. package/templates/monorepo-starter/server/test/app.e2e-spec.ts +0 -25
  348. package/templates/monorepo-starter/server/test/jest-e2e.json +0 -9
  349. /package/templates/monorepo-starter/{packages/ui/src/hooks → apps/dashboard/public}/.gitkeep +0 -0
@@ -0,0 +1,189 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ "use client";
3
+
4
+ import React, { useRef, useState } from "react";
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@workspace/ui/components/card";
11
+ import { Badge } from "@workspace/ui/components/badge";
12
+ import type { AnyFormApi } from "@workspace/ui/components/form";
13
+ import { Button } from "@workspace/ui/components/button";
14
+ import GenericFormTable from "@/components/shared/GenericFormTable";
15
+ import type { ColumnConfig } from "@/components/shared/GenericTable";
16
+
17
+ export interface ArrayFormItemProps<TItem, TCtx = any> {
18
+ onSubmit: (item: TItem) => void;
19
+ disabled?: boolean;
20
+ children: (submit: () => void) => React.ReactNode;
21
+ editData?: TItem;
22
+ clearEditData: () => void;
23
+ items: TItem[];
24
+ ctx?: TCtx;
25
+ }
26
+
27
+ export type ArrayFormItem<TItem, TCtx = any> = React.ComponentType<
28
+ ArrayFormItemProps<TItem, TCtx>
29
+ >;
30
+
31
+ export type ArrayFieldSideEffect<TFormData, TItem, TCtx> = (args: {
32
+ form: AnyFormApi<TFormData>;
33
+ items: TItem[];
34
+ ctx?: TCtx;
35
+ }) => void;
36
+
37
+ interface GenericArrayFieldProps<
38
+ TFormData,
39
+ TName extends keyof TFormData,
40
+ TFormKeys extends readonly (keyof TFormData)[] = readonly (keyof TFormData)[],
41
+ TItem = ArrayItem<TFormData[TName]>,
42
+ TCtx = Pick<TFormData, TFormKeys[number]>,
43
+ > {
44
+ name: TName & string;
45
+ form: AnyFormApi<TFormData>;
46
+ disabled?: boolean;
47
+ columns: ColumnConfig<TItem>[];
48
+ FormItem?: ArrayFormItem<TItem, TCtx>;
49
+ subscribeKeys?: TFormKeys;
50
+ effects?: ArrayFieldSideEffect<TFormData, TItem, TCtx>;
51
+ renderFormItem?: (ctx: {
52
+ items: TItem[];
53
+ onAdd: (item: TItem) => void;
54
+ editData?: TItem;
55
+ clearEditData: () => void;
56
+ ctx?: TCtx;
57
+ }) => React.ReactNode;
58
+ }
59
+
60
+ function GenericArrayField<
61
+ TFormData,
62
+ TName extends keyof TFormData,
63
+ TFormKeys extends readonly (keyof TFormData)[],
64
+ >({
65
+ name,
66
+ form,
67
+ effects,
68
+ FormItem,
69
+ columns,
70
+ subscribeKeys,
71
+ renderFormItem,
72
+ disabled = false,
73
+ }: GenericArrayFieldProps<TFormData, TName, TFormKeys>) {
74
+ const [editData, setEditData] = useState();
75
+ const effectsAppliedRef = useRef<string>("");
76
+
77
+ const entityName = name.replace(/s$/, "");
78
+
79
+ return (
80
+ <form.Subscribe
81
+ selector={(state) => {
82
+ const ctx = subscribeKeys?.reduce(
83
+ (acc, key) => {
84
+ acc[key] = state.values[key];
85
+ return acc;
86
+ },
87
+ {} as Pick<TFormData, TFormKeys[number]>,
88
+ );
89
+ return { ctx, items: state.values[name] ?? [] };
90
+ }}
91
+ >
92
+ {({ items, ctx }) => (
93
+ <form.Field name={name} mode="array">
94
+ {(field) => {
95
+ const ArrItems = Array.isArray(items) ? items : [];
96
+
97
+ if (effects) {
98
+ const effectKey = JSON.stringify({ items: ArrItems, ctx });
99
+ if (effectsAppliedRef.current !== effectKey) {
100
+ effectsAppliedRef.current = effectKey;
101
+ setTimeout(() => {
102
+ effects({ form, items: ArrItems, ctx });
103
+ }, 0);
104
+ }
105
+ }
106
+
107
+ const handleAdd = (item: any) => {
108
+ field.pushValue(item);
109
+ if (editData) setEditData(undefined);
110
+ };
111
+ const handleEdit = (i: number, item: any) => {
112
+ setEditData(item);
113
+ field.removeValue(i);
114
+ };
115
+ const handleRemove = (index: number) => field.removeValue(index);
116
+
117
+ return (
118
+ <>
119
+ <Card>
120
+ <CardHeader>
121
+ <CardTitle className="capitalize">{name}</CardTitle>
122
+ </CardHeader>
123
+
124
+ <CardContent>
125
+ {renderFormItem
126
+ ? renderFormItem({
127
+ ctx,
128
+ items: ArrItems,
129
+ onAdd: handleAdd,
130
+ clearEditData: () => setEditData(undefined),
131
+ })
132
+ : FormItem && (
133
+ <FormItem
134
+ onSubmit={handleAdd}
135
+ disabled={disabled}
136
+ editData={editData}
137
+ items={ArrItems}
138
+ clearEditData={() => setEditData(undefined)}
139
+ ctx={ctx}
140
+ >
141
+ {(handleSubmit) => (
142
+ <div className="flex items-center justify-between">
143
+ <p className="text-xs text-muted-foreground">
144
+ Total {entityName}: <b>{ArrItems.length}</b>
145
+ </p>
146
+ <Button
147
+ type="button"
148
+ variant="secondary"
149
+ onClick={handleSubmit}
150
+ >
151
+ {`Add ${entityName}`}
152
+ </Button>
153
+ </div>
154
+ )}
155
+ </FormItem>
156
+ )}
157
+ </CardContent>
158
+ </Card>
159
+
160
+ {ArrItems.length > 0 && (
161
+ <Card className="p-0 pt-6 bg-input/30">
162
+ <div className="px-6 flex items-center justify-between">
163
+ <CardTitle className="capitalize">
164
+ {entityName} History
165
+ </CardTitle>
166
+ <Badge variant="outline">{ArrItems.length} items</Badge>
167
+ </div>
168
+
169
+ <CardContent className="p-0">
170
+ <GenericFormTable
171
+ items={ArrItems}
172
+ columns={columns}
173
+ onEdit={handleEdit}
174
+ onRemove={handleRemove}
175
+ entityTitle={entityName}
176
+ />
177
+ </CardContent>
178
+ </Card>
179
+ )}
180
+ </>
181
+ );
182
+ }}
183
+ </form.Field>
184
+ )}
185
+ </form.Subscribe>
186
+ );
187
+ }
188
+
189
+ export default GenericArrayField;
@@ -0,0 +1,124 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ "use client";
3
+ import { toast } from "sonner";
4
+ import { useEffect, useState } from "react";
5
+ import { usePathname, useRouter } from "next/navigation";
6
+ import { useForm, type FormValidateOrFn } from "@tanstack/react-form";
7
+
8
+ import type { ApiException } from "@workspace/sdk";
9
+ import { getBackPath } from "@workspace/ui/lib/utils";
10
+ import { Form, type AnyFormApi } from "@workspace/ui/components/form";
11
+ import type { ColumnConfig } from "@/components/shared/GenericTable";
12
+ import CUFormSkeleton from "@/components/skeleton/CUFormSkeleton";
13
+ import GenericArrayField, { type ArrayFormItem } from "./GenericArrayField";
14
+
15
+ interface UseQueryResult<TData, TFormData> {
16
+ data?: TData;
17
+ isLoading?: boolean;
18
+ fetchError: ApiException | null;
19
+ mutateAsync: (data: TFormData) => Promise<TData>;
20
+ isPending: boolean;
21
+ mutateError: ApiException | null;
22
+ }
23
+
24
+ interface GenericCUFormProps<TData, TFormData> extends BaseCUFormProps {
25
+ entityName: string;
26
+ defaultValues: Partial<TFormData>;
27
+ schema: FormValidateOrFn<TFormData>;
28
+ children?: (
29
+ form: AnyFormApi<TFormData>,
30
+ formType: FormSectionType,
31
+ data?: TData,
32
+ ) => React.ReactNode;
33
+ useQuery: (...args: string[]) => UseQueryResult<TData, TFormData>;
34
+ formHeader?: (
35
+ form: AnyFormApi<TFormData>,
36
+ formType: FormSectionType,
37
+ data?: TData,
38
+ ) => React.ReactNode;
39
+ arrayFields?: {
40
+ [K in keyof TFormData]: TFormData[K] extends readonly any[]
41
+ ? {
42
+ name: K & string;
43
+ component: ArrayFormItem<ArrayItem<TFormData[K]>>;
44
+ columns: ColumnConfig<ArrayItem<TFormData[K]>>[];
45
+ }
46
+ : never;
47
+ }[keyof TFormData];
48
+ }
49
+
50
+ export function GenericCUForm<
51
+ TData extends Record<string, any>,
52
+ TFormData extends Record<string, any>,
53
+ >({
54
+ entityId,
55
+ entityName,
56
+ formType,
57
+ children,
58
+ defaultValues,
59
+ schema,
60
+ useQuery,
61
+ formHeader,
62
+ arrayFields,
63
+ }: GenericCUFormProps<TData, TFormData>) {
64
+ const router = useRouter();
65
+ const pathname = usePathname();
66
+ const listPath = getBackPath(pathname, formType === "add" ? 1 : 2);
67
+ const { data, mutateAsync, isLoading, isPending } = useQuery(entityId!);
68
+ const [isFormReady, setIsFormReady] = useState(!entityId);
69
+
70
+ const form = useForm({
71
+ defaultValues: entityId ? undefined : (defaultValues as TFormData),
72
+ validators: {
73
+ onSubmit: schema,
74
+ },
75
+ onSubmit: async ({ value }) => {
76
+ try {
77
+ await mutateAsync(value);
78
+ toast.success(
79
+ `${entityName} ${entityId ? "updated" : "created"} successfully!`,
80
+ );
81
+ form.reset();
82
+ router.push(listPath);
83
+ } catch (err: any) {
84
+ toast.error(err?.message ?? "Something went wrong.");
85
+ }
86
+ },
87
+ });
88
+
89
+ useEffect(() => {
90
+ if (data && !isFormReady) {
91
+ form.reset(data as unknown as TFormData);
92
+ setIsFormReady(true);
93
+ }
94
+ }, [data, form, isFormReady]);
95
+
96
+ if (isLoading || !isFormReady) return <CUFormSkeleton />;
97
+
98
+ return (
99
+ <Form
100
+ form={form}
101
+ isPending={isPending}
102
+ title={
103
+ <>
104
+ {formType === "add" ? "Add New" : "Update"} {entityName}
105
+ {entityId && `: ${entityId}`}
106
+ </>
107
+ }
108
+ btnText={`${formType} ${entityName}`}
109
+ >
110
+ {formHeader && formHeader(form, formType)}
111
+
112
+ {arrayFields && (
113
+ <GenericArrayField
114
+ form={form}
115
+ name={arrayFields.name}
116
+ FormItem={arrayFields.component}
117
+ columns={arrayFields.columns}
118
+ />
119
+ )}
120
+
121
+ {children?.(form, formType)}
122
+ </Form>
123
+ );
124
+ }
@@ -0,0 +1,291 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ "use client";
3
+
4
+ import React from "react";
5
+ import Link from "next/link";
6
+ import { usePathname } from "next/navigation";
7
+ import { ArrowLeft, Edit, Eye } from "lucide-react";
8
+
9
+ import type { ApiException } from "@workspace/sdk";
10
+ import { cn } from "@workspace/ui/lib/utils";
11
+
12
+ import {
13
+ Card,
14
+ CardContent,
15
+ CardDescription,
16
+ CardHeader,
17
+ CardTitle,
18
+ } from "@workspace/ui/components/card";
19
+ import { Button } from "@workspace/ui/components/button";
20
+ import DetailsPageSkeleton from "@/components/skeleton/DetailsPageSkeleton";
21
+
22
+ export interface DetailFieldConfig<TData> {
23
+ label: string;
24
+ accessor: keyof TData | ((data: TData) => React.ReactNode);
25
+ icon?: React.ComponentType<{ className?: string }>;
26
+ className?: string;
27
+ format?: (value: any) => string;
28
+ render?: (value: any, data: TData) => React.ReactNode;
29
+ }
30
+
31
+ export interface SectionConfig<TData> {
32
+ title: string;
33
+ description?: (data: TData) => React.ReactNode;
34
+ fields: DetailFieldConfig<TData>[];
35
+ columns?: 1 | 2 | 3;
36
+ }
37
+
38
+ export interface RelatedEntityConfig<TData, TKey extends keyof TData> {
39
+ title: string;
40
+ dataKey: TKey;
41
+ columns: Array<{
42
+ header: string;
43
+ accessor: (item: ArrayItem<TData[TKey]>) => React.ReactNode;
44
+ }>;
45
+ viewPath?: (item: ArrayItem<TData[TKey]>) => string;
46
+ }
47
+
48
+ interface GenericDetailsPageProps<
49
+ TData extends BaseResponse,
50
+ TKey extends keyof TData,
51
+ > {
52
+ entityId: string;
53
+ entityName: string;
54
+ sections: SectionConfig<TData>[];
55
+ relatedEntities?: RelatedEntityConfig<TData, TKey>[];
56
+ renderHeader: (data: TData) => React.ReactNode;
57
+ renderActions?: (data: TData) => React.ReactNode;
58
+ children?: (data: TData) => React.ReactNode;
59
+ useQuery: (...args: string[]) => {
60
+ data?: TData;
61
+ isLoading?: boolean;
62
+ fetchError: ApiException | null;
63
+ };
64
+ }
65
+
66
+ export function GenericDetailsPage<
67
+ TData extends BaseResponse,
68
+ TKey extends keyof TData,
69
+ >({
70
+ entityId,
71
+ entityName,
72
+ sections,
73
+ useQuery,
74
+ relatedEntities,
75
+ renderHeader,
76
+ renderActions,
77
+ children,
78
+ }: GenericDetailsPageProps<TData, TKey>) {
79
+ const pathname = usePathname();
80
+ const backPath = pathname.slice(0, pathname.lastIndexOf("/")) || "/";
81
+
82
+ const { data, isLoading, fetchError } = useQuery(entityId);
83
+
84
+ if (isLoading) {
85
+ return <DetailsPageSkeleton sections={sections} />;
86
+ }
87
+
88
+ if (fetchError || !data) {
89
+ return (
90
+ <div className="container mx-auto p-6">
91
+ <div className="flex items-center gap-2 mb-6">
92
+ <Button variant="ghost" size="icon" href={backPath}>
93
+ <ArrowLeft className="h-4 w-4" />
94
+ </Button>
95
+ <h1 className="text-3xl font-bold">Error</h1>
96
+ </div>
97
+ <Card>
98
+ <CardContent className="pt-6">
99
+ <div className="text-center py-12">
100
+ <p className="text-destructive text-lg">
101
+ {fetchError?.message || `Failed to load ${entityName}`}
102
+ </p>
103
+ <Button variant="outline" className="mt-4" href={backPath}>
104
+ Go Back
105
+ </Button>
106
+ </div>
107
+ </CardContent>
108
+ </Card>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ const getFieldValue = (field: DetailFieldConfig<TData>, data: TData) => {
114
+ if (typeof field.accessor === "function") {
115
+ return field.accessor(data);
116
+ }
117
+
118
+ const value = data[field.accessor];
119
+
120
+ if (field.format && value) {
121
+ return field.format(value);
122
+ }
123
+
124
+ if (field.render) {
125
+ return field.render(value, data);
126
+ }
127
+
128
+ return value ?? "—";
129
+ };
130
+
131
+ const getGridColumns = (columns: 1 | 2 | 3 = 1) => {
132
+ switch (columns) {
133
+ case 1:
134
+ return "grid-cols-1";
135
+ case 2:
136
+ return "grid-cols-1 md:grid-cols-2";
137
+ case 3:
138
+ return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3";
139
+ default:
140
+ return "grid-cols-1 md:grid-cols-2";
141
+ }
142
+ };
143
+
144
+ return (
145
+ <div className="container mx-auto p-6 space-y-6">
146
+ {/* Header */}
147
+ <div className="flex flex-col gap-4">
148
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
149
+ <div className="flex items-center gap-3">
150
+ <Button variant="ghost" size="icon" href={backPath}>
151
+ <ArrowLeft className="h-4 w-4" />
152
+ </Button>
153
+ <div>
154
+ <h1 className="text-3xl font-bold capitalize">
155
+ {entityName} Details
156
+ </h1>
157
+ <p className="text-muted-foreground">
158
+ Viewing details for {entityName.toLowerCase()}: {entityId}
159
+ </p>
160
+ </div>
161
+ </div>
162
+
163
+ <div className="flex items-center gap-2">
164
+ {renderActions?.(data)}
165
+ <Button asChild>
166
+ <Link href={`${pathname}/edit`}>
167
+ <Edit className="h-4 w-4 mr-2" />
168
+ Edit
169
+ </Link>
170
+ </Button>
171
+ </div>
172
+ </div>
173
+
174
+ <Card className="border-0 shadow-none bg-linear-to-r from-primary/20 to-secondary/50">
175
+ <CardContent className="pt-6">{renderHeader(data)}</CardContent>
176
+ </Card>
177
+ </div>
178
+
179
+ {/* Main Content Sections */}
180
+ <div className="grid gap-6">
181
+ {sections.map(({ title, description, fields, columns }, index) => (
182
+ <Card key={index}>
183
+ <CardHeader>
184
+ <CardTitle className="text-lg">{title}</CardTitle>
185
+ {description && (
186
+ <CardDescription>{description(data)}</CardDescription>
187
+ )}
188
+ </CardHeader>
189
+ <CardContent>
190
+ <div className={cn("grid gap-4", getGridColumns(columns))}>
191
+ {fields.map((field, fieldIndex) => {
192
+ const Icon = field.icon;
193
+ const value = getFieldValue(field, data);
194
+
195
+ return (
196
+ <div
197
+ key={fieldIndex}
198
+ className={cn("space-y-2", field.className)}
199
+ >
200
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
201
+ {Icon && <Icon className="h-4 w-4" />}
202
+ <span>{field.label}</span>
203
+ </div>
204
+ <div className="text-sm font-medium">
205
+ {value as string}
206
+ </div>
207
+ </div>
208
+ );
209
+ })}
210
+ </div>
211
+ </CardContent>
212
+ </Card>
213
+ ))}
214
+ </div>
215
+
216
+ {/* Related Entities */}
217
+ {relatedEntities?.map((related, index) => {
218
+ const relatedData = data[related.dataKey];
219
+ if (
220
+ !relatedData ||
221
+ (Array.isArray(relatedData) && relatedData.length === 0)
222
+ ) {
223
+ return null;
224
+ }
225
+
226
+ return (
227
+ <Card key={index}>
228
+ <CardHeader>
229
+ <CardTitle className="text-lg">{related.title}</CardTitle>
230
+ </CardHeader>
231
+ <CardContent>
232
+ <div className="overflow-hidden rounded-lg border">
233
+ <table className="w-full text-sm">
234
+ <thead className="bg-muted/50">
235
+ <tr>
236
+ {related.columns.map((col, colIndex) => (
237
+ <th
238
+ key={colIndex}
239
+ className="px-4 py-2 text-left font-medium"
240
+ >
241
+ {col.header}
242
+ </th>
243
+ ))}
244
+ {related.viewPath && <th className="w-20"></th>}
245
+ </tr>
246
+ </thead>
247
+ <tbody className="divide-y">
248
+ {Array.isArray(relatedData) ? (
249
+ relatedData.map((item: any, itemIndex: number) => (
250
+ <tr key={itemIndex} className="hover:bg-muted/50">
251
+ {related.columns.map((col, colIndex) => (
252
+ <td key={colIndex} className="px-4 py-3">
253
+ {typeof col.accessor === "function"
254
+ ? col.accessor(item)
255
+ : (item[col.accessor] ?? "—")}
256
+ </td>
257
+ ))}
258
+ {related.viewPath && (
259
+ <td className="px-4 py-3 text-right">
260
+ <Button variant="ghost" size="sm" asChild>
261
+ <Link href={related.viewPath(item)}>
262
+ <Eye className="h-4 w-4" />
263
+ </Link>
264
+ </Button>
265
+ </td>
266
+ )}
267
+ </tr>
268
+ ))
269
+ ) : (
270
+ <tr>
271
+ <td
272
+ colSpan={related.columns.length + 1}
273
+ className="px-4 py-3 text-center"
274
+ >
275
+ No data available
276
+ </td>
277
+ </tr>
278
+ )}
279
+ </tbody>
280
+ </table>
281
+ </div>
282
+ </CardContent>
283
+ </Card>
284
+ );
285
+ })}
286
+
287
+ {/* Custom Children */}
288
+ {children?.(data)}
289
+ </div>
290
+ );
291
+ }