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
@@ -0,0 +1,78 @@
1
+ import { createEnv } from '@t3-oss/env-nextjs'
2
+ import { z } from 'zod'
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Specify your server-side environment variables schema here. This way you can ensure the app
7
+ * isn't built with invalid env vars.
8
+ */
9
+ server: {
10
+ NODE_ENV: z.enum(['development', 'test', 'production']),
11
+ DATABASE_URL: z.string().url(),
12
+ NEXTAUTH_SECRET:
13
+ process.env.NODE_ENV === 'production'
14
+ ? z.string().min(1)
15
+ : z.string().min(1).optional(),
16
+ NEXTAUTH_URL: z.preprocess(
17
+ // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
18
+ // Since NextAuth.js automatically uses the VERCEL_URL if present.
19
+ str => process.env.VERCEL_URL ?? str,
20
+ // VERCEL_URL doesn't include `https` so it cant be validated as a URL
21
+ process.env.VERCEL ? z.string().min(1) : z.string().url()
22
+ ),
23
+ EMAIL_SERVER_HOST: z.string().min(1),
24
+ EMAIL_SERVER_PORT: z.number(),
25
+ EMAIL_SERVER_USER: z.string().min(1),
26
+ EMAIL_SERVER_PASSWORD: z.string().min(1),
27
+ EMAIL_FROM: z.string().email(),
28
+ GOOGLE_ID: z.string().min(1),
29
+ GOOGLE_SECRET: z.string().min(1),
30
+ GITHUB_ID: z.string().min(1),
31
+ GITHUB_SECRET: z.string().min(1),
32
+ FIREBASE_PROJECT_ID: z.string().min(1),
33
+ FIREBASE_PRIVATE_KEY: z.string().min(1),
34
+ FIREBASE_CLIENT_EMAIL: z.string().min(1),
35
+ FIREBASE_STORAGE_BUCKET: z.string().min(1)
36
+ },
37
+
38
+ /**
39
+ * Specify your client-side environment variables schema here. This way you can ensure the app
40
+ * isn't built with invalid env vars. To expose them to the client, prefix them with
41
+ * `NEXT_PUBLIC_`.
42
+ */
43
+ client: {
44
+ // NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
45
+ },
46
+
47
+ /**
48
+ * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
49
+ * middlewares) or client-side so we need to destruct manually.
50
+ */
51
+ runtimeEnv: {
52
+ NODE_ENV: process.env.NODE_ENV,
53
+ DATABASE_URL: process.env.DATABASE_URL,
54
+ NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
55
+ NEXTAUTH_URL: process.env.NEXTAUTH_URL,
56
+ EMAIL_SERVER_HOST: process.env.EMAIL_SERVER_HOST,
57
+ EMAIL_SERVER_PORT: parseInt(process.env.EMAIL_SERVER_PORT || ''),
58
+ EMAIL_SERVER_USER: process.env.EMAIL_SERVER_USER,
59
+ EMAIL_SERVER_PASSWORD: process.env.EMAIL_SERVER_PASSWORD,
60
+ EMAIL_FROM: process.env.EMAIL_FROM,
61
+ GOOGLE_ID: process.env.GOOGLE_ID,
62
+ GOOGLE_SECRET: process.env.GOOGLE_SECRET,
63
+ GITHUB_ID: process.env.GITHUB_ID,
64
+ GITHUB_SECRET: process.env.GITHUB_SECRET,
65
+ FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
66
+ FIREBASE_PRIVATE_KEY: process.env.FIREBASE_PRIVATE_KEY,
67
+ FIREBASE_CLIENT_EMAIL: process.env.FIREBASE_CLIENT_EMAIL,
68
+ FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET
69
+ },
70
+ /**
71
+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
72
+ * This is especially useful for Docker builds.
73
+ */
74
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
75
+
76
+ // This makes empty strings be treated as undefined
77
+ emptyStringAsUndefined: true
78
+ })
@@ -0,0 +1,17 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+
5
+ export function useDebounce<T>(value: T, delay?: number): T {
6
+ const [debouncedValue, setDebouncedValue] = useState<T>(value)
7
+
8
+ useEffect(() => {
9
+ const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
10
+
11
+ return () => {
12
+ clearTimeout(timer)
13
+ }
14
+ }, [value, delay])
15
+
16
+ return debouncedValue
17
+ }
@@ -0,0 +1,12 @@
1
+ import { useLayoutEffect } from 'react'
2
+
3
+ // @see https://usehooks.com/useLockBodyScroll.
4
+ export function useLockBody() {
5
+ useLayoutEffect((): (() => void) => {
6
+ const originalStyle: string = window.getComputedStyle(
7
+ document.body
8
+ ).overflow
9
+ document.body.style.overflow = 'hidden'
10
+ return () => (document.body.style.overflow = originalStyle)
11
+ }, [])
12
+ }
@@ -0,0 +1,80 @@
1
+ import { PrismaAdapter } from '@next-auth/prisma-adapter'
2
+ import { User as PrismaUser } from '@prisma/client'
3
+ import {
4
+ getServerSession,
5
+ type DefaultSession,
6
+ type NextAuthOptions
7
+ } from 'next-auth'
8
+ import EmailProvider from 'next-auth/providers/email'
9
+ import GitHubProvider from 'next-auth/providers/github'
10
+ import GoogleProvider from 'next-auth/providers/google'
11
+
12
+ import { env } from '@/env.mjs'
13
+ import { prisma } from '@/lib/db'
14
+
15
+ /**
16
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
17
+ * object and keep type safety.
18
+ *
19
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
20
+ */
21
+ declare module 'next-auth' {
22
+ interface Session extends DefaultSession {
23
+ user: {
24
+ id: string
25
+ } & DefaultSession['user']
26
+ }
27
+
28
+ interface User extends PrismaUser {}
29
+ }
30
+
31
+ /**
32
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
33
+ *
34
+ * @see https://next-auth.js.org/configuration/options
35
+ */
36
+ export const authOptions: NextAuthOptions = {
37
+ pages: {
38
+ signIn: '/login'
39
+ },
40
+ callbacks: {
41
+ session({ session, user }) {
42
+ return {
43
+ ...session,
44
+ user: {
45
+ ...session.user,
46
+ id: user.id
47
+ }
48
+ }
49
+ }
50
+ },
51
+ adapter: PrismaAdapter(prisma),
52
+ providers: [
53
+ EmailProvider({
54
+ server: {
55
+ host: env.EMAIL_SERVER_HOST,
56
+ port: env.EMAIL_SERVER_PORT,
57
+ auth: {
58
+ user: env.EMAIL_SERVER_USER,
59
+ pass: env.EMAIL_SERVER_PASSWORD
60
+ }
61
+ },
62
+ from: env.EMAIL_FROM
63
+ }),
64
+ GoogleProvider({
65
+ clientId: env.GOOGLE_ID,
66
+ clientSecret: env.GOOGLE_SECRET
67
+ }),
68
+ GitHubProvider({
69
+ clientId: env.GITHUB_ID,
70
+ clientSecret: env.GITHUB_SECRET
71
+ })
72
+ ]
73
+ }
74
+
75
+ /**
76
+ * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
77
+ *
78
+ * @see https://next-auth.js.org/configuration/nextjs
79
+ */
80
+ export const getAuthSession = () => getServerSession(authOptions)
@@ -0,0 +1,4 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs))
@@ -0,0 +1,2 @@
1
+ export const delay = (ms: number) =>
2
+ new Promise(resolve => setTimeout(resolve, ms))
@@ -0,0 +1,11 @@
1
+ import dayjs from 'dayjs'
2
+ import relativeTime from 'dayjs/plugin/relativeTime'
3
+
4
+ dayjs.extend(relativeTime)
5
+
6
+ export const formatDate = (date: Date) => dayjs(date).format('DD/MM/YYYY')
7
+
8
+ export const formatDateTime = (date: Date) =>
9
+ dayjs(date).format('DD/MM/YYYY hh:mm:ss A')
10
+
11
+ export const timesAgo = (date: Date) => dayjs(date).fromNow()
@@ -0,0 +1,5 @@
1
+ export const generateLabel = (str: string) =>
2
+ str
3
+ .replaceAll('_', ' ')
4
+ .replaceAll(/([A-Z])/g, ' $1')
5
+ .trim()
@@ -0,0 +1,4 @@
1
+ export const siteConfig = {
2
+ name: 'Project Name',
3
+ description: 'Project Description'
4
+ }
@@ -0,0 +1,76 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 0 0% 3.9%;
9
+
10
+ --card: 0 0% 100%;
11
+ --card-foreground: 0 0% 3.9%;
12
+
13
+ --popover: 0 0% 100%;
14
+ --popover-foreground: 0 0% 3.9%;
15
+
16
+ --primary: 0 0% 9%;
17
+ --primary-foreground: 0 0% 98%;
18
+
19
+ --secondary: 0 0% 96.1%;
20
+ --secondary-foreground: 0 0% 9%;
21
+
22
+ --muted: 0 0% 96.1%;
23
+ --muted-foreground: 0 0% 45.1%;
24
+
25
+ --accent: 0 0% 96.1%;
26
+ --accent-foreground: 0 0% 9%;
27
+
28
+ --destructive: 0 84.2% 60.2%;
29
+ --destructive-foreground: 0 0% 98%;
30
+
31
+ --border: 0 0% 89.8%;
32
+ --input: 0 0% 89.8%;
33
+ --ring: 0 0% 3.9%;
34
+
35
+ --radius: 0.5rem;
36
+ }
37
+
38
+ .dark {
39
+ --background: 0 0% 3.9%;
40
+ --foreground: 0 0% 98%;
41
+
42
+ --card: 0 0% 3.9%;
43
+ --card-foreground: 0 0% 98%;
44
+
45
+ --popover: 0 0% 3.9%;
46
+ --popover-foreground: 0 0% 98%;
47
+
48
+ --primary: 0 0% 98%;
49
+ --primary-foreground: 0 0% 9%;
50
+
51
+ --secondary: 0 0% 14.9%;
52
+ --secondary-foreground: 0 0% 98%;
53
+
54
+ --muted: 0 0% 14.9%;
55
+ --muted-foreground: 0 0% 63.9%;
56
+
57
+ --accent: 0 0% 14.9%;
58
+ --accent-foreground: 0 0% 98%;
59
+
60
+ --destructive: 0 62.8% 30.6%;
61
+ --destructive-foreground: 0 0% 98%;
62
+
63
+ --border: 0 0% 14.9%;
64
+ --input: 0 0% 14.9%;
65
+ --ring: 0 0% 83.1%;
66
+ }
67
+ }
68
+
69
+ @layer base {
70
+ * {
71
+ @apply border-border;
72
+ }
73
+ body {
74
+ @apply bg-background text-foreground;
75
+ }
76
+ }
@@ -0,0 +1,80 @@
1
+ import type { Config } from 'tailwindcss'
2
+
3
+ const config = {
4
+ darkMode: ['class'],
5
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
6
+ prefix: '',
7
+ theme: {
8
+ container: {
9
+ center: true,
10
+ padding: '2rem',
11
+ screens: {
12
+ '2xl': '1400px'
13
+ }
14
+ },
15
+ extend: {
16
+ backgroundImage: {
17
+ 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
18
+ 'gradient-conic':
19
+ 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
20
+ },
21
+ colors: {
22
+ border: 'hsl(var(--border))',
23
+ input: 'hsl(var(--input))',
24
+ ring: 'hsl(var(--ring))',
25
+ background: 'hsl(var(--background))',
26
+ foreground: 'hsl(var(--foreground))',
27
+ primary: {
28
+ DEFAULT: 'hsl(var(--primary))',
29
+ foreground: 'hsl(var(--primary-foreground))'
30
+ },
31
+ secondary: {
32
+ DEFAULT: 'hsl(var(--secondary))',
33
+ foreground: 'hsl(var(--secondary-foreground))'
34
+ },
35
+ destructive: {
36
+ DEFAULT: 'hsl(var(--destructive))',
37
+ foreground: 'hsl(var(--destructive-foreground))'
38
+ },
39
+ muted: {
40
+ DEFAULT: 'hsl(var(--muted))',
41
+ foreground: 'hsl(var(--muted-foreground))'
42
+ },
43
+ accent: {
44
+ DEFAULT: 'hsl(var(--accent))',
45
+ foreground: 'hsl(var(--accent-foreground))'
46
+ },
47
+ popover: {
48
+ DEFAULT: 'hsl(var(--popover))',
49
+ foreground: 'hsl(var(--popover-foreground))'
50
+ },
51
+ card: {
52
+ DEFAULT: 'hsl(var(--card))',
53
+ foreground: 'hsl(var(--card-foreground))'
54
+ }
55
+ },
56
+ borderRadius: {
57
+ lg: 'var(--radius)',
58
+ md: 'calc(var(--radius) - 2px)',
59
+ sm: 'calc(var(--radius) - 4px)'
60
+ },
61
+ keyframes: {
62
+ 'accordion-down': {
63
+ from: { height: '0' },
64
+ to: { height: 'var(--radix-accordion-content-height)' }
65
+ },
66
+ 'accordion-up': {
67
+ from: { height: 'var(--radix-accordion-content-height)' },
68
+ to: { height: '0' }
69
+ }
70
+ },
71
+ animation: {
72
+ 'accordion-down': 'accordion-down 0.2s ease-out',
73
+ 'accordion-up': 'accordion-up 0.2s ease-out'
74
+ }
75
+ }
76
+ },
77
+ plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')]
78
+ } satisfies Config
79
+
80
+ export default config
@@ -0,0 +1,46 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "noImplicitAny": true,
10
+ "strictNullChecks": true,
11
+ "strictFunctionTypes": true,
12
+ "noImplicitThis": true,
13
+ "noUncheckedIndexedAccess": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "noImplicitReturns": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "noEmit": true,
20
+ "esModuleInterop": true,
21
+ "module": "ESNext",
22
+ "moduleResolution": "Bundler",
23
+ "resolveJsonModule": true,
24
+ "isolatedModules": true,
25
+ "jsx": "preserve",
26
+ "incremental": true,
27
+ "plugins": [
28
+ {
29
+ "name": "next"
30
+ }
31
+ ],
32
+ "paths": {
33
+ "@/*": ["./src/*"]
34
+ }
35
+ },
36
+ "include": [
37
+ ".eslintrc.cjs",
38
+ "next-env.d.ts",
39
+ "**/*.ts",
40
+ "**/*.tsx",
41
+ "**/*.cjs",
42
+ "**/*.mjs",
43
+ ".next/types/**/*.ts"
44
+ ],
45
+ "exclude": ["node_modules"]
46
+ }
@@ -7,7 +7,8 @@
7
7
  "config": "tailwind.config.ts",
