create-ej-app 25.0.0 → 27.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 (385) hide show
  1. package/README.md +125 -226
  2. package/cli.ts +83 -28
  3. package/package.json +2 -2
  4. package/template/api/.vscode/settings.json +1 -1
  5. package/template/api/{.github/copilot-instructions.md → AGENTS.md} +0 -2
  6. package/template/api/CLAUDE.md +1 -0
  7. package/template/api/package.json +10 -10
  8. package/template/nextjs/.dockerignore +1 -0
  9. package/template/nextjs/AGENTS.md +260 -0
  10. package/template/nextjs/CLAUDE.md +1 -0
  11. package/template/nextjs/README.md +109 -0
  12. package/template/nextjs/bunfig.toml +3 -0
  13. package/template/nextjs/eslint.config.mjs +6 -5
  14. package/template/nextjs/gitignore +6 -0
  15. package/template/nextjs/next.config.ts +8 -1
  16. package/template/nextjs/package.json +47 -40
  17. package/template/nextjs/public/logo-dark.svg +4 -0
  18. package/template/nextjs/public/logo.svg +4 -0
  19. package/template/nextjs/src/app/(admin)/actions/users.ts +183 -0
  20. package/template/nextjs/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
  21. package/template/nextjs/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
  22. package/template/nextjs/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
  23. package/template/nextjs/src/app/(admin)/admin/layout.tsx +53 -0
  24. package/template/nextjs/src/app/(admin)/admin/page.tsx +11 -2
  25. package/template/nextjs/src/app/(admin)/admin/settings/layout.tsx +24 -0
  26. package/template/nextjs/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
  27. package/template/nextjs/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
  28. package/template/nextjs/src/app/(admin)/admin/settings/users/page.tsx +377 -0
  29. package/template/nextjs/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
  30. package/template/nextjs/src/app/(admin)/admin/settings/users/validation.ts +23 -0
  31. package/template/nextjs/src/app/(app)/app/layout.tsx +44 -0
  32. package/template/nextjs/src/app/(app)/app/page.tsx +14 -2
  33. package/template/nextjs/src/app/(auth)/(account)/layout.tsx +64 -0
  34. package/template/nextjs/src/app/(auth)/(account)/profile/page.tsx +16 -0
  35. package/template/nextjs/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
  36. package/template/nextjs/src/app/(auth)/login/email-login-form.tsx +56 -29
  37. package/template/nextjs/src/app/(auth)/login/page.tsx +45 -12
  38. package/template/nextjs/src/app/(auth)/login/social-login-buttons.tsx +85 -17
  39. package/template/nextjs/src/app/(marketing)/layout.tsx +35 -4
  40. package/template/nextjs/src/app/(marketing)/legal.tsx +56 -0
  41. package/template/nextjs/src/app/(marketing)/page.tsx +20 -2
  42. package/template/nextjs/src/app/(marketing)/privacy/page.tsx +43 -2
  43. package/template/nextjs/src/app/(marketing)/terms/page.tsx +33 -2
  44. package/template/nextjs/src/app/error.tsx +37 -0
  45. package/template/nextjs/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
  46. package/template/nextjs/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
  47. package/template/nextjs/src/app/fonts/index.ts +60 -0
  48. package/template/nextjs/src/app/global-error.tsx +38 -0
  49. package/template/nextjs/src/app/globals.css +29 -0
  50. package/template/nextjs/src/app/layout.tsx +45 -18
  51. package/template/nextjs/src/app/not-found.tsx +25 -3
  52. package/template/nextjs/src/app/robots.ts +29 -0
  53. package/template/nextjs/src/app/sitemap.ts +21 -0
  54. package/template/nextjs/src/components/confirm-dialog.tsx +79 -0
  55. package/template/nextjs/src/components/dashboard-header.tsx +70 -0
  56. package/template/nextjs/src/components/data-table/enum.ts +5 -0
  57. package/template/nextjs/src/components/data-table/features.ts +49 -0
  58. package/template/nextjs/src/components/data-table/index.tsx +961 -0
  59. package/template/nextjs/src/components/data-table/types.ts +76 -0
  60. package/template/nextjs/src/components/error.tsx +71 -0
  61. package/template/nextjs/src/components/loader.tsx +18 -2
  62. package/template/nextjs/src/components/logo.tsx +49 -0
  63. package/template/nextjs/src/components/navigation-search.tsx +140 -0
  64. package/template/nextjs/src/components/navigation-sidebar.tsx +241 -0
  65. package/template/nextjs/src/components/page-heading.tsx +38 -0
  66. package/template/nextjs/src/components/react-query-provider.tsx +41 -2
  67. package/template/nextjs/src/components/route-error.tsx +55 -0
  68. package/template/nextjs/src/components/screen-size.tsx +36 -0
  69. package/template/nextjs/src/components/site-footer.tsx +20 -4
  70. package/template/nextjs/src/components/theme-provider.tsx +48 -0
  71. package/template/nextjs/src/components/theme-toggle.tsx +58 -5
  72. package/template/nextjs/src/components/ui/alert-dialog.tsx +199 -0
  73. package/template/nextjs/src/components/ui/avatar.tsx +112 -0
  74. package/template/nextjs/src/components/ui/badge.tsx +54 -0
  75. package/template/nextjs/src/components/ui/button.tsx +3 -1
  76. package/template/nextjs/src/components/ui/card.tsx +4 -4
  77. package/template/nextjs/src/components/ui/collapsible.tsx +33 -0
  78. package/template/nextjs/src/components/ui/command.tsx +192 -0
  79. package/template/nextjs/src/components/ui/dialog.tsx +164 -0
  80. package/template/nextjs/src/components/ui/dropdown-menu.tsx +273 -0
  81. package/template/nextjs/src/components/ui/input-group.tsx +156 -0
  82. package/template/nextjs/src/components/ui/select.tsx +195 -0
  83. package/template/nextjs/src/components/ui/sheet.tsx +143 -0
  84. package/template/nextjs/src/components/ui/sidebar.tsx +702 -0
  85. package/template/nextjs/src/components/ui/skeleton.tsx +13 -0
  86. package/template/nextjs/src/components/ui/table.tsx +116 -0
  87. package/template/nextjs/src/components/ui/textarea.tsx +18 -0
  88. package/template/nextjs/src/components/ui/tooltip.tsx +57 -0
  89. package/template/nextjs/src/components/user-menu.tsx +206 -0
  90. package/template/nextjs/src/db/common.ts +10 -0
  91. package/template/nextjs/src/db/index.ts +16 -6
  92. package/template/nextjs/src/db/relations.ts +21 -0
  93. package/template/nextjs/src/db/schema.ts +85 -46
  94. package/template/nextjs/src/emails/components/email-layout.tsx +27 -11
  95. package/template/nextjs/src/emails/magic-link.tsx +15 -13
  96. package/template/nextjs/src/emails/test.tsx +11 -9
  97. package/template/nextjs/src/emails/welcome.tsx +12 -9
  98. package/template/nextjs/src/env.ts +1 -1
  99. package/template/nextjs/src/hooks/use-mobile.ts +14 -12
  100. package/template/nextjs/src/instrumentation.ts +20 -2
  101. package/template/nextjs/src/lib/actions.ts +55 -0
  102. package/template/nextjs/src/lib/admin-navigation.ts +43 -0
  103. package/template/nextjs/src/lib/auth-client.ts +20 -9
  104. package/template/nextjs/src/lib/auth-permissions.ts +61 -0
  105. package/template/nextjs/src/lib/auth.ts +33 -3
  106. package/template/nextjs/src/lib/callback-url.ts +28 -0
  107. package/template/nextjs/src/lib/constants.ts +21 -0
  108. package/template/nextjs/src/lib/delay.ts +10 -0
  109. package/template/nextjs/src/lib/email-service.ts +21 -0
  110. package/template/nextjs/src/lib/errors.ts +74 -0
  111. package/template/nextjs/src/lib/format-currency.ts +22 -24
  112. package/template/nextjs/src/lib/format-date.ts +69 -6
  113. package/template/nextjs/src/lib/nodemailer.ts +27 -3
  114. package/template/nextjs/src/lib/otel.ts +92 -2
  115. package/template/nextjs/src/lib/safe-action-client.ts +60 -9
  116. package/template/nextjs/src/lib/safe-action.ts +152 -16
  117. package/template/nextjs/src/lib/search.ts +16 -0
  118. package/template/nextjs/src/lib/seo.ts +7 -0
  119. package/template/nextjs/src/lib/siteConfig.ts +17 -1
  120. package/template/nextjs/src/lib/toast-message.ts +87 -1
  121. package/template/nextjs/src/lib/validations.ts +30 -0
  122. package/template/nextjs/src/proxy.ts +48 -0
  123. package/template/nextjs/src/scripts/reset-db.ts +22 -0
  124. package/template/nextjs/src/scripts/seed.ts +59 -8
  125. package/template/nextjs/src/scripts/test-mail.ts +28 -20
  126. package/template/nextjs/tests/admin/users.test.ts +64 -0
  127. package/template/nextjs/tests/db/fk-indexes.test.ts +62 -0
  128. package/template/nextjs/tests/db/seed.test.ts +49 -0
  129. package/template/nextjs/tests/helpers/action.ts +71 -0
  130. package/template/nextjs/tests/helpers/fixtures.ts +78 -0
  131. package/template/nextjs/tests/helpers/index.ts +2 -0
  132. package/template/nextjs/tests/lib/callback-url.test.ts +48 -0
  133. package/template/nextjs/tests/lib/client-server-boundary.test.ts +57 -0
  134. package/template/nextjs/tests/lib/errors.test.ts +10 -0
  135. package/template/nextjs/tests/lib/search.test.ts +23 -0
  136. package/template/nextjs/tests/setup/pglite-db.ts +63 -0
  137. package/template/nextjs/tests/setup/preload.ts +95 -0
  138. package/template/nextjs/tsconfig.json +1 -0
  139. package/template/saas-starter/.dockerignore +8 -0
  140. package/template/saas-starter/.env.example +25 -0
  141. package/template/saas-starter/.vscode/extensions.json +11 -0
  142. package/template/saas-starter/.vscode/settings.json +3 -0
  143. package/template/saas-starter/AGENTS.md +302 -0
  144. package/template/saas-starter/CLAUDE.md +1 -0
  145. package/template/saas-starter/Dockerfile +15 -0
  146. package/template/saas-starter/README.md +125 -0
  147. package/template/saas-starter/bunfig.toml +3 -0
  148. package/template/saas-starter/components.json +25 -0
  149. package/template/saas-starter/drizzle.config.ts +10 -0
  150. package/template/saas-starter/eslint.config.mjs +24 -0
  151. package/template/saas-starter/gitignore +14 -0
  152. package/template/saas-starter/next.config.ts +32 -0
  153. package/template/saas-starter/package.json +97 -0
  154. package/template/saas-starter/postcss.config.mjs +7 -0
  155. package/template/saas-starter/prettier.config.mjs +11 -0
  156. package/template/saas-starter/public/logo-dark.svg +4 -0
  157. package/template/saas-starter/public/logo.svg +4 -0
  158. package/template/saas-starter/public/placeholder.svg +1 -0
  159. package/template/saas-starter/src/app/(admin)/actions/contact-leads.ts +51 -0
  160. package/template/saas-starter/src/app/(admin)/actions/feedbacks.ts +91 -0
  161. package/template/saas-starter/src/app/(admin)/actions/users.ts +183 -0
  162. package/template/saas-starter/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
  163. package/template/saas-starter/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
  164. package/template/saas-starter/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
  165. package/template/saas-starter/src/app/(admin)/admin/contact-leads/page.tsx +123 -0
  166. package/template/saas-starter/src/app/(admin)/admin/feedbacks/page.tsx +161 -0
  167. package/template/saas-starter/src/app/(admin)/admin/layout.tsx +53 -0
  168. package/template/saas-starter/src/app/(admin)/admin/page.tsx +60 -0
  169. package/template/saas-starter/src/app/(admin)/admin/settings/layout.tsx +24 -0
  170. package/template/saas-starter/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
  171. package/template/saas-starter/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
  172. package/template/saas-starter/src/app/(admin)/admin/settings/users/page.tsx +377 -0
  173. package/template/saas-starter/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
  174. package/template/saas-starter/src/app/(admin)/admin/settings/users/validation.ts +23 -0
  175. package/template/saas-starter/src/app/(admin)/admin/support-tickets/[ticketId]/page.tsx +17 -0
  176. package/template/saas-starter/src/app/(admin)/admin/support-tickets/page.tsx +5 -0
  177. package/template/saas-starter/src/app/(app)/actions/billing.ts +216 -0
  178. package/template/saas-starter/src/app/(app)/actions/feedbacks.ts +28 -0
  179. package/template/saas-starter/src/app/(app)/actions/feedbacks.validation.ts +12 -0
  180. package/template/saas-starter/src/app/(app)/actions/members.ts +189 -0
  181. package/template/saas-starter/src/app/(app)/actions/organizations.ts +230 -0
  182. package/template/saas-starter/src/app/(app)/actions/organizations.validation.ts +17 -0
  183. package/template/saas-starter/src/app/(app)/app/[orgId]/dashboard/page.tsx +17 -0
  184. package/template/saas-starter/src/app/(app)/app/[orgId]/layout.tsx +31 -0
  185. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/billing-email-form.tsx +92 -0
  186. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/danger-zone.tsx +357 -0
  187. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/general-settings-form.tsx +130 -0
  188. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/plans-section.tsx +222 -0
  189. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/billing/page.tsx +115 -0
  190. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/layout.tsx +24 -0
  191. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/invite-member-dialog.tsx +168 -0
  192. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/members-tabs.tsx +604 -0
  193. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/page.tsx +33 -0
  194. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/page.tsx +55 -0
  195. package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/[ticketId]/page.tsx +25 -0
  196. package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/page.tsx +25 -0
  197. package/template/saas-starter/src/app/(app)/app/_components/app-header.tsx +31 -0
  198. package/template/saas-starter/src/app/(app)/app/_components/app-search.tsx +26 -0
  199. package/template/saas-starter/src/app/(app)/app/_components/app-sidebar.tsx +30 -0
  200. package/template/saas-starter/src/app/(app)/app/_components/feedback-dialog.tsx +108 -0
  201. package/template/saas-starter/src/app/(app)/app/_components/organization-context.tsx +37 -0
  202. package/template/saas-starter/src/app/(app)/app/_components/organization-switcher.tsx +200 -0
  203. package/template/saas-starter/src/app/(app)/app/layout.tsx +64 -0
  204. package/template/saas-starter/src/app/(app)/app/onboarding/_components/create-organization-form.tsx +117 -0
  205. package/template/saas-starter/src/app/(app)/app/onboarding/page.tsx +40 -0
  206. package/template/saas-starter/src/app/(app)/app/page.tsx +76 -0
  207. package/template/saas-starter/src/app/(auth)/(account)/layout.tsx +64 -0
  208. package/template/saas-starter/src/app/(auth)/(account)/profile/page.tsx +16 -0
  209. package/template/saas-starter/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
  210. package/template/saas-starter/src/app/(auth)/(account)/support/[ticketId]/page.tsx +19 -0
  211. package/template/saas-starter/src/app/(auth)/(account)/support/page.tsx +9 -0
  212. package/template/saas-starter/src/app/(auth)/login/email-login-form.tsx +131 -0
  213. package/template/saas-starter/src/app/(auth)/login/page.tsx +105 -0
  214. package/template/saas-starter/src/app/(auth)/login/social-login-buttons.tsx +137 -0
  215. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/accept-invitation-form.tsx +94 -0
  216. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/page.tsx +153 -0
  217. package/template/saas-starter/src/app/(invitation)/actions/invitations.ts +32 -0
  218. package/template/saas-starter/src/app/(marketing)/contact-us/actions.ts +23 -0
  219. package/template/saas-starter/src/app/(marketing)/contact-us/contact-form.tsx +118 -0
  220. package/template/saas-starter/src/app/(marketing)/contact-us/page.tsx +20 -0
  221. package/template/saas-starter/src/app/(marketing)/contact-us/validation.ts +22 -0
  222. package/template/saas-starter/src/app/(marketing)/layout.tsx +48 -0
  223. package/template/saas-starter/src/app/(marketing)/legal.tsx +66 -0
  224. package/template/saas-starter/src/app/(marketing)/page.tsx +24 -0
  225. package/template/saas-starter/src/app/(marketing)/privacy/page.tsx +63 -0
  226. package/template/saas-starter/src/app/(marketing)/terms/page.tsx +41 -0
  227. package/template/saas-starter/src/app/actions/support-tickets.ts +510 -0
  228. package/template/saas-starter/src/app/actions/support-tickets.validation.ts +112 -0
  229. package/template/saas-starter/src/app/actions/uploads.ts +36 -0
  230. package/template/saas-starter/src/app/actions/uploads.validation.ts +24 -0
  231. package/template/saas-starter/src/app/api/auth/[...all]/route.ts +4 -0
  232. package/template/saas-starter/src/app/api/webhooks/stripe/route.ts +94 -0
  233. package/template/saas-starter/src/app/error.tsx +37 -0
  234. package/template/saas-starter/src/app/favicon.ico +0 -0
  235. package/template/saas-starter/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
  236. package/template/saas-starter/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
  237. package/template/saas-starter/src/app/fonts/index.ts +60 -0
  238. package/template/saas-starter/src/app/global-error.tsx +38 -0
  239. package/template/saas-starter/src/app/globals.css +269 -0
  240. package/template/saas-starter/src/app/layout.tsx +77 -0
  241. package/template/saas-starter/src/app/not-found.tsx +49 -0
  242. package/template/saas-starter/src/app/robots.ts +36 -0
  243. package/template/saas-starter/src/app/sitemap.ts +21 -0
  244. package/template/saas-starter/src/components/confirm-dialog.tsx +79 -0
  245. package/template/saas-starter/src/components/dashboard-header.tsx +70 -0
  246. package/template/saas-starter/src/components/data-table/enum.ts +5 -0
  247. package/template/saas-starter/src/components/data-table/features.ts +49 -0
  248. package/template/saas-starter/src/components/data-table/index.tsx +961 -0
  249. package/template/saas-starter/src/components/data-table/types.ts +76 -0
  250. package/template/saas-starter/src/components/error.tsx +71 -0
  251. package/template/saas-starter/src/components/file-upload.tsx +254 -0
  252. package/template/saas-starter/src/components/loader.tsx +41 -0
  253. package/template/saas-starter/src/components/logo.tsx +49 -0
  254. package/template/saas-starter/src/components/navigation-search.tsx +140 -0
  255. package/template/saas-starter/src/components/navigation-sidebar.tsx +241 -0
  256. package/template/saas-starter/src/components/page-heading.tsx +38 -0
  257. package/template/saas-starter/src/components/react-query-provider.tsx +51 -0
  258. package/template/saas-starter/src/components/route-error.tsx +55 -0
  259. package/template/saas-starter/src/components/screen-size.tsx +76 -0
  260. package/template/saas-starter/src/components/site-footer.tsx +39 -0
  261. package/template/saas-starter/src/components/support/create-support-ticket-dialog.tsx +122 -0
  262. package/template/saas-starter/src/components/support/support-ticket-conversation.tsx +236 -0
  263. package/template/saas-starter/src/components/support/support-ticket-shared.tsx +58 -0
  264. package/template/saas-starter/src/components/support/support-tickets-table.tsx +281 -0
  265. package/template/saas-starter/src/components/theme-provider.tsx +115 -0
  266. package/template/saas-starter/src/components/theme-toggle.tsx +75 -0
  267. package/template/saas-starter/src/components/ui/alert-dialog.tsx +199 -0
  268. package/template/saas-starter/src/components/ui/avatar.tsx +112 -0
  269. package/template/saas-starter/src/components/ui/badge.tsx +54 -0
  270. package/template/saas-starter/src/components/ui/button.tsx +69 -0
  271. package/template/saas-starter/src/components/ui/card.tsx +103 -0
  272. package/template/saas-starter/src/components/ui/checkbox.tsx +32 -0
  273. package/template/saas-starter/src/components/ui/collapsible.tsx +33 -0
  274. package/template/saas-starter/src/components/ui/command.tsx +192 -0
  275. package/template/saas-starter/src/components/ui/dialog.tsx +164 -0
  276. package/template/saas-starter/src/components/ui/dropdown-menu.tsx +273 -0
  277. package/template/saas-starter/src/components/ui/field.tsx +238 -0
  278. package/template/saas-starter/src/components/ui/input-group.tsx +156 -0
  279. package/template/saas-starter/src/components/ui/input.tsx +19 -0
  280. package/template/saas-starter/src/components/ui/label.tsx +24 -0
  281. package/template/saas-starter/src/components/ui/popover.tsx +89 -0
  282. package/template/saas-starter/src/components/ui/select.tsx +195 -0
  283. package/template/saas-starter/src/components/ui/separator.tsx +28 -0
  284. package/template/saas-starter/src/components/ui/sheet.tsx +143 -0
  285. package/template/saas-starter/src/components/ui/sidebar.tsx +702 -0
  286. package/template/saas-starter/src/components/ui/skeleton.tsx +13 -0
  287. package/template/saas-starter/src/components/ui/sonner.tsx +45 -0
  288. package/template/saas-starter/src/components/ui/switch.tsx +33 -0
  289. package/template/saas-starter/src/components/ui/table.tsx +116 -0
  290. package/template/saas-starter/src/components/ui/tabs.tsx +90 -0
  291. package/template/saas-starter/src/components/ui/textarea.tsx +18 -0
  292. package/template/saas-starter/src/components/ui/tooltip.tsx +57 -0
  293. package/template/saas-starter/src/components/user-menu.tsx +213 -0
  294. package/template/saas-starter/src/db/common.ts +63 -0
  295. package/template/saas-starter/src/db/enums.ts +50 -0
  296. package/template/saas-starter/src/db/index.ts +60 -0
  297. package/template/saas-starter/src/db/relations.ts +112 -0
  298. package/template/saas-starter/src/db/schema.ts +342 -0
  299. package/template/saas-starter/src/emails/components/email-layout.tsx +79 -0
  300. package/template/saas-starter/src/emails/invitation.tsx +69 -0
  301. package/template/saas-starter/src/emails/magic-link.tsx +51 -0
  302. package/template/saas-starter/src/emails/test.tsx +34 -0
  303. package/template/saas-starter/src/emails/welcome.tsx +37 -0
  304. package/template/saas-starter/src/env.ts +66 -0
  305. package/template/saas-starter/src/hooks/use-mobile.ts +21 -0
  306. package/template/saas-starter/src/instrumentation.ts +44 -0
  307. package/template/saas-starter/src/lib/actions.ts +55 -0
  308. package/template/saas-starter/src/lib/admin-navigation.ts +63 -0
  309. package/template/saas-starter/src/lib/app-navigation.ts +63 -0
  310. package/template/saas-starter/src/lib/auth-client.ts +34 -0
  311. package/template/saas-starter/src/lib/auth-permissions.ts +61 -0
  312. package/template/saas-starter/src/lib/auth.ts +312 -0
  313. package/template/saas-starter/src/lib/billing.ts +152 -0
  314. package/template/saas-starter/src/lib/callback-url.ts +28 -0
  315. package/template/saas-starter/src/lib/cn.ts +4 -0
  316. package/template/saas-starter/src/lib/constants.ts +91 -0
  317. package/template/saas-starter/src/lib/delay.ts +12 -0
  318. package/template/saas-starter/src/lib/email-service.ts +118 -0
  319. package/template/saas-starter/src/lib/errors.ts +74 -0
  320. package/template/saas-starter/src/lib/format-currency.ts +25 -0
  321. package/template/saas-starter/src/lib/format-date.ts +75 -0
  322. package/template/saas-starter/src/lib/image-url.ts +54 -0
  323. package/template/saas-starter/src/lib/nodemailer.ts +67 -0
  324. package/template/saas-starter/src/lib/organization-access.ts +73 -0
  325. package/template/saas-starter/src/lib/otel.ts +117 -0
  326. package/template/saas-starter/src/lib/plan-limits.ts +37 -0
  327. package/template/saas-starter/src/lib/plans.ts +90 -0
  328. package/template/saas-starter/src/lib/rbac.ts +44 -0
  329. package/template/saas-starter/src/lib/safe-action-client.ts +126 -0
  330. package/template/saas-starter/src/lib/safe-action.ts +187 -0
  331. package/template/saas-starter/src/lib/search.ts +16 -0
  332. package/template/saas-starter/src/lib/seo.ts +7 -0
  333. package/template/saas-starter/src/lib/siteConfig.ts +20 -0
  334. package/template/saas-starter/src/lib/storage.ts +311 -0
  335. package/template/saas-starter/src/lib/stripe.ts +9 -0
  336. package/template/saas-starter/src/lib/toast-message.ts +101 -0
  337. package/template/saas-starter/src/lib/validations.ts +95 -0
  338. package/template/saas-starter/src/proxy.ts +48 -0
  339. package/template/saas-starter/src/scripts/cleanup-temp-uploads.ts +24 -0
  340. package/template/saas-starter/src/scripts/reset-db.ts +22 -0
  341. package/template/saas-starter/src/scripts/seed.ts +85 -0
  342. package/template/saas-starter/src/scripts/setup-db.ts +72 -0
  343. package/template/saas-starter/src/scripts/setup-stripe-plans.ts +206 -0
  344. package/template/saas-starter/src/scripts/test-mail.ts +43 -0
  345. package/template/saas-starter/tests/admin/users.test.ts +64 -0
  346. package/template/saas-starter/tests/db/fk-indexes.test.ts +62 -0
  347. package/template/saas-starter/tests/db/seed.test.ts +57 -0
  348. package/template/saas-starter/tests/helpers/action.ts +71 -0
  349. package/template/saas-starter/tests/helpers/fixtures.ts +151 -0
  350. package/template/saas-starter/tests/helpers/index.ts +2 -0
  351. package/template/saas-starter/tests/lib/callback-url.test.ts +48 -0
  352. package/template/saas-starter/tests/lib/client-server-boundary.test.ts +57 -0
  353. package/template/saas-starter/tests/lib/errors.test.ts +10 -0
  354. package/template/saas-starter/tests/lib/image-url.test.ts +31 -0
  355. package/template/saas-starter/tests/lib/search.test.ts +23 -0
  356. package/template/saas-starter/tests/marketing/contact-and-feedback.test.ts +86 -0
  357. package/template/saas-starter/tests/organizations/organizations.test.ts +175 -0
  358. package/template/saas-starter/tests/setup/pglite-db.ts +67 -0
  359. package/template/saas-starter/tests/setup/preload.ts +108 -0
  360. package/template/saas-starter/tests/support/support-tickets.test.ts +145 -0
  361. package/template/saas-starter/tsconfig.json +52 -0
  362. package/cli.js +0 -143
  363. package/template/nextjs/src/app/(admin)/layout.tsx +0 -20
  364. package/template/nextjs/src/app/(app)/layout.tsx +0 -20
  365. package/template/nextjs/terraform/artifact-registry.tf +0 -5
  366. package/template/nextjs/terraform/backend-service.tf +0 -13
  367. package/template/nextjs/terraform/cleanup.sh +0 -9
  368. package/template/nextjs/terraform/cloudbuild.tf +0 -70
  369. package/template/nextjs/terraform/cloudrun.tf +0 -68
  370. package/template/nextjs/terraform/database.tf +0 -60
  371. package/template/nextjs/terraform/forwarding-rule.tf +0 -43
  372. package/template/nextjs/terraform/gitignore +0 -4
  373. package/template/nextjs/terraform/ip-address.tf +0 -11
  374. package/template/nextjs/terraform/network-endpoint-group.tf +0 -11
  375. package/template/nextjs/terraform/network.tf +0 -21
  376. package/template/nextjs/terraform/pre.sh +0 -3
  377. package/template/nextjs/terraform/providers.tf +0 -18
  378. package/template/nextjs/terraform/secret.txt +0 -11
  379. package/template/nextjs/terraform/setup.sh +0 -25
  380. package/template/nextjs/terraform/ssl-certs.tf +0 -32
  381. package/template/nextjs/terraform/target-https-proxy copy.tf +0 -14
  382. package/template/nextjs/terraform/target-https-proxy.tf +0 -14
  383. package/template/nextjs/terraform/terraform.tfvars +0 -4
  384. package/template/nextjs/terraform/url-map.tf +0 -23
  385. package/template/nextjs/terraform/variables.tf +0 -21
