create-ej-app 3.1.0 → 4.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 (278) hide show
  1. package/cli.js +5 -1
  2. package/cli.ts +40 -33
  3. package/package.json +4 -4
  4. package/template/base/.env.example +10 -9
  5. package/template/base/.eslintrc.cjs +17 -15
  6. package/template/base/components.json +3 -2
  7. package/template/base/drizzle.config.ts +11 -0
  8. package/template/base/next.config.mjs +1 -12
  9. package/template/base/package.json +63 -56
  10. package/template/base/postcss.config.mjs +8 -0
  11. package/template/base/prettier.config.mjs +11 -0
  12. package/template/base/public/vercel.svg +1 -0
  13. package/template/base/src/app/(app)/dashboard/layout.tsx +14 -0
  14. package/template/base/src/app/(app)/dashboard/page.tsx +0 -7
  15. package/template/base/src/app/(app)/layout.tsx +55 -20
  16. package/template/base/src/app/(app)/roles/[id]/actions.ts +59 -0
  17. package/template/base/src/app/(app)/roles/[id]/page.tsx +46 -0
  18. package/template/base/src/app/(app)/roles/[id]/render.tsx +264 -0
  19. package/template/base/src/app/(app)/roles/[id]/validation.ts +22 -0
  20. package/template/base/src/app/(app)/roles/actions.ts +33 -0
  21. package/template/base/src/app/(app)/roles/page.tsx +52 -0
  22. package/template/base/src/app/(app)/roles/render.tsx +80 -0
  23. package/template/base/src/app/(app)/settings/actions.ts +11 -12
  24. package/template/base/src/app/(app)/settings/layout.tsx +14 -0
  25. package/template/base/src/app/(app)/settings/page.tsx +1 -10
  26. package/template/base/src/app/(app)/settings/render.tsx +11 -17
  27. package/template/base/src/app/(app)/settings/validation.ts +5 -0
  28. package/template/base/src/app/(app)/tasks/[id]/actions.ts +69 -124
  29. package/template/base/src/app/(app)/tasks/[id]/page.tsx +37 -3
  30. package/template/base/src/app/(app)/tasks/[id]/render.tsx +162 -93
  31. package/template/base/src/app/(app)/tasks/[id]/validation.ts +18 -0
  32. package/template/base/src/app/(app)/tasks/actions.ts +34 -64
  33. package/template/base/src/app/(app)/tasks/page.tsx +15 -9
  34. package/template/base/src/app/(app)/tasks/render.tsx +28 -6
  35. package/template/base/src/app/(app)/tasks/statusEnum.ts +3 -0
  36. package/template/base/src/app/(app)/users/[id]/actions.ts +47 -62
  37. package/template/base/src/app/(app)/users/[id]/page.tsx +15 -8
  38. package/template/base/src/app/(app)/users/[id]/render.tsx +119 -34
  39. package/template/base/src/app/(app)/users/[id]/validation.ts +18 -0
  40. package/template/base/src/app/(app)/users/actions.ts +28 -49
  41. package/template/base/src/app/(app)/users/page.tsx +22 -9
  42. package/template/base/src/app/(app)/users/render.tsx +47 -3
  43. package/template/base/src/app/(auth)/layout.tsx +7 -0
  44. package/template/base/src/app/(auth)/login/page.tsx +2 -2
  45. package/template/base/src/app/(auth)/login/render.tsx +146 -0
  46. package/template/base/src/app/(marketing)/layout.tsx +1 -1
  47. package/template/base/src/app/(marketing)/page.tsx +8 -7
  48. package/template/base/src/app/favicon.ico +0 -0
  49. package/template/base/src/app/layout.tsx +14 -17
  50. package/template/base/src/components/app-nav-command.tsx +18 -0
  51. package/template/base/src/components/data-table/data-table-column-header.tsx +2 -2
  52. package/template/base/src/components/icons.tsx +23 -5
  53. package/template/base/src/components/ui/alert-dialog.tsx +3 -3
  54. package/template/base/src/components/ui/command.tsx +2 -2
  55. package/template/base/src/components/ui/dialog.tsx +2 -2
  56. package/template/base/src/components/ui/select.tsx +42 -3
  57. package/template/base/src/components/ui/sonner.tsx +31 -0
  58. package/template/base/src/components/ui/table.tsx +4 -1
  59. package/template/base/src/components/user-avatar.tsx +4 -2
  60. package/template/base/src/data-access/resource.ts +85 -0
  61. package/template/base/src/data-access/role-permission.ts +45 -0
  62. package/template/base/src/data-access/role.ts +105 -0
  63. package/template/base/src/data-access/task.ts +141 -0
  64. package/template/base/src/data-access/user.ts +141 -0
  65. package/template/base/src/db/index.ts +14 -0
  66. package/template/base/src/db/schema.ts +287 -0
  67. package/template/base/src/env.mjs +13 -30
  68. package/template/base/src/hooks/use-lock-body.tsx +2 -1
  69. package/template/base/src/lib/auth.ts +32 -22
  70. package/template/base/src/lib/currentUser.ts +8 -0
  71. package/template/base/src/lib/s3Client.ts +156 -0
  72. package/template/base/src/lib/safe-action.ts +19 -0
  73. package/template/base/src/lib/sortOrderEnum.ts +2 -0
  74. package/template/base/src/use-case/role-permission.ts +19 -0
  75. package/template/base/src/use-case/role.ts +110 -0
  76. package/template/base/src/use-case/task.ts +166 -0
  77. package/template/base/src/use-case/user.ts +121 -0
  78. package/template/base/tailwind.config.ts +9 -7
  79. package/template/base/tsconfig.json +3 -2
  80. package/template/prisma/.env.example +16 -0
  81. package/template/prisma/.eslintrc.cjs +24 -0
  82. package/template/prisma/components.json +17 -0
  83. package/template/prisma/next.config.mjs +15 -0
  84. package/template/prisma/package.json +80 -0
  85. package/template/{base → prisma}/prisma/schema.prisma +0 -1
  86. package/template/prisma/public/next.svg +1 -0
  87. package/template/prisma/src/app/(app)/dashboard/page.tsx +19 -0
  88. package/template/prisma/src/app/(app)/layout.tsx +90 -0
  89. package/template/prisma/src/app/(app)/settings/actions.ts +18 -0
  90. package/template/prisma/src/app/(app)/settings/loading.tsx +28 -0
  91. package/template/prisma/src/app/(app)/settings/page.tsx +28 -0
  92. package/template/prisma/src/app/(app)/settings/render.tsx +113 -0
  93. package/template/prisma/src/app/(app)/tasks/[id]/actions.ts +147 -0
  94. package/template/prisma/src/app/(app)/tasks/[id]/page.tsx +25 -0
  95. package/template/prisma/src/app/(app)/tasks/[id]/render.tsx +413 -0
  96. package/template/prisma/src/app/(app)/tasks/actions.ts +69 -0
  97. package/template/prisma/src/app/(app)/tasks/page.tsx +53 -0
  98. package/template/prisma/src/app/(app)/tasks/render.tsx +107 -0
  99. package/template/prisma/src/app/(app)/users/[id]/actions.ts +83 -0
  100. package/template/prisma/src/app/(app)/users/[id]/page.tsx +32 -0
  101. package/template/prisma/src/app/(app)/users/[id]/render.tsx +240 -0
  102. package/template/prisma/src/app/(app)/users/actions.ts +55 -0
  103. package/template/prisma/src/app/(app)/users/page.tsx +49 -0
  104. package/template/prisma/src/app/(app)/users/render.tsx +80 -0
  105. package/template/prisma/src/app/(auth)/layout.tsx +20 -0
  106. package/template/prisma/src/app/(auth)/login/page.tsx +49 -0
  107. package/template/prisma/src/app/(marketing)/layout.tsx +42 -0
  108. package/template/prisma/src/app/(marketing)/page.tsx +96 -0
  109. package/template/prisma/src/app/api/auth/[...nextauth]/route.ts +7 -0
  110. package/template/prisma/src/app/favicon.ico +0 -0
  111. package/template/prisma/src/app/layout.tsx +34 -0
  112. package/template/prisma/src/components/app-nav-command.tsx +160 -0
  113. package/template/prisma/src/components/dark-mode-toggle.tsx +43 -0
  114. package/template/prisma/src/components/data-table/data-table-column-header.tsx +91 -0
  115. package/template/prisma/src/components/data-table/data-table-faceted-filter.tsx +149 -0
  116. package/template/prisma/src/components/data-table/data-table-loading.tsx +77 -0
  117. package/template/prisma/src/components/data-table/data-table-pagination.tsx +94 -0
  118. package/template/prisma/src/components/data-table/data-table-toolbar.tsx +78 -0
  119. package/template/prisma/src/components/data-table/data-table-types.ts +17 -0
  120. package/template/prisma/src/components/data-table/data-table-view-options.tsx +59 -0
  121. package/template/prisma/src/components/data-table/data-table.tsx +301 -0
  122. package/template/prisma/src/components/data-table/simple-data-table.tsx +109 -0
  123. package/template/prisma/src/components/empty-placeholder.tsx +78 -0
  124. package/template/prisma/src/components/heading.tsx +17 -0
  125. package/template/prisma/src/components/icons.tsx +85 -0
  126. package/template/prisma/src/components/main-nav.tsx +64 -0
  127. package/template/prisma/src/components/shell.tsx +13 -0
  128. package/template/prisma/src/components/side-nav.tsx +51 -0
  129. package/template/prisma/src/components/site-footer.tsx +31 -0
  130. package/template/prisma/src/components/system-info.tsx +36 -0
  131. package/template/prisma/src/components/ui/alert-dialog.tsx +141 -0
  132. package/template/prisma/src/components/ui/avatar.tsx +50 -0
  133. package/template/prisma/src/components/ui/badge.tsx +36 -0
  134. package/template/prisma/src/components/ui/button.tsx +56 -0
  135. package/template/prisma/src/components/ui/card.tsx +79 -0
  136. package/template/prisma/src/components/ui/checkbox.tsx +30 -0
  137. package/template/prisma/src/components/ui/command.tsx +155 -0
  138. package/template/prisma/src/components/ui/dialog.tsx +122 -0
  139. package/template/prisma/src/components/ui/dropdown-menu.tsx +200 -0
  140. package/template/prisma/src/components/ui/form.tsx +176 -0
  141. package/template/prisma/src/components/ui/input.tsx +25 -0
  142. package/template/prisma/src/components/ui/label.tsx +26 -0
  143. package/template/prisma/src/components/ui/popover.tsx +31 -0
  144. package/template/prisma/src/components/ui/select.tsx +160 -0
  145. package/template/prisma/src/components/ui/separator.tsx +31 -0
  146. package/template/prisma/src/components/ui/skeleton.tsx +15 -0
  147. package/template/prisma/src/components/ui/table.tsx +117 -0
  148. package/template/prisma/src/components/user-account-nav.tsx +60 -0
  149. package/template/prisma/src/components/user-avatar.tsx +25 -0
  150. package/template/prisma/src/env.mjs +78 -0
  151. package/template/prisma/src/hooks/use-debounce.ts +17 -0
  152. package/template/prisma/src/hooks/use-lock-body.tsx +12 -0
  153. package/template/prisma/src/lib/auth.ts +80 -0
  154. package/template/prisma/src/lib/cn.ts +4 -0
  155. package/template/prisma/src/lib/delay.ts +2 -0
  156. package/template/prisma/src/lib/formatDate.ts +11 -0
  157. package/template/prisma/src/lib/generateLabel.ts +5 -0
  158. package/template/prisma/src/lib/siteConfig.ts +4 -0
  159. package/template/prisma/src/styles/globals.css +76 -0
  160. package/template/prisma/tailwind.config.ts +80 -0
  161. package/template/prisma/tsconfig.json +46 -0
  162. package/template/{advanced → prisma-advanced}/components.json +2 -1
  163. package/template/{advanced → prisma-advanced}/package.json +10 -10
  164. package/template/{advanced → prisma-advanced}/src/components/icons.tsx +2 -0
  165. package/template/{advanced → prisma-advanced}/src/components/ui/alert-dialog.tsx +3 -3
  166. package/template/{advanced → prisma-advanced}/src/components/ui/command.tsx +1 -1
  167. package/template/{advanced → prisma-advanced}/src/components/ui/dialog.tsx +2 -2
  168. package/template/{advanced → prisma-advanced}/src/components/ui/select.tsx +42 -3
  169. package/template/{advanced → prisma-advanced}/src/components/ui/table.tsx +4 -1
  170. package/template/{advanced → prisma-advanced}/src/env.mjs +4 -1
  171. package/template/{advanced → prisma-advanced}/src/lib/auth.ts +2 -5
  172. package/template/{advanced → prisma-advanced}/tailwind.config.ts +4 -3
  173. /package/template/{advanced/src/styles → base/src/app}/globals.css +0 -0
  174. /package/template/{advanced/src/hooks/use-debounce.ts → base/src/hooks/use-debounce.tsx} +0 -0
  175. /package/template/{base → prisma}/postcss.config.cjs +0 -0
  176. /package/template/{base → prisma}/prettier.config.ts +0 -0
  177. /package/template/{base → prisma}/src/app/(auth)/login/user-auth-form.tsx +0 -0
  178. /package/template/{base → prisma}/src/app/api/upload/route.ts +0 -0
  179. /package/template/{advanced → prisma}/src/components/mobile-nav.tsx +0 -0
  180. /package/template/{base → prisma}/src/components/theme-provider.tsx +0 -0
  181. /package/template/{base → prisma}/src/components/ui/textarea.tsx +0 -0
  182. /package/template/{base → prisma}/src/components/ui/toast.tsx +0 -0
  183. /package/template/{base → prisma}/src/components/ui/toaster.tsx +0 -0
  184. /package/template/{base → prisma}/src/components/ui/use-toast.ts +0 -0
  185. /package/template/{base → prisma}/src/lib/db.ts +0 -0
  186. /package/template/{base → prisma}/src/lib/storageClient.ts +0 -0
  187. /package/template/{base → prisma}/src/styles/editor.css +0 -0
  188. /package/template/{base → prisma}/src/types/UnwrapPromise.ts +0 -0
  189. /package/template/{advanced → prisma-advanced}/.env.example +0 -0
  190. /package/template/{advanced → prisma-advanced}/.eslintrc.cjs +0 -0
  191. /package/template/{advanced → prisma-advanced}/next.config.mjs +0 -0
  192. /package/template/{advanced → prisma-advanced}/postcss.config.cjs +0 -0
  193. /package/template/{advanced → prisma-advanced}/prettier.config.ts +0 -0
  194. /package/template/{advanced → prisma-advanced}/prisma/schema.prisma +0 -0
  195. /package/template/{advanced → prisma-advanced}/public/next.svg +0 -0
  196. /package/template/{advanced → prisma-advanced}/src/app/(app)/dashboard/page.tsx +0 -0
  197. /package/template/{advanced → prisma-advanced}/src/app/(app)/layout.tsx +0 -0
  198. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/[id]/actions.ts +0 -0
  199. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/[id]/page.tsx +0 -0
  200. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/[id]/render.tsx +0 -0
  201. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/actions.ts +0 -0
  202. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/page.tsx +0 -0
  203. /package/template/{advanced → prisma-advanced}/src/app/(app)/roles/render.tsx +0 -0
  204. /package/template/{advanced → prisma-advanced}/src/app/(app)/settings/actions.ts +0 -0
  205. /package/template/{advanced → prisma-advanced}/src/app/(app)/settings/loading.tsx +0 -0
  206. /package/template/{advanced → prisma-advanced}/src/app/(app)/settings/page.tsx +0 -0
  207. /package/template/{advanced → prisma-advanced}/src/app/(app)/settings/render.tsx +0 -0
  208. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/[id]/actions.ts +0 -0
  209. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/[id]/page.tsx +0 -0
  210. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/[id]/render.tsx +0 -0
  211. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/actions.ts +0 -0
  212. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/page.tsx +0 -0
  213. /package/template/{advanced → prisma-advanced}/src/app/(app)/tasks/render.tsx +0 -0
  214. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/[id]/actions.ts +0 -0
  215. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/[id]/page.tsx +0 -0
  216. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/[id]/render.tsx +0 -0
  217. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/actions.ts +0 -0
  218. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/page.tsx +0 -0
  219. /package/template/{advanced → prisma-advanced}/src/app/(app)/users/render.tsx +0 -0
  220. /package/template/{advanced → prisma-advanced}/src/app/(auth)/layout.tsx +0 -0
  221. /package/template/{advanced → prisma-advanced}/src/app/(auth)/login/page.tsx +0 -0
  222. /package/template/{advanced → prisma-advanced}/src/app/(auth)/login/user-auth-form.tsx +0 -0
  223. /package/template/{advanced → prisma-advanced}/src/app/(marketing)/layout.tsx +0 -0
  224. /package/template/{advanced → prisma-advanced}/src/app/(marketing)/page.tsx +0 -0
  225. /package/template/{advanced → prisma-advanced}/src/app/api/auth/[...nextauth]/route.ts +0 -0
  226. /package/template/{advanced → prisma-advanced}/src/app/api/upload/route.ts +0 -0
  227. /package/template/{advanced → prisma-advanced}/src/app/favicon.ico +0 -0
  228. /package/template/{advanced → prisma-advanced}/src/app/layout.tsx +0 -0
  229. /package/template/{advanced → prisma-advanced}/src/components/app-nav-command.tsx +0 -0
  230. /package/template/{advanced → prisma-advanced}/src/components/dark-mode-toggle.tsx +0 -0
  231. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-column-header.tsx +0 -0
  232. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-faceted-filter.tsx +0 -0
  233. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-loading.tsx +0 -0
  234. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-pagination.tsx +0 -0
  235. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-toolbar.tsx +0 -0
  236. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-types.ts +0 -0
  237. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table-view-options.tsx +0 -0
  238. /package/template/{advanced → prisma-advanced}/src/components/data-table/data-table.tsx +0 -0
  239. /package/template/{advanced → prisma-advanced}/src/components/data-table/simple-data-table.tsx +0 -0
  240. /package/template/{advanced → prisma-advanced}/src/components/empty-placeholder.tsx +0 -0
  241. /package/template/{advanced → prisma-advanced}/src/components/heading.tsx +0 -0
  242. /package/template/{advanced → prisma-advanced}/src/components/main-nav.tsx +0 -0
  243. /package/template/{advanced → prisma-advanced}/src/components/shell.tsx +0 -0
  244. /package/template/{advanced → prisma-advanced}/src/components/side-nav.tsx +0 -0
  245. /package/template/{advanced → prisma-advanced}/src/components/site-footer.tsx +0 -0
  246. /package/template/{advanced → prisma-advanced}/src/components/system-info.tsx +0 -0
  247. /package/template/{advanced → prisma-advanced}/src/components/theme-provider.tsx +0 -0
  248. /package/template/{advanced → prisma-advanced}/src/components/ui/avatar.tsx +0 -0
  249. /package/template/{advanced → prisma-advanced}/src/components/ui/badge.tsx +0 -0
  250. /package/template/{advanced → prisma-advanced}/src/components/ui/button.tsx +0 -0
  251. /package/template/{advanced → prisma-advanced}/src/components/ui/card.tsx +0 -0
  252. /package/template/{advanced → prisma-advanced}/src/components/ui/checkbox.tsx +0 -0
  253. /package/template/{advanced → prisma-advanced}/src/components/ui/dropdown-menu.tsx +0 -0
  254. /package/template/{advanced → prisma-advanced}/src/components/ui/form.tsx +0 -0
  255. /package/template/{advanced → prisma-advanced}/src/components/ui/input.tsx +0 -0
  256. /package/template/{advanced → prisma-advanced}/src/components/ui/label.tsx +0 -0
  257. /package/template/{advanced → prisma-advanced}/src/components/ui/popover.tsx +0 -0
  258. /package/template/{advanced → prisma-advanced}/src/components/ui/separator.tsx +0 -0
  259. /package/template/{advanced → prisma-advanced}/src/components/ui/skeleton.tsx +0 -0
  260. /package/template/{advanced → prisma-advanced}/src/components/ui/textarea.tsx +0 -0
  261. /package/template/{advanced → prisma-advanced}/src/components/ui/toast.tsx +0 -0
  262. /package/template/{advanced → prisma-advanced}/src/components/ui/toaster.tsx +0 -0
  263. /package/template/{advanced → prisma-advanced}/src/components/ui/use-toast.ts +0 -0
  264. /package/template/{advanced → prisma-advanced}/src/components/user-account-nav.tsx +0 -0
  265. /package/template/{advanced → prisma-advanced}/src/components/user-avatar.tsx +0 -0
  266. /package/template/{base → prisma-advanced}/src/hooks/use-debounce.ts +0 -0
  267. /package/template/{advanced → prisma-advanced}/src/hooks/use-lock-body.tsx +0 -0
  268. /package/template/{advanced → prisma-advanced}/src/lib/cn.ts +0 -0
  269. /package/template/{advanced → prisma-advanced}/src/lib/db.ts +0 -0
  270. /package/template/{advanced → prisma-advanced}/src/lib/delay.ts +0 -0
  271. /package/template/{advanced → prisma-advanced}/src/lib/formatDate.ts +0 -0
  272. /package/template/{advanced → prisma-advanced}/src/lib/generateLabel.ts +0 -0
  273. /package/template/{advanced → prisma-advanced}/src/lib/siteConfig.ts +0 -0
  274. /package/template/{advanced → prisma-advanced}/src/lib/storageClient.ts +0 -0
  275. /package/template/{advanced → prisma-advanced}/src/styles/editor.css +0 -0
  276. /package/template/{base → prisma-advanced}/src/styles/globals.css +0 -0
  277. /package/template/{advanced → prisma-advanced}/src/types/UnwrapPromise.ts +0 -0
  278. /package/template/{advanced → prisma-advanced}/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { DialogProps } from '@radix-ui/react-dialog'