8
8
  "css": "src/styles/globals.css",
9
9
  "baseColor": "neutral",
10
- "cssVariables": true
10
+ "cssVariables": true,
11
+ "prefix": ""
11
12
  },
12
13
  "aliases": {
13
14
  "components": "@/components",
@@ -9,9 +9,9 @@
9
9
  "dev": "next dev",
10
10
  "build": "next build",
11
11
  "start": "next start",
12
- "prisma:push": "prisma db push",
13
- "prisma:gen": "prisma generate",
14
- "prisma:studio": "prisma studio"
12
+ "db:push": "prisma db push",
13
+ "db:gen": "prisma generate",
14
+ "db:studio": "prisma studio"
15
15
  },
16
16
  "dependencies": {
17
17
  "@editorjs/checklist": "^1.6.0",
@@ -22,8 +22,8 @@
22
22
  "@editorjs/list": "^1.9.0",
23
23
  "@editorjs/quote": "^2.6.0",
24
24
  "@editorjs/table": "^2.3.0",
25
- "@ejekanshjain/cloud-storage": "^3.1.0",
26
- "@hookform/resolvers": "^3.3.2",
25
+ "@ejekanshjain/cloud-storage": "^3.2.0",
26
+ "@hookform/resolvers": "^3.3.3",
27
27
  "@next-auth/prisma-adapter": "^1.0.7",
28
28
  "@prisma/client": "^5.7.1",
29
29
  "@radix-ui/react-alert-dialog": "^1.0.5",
@@ -44,10 +44,10 @@
44
44
  "@types/lodash": "^4.14.202",
45
45
  "@types/node": "^20.10.5",
46
46
  "@types/nodemailer": "^6.4.14",
47
- "@types/react": "^18.2.45",
47
+ "@types/react": "^18.2.46",
48
48
  "@types/react-dom": "^18.2.18",
49
- "@typescript-eslint/eslint-plugin": "^6.15.0",
50
- "@typescript-eslint/parser": "^6.15.0",
49
+ "@typescript-eslint/eslint-plugin": "^6.16.0",
50
+ "@typescript-eslint/parser": "^6.16.0",
51
51
  "@vercel/analytics": "^1.1.1",
52
52
  "@vercel/speed-insights": "^1.0.2",
53
53
  "autoprefixer": "^10.4.16",
@@ -59,14 +59,14 @@
59
59
  "eslint": "^8.56.0",
60
60
  "eslint-config-next": "^14.0.4",
61
61
  "lodash": "^4.17.21",
62
- "lucide-react": "^0.302.0",
62
+ "lucide-react": "^0.303.0",
63
63
  "next": "^14.0.4",
64
64
  "next-auth": "^4.24.5",
65
65
  "next-themes": "^0.2.1",
66
66
  "nodemailer": "^6.9.7",
67
67
  "postcss": "^8.4.32",
68
68
  "prettier": "^3.1.1",
69
- "prettier-plugin-tailwindcss": "^0.5.9",
69
+ "prettier-plugin-tailwindcss": "^0.5.10",
70
70
  "prisma": "^5.7.1",
71
71
  "react": "^18.2.0",
72
72
  "react-dom": "^18.2.0",
@@ -1,3 +1,4 @@
1
+ import { cn } from '@/lib/cn'
1
2
  import {
2
3
  ArrowDown,
3
4
  ArrowRight,
@@ -53,6 +54,7 @@ export const Icons = {
53
54
  xmlns="http://www.w3.org/2000/svg"
54
55
  viewBox="0 0 30 30"
55
56
  {...props}
57
+ className={cn('fill-current', props.className)}
56
58
  >
57
59
  <path d="M 15.003906 3 C 8.3749062 3 3 8.373 3 15 C 3 21.627 8.3749062 27 15.003906 27 C 25.013906 27 27.269078 17.707 26.330078 13 L 25 13 L 22.732422 13 L 15 13 L 15 17 L 22.738281 17 C 21.848702 20.448251 18.725955 23 15 23 C 10.582 23 7 19.418 7 15 C 7 10.582 10.582 7 15 7 C 17.009 7 18.839141 7.74575 20.244141 8.96875 L 23.085938 6.1289062 C 20.951937 4.1849063 18.116906 3 15.003906 3 z"></path>
58
60
  </svg>
@@ -15,10 +15,10 @@ const AlertDialogPortal = AlertDialogPrimitive.Portal
15
15
  const AlertDialogOverlay = React.forwardRef<
16
16
  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
17
17
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
- >(({ className, children, ...props }, ref) => (
18
+ >(({ className, ...props }, ref) => (
19
19
  <AlertDialogPrimitive.Overlay
20
20
  className={cn(
21
- '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',
21
+ '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',
22
22
  className
23
23
  )}
24
24
  {...props}
@@ -36,7 +36,7 @@ const AlertDialogContent = React.forwardRef<
36
36
  <AlertDialogPrimitive.Content
37
37
  ref={ref}
38
38
  className={cn(
39
- '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',
39
+ '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',
40
40
  className
41
41
  )}
42
42
  {...props}
@@ -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'
@@ -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
@@ -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
  ))