@@ -0,0 +1,192 @@
1
+ 'use client'
2
+
3
+ import { Command as CommandPrimitive } from 'cmdk'
4
+ import * as React from 'react'
5
+
6
+ import { CheckIcon, SearchIcon } from 'lucide-react'
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogHeader,
12
+ DialogTitle
13
+ } from '~/components/ui/dialog'
14
+ import { InputGroup, InputGroupAddon } from '~/components/ui/input-group'
15
+ import { cn } from '~/lib/cn'
16
+
17
+ function Command({
18
+ className,
19
+ ...props
20
+ }: React.ComponentProps<typeof CommandPrimitive>) {
21
+ return (
22
+ <CommandPrimitive
23
+ data-slot="command"
24
+ className={cn(
25
+ 'bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-xl! p-1',
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ function CommandDialog({
34
+ title = 'Command Palette',
35
+ description = 'Search for a command to run…',
36
+ children,
37
+ className,
38
+ showCloseButton = false,
39
+ ...props
40
+ }: React.ComponentProps<typeof Dialog> & {
41
+ title?: string
42
+ description?: string
43
+ className?: string
44
+ showCloseButton?: boolean
45
+ }) {
46
+ return (
47
+ <Dialog {...props}>
48
+ <DialogHeader className="sr-only">
49
+ <DialogTitle>{title}</DialogTitle>
50
+ <DialogDescription>{description}</DialogDescription>
51
+ </DialogHeader>
52
+ <DialogContent
53
+ className={cn(
54
+ 'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',
55
+ className
56
+ )}
57
+ showCloseButton={showCloseButton}
58
+ >
59
+ {children}
60
+ </DialogContent>
61
+ </Dialog>
62
+ )
63
+ }
64
+
65
+ function CommandInput({
66
+ className,
67
+ ...props
68
+ }: React.ComponentProps<typeof CommandPrimitive.Input>) {
69
+ return (
70
+ <div data-slot="command-input-wrapper" className="p-1 pb-0">
71
+ <InputGroup className="border-input/30 bg-input/30 h-8! rounded-lg! shadow-none! *:data-[slot=input-group-addon]:pl-2!">
72
+ <CommandPrimitive.Input
73
+ data-slot="command-input"
74
+ className={cn(
75
+ 'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ <InputGroupAddon>
81
+ <SearchIcon className="size-4 shrink-0 opacity-50" />
82
+ </InputGroupAddon>
83
+ </InputGroup>
84
+ </div>
85
+ )
86
+ }
87
+
88
+ function CommandList({
89
+ className,
90
+ ...props
91
+ }: React.ComponentProps<typeof CommandPrimitive.List>) {
92
+ return (
93
+ <CommandPrimitive.List
94
+ data-slot="command-list"
95
+ className={cn(
96
+ 'no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none',
97
+ className
98
+ )}
99
+ {...props}
100
+ />
101
+ )
102
+ }
103
+
104
+ function CommandEmpty({
105
+ className,
106
+ ...props
107
+ }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
108
+ return (
109
+ <CommandPrimitive.Empty
110
+ data-slot="command-empty"
111
+ className={cn('py-6 text-center text-sm', className)}
112
+ {...props}
113
+ />
114
+ )
115
+ }
116
+
117
+ function CommandGroup({
118
+ className,
119
+ ...props
120
+ }: React.ComponentProps<typeof CommandPrimitive.Group>) {
121
+ return (
122
+ <CommandPrimitive.Group
123
+ data-slot="command-group"
124
+ className={cn(
125
+ 'text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium',
126
+ className
127
+ )}
128
+ {...props}
129
+ />
130
+ )
131
+ }
132
+
133
+ function CommandSeparator({
134
+ className,
135
+ ...props
136
+ }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
137
+ return (
138
+ <CommandPrimitive.Separator
139
+ data-slot="command-separator"
140
+ className={cn('bg-border -mx-1 h-px w-auto', className)}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function CommandItem({
147
+ className,
148
+ children,
149
+ ...props
150
+ }: React.ComponentProps<typeof CommandPrimitive.Item>) {
151
+ return (
152
+ <CommandPrimitive.Item
153
+ data-slot="command-item"
154
+ className={cn(
155
+ "group/command-item data-selected:bg-muted data-selected:text-foreground data-selected:**:[svg]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
156
+ className
157
+ )}
158
+ {...props}
159
+ >
160
+ {children}
161
+ <CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
162
+ </CommandPrimitive.Item>
163
+ )
164
+ }
165
+
166
+ function CommandShortcut({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<'span'>) {
170
+ return (
171
+ <span
172
+ data-slot="command-shortcut"
173
+ className={cn(
174
+ 'text-muted-foreground group-data-selected/command-item:text-foreground ml-auto text-xs tracking-widest',
175
+ className
176
+ )}
177
+ {...props}
178
+ />
179
+ )
180
+ }
181
+
182
+ export {
183
+ Command,
184
+ CommandDialog,
185
+ CommandEmpty,
186
+ CommandGroup,
187
+ CommandInput,
188
+ CommandItem,
189
+ CommandList,
190
+ CommandSeparator,
191
+ CommandShortcut
192
+ }
@@ -0,0 +1,164 @@
1
+ 'use client'
2
+
3
+ import { Dialog as DialogPrimitive } from 'radix-ui'
4
+ import * as React from 'react'
5
+
6
+ import { XIcon } from 'lucide-react'
7
+ import { Button } from '~/components/ui/button'
8
+ import { cn } from '~/lib/cn'
9
+
10
+ function Dialog({
11
+ ...props
12
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
13
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
14
+ }
15
+
16
+ function DialogTrigger({
17
+ ...props
18
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
19
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
20
+ }
21
+
22
+ function DialogPortal({
23
+ ...props
24
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
25
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
26
+ }
27
+
28
+ function DialogClose({
29
+ ...props
30
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
31
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
32
+ }
33
+
34
+ function DialogOverlay({
35
+ className,
36
+ ...props
37
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
38
+ return (
39
+ <DialogPrimitive.Overlay
40
+ data-slot="dialog-overlay"
41
+ className={cn(
42
+ 'data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs',
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function DialogContent({
51
+ className,
52
+ children,
53
+ showCloseButton = true,
54
+ ...props
55
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
56
+ showCloseButton?: boolean
57
+ }) {
58
+ return (
59
+ <DialogPortal>
60
+ <DialogOverlay />
61
+ <DialogPrimitive.Content
62
+ data-slot="dialog-content"
63
+ className={cn(
64
+ 'bg-popover text-popover-foreground ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl p-6 text-sm ring-1 duration-100 outline-none sm:max-w-md',
65
+ className
66
+ )}
67
+ {...props}
68
+ >
69
+ {children}
70
+ {showCloseButton && (
71
+ <DialogPrimitive.Close data-slot="dialog-close" asChild>
72
+ <Button
73
+ variant="ghost"
74
+ className="absolute top-4 right-4"
75
+ size="icon-sm"
76
+ >
77
+ <XIcon />
78
+ <span className="sr-only">Close</span>
79
+ </Button>
80
+ </DialogPrimitive.Close>
81
+ )}
82
+ </DialogPrimitive.Content>
83
+ </DialogPortal>
84
+ )
85
+ }
86
+
87
+ function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
88
+ return (
89
+ <div
90
+ data-slot="dialog-header"
91
+ className={cn('flex flex-col gap-2', className)}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ function DialogFooter({
98
+ className,
99
+ showCloseButton = false,
100
+ children,
101
+ ...props
102
+ }: React.ComponentProps<'div'> & {
103
+ showCloseButton?: boolean
104
+ }) {
105
+ return (
106
+ <div
107
+ data-slot="dialog-footer"
108
+ className={cn(
109
+ 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
110
+ className
111
+ )}
112
+ {...props}
113
+ >
114
+ {children}
115
+ {showCloseButton && (
116
+ <DialogPrimitive.Close asChild>
117
+ <Button variant="outline">Close</Button>
118
+ </DialogPrimitive.Close>
119
+ )}
120
+ </div>
121
+ )
122
+ }
123
+
124
+ function DialogTitle({
125
+ className,
126
+ ...props
127
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
128
+ return (
129
+ <DialogPrimitive.Title
130
+ data-slot="dialog-title"
131
+ className={cn('font-heading leading-none font-medium', className)}
132
+ {...props}
133
+ />
134
+ )
135
+ }
136
+
137
+ function DialogDescription({
138
+ className,
139
+ ...props
140
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
141
+ return (
142
+ <DialogPrimitive.Description
143
+ data-slot="dialog-description"
144
+ className={cn(
145
+ 'text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3',
146
+ className
147
+ )}
148
+ {...props}
149
+ />
150
+ )
151
+ }
152
+
153
+ export {
154
+ Dialog,
155
+ DialogClose,
156
+ DialogContent,
157
+ DialogDescription,
158
+ DialogFooter,
159
+ DialogHeader,
160
+ DialogOverlay,
161
+ DialogPortal,
162
+ DialogTitle,
163
+ DialogTrigger
164
+ }
@@ -0,0 +1,273 @@
1
+ 'use client'
2
+
3
+ import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui'
4
+ import * as React from 'react'
5
+
6
+ import { CheckIcon, ChevronRightIcon } from 'lucide-react'
7
+ import { cn } from '~/lib/cn'
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ align = 'start',
37
+ sideOffset = 4,
38
+ ...props
39
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
40
+ return (
41
+ <DropdownMenuPrimitive.Portal>
42
+ <DropdownMenuPrimitive.Content
43
+ data-slot="dropdown-menu-content"
44
+ sideOffset={sideOffset}
45
+ align={align}
46
+ className={cn(
47
+ 'bg-popover text-popover-foreground ring-foreground/10 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md p-1 shadow-md ring-1 duration-100 data-[state=closed]:overflow-hidden',
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ </DropdownMenuPrimitive.Portal>
53
+ )
54
+ }
55
+
56
+ function DropdownMenuGroup({
57
+ ...props
58
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
59
+ return (
60
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
61
+ )
62
+ }
63
+
64
+ function DropdownMenuItem({
65
+ className,
66
+ inset,
67
+ variant = 'default',
68
+ ...props
69
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
70
+ inset?: boolean
71
+ variant?: 'default' | 'destructive'
72
+ }) {
73
+ return (
74
+ <DropdownMenuPrimitive.Item
75
+ data-slot="dropdown-menu-item"
76
+ data-inset={inset}
77
+ data-variant={variant}
78
+ className={cn(
79
+ "group/dropdown-menu-item focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
80
+ className
81
+ )}
82
+ {...props}
83
+ />
84
+ )
85
+ }
86
+
87
+ function DropdownMenuCheckboxItem({
88
+ className,
89
+ children,
90
+ checked,
91
+ inset,
92
+ ...props
93
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
94
+ inset?: boolean
95
+ }) {
96
+ return (
97
+ <DropdownMenuPrimitive.CheckboxItem
98
+ data-slot="dropdown-menu-checkbox-item"
99
+ data-inset={inset}
100
+ className={cn(
101
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
102
+ className
103
+ )}
104
+ checked={checked}
105
+ {...props}
106
+ >
107
+ <span
108
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
109
+ data-slot="dropdown-menu-checkbox-item-indicator"
110
+ >
111
+ <DropdownMenuPrimitive.ItemIndicator>
112
+ <CheckIcon />
113
+ </DropdownMenuPrimitive.ItemIndicator>
114
+ </span>
115
+ {children}
116
+ </DropdownMenuPrimitive.CheckboxItem>
117
+ )
118
+ }
119
+
120
+ function DropdownMenuRadioGroup({
121
+ ...props
122
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
123
+ return (
124
+ <DropdownMenuPrimitive.RadioGroup
125
+ data-slot="dropdown-menu-radio-group"
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function DropdownMenuRadioItem({
132
+ className,
133
+ children,
134
+ inset,
135
+ ...props
136
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
137
+ inset?: boolean
138
+ }) {
139
+ return (
140
+ <DropdownMenuPrimitive.RadioItem
141
+ data-slot="dropdown-menu-radio-item"
142
+ data-inset={inset}
143
+ className={cn(
144
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
145
+ className
146
+ )}
147
+ {...props}
148
+ >
149
+ <span
150
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
151
+ data-slot="dropdown-menu-radio-item-indicator"
152
+ >
153
+ <DropdownMenuPrimitive.ItemIndicator>
154
+ <CheckIcon />
155
+ </DropdownMenuPrimitive.ItemIndicator>
156
+ </span>
157
+ {children}
158
+ </DropdownMenuPrimitive.RadioItem>
159
+ )
160
+ }
161
+
162
+ function DropdownMenuLabel({
163
+ className,
164
+ inset,
165
+ ...props
166
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
167
+ inset?: boolean
168
+ }) {
169
+ return (
170
+ <DropdownMenuPrimitive.Label
171
+ data-slot="dropdown-menu-label"
172
+ data-inset={inset}
173
+ className={cn(
174
+ 'text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8',
175
+ className
176
+ )}
177
+ {...props}
178
+ />
179
+ )
180
+ }
181
+
182
+ function DropdownMenuSeparator({
183
+ className,
184
+ ...props
185
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
186
+ return (
187
+ <DropdownMenuPrimitive.Separator
188
+ data-slot="dropdown-menu-separator"
189
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ function DropdownMenuShortcut({
196
+ className,
197
+ ...props
198
+ }: React.ComponentProps<'span'>) {
199
+ return (
200
+ <span
201
+ data-slot="dropdown-menu-shortcut"
202
+ className={cn(
203
+ 'text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest',
204
+ className
205
+ )}
206
+ {...props}
207
+ />
208
+ )
209
+ }
210
+
211
+ function DropdownMenuSub({
212
+ ...props
213
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
214
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
215
+ }
216
+
217
+ function DropdownMenuSubTrigger({
218
+ className,
219
+ inset,
220
+ children,
221
+ ...props
222
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
223
+ inset?: boolean
224
+ }) {
225
+ return (
226
+ <DropdownMenuPrimitive.SubTrigger
227
+ data-slot="dropdown-menu-sub-trigger"
228
+ data-inset={inset}
229
+ className={cn(
230
+ "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
231
+ className
232
+ )}
233
+ {...props}
234
+ >
235
+ {children}
236
+ <ChevronRightIcon className="ml-auto" />
237
+ </DropdownMenuPrimitive.SubTrigger>
238
+ )
239
+ }
240
+
241
+ function DropdownMenuSubContent({
242
+ className,
243
+ ...props
244
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
245
+ return (
246
+ <DropdownMenuPrimitive.SubContent
247
+ data-slot="dropdown-menu-sub-content"
248
+ className={cn(
249
+ 'bg-popover text-popover-foreground ring-foreground/10 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md p-1 shadow-lg ring-1 duration-100',
250
+ className
251
+ )}
252
+ {...props}
253
+ />
254
+ )
255
+ }
256
+
257
+ export {
258
+ DropdownMenu,
259
+ DropdownMenuCheckboxItem,
260
+ DropdownMenuContent,
261
+ DropdownMenuGroup,
262
+ DropdownMenuItem,
263
+ DropdownMenuLabel,
264
+ DropdownMenuPortal,
265
+ DropdownMenuRadioGroup,
266
+ DropdownMenuRadioItem,
267
+ DropdownMenuSeparator,
268
+ DropdownMenuShortcut,
269
+ DropdownMenuSub,
270
+ DropdownMenuSubContent,
271
+ DropdownMenuSubTrigger,
272
+ DropdownMenuTrigger
273
+ }