3
+ import { type DialogProps } from '@radix-ui/react-dialog'
4
4
  import { Command as CommandPrimitive } from 'cmdk'
5
5
  import { Search } from 'lucide-react'
6
6
  import * as React from 'react'
@@ -117,7 +117,7 @@ const CommandItem = React.forwardRef<
117
117
  <CommandPrimitive.Item
118
118
  ref={ref}
119
119
  className={cn(
120
- 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
120
+ 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50',
121
121
  className
122
122
  )}
123
123
  {...props}
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
21
21
  <DialogPrimitive.Overlay
22
22
  ref={ref}
23
23
  className={cn(
24
- 'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
24
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
25
25
  className
26
26
  )}
27
27
  {...props}
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
38
38
  <DialogPrimitive.Content
39
39
  ref={ref}
40
40
  className={cn(
41
- 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
41
+ 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
42
42
  className
43
43
  )}
44
44
  {...props}
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as SelectPrimitive from '@radix-ui/react-select'
4
- import { Check, ChevronDown } from 'lucide-react'
4
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react'
5
5
  import * as React from 'react'
6
6
 
7
7
  import { cn } from '@/lib/cn'
@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
19
19
  <SelectPrimitive.Trigger
20
20
  ref={ref}
21
21
  className={cn(
22
- 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
22
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
23
23
  className
24
24
  )}
25
25
  {...props}
@@ -32,6 +32,41 @@ const SelectTrigger = React.forwardRef<
32
32
  ))
33
33
  SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
34
 
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ 'flex cursor-default items-center justify-center py-1',
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ))
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ 'flex cursor-default items-center justify-center py-1',
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ))
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName
69
+
35
70
  const SelectContent = React.forwardRef<
36
71
  React.ElementRef<typeof SelectPrimitive.Content>,
37
72
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
@@ -40,7 +75,7 @@ const SelectContent = React.forwardRef<
40
75
  <SelectPrimitive.Content
41
76
  ref={ref}
42
77
  className={cn(
43
- 'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
78
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
44
79
  position === 'popper' &&
45
80
  'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
46
81
  className
@@ -48,6 +83,7 @@ const SelectContent = React.forwardRef<
48
83
  position={position}
49
84
  {...props}
50
85
  >
86
+ <SelectScrollUpButton />
51
87
  <SelectPrimitive.Viewport
52
88
  className={cn(
53
89
  'p-1',
@@ -57,6 +93,7 @@ const SelectContent = React.forwardRef<
57
93
  >
58
94
  {children}
59
95
  </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
60
97
  </SelectPrimitive.Content>
61
98
  </SelectPrimitive.Portal>
62
99
  ))
@@ -115,6 +152,8 @@ export {
115
152
  SelectGroup,
116
153
  SelectItem,
117
154
  SelectLabel,
155
+ SelectScrollDownButton,
156
+ SelectScrollUpButton,
118
157
  SelectSeparator,
119
158
  SelectTrigger,
120
159
  SelectValue
@@ -0,0 +1,31 @@
1
+ 'use client'
2
+
3
+ import { useTheme } from 'next-themes'
4
+ import { Toaster as Sonner } from 'sonner'
5
+
6
+ type ToasterProps = React.ComponentProps<typeof Sonner>
7
+
8
+ const Toaster = ({ ...props }: ToasterProps) => {
9
+ const { theme = 'system' } = useTheme()
10
+
11
+ return (
12
+ <Sonner
13
+ theme={theme as ToasterProps['theme']}
14
+ className="toaster group"
15
+ toastOptions={{
16
+ classNames: {
17
+ toast:
18
+ 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
19
+ description: 'group-[.toast]:text-muted-foreground',
20
+ actionButton:
21
+ 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
22
+ cancelButton:
23
+ 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground'
24
+ }
25
+ }}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ export { Toaster }
@@ -42,7 +42,10 @@ const TableFooter = React.forwardRef<
42
42
  >(({ className, ...props }, ref) => (
43
43
  <tfoot
44
44
  ref={ref}
45
- className={cn('bg-primary font-medium text-primary-foreground', className)}
45
+ className={cn(
46
+ 'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
47
+ className
48
+ )}
46
49
  {...props}
47
50
  />
48
51
  ))
@@ -1,4 +1,3 @@
1
- import { User } from '@prisma/client'
2
1
  import { AvatarProps } from '@radix-ui/react-avatar'
3
2
  import { FC } from 'react'
4
3
 
@@ -6,7 +5,10 @@ import { Icons } from '@/components/icons'
6
5
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
7
6
 
8
7
  interface UserAvatarProps extends AvatarProps {
9
- user: Pick<User, 'image' | 'name'>
8
+ user: {
9
+ name?: string | null
10
+ image?: string | null
11
+ }
10
12
  }
11
13
 
12
14
  export const UserAvatar: FC<UserAvatarProps> = ({ user, ...props }) => {
@@ -0,0 +1,85 @@
1
+ import { db } from '@/db'
2
+ import { Resource } from '@/db/schema'
3
+ import { eq, or, sql } from 'drizzle-orm'
4
+
5
+ type createResourceInput = {
6
+ id?: string
7
+ filename: string
8
+ key: string
9
+ isTemp: boolean
10
+ url: string
11
+ size: number
12
+ contentType: string
13
+ createdById?: string | null
14
+
15
+ taskId?: string | null
16
+ }
17
+
18
+ export const createResource = async (data: createResourceInput) => {
19
+ const newResource = await db
20
+ .insert(Resource)
21
+ .values({
22
+ ...data
23
+ })
24
+ .returning({
25
+ id: Resource.id
26
+ })
27
+
28
+ const id = newResource[0]?.id
29
+
30
+ if (!id) throw new Error('Failed to create resource')
31
+
32
+ return id
33
+ }
34
+
35
+ export const getResourceById = async (id: string) => {
36
+ return await db.query.Resource.findFirst({
37
+ where: eq(Resource.id, id)
38
+ })
39
+ }
40
+
41
+ type updateResourceByIdInput = {
42
+ id: string
43
+ isTemp?: boolean
44
+ contentType?: string
45
+ size?: number
46
+ updatedById?: string | null
47
+
48
+ taskId?: string | null
49
+ }
50
+
51
+ export const updateResourceById = async (data: updateResourceByIdInput) => {
52
+ return await db
53
+ .update(Resource)
54
+ .set({
55
+ ...data,
56
+ updatedAt: sql`now()`
57
+ })
58
+ .where(eq(Resource.id, data.id))
59
+ }
60
+
61
+ type updateResourceByIdsInput = {
62
+ ids: string[]
63
+ taskId?: string | null
64
+ updatedById?: string | null
65
+ }
66
+
67
+ export const updateResourceByIds = async (data: updateResourceByIdsInput) => {
68
+ return await db
69
+ .update(Resource)
70
+ .set({
71
+ ...data,
72
+ updatedAt: sql`now()`
73
+ })
74
+ .where(or(...data.ids.map(id => eq(Resource.id, id))))
75
+ }
76
+
77
+ export const getResourcesByTaskId = async (taskId: string) => {
78
+ return await db.query.Resource.findMany({
79
+ where: eq(Resource.taskId, taskId)
80
+ })
81
+ }
82
+
83
+ export const deleteResource = async (id: string) => {
84
+ return await db.delete(Resource).where(eq(Resource.id, id))
85
+ }
@@ -0,0 +1,45 @@
1
+ import { InferSelectModel, and, count, eq } from 'drizzle-orm'
2
+
3
+ import { db } from '@/db'
4
+ import { RolePermission } from '@/db/schema'
5
+
6
+ export type RolePermissionType = InferSelectModel<typeof RolePermission>
7
+
8
+ export const checkForRolePermission = async (
9
+ roleId: string,
10
+ permission: RolePermissionType['permission']
11
+ ) => {
12
+ const c = await db
13
+ .select({ count: count() })
14
+ .from(RolePermission)
15
+ .where(
16
+ and(
17
+ eq(RolePermission.roleId, roleId),
18
+ eq(RolePermission.permission, permission)
19
+ )
20
+ )
21
+
22
+ return !!c?.[0]?.count
23
+ }
24
+
25
+ type createRolePermissionsInput = {
26
+ roleId: string
27
+ permissions: RolePermissionType['permission'][]
28
+ }
29
+
30
+ export const createRolePermissions = async (
31
+ data: createRolePermissionsInput
32
+ ) => {
33
+ return await db.insert(RolePermission).values(
34
+ data.permissions.map(p => ({
35
+ roleId: data.roleId,
36
+ permission: p
37
+ }))
38
+ )
39
+ }
40
+
41
+ export const deleteRolePermissionsByRoleId = async (roleId: string) => {
42
+ return await db
43
+ .delete(RolePermission)
44
+ .where(eq(RolePermission.roleId, roleId))
45
+ }
@@ -0,0 +1,105 @@
1
+ import { InferSelectModel, asc, count, desc, eq, ilike, sql } from 'drizzle-orm'
2
+
3
+ import { db } from '@/db'
4
+ import { Role } from '@/db/schema'
5
+ import { type SortOrderEnum } from '@/lib/sortOrderEnum'
6
+
7
+ export type RoleType = InferSelectModel<typeof Role>
8
+
9
+ type getRolesInput = {
10
+ page: number
11
+ limit: number
12
+ sortBy?: keyof RoleType
13
+ sortOrder?: SortOrderEnum
14
+ filters?: {
15
+ name?: string
16
+ }
17
+ }
18
+
19
+ export const getRoles = async ({
20
+ page,
21
+ limit,
22
+ sortBy,
23
+ sortOrder,
24
+ filters
25
+ }: getRolesInput) => {
26
+ const where = filters?.name
27
+ ? ilike(Role.name, `%${filters.name}%`)
28
+ : undefined
29
+
30
+ const [roles, total] = await Promise.all([
31
+ db.query.Role.findMany({
32
+ columns: {
33
+ id: true,
34
+ name: true,
35
+ createdAt: true,
36
+ updatedAt: true
37
+ },
38
+ where,
39
+ orderBy: [
40
+ sortOrder === 'asc'
41
+ ? asc(Role[sortBy || 'createdAt'])
42
+ : desc(Role[sortBy || 'createdAt'])
43
+ ],
44
+ offset: (page - 1) * limit,
45
+ limit
46
+ }),
47
+ db
48
+ .select({
49
+ count: count()
50
+ })
51
+ .from(Role)
52
+ .where(where)
53
+ ])
54
+
55
+ return { roles, total: total[0]?.count || 0 }
56
+ }
57
+
58
+ export const getRoleByIdWithPermissions = async (id: string) => {
59
+ return await db.query.Role.findFirst({
60
+ where: eq(Role.id, id),
61
+ with: {
62
+ permissions: true
63
+ }
64
+ })
65
+ }
66
+
67
+ type createRoleInput = {
68
+ name: string
69
+ }
70
+
71
+ export const createRole = async (data: createRoleInput) => {
72
+ const newRole = await db.insert(Role).values(data).returning({
73
+ id: Role.id
74
+ })
75
+
76
+ const id = newRole[0]?.id
77
+ if (!id) throw new Error('Failed to create role')
78
+
79
+ return id
80
+ }
81
+
82
+ type updateRoleInput = createRoleInput & {
83
+ id: string
84
+ }
85
+
86
+ export const updateRole = async (data: updateRoleInput) => {
87
+ return await db
88
+ .update(Role)
89
+ .set({ ...data, updatedAt: sql`now()` })
90
+ .where(eq(Role.id, data.id))
91
+ }
92
+
93
+ export const deleteRole = async (id: string) => {
94
+ return await db.delete(Role).where(eq(Role.id, id))
95
+ }
96
+
97
+ export const getRolesMini = async () => {
98
+ return await db.query.Role.findMany({
99
+ columns: {
100
+ id: true,
101
+ name: true
102
+ },
103
+ orderBy: [asc(Role.name)]
104
+ })
105
+ }
@@ -0,0 +1,141 @@
1
+ import {
2
+ InferSelectModel,
3
+ asc,
4
+ count,
5
+ desc,
6
+ eq,
7
+ ilike,
8
+ or,
9
+ sql
10
+ } from 'drizzle-orm'
11
+
12
+ import { db } from '@/db'
13
+ import { Task } from '@/db/schema'
14
+ import { type SortOrderEnum } from '@/lib/sortOrderEnum'
15
+
16
+ export type TaskType = InferSelectModel<typeof Task>
17
+
18
+ type getTasksInput = {
19
+ page: number
20
+ limit: number
21
+ sortBy?: keyof TaskType
22
+ sortOrder?: SortOrderEnum
23
+ filters?: {
24
+ title?: string
25
+ status?: TaskType['status'][]
26
+ }
27
+ }
28
+
29
+ export const getTasksWithUser = async ({
30
+ page,
31
+ limit,
32
+ sortBy,
33
+ sortOrder,
34
+ filters
35
+ }: getTasksInput) => {
36
+ const where = or(
37
+ filters?.title ? ilike(Task.title, `%${filters.title}%`) : undefined,
38
+ ...(filters?.status || []).map(f => eq(Task.status, f))
39
+ )
40
+
41
+ const [tasks, total] = await Promise.all([
42
+ db.query.Task.findMany({
43
+ columns: {
44
+ id: true,
45
+ title: true,
46
+ status: true,
47
+ createdAt: true,
48
+ updatedAt: true
49
+ },
50
+ with: {
51
+ createdBy: {
52
+ columns: {
53
+ id: true,
54
+ name: true
55
+ }
56
+ },
57
+ updatedBy: {
58
+ columns: {
59
+ id: true,
60
+ name: true
61
+ }
62
+ }
63
+ },
64
+ where,
65
+ orderBy: [
66
+ sortOrder === 'asc'
67
+ ? asc(Task[sortBy || 'createdAt'])
68
+ : desc(Task[sortBy || 'createdAt'])
69
+ ],
70
+ offset: (page - 1) * limit,
71
+ limit
72
+ }),
73
+ db
74
+ .select({
75
+ count: count()
76
+ })
77
+ .from(Task)
78
+ .where(where)
79
+ ])
80
+
81
+ return { tasks, total: total[0]?.count || 0 }
82
+ }
83
+
84
+ export const getTaskByIdWithUser = async (id: string) => {
85
+ return await db.query.Task.findFirst({
86
+ where: eq(Task.id, id),
87
+ with: {
88
+ createdBy: {
89
+ columns: {
90
+ id: true,
91
+ name: true
92
+ }
93
+ },
94
+ updatedBy: {
95
+ columns: {
96
+ id: true,
97
+ name: true
98
+ }
99
+ }
100
+ }
101
+ })
102
+ }
103
+
104
+ type createTaskInput = {
105
+ title: string
106
+ description?: string | null
107
+ status: TaskType['status']
108
+ createdById: string
109
+ }
110
+
111
+ export const createTask = async (data: createTaskInput) => {
112
+ const newTask = await db.insert(Task).values(data).returning({
113
+ id: Task.id
114
+ })
115
+
116
+ if (!newTask[0]?.id) throw new Error('Failed to create task')
117
+
118
+ return newTask[0].id
119
+ }
120
+
121
+ type updateTaskInput = {
122
+ id: string
123
+ title?: string
124
+ description?: string | null
125
+ status?: TaskType['status']
126
+ updatedById: string
127
+ }
128
+
129
+ export const updateTask = async (data: updateTaskInput) => {
130
+ return await db
131
+ .update(Task)
132
+ .set({
133
+ ...data,
134
+ updatedAt: sql`now()`
135
+ })
136
+ .where(eq(Task.id, data.id))
137
+ }
138
+
139
+ export const deleteTask = async (id: string) => {
140
+ return await db.delete(Task).where(eq(Task.id, id))
141
+ }
@@ -0,0 +1,141 @@
1
+ import {
2
+ InferSelectModel,
3
+ asc,
4
+ count,
5
+ desc,
6
+ eq,
7
+ ilike,
8
+ or,
9
+ sql
10
+ } from 'drizzle-orm'
11
+
12
+ import { db } from '@/db'
13
+ import { User } from '@/db/schema'
14
+ import { type SortOrderEnum } from '@/lib/sortOrderEnum'
15
+
16
+ export type UserType = InferSelectModel<typeof User>
17
+
18
+ type updateUserByIdInput = {
19
+ name?: string | null
20
+ type?: UserType['type']
21
+ roleId?: string | null
22
+ }
23
+
24
+ export const updateUser = async (id: string, data: updateUserByIdInput) => {
25
+ await db
26
+ .update(User)
27
+ .set({ ...data, updatedAt: sql`now()` })
28
+ .where(eq(User.id, id))
29
+ }
30
+
31
+ type getUsersInput = {
32
+ page: number
33
+ limit: number
34
+ sortBy?: keyof UserType
35
+ sortOrder?: SortOrderEnum
36
+ filters?: {
37
+ name?: string
38
+ email?: string
39
+ type?: UserType['type'][]
40
+ }
41
+ }
42
+
43
+ export const getUsersWithRole = async ({
44
+ page,
45
+ limit,
46
+ sortBy,
47
+ sortOrder,
48
+ filters
49
+ }: getUsersInput) => {
50
+ const where = or(
51
+ filters?.name ? ilike(User.name, `%${filters.name}%`) : undefined,
52
+ filters?.email ? ilike(User.email, `%${filters.email}%`) : undefined,
53
+ ...(filters?.type || []).map(f => eq(User.type, f))
54
+ )
55
+
56
+ const [users, total] = await Promise.all([
57
+ db.query.User.findMany({
58
+ columns: {
59
+ id: true,
60
+ name: true,
61
+ email: true,
62
+ type: true,
63
+ roleId: true,
64
+ image: true,
65
+ emailVerified: true,
66
+ createdAt: true,
67
+ updatedAt: true
68
+ },
69
+ with: {
70
+ role: true
71
+ },
72
+ where,
73
+ orderBy: [
74
+ sortOrder === 'asc'
75
+ ? asc(User[sortBy || 'createdAt'])
76
+ : desc(User[sortBy || 'createdAt'])
77
+ ],
78
+ offset: (page - 1) * limit,
79
+ limit
80
+ }),
81
+ db
82
+ .select({
83
+ count: count()
84
+ })
85
+ .from(User)
86
+ .where(where)
87
+ ])
88
+
89
+ return { users, total: total[0]?.count || 0 }
90
+ }
91
+
92
+ export const getUserById = async (id: string) => {
93
+ return await db.query.User.findFirst({
94
+ where: eq(User.id, id),
95
+ columns: {
96
+ id: true,
97
+ name: true,
98
+ email: true,
99
+ emailVerified: true,
100
+ type: true,
101
+ roleId: true,
102
+ createdAt: true,
103
+ updatedAt: true
104
+ }
105
+ })
106
+ }
107
+
108
+ type createUserInput = {
109
+ name?: string | null
110
+ email: string
111
+ type: UserType['type']
112
+ roleId?: string | null
113
+ }
114
+
115
+ export const createUser = async ({
116
+ name,
117
+ email,
118
+ type,
119
+ roleId
120
+ }: createUserInput) => {
121
+ const newUser = await db
122
+ .insert(User)
123
+ .values({
124
+ name,
125
+ email,
126
+ type,
127
+ roleId
128
+ })
129
+ .returning({
130
+ id: User.id
131
+ })
132
+
133
+ const id = newUser[0]?.id
134
+ if (!id) throw new Error('Failed to create user')
135
+
136
+ return id
137
+ }
138
+
139
+ export const deleteUser = async (id: string) => {
140
+ return await db.delete(User).where(eq(User.id, id))
141
+ }