betterstart-cli 0.0.1

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 (369) hide show
  1. package/README.md +5 -0
  2. package/dist/assets/adapters/next/integrations/mailchimp/actions/mailchimp.ts +38 -0
  3. package/dist/assets/adapters/next/integrations/mailchimp/integration.ts +33 -0
  4. package/dist/assets/adapters/next/integrations/r2/actions/r2.ts +77 -0
  5. package/dist/assets/adapters/next/integrations/r2/integration.ts +39 -0
  6. package/dist/assets/adapters/next/integrations/resend/actions/resend.ts +138 -0
  7. package/dist/assets/adapters/next/integrations/resend/integration.ts +36 -0
  8. package/dist/assets/adapters/next/plugins/blog/plugin.ts +17 -0
  9. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +169 -0
  10. package/dist/assets/adapters/next/templates/init/admin-globals.css +677 -0
  11. package/dist/assets/adapters/next/templates/init/api/auth-route.ts +3 -0
  12. package/dist/assets/adapters/next/templates/init/api/upload-route.ts +132 -0
  13. package/dist/assets/adapters/next/templates/init/components/layouts/admin-header.tsx +32 -0
  14. package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +23 -0
  15. package/dist/assets/adapters/next/templates/init/components/layouts/admin-providers.tsx +33 -0
  16. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-branding-rsc.tsx +8 -0
  17. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-branding-skeleton.tsx +10 -0
  18. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link-skeleton.tsx +11 -0
  19. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -0
  20. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu-skeleton.tsx +15 -0
  21. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu.tsx +90 -0
  22. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +78 -0
  23. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sign-out.tsx +44 -0
  24. package/dist/assets/adapters/next/templates/init/components/layouts/content-skeleton.tsx +44 -0
  25. package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +41 -0
  26. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table-pagination.tsx +139 -0
  27. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +236 -0
  28. package/dist/assets/adapters/next/templates/init/components/shared/delete-dialog.tsx +67 -0
  29. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/copyable-code-block.tsx +104 -0
  30. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-code-mirror.tsx +68 -0
  31. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +75 -0
  32. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +111 -0
  33. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/plain-code-fallback.tsx +11 -0
  34. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +125 -0
  35. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +108 -0
  36. package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +184 -0
  37. package/dist/assets/adapters/next/templates/init/components/shared/entity-metadata.tsx +93 -0
  38. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-item.tsx +55 -0
  39. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-restore-dialog.tsx +80 -0
  40. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-button.tsx +74 -0
  41. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-current-row.tsx +48 -0
  42. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +79 -0
  43. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +222 -0
  44. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog.tsx +56 -0
  45. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +83 -0
  46. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +148 -0
  47. package/dist/assets/adapters/next/templates/init/components/shared/media/media-empty-state.tsx +45 -0
  48. package/dist/assets/adapters/next/templates/init/components/shared/media/media-filters-bar.tsx +129 -0
  49. package/dist/assets/adapters/next/templates/init/components/shared/media/media-gallery-dialog.tsx +182 -0
  50. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +56 -0
  51. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +114 -0
  52. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid.tsx +44 -0
  53. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +69 -0
  54. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +139 -0
  55. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +46 -0
  56. package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +88 -0
  57. package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +24 -0
  58. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-dialog.tsx +242 -0
  59. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-drag-overlay-item.tsx +15 -0
  60. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-item.tsx +32 -0
  61. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-types.ts +9 -0
  62. package/dist/assets/adapters/next/templates/init/data/navigation.ts +43 -0
  63. package/dist/assets/adapters/next/templates/init/drizzle.config.ts +36 -0
  64. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-blockquote.ts +251 -0
  65. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-code-block.ts +258 -0
  66. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-color-highlight.ts +347 -0
  67. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-media-insertion.ts +59 -0
  68. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-mobile-toolbar.ts +17 -0
  69. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-slash-menu.ts +116 -0
  70. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +638 -0
  71. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-table-add-controls.ts +174 -0
  72. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor.ts +288 -0
  73. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-heading-dropdown-menu.ts +127 -0
  74. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-heading.ts +269 -0
  75. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-is-breakpoint.ts +32 -0
  76. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-link-popover.ts +278 -0
  77. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-list-dropdown-menu.ts +199 -0
  78. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-list.ts +290 -0
  79. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-mark.ts +199 -0
  80. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-menu-navigation.ts +221 -0
  81. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-tiptap-editor.ts +46 -0
  82. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-undo-redo.ts +169 -0
  83. package/dist/assets/adapters/next/templates/init/hooks/use-admin-theme.tsx +74 -0
  84. package/dist/assets/adapters/next/templates/init/hooks/use-copy-to-clipboard.ts +48 -0
  85. package/dist/assets/adapters/next/templates/init/hooks/use-dev-mode-integration.ts +43 -0
  86. package/dist/assets/adapters/next/templates/init/hooks/use-entity-versions.ts +32 -0
  87. package/dist/assets/adapters/next/templates/init/hooks/use-failed-image-url.ts +21 -0
  88. package/dist/assets/adapters/next/templates/init/hooks/use-local-storage.ts +46 -0
  89. package/dist/assets/adapters/next/templates/init/hooks/use-media-filters-bar.ts +72 -0
  90. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +102 -0
  91. package/dist/assets/adapters/next/templates/init/hooks/use-mobile.ts +19 -0
  92. package/dist/assets/adapters/next/templates/init/hooks/use-page-boundary-blur-visibility.ts +59 -0
  93. package/dist/assets/adapters/next/templates/init/hooks/use-page-scroll-threshold.ts +27 -0
  94. package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +315 -0
  95. package/dist/assets/adapters/next/templates/init/hooks/use-upload.ts +321 -0
  96. package/dist/assets/adapters/next/templates/init/hooks/use-users.ts +12 -0
  97. package/dist/assets/adapters/next/templates/init/lib/actions/auth/auth.ts +58 -0
  98. package/dist/assets/adapters/next/templates/init/lib/actions/auth/client.ts +12 -0
  99. package/dist/assets/adapters/next/templates/init/lib/actions/auth/middleware.ts +46 -0
  100. package/dist/assets/adapters/next/templates/init/lib/actions/email/form-delivery.ts +72 -0
  101. package/dist/assets/adapters/next/templates/init/lib/actions/email/index.ts +4 -0
  102. package/dist/assets/adapters/next/templates/init/lib/actions/email/is-email-delivery-configured.ts +5 -0
  103. package/dist/assets/adapters/next/templates/init/lib/actions/email/none.ts +24 -0
  104. package/dist/assets/adapters/next/templates/init/lib/actions/email/provider.ts +6 -0
  105. package/dist/assets/adapters/next/templates/init/lib/actions/email/send-email.ts +6 -0
  106. package/dist/assets/adapters/next/templates/init/lib/actions/email/send-password-reset-email.ts +10 -0
  107. package/dist/assets/adapters/next/templates/init/lib/actions/email/types.ts +25 -0
  108. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/get-entity-versions.ts +48 -0
  109. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/index.ts +10 -0
  110. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/internal-create-entity-version.ts +43 -0
  111. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/internal-delete-entity-versions.ts +19 -0
  112. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/types.ts +26 -0
  113. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-all-form-settings.ts +21 -0
  114. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-form-settings.ts +27 -0
  115. package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +9 -0
  116. package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +40 -0
  117. package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +26 -0
  118. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +40 -0
  119. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +39 -0
  120. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +29 -0
  121. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +22 -0
  122. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media-by-id.ts +18 -0
  123. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media-by-ids.ts +25 -0
  124. package/dist/assets/adapters/next/templates/init/lib/actions/media/get-media.ts +71 -0
  125. package/dist/assets/adapters/next/templates/init/lib/actions/media/index.ts +14 -0
  126. package/dist/assets/adapters/next/templates/init/lib/actions/media/types.ts +31 -0
  127. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +35 -0
  128. package/dist/assets/adapters/next/templates/init/lib/actions/profile/index.ts +4 -0
  129. package/dist/assets/adapters/next/templates/init/lib/actions/profile/invalidate-users-cache.ts +14 -0
  130. package/dist/assets/adapters/next/templates/init/lib/actions/profile/is-email-configured.ts +7 -0
  131. package/dist/assets/adapters/next/templates/init/lib/actions/profile/types.ts +4 -0
  132. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +57 -0
  133. package/dist/assets/adapters/next/templates/init/lib/actions/storage/index.ts +2 -0
  134. package/dist/assets/adapters/next/templates/init/lib/actions/storage/local.ts +31 -0
  135. package/dist/assets/adapters/next/templates/init/lib/actions/storage/provider.ts +6 -0
  136. package/dist/assets/adapters/next/templates/init/lib/actions/storage/save-upload.ts +11 -0
  137. package/dist/assets/adapters/next/templates/init/lib/actions/storage/types.ts +18 -0
  138. package/dist/assets/adapters/next/templates/init/lib/actions/upload/index.ts +9 -0
  139. package/dist/assets/adapters/next/templates/init/lib/actions/upload/types.ts +17 -0
  140. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-file.ts +11 -0
  141. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-files.ts +92 -0
  142. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-image-from-url.ts +22 -0
  143. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +133 -0
  144. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +55 -0
  145. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +24 -0
  146. package/dist/assets/adapters/next/templates/init/lib/actions/users/get-users.ts +49 -0
  147. package/dist/assets/adapters/next/templates/init/lib/actions/users/index.ts +12 -0
  148. package/dist/assets/adapters/next/templates/init/lib/actions/users/types.ts +43 -0
  149. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +28 -0
  150. package/dist/assets/adapters/next/templates/init/lib/db/client.ts +13 -0
  151. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +160 -0
  152. package/dist/assets/adapters/next/templates/init/lib/db/schema.ts +1 -0
  153. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/index.ts +19 -0
  154. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.local.ts +2 -0
  155. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +9 -0
  156. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/registry.ts +55 -0
  157. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +51 -0
  158. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +39 -0
  159. package/dist/assets/adapters/next/templates/init/pages/account-layout.tsx +11 -0
  160. package/dist/assets/adapters/next/templates/init/pages/account-shell-rsc.tsx +30 -0
  161. package/dist/assets/adapters/next/templates/init/pages/admin-layout.tsx +24 -0
  162. package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +6 -0
  163. package/dist/assets/adapters/next/templates/init/pages/authenticated-layout.tsx +18 -0
  164. package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +121 -0
  165. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +124 -0
  166. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +24 -0
  167. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page.tsx +21 -0
  168. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +131 -0
  169. package/dist/assets/adapters/next/templates/init/pages/login-page-rsc.tsx +14 -0
  170. package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +26 -0
  171. package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +21 -0
  172. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +273 -0
  173. package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +7 -0
  174. package/dist/assets/adapters/next/templates/init/pages/media/media-page.tsx +11 -0
  175. package/dist/assets/adapters/next/templates/init/pages/minimal-account-shell.tsx +25 -0
  176. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +281 -0
  177. package/dist/assets/adapters/next/templates/init/pages/profile/profile-page.tsx +31 -0
  178. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +161 -0
  179. package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +26 -0
  180. package/dist/assets/adapters/next/templates/init/pages/reset-password-page.tsx +21 -0
  181. package/dist/assets/adapters/next/templates/init/pages/users/columns.tsx +170 -0
  182. package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +221 -0
  183. package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +172 -0
  184. package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +91 -0
  185. package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +25 -0
  186. package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +7 -0
  187. package/dist/assets/adapters/next/templates/init/pages/users/users-page.tsx +11 -0
  188. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +221 -0
  189. package/dist/assets/adapters/next/templates/init/types/auth.ts +71 -0
  190. package/dist/assets/adapters/next/templates/init/types/index.ts +108 -0
  191. package/dist/assets/adapters/next/templates/init/types/navigation.ts +11 -0
  192. package/dist/assets/adapters/next/templates/init/types/table-meta.ts +14 -0
  193. package/dist/assets/adapters/next/templates/init/utils/auth/roles.ts +17 -0
  194. package/dist/assets/adapters/next/templates/init/utils/date/date.ts +90 -0
  195. package/dist/assets/adapters/next/templates/init/utils/dev-mode/code-block-height.ts +9 -0
  196. package/dist/assets/adapters/next/templates/init/utils/editor/content-editor-rich-extensions.ts +824 -0
  197. package/dist/assets/adapters/next/templates/init/utils/editor/content-editor.ts +316 -0
  198. package/dist/assets/adapters/next/templates/init/utils/editor/editor-view.ts +19 -0
  199. package/dist/assets/adapters/next/templates/init/utils/editor/markdown.ts +542 -0
  200. package/dist/assets/adapters/next/templates/init/utils/editor/node-attrs.ts +25 -0
  201. package/dist/assets/adapters/next/templates/init/utils/editor/slash-commands.ts +148 -0
  202. package/dist/assets/adapters/next/templates/init/utils/editor/source-media.ts +11 -0
  203. package/dist/assets/adapters/next/templates/init/utils/editor/table-add-controls.ts +91 -0
  204. package/dist/assets/adapters/next/templates/init/utils/editor/table-bubble.ts +172 -0
  205. package/dist/assets/adapters/next/templates/init/utils/editor/table-input.ts +5 -0
  206. package/dist/assets/adapters/next/templates/init/utils/editor/task-item.ts +19 -0
  207. package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +991 -0
  208. package/dist/assets/adapters/next/templates/init/utils/email/form-delivery.ts +104 -0
  209. package/dist/assets/adapters/next/templates/init/utils/media/fallback.ts +37 -0
  210. package/dist/assets/adapters/next/templates/init/utils/media/media.ts +91 -0
  211. package/dist/assets/adapters/next/templates/init/utils/media/query.ts +96 -0
  212. package/dist/assets/adapters/next/templates/init/utils/navigation/order.ts +6 -0
  213. package/dist/assets/adapters/next/templates/init/utils/navigation/sidebar.ts +26 -0
  214. package/dist/assets/adapters/next/templates/init/utils/page/boundary.ts +32 -0
  215. package/dist/assets/adapters/next/templates/init/utils/seo/seo.ts +90 -0
  216. package/dist/assets/adapters/next/templates/init/utils/shared/cn.ts +6 -0
  217. package/dist/assets/adapters/next/templates/init/utils/storage/local.ts +9 -0
  218. package/dist/assets/adapters/next/templates/init/utils/table/table.ts +10 -0
  219. package/dist/assets/adapters/next/templates/init/utils/text/text.ts +4 -0
  220. package/dist/assets/adapters/next/templates/init/utils/theme/system.ts +6 -0
  221. package/dist/assets/adapters/next/templates/init/utils/upload/remote.ts +55 -0
  222. package/dist/assets/adapters/next/templates/init/utils/upload/upload.ts +26 -0
  223. package/dist/assets/adapters/next/templates/init/utils/user/user.ts +11 -0
  224. package/dist/assets/adapters/next/templates/init/utils/validation/validation.ts +114 -0
  225. package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +28 -0
  226. package/dist/assets/shared-assets/react-admin/custom/content-editor/editor-toolbar.tsx +25 -0
  227. package/dist/assets/shared-assets/react-admin/custom/content-editor/horizontal-rule-button.tsx +22 -0
  228. package/dist/assets/shared-assets/react-admin/custom/content-editor/index.tsx +142 -0
  229. package/dist/assets/shared-assets/react-admin/custom/content-editor/main-toolbar-content.tsx +118 -0
  230. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +80 -0
  231. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-button.tsx +22 -0
  232. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-editor-controls.tsx +117 -0
  233. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-popover-button.tsx +59 -0
  234. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-popover.tsx +6 -0
  235. package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +31 -0
  236. package/dist/assets/shared-assets/react-admin/custom/content-editor/mobile-toolbar-content.tsx +56 -0
  237. package/dist/assets/shared-assets/react-admin/custom/content-editor/mode-toggle-button.tsx +29 -0
  238. package/dist/assets/shared-assets/react-admin/custom/content-editor/remove-table-part-icon.tsx +17 -0
  239. package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +105 -0
  240. package/dist/assets/shared-assets/react-admin/custom/content-editor/slash-command-menu.tsx +65 -0
  241. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode-dropdown-button.tsx +52 -0
  242. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +360 -0
  243. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +46 -0
  244. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +290 -0
  245. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +116 -0
  246. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-extension/node-background-extension.ts +138 -0
  247. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +10 -0
  248. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/index.tsx +1 -0
  249. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/media-gallery-placeholder-node-extension.ts +117 -0
  250. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/media-gallery-placeholder-node/media-gallery-placeholder-node.tsx +63 -0
  251. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/index.tsx +1 -0
  252. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node-extension.ts +11 -0
  253. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node.tsx +168 -0
  254. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/task-item-node/task-item-node-extension.tsx +142 -0
  255. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/blockquote-button.tsx +114 -0
  256. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/index.tsx +1 -0
  257. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/code-block-button.tsx +112 -0
  258. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/index.tsx +1 -0
  259. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/color-highlight-button.tsx +185 -0
  260. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/index.tsx +1 -0
  261. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-button.tsx +40 -0
  262. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-content.tsx +130 -0
  263. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +98 -0
  264. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/highlight-color-button.tsx +75 -0
  265. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/highlight-colors.ts +24 -0
  266. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/index.tsx +1 -0
  267. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/source-color-highlight-popover.tsx +65 -0
  268. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/control-options.ts +27 -0
  269. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu-item.tsx +35 -0
  270. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +119 -0
  271. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/index.tsx +1 -0
  272. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/index.tsx +1 -0
  273. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-button.tsx +39 -0
  274. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-content.tsx +13 -0
  275. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +90 -0
  276. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-main.tsx +96 -0
  277. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +121 -0
  278. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/index.tsx +1 -0
  279. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu-item.tsx +44 -0
  280. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +115 -0
  281. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/index.tsx +1 -0
  282. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/mark-button.tsx +117 -0
  283. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/index.tsx +1 -0
  284. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/undo-redo-button.tsx +115 -0
  285. package/dist/assets/shared-assets/react-admin/custom/date-picker.tsx +70 -0
  286. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +216 -0
  287. package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +111 -0
  288. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +124 -0
  289. package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +39 -0
  290. package/dist/assets/shared-assets/react-admin/custom/icon-picker.tsx +377 -0
  291. package/dist/assets/shared-assets/react-admin/custom/icons-column-skeleton.tsx +16 -0
  292. package/dist/assets/shared-assets/react-admin/custom/icons-data.ts +8 -0
  293. package/dist/assets/shared-assets/react-admin/custom/logo.tsx +115 -0
  294. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +182 -0
  295. package/dist/assets/shared-assets/react-admin/custom/page-skeleton.tsx +11 -0
  296. package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +34 -0
  297. package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +25 -0
  298. package/dist/assets/shared-assets/react-admin/custom/progressive-blur.tsx +62 -0
  299. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +68 -0
  300. package/dist/assets/shared-assets/react-admin/custom/upload-dropzone.tsx +107 -0
  301. package/dist/assets/shared-assets/react-admin/dependencies.ts +73 -0
  302. package/dist/assets/shared-assets/react-admin/schema.json +1670 -0
  303. package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +86 -0
  304. package/dist/assets/shared-assets/react-admin/ui/alert-dialog.tsx +178 -0
  305. package/dist/assets/shared-assets/react-admin/ui/alert.tsx +72 -0
  306. package/dist/assets/shared-assets/react-admin/ui/aspect-ratio.tsx +9 -0
  307. package/dist/assets/shared-assets/react-admin/ui/avatar.tsx +95 -0
  308. package/dist/assets/shared-assets/react-admin/ui/badge.tsx +48 -0
  309. package/dist/assets/shared-assets/react-admin/ui/breadcrumb.tsx +99 -0
  310. package/dist/assets/shared-assets/react-admin/ui/button-group.tsx +76 -0
  311. package/dist/assets/shared-assets/react-admin/ui/button.tsx +66 -0
  312. package/dist/assets/shared-assets/react-admin/ui/calendar.tsx +184 -0
  313. package/dist/assets/shared-assets/react-admin/ui/card.tsx +94 -0
  314. package/dist/assets/shared-assets/react-admin/ui/carousel.tsx +239 -0
  315. package/dist/assets/shared-assets/react-admin/ui/chart.tsx +336 -0
  316. package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +28 -0
  317. package/dist/assets/shared-assets/react-admin/ui/collapsible.tsx +21 -0
  318. package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +272 -0
  319. package/dist/assets/shared-assets/react-admin/ui/command.tsx +180 -0
  320. package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +243 -0
  321. package/dist/assets/shared-assets/react-admin/ui/dialog.tsx +141 -0
  322. package/dist/assets/shared-assets/react-admin/ui/direction.tsx +20 -0
  323. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +119 -0
  324. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +253 -0
  325. package/dist/assets/shared-assets/react-admin/ui/empty.tsx +93 -0
  326. package/dist/assets/shared-assets/react-admin/ui/field.tsx +234 -0
  327. package/dist/assets/shared-assets/react-admin/ui/form.tsx +172 -0
  328. package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +37 -0
  329. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +134 -0
  330. package/dist/assets/shared-assets/react-admin/ui/input-otp.tsx +85 -0
  331. package/dist/assets/shared-assets/react-admin/ui/input.tsx +18 -0
  332. package/dist/assets/shared-assets/react-admin/ui/item.tsx +180 -0
  333. package/dist/assets/shared-assets/react-admin/ui/kbd.tsx +26 -0
  334. package/dist/assets/shared-assets/react-admin/ui/label.tsx +20 -0
  335. package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +259 -0
  336. package/dist/assets/shared-assets/react-admin/ui/native-select.tsx +54 -0
  337. package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +159 -0
  338. package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +111 -0
  339. package/dist/assets/shared-assets/react-admin/ui/popover.tsx +75 -0
  340. package/dist/assets/shared-assets/react-admin/ui/progress.tsx +30 -0
  341. package/dist/assets/shared-assets/react-admin/ui/radio-group.tsx +43 -0
  342. package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +41 -0
  343. package/dist/assets/shared-assets/react-admin/ui/scroll-area.tsx +54 -0
  344. package/dist/assets/shared-assets/react-admin/ui/select.tsx +183 -0
  345. package/dist/assets/shared-assets/react-admin/ui/separator.tsx +27 -0
  346. package/dist/assets/shared-assets/react-admin/ui/sheet.tsx +129 -0
  347. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +688 -0
  348. package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +13 -0
  349. package/dist/assets/shared-assets/react-admin/ui/slider.tsx +53 -0
  350. package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +45 -0
  351. package/dist/assets/shared-assets/react-admin/ui/spinner.tsx +15 -0
  352. package/dist/assets/shared-assets/react-admin/ui/switch.tsx +32 -0
  353. package/dist/assets/shared-assets/react-admin/ui/table.tsx +101 -0
  354. package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +79 -0
  355. package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +17 -0
  356. package/dist/assets/shared-assets/react-admin/ui/toggle-group.tsx +85 -0
  357. package/dist/assets/shared-assets/react-admin/ui/toggle.tsx +45 -0
  358. package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +51 -0
  359. package/dist/chunk-MUZQCVQA.js +306 -0
  360. package/dist/chunk-MUZQCVQA.js.map +1 -0
  361. package/dist/cli.d.ts +2 -0
  362. package/dist/cli.js +23437 -0
  363. package/dist/cli.js.map +1 -0
  364. package/dist/index.d.ts +90 -0
  365. package/dist/index.js +8 -0
  366. package/dist/index.js.map +1 -0
  367. package/dist/template-reader-YKWE2C7O.js +13 -0
  368. package/dist/template-reader-YKWE2C7O.js.map +1 -0
  369. package/package.json +74 -0
@@ -0,0 +1,321 @@
1
+ 'use client'
2
+
3
+ import { uploadMediaFromUrl } from '@admin/actions/upload'
4
+ import type { UploadFileResult, UploadProgress } from '@admin/types'
5
+ import { parseAcceptTypes } from '@admin/utils/upload/upload'
6
+ import { type FileValidationConfig, validateFiles } from '@admin/utils/validation/validation'
7
+ import { type UseMutationResult, useMutation } from '@tanstack/react-query'
8
+ import * as React from 'react'
9
+
10
+ export interface UseUploadOptions {
11
+ /** File validation configuration */
12
+ validationConfig?: FileValidationConfig
13
+ /** Accept string (e.g., "image/*") */
14
+ accept?: string
15
+ /** Optional maximum file size in MB */
16
+ maxSizeInMB?: number
17
+ /** Callback fired when upload progress changes */
18
+ onProgress?: (progress: UploadProgress[]) => void
19
+ /** Callback fired when upload succeeds */
20
+ onSuccess?: (result: UploadFileResult) => void
21
+ /** Callback fired when upload fails */
22
+ onError?: (error: Error) => void
23
+ /** Optional prefix for file paths in R2 */
24
+ prefix?: string
25
+ /** Whether completion callbacks should run for this upload surface */
26
+ isActive?: boolean
27
+ }
28
+
29
+ export interface UploadMutationVariables {
30
+ files: File[]
31
+ prefix?: string
32
+ activityId?: number
33
+ }
34
+
35
+ export interface UploadUrlMutationVariables {
36
+ url: string
37
+ prefix?: string
38
+ accept?: string
39
+ activityId?: number
40
+ }
41
+
42
+ export interface UseUploadReturn {
43
+ mutation: UseMutationResult<UploadFileResult, Error, UploadMutationVariables, unknown>
44
+ urlMutation: UseMutationResult<UploadFileResult, Error, UploadUrlMutationVariables, unknown>
45
+ progress: UploadProgress[]
46
+ isUploading: boolean
47
+ upload: (files: File[], prefix?: string) => void
48
+ uploadFromUrl: (
49
+ url: string,
50
+ prefix?: string,
51
+ acceptOverride?: string
52
+ ) => Promise<UploadFileResult>
53
+ validate: (files: File[]) => { valid: boolean; errors: string[] }
54
+ }
55
+
56
+ export function useUpload(options: UseUploadOptions = {}): UseUploadReturn {
57
+ const {
58
+ validationConfig: providedConfig,
59
+ accept,
60
+ maxSizeInMB,
61
+ onProgress,
62
+ onSuccess,
63
+ onError,
64
+ prefix: defaultPrefix,
65
+ isActive = true
66
+ } = options
67
+
68
+ const validationConfig = React.useMemo<FileValidationConfig>(() => {
69
+ const config: FileValidationConfig = { ...providedConfig }
70
+
71
+ if (accept) {
72
+ const parsedTypes = parseAcceptTypes(accept)
73
+ if (config.allowedTypes && config.allowedTypes.length > 0) {
74
+ config.allowedTypes = [...config.allowedTypes, ...parsedTypes].filter(
75
+ (v, i, a) => a.indexOf(v) === i
76
+ )
77
+ } else {
78
+ config.allowedTypes = parsedTypes
79
+ }
80
+ }
81
+
82
+ if (maxSizeInMB !== undefined) {
83
+ config.maxSizeInBytes = maxSizeInMB * 1024 * 1024
84
+ }
85
+
86
+ return config
87
+ }, [providedConfig, accept, maxSizeInMB])
88
+
89
+ const [progress, setProgress] = React.useState<UploadProgress[]>([])
90
+ const [_isPending, startTransition] = React.useTransition()
91
+ const mountedRef = React.useRef(true)
92
+ const activeRef = React.useRef(isActive)
93
+ const activeActivityIdRef = React.useRef(0)
94
+ const previousIsActiveRef = React.useRef(isActive)
95
+ const uploadAbortControllerRef = React.useRef<AbortController | null>(null)
96
+ const progressIntervalRef = React.useRef<ReturnType<typeof setInterval> | null>(null)
97
+ const progressResetTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
98
+ if (previousIsActiveRef.current !== isActive) {
99
+ previousIsActiveRef.current = isActive
100
+ activeActivityIdRef.current += 1
101
+ }
102
+ activeRef.current = isActive
103
+
104
+ const clearProgressResetTimeout = React.useCallback(() => {
105
+ if (progressResetTimeoutRef.current) {
106
+ clearTimeout(progressResetTimeoutRef.current)
107
+ progressResetTimeoutRef.current = null
108
+ }
109
+ }, [])
110
+
111
+ const clearProgressInterval = React.useCallback(() => {
112
+ if (progressIntervalRef.current) {
113
+ clearInterval(progressIntervalRef.current)
114
+ progressIntervalRef.current = null
115
+ }
116
+ }, [])
117
+
118
+ const abortActiveUpload = React.useCallback(() => {
119
+ if (uploadAbortControllerRef.current) {
120
+ uploadAbortControllerRef.current.abort()
121
+ uploadAbortControllerRef.current = null
122
+ }
123
+ }, [])
124
+
125
+ const cleanupActiveUpload = React.useCallback(() => {
126
+ clearProgressResetTimeout()
127
+ clearProgressInterval()
128
+ abortActiveUpload()
129
+ }, [abortActiveUpload, clearProgressInterval, clearProgressResetTimeout])
130
+
131
+ const isCurrentActiveMutation = React.useCallback((activityId?: number) => {
132
+ if (!mountedRef.current || !activeRef.current) return false
133
+ return activityId === undefined || activityId === activeActivityIdRef.current
134
+ }, [])
135
+
136
+ React.useEffect(() => {
137
+ mountedRef.current = true
138
+
139
+ return () => {
140
+ mountedRef.current = false
141
+ cleanupActiveUpload()
142
+ }
143
+ }, [cleanupActiveUpload])
144
+
145
+ const mutation = useMutation<UploadFileResult, Error, UploadMutationVariables>({
146
+ mutationFn: async ({ files, prefix = defaultPrefix }) => {
147
+ cleanupActiveUpload()
148
+ const abortController = new AbortController()
149
+ uploadAbortControllerRef.current = abortController
150
+
151
+ const initialProgress: UploadProgress[] = files.map((file) => ({
152
+ filename: file.name,
153
+ progress: 0,
154
+ loaded: 0,
155
+ total: file.size
156
+ }))
157
+ setProgress(initialProgress)
158
+ onProgress?.(initialProgress)
159
+
160
+ const formData = new FormData()
161
+ if (prefix) {
162
+ formData.append('prefix', prefix)
163
+ }
164
+
165
+ files.forEach((file, index) => {
166
+ formData.append(`file${index}`, file)
167
+ })
168
+
169
+ // Simulate progress since server doesn't support streaming
170
+ const progressInterval = setInterval(() => {
171
+ if (!mountedRef.current) return
172
+
173
+ setProgress((prev) =>
174
+ prev.map((p) => {
175
+ const newProgress = Math.min(p.progress + 10, 90)
176
+ return {
177
+ ...p,
178
+ progress: newProgress,
179
+ loaded: Math.floor((p.total * newProgress) / 100)
180
+ }
181
+ })
182
+ )
183
+ }, 200)
184
+ progressIntervalRef.current = progressInterval
185
+
186
+ try {
187
+ if (validationConfig?.maxSizeInBytes) {
188
+ formData.append('maxSizeInBytes', validationConfig.maxSizeInBytes.toString())
189
+ }
190
+ if (validationConfig?.maxFiles) {
191
+ formData.append('maxFiles', validationConfig.maxFiles.toString())
192
+ }
193
+ if (validationConfig?.allowedTypes?.length) {
194
+ formData.append('allowedTypes', validationConfig.allowedTypes.join(','))
195
+ }
196
+
197
+ const response = await fetch('/api/admin/upload', {
198
+ method: 'POST',
199
+ body: formData,
200
+ signal: abortController.signal
201
+ })
202
+
203
+ const result = (await response.json()) as UploadFileResult
204
+
205
+ if (result.success && mountedRef.current) {
206
+ const completeProgress: UploadProgress[] = files.map((file) => ({
207
+ filename: file.name,
208
+ progress: 100,
209
+ loaded: file.size,
210
+ total: file.size
211
+ }))
212
+ setProgress(completeProgress)
213
+ onProgress?.(completeProgress)
214
+ }
215
+
216
+ return result
217
+ } finally {
218
+ clearInterval(progressInterval)
219
+ if (progressIntervalRef.current === progressInterval) {
220
+ progressIntervalRef.current = null
221
+ }
222
+ if (uploadAbortControllerRef.current === abortController) {
223
+ uploadAbortControllerRef.current = null
224
+ }
225
+ }
226
+ },
227
+ onSuccess: (result, variables) => {
228
+ if (!mountedRef.current) return
229
+
230
+ if (!isCurrentActiveMutation(variables.activityId)) {
231
+ clearProgressResetTimeout()
232
+ startTransition(() => {
233
+ setProgress([])
234
+ })
235
+ return
236
+ }
237
+
238
+ onSuccess?.(result)
239
+ clearProgressResetTimeout()
240
+ progressResetTimeoutRef.current = setTimeout(() => {
241
+ progressResetTimeoutRef.current = null
242
+ startTransition(() => {
243
+ setProgress([])
244
+ })
245
+ }, 2000)
246
+ },
247
+ onError: (error, variables) => {
248
+ if (!mountedRef.current || error.name === 'AbortError') return
249
+
250
+ clearProgressResetTimeout()
251
+ setProgress([])
252
+ if (!isCurrentActiveMutation(variables.activityId)) return
253
+
254
+ onError?.(error)
255
+ }
256
+ })
257
+
258
+ const urlMutation = useMutation<UploadFileResult, Error, UploadUrlMutationVariables>({
259
+ mutationFn: async ({ url, prefix = defaultPrefix, accept: explicitAccept = accept }) =>
260
+ uploadMediaFromUrl(url, {
261
+ prefix,
262
+ accept: explicitAccept,
263
+ maxSizeInBytes: validationConfig.maxSizeInBytes
264
+ }),
265
+ onSuccess: (result, variables) => {
266
+ if (!isCurrentActiveMutation(variables.activityId)) return
267
+
268
+ onSuccess?.(result)
269
+ },
270
+ onError: (error, variables) => {
271
+ if (!isCurrentActiveMutation(variables.activityId)) return
272
+
273
+ onError?.(error)
274
+ }
275
+ })
276
+
277
+ const upload = React.useCallback(
278
+ (files: File[], prefix?: string) => {
279
+ mutation.mutate({
280
+ files,
281
+ prefix,
282
+ activityId: activeActivityIdRef.current
283
+ })
284
+ },
285
+ [mutation]
286
+ )
287
+
288
+ const uploadFromUrl = React.useCallback(
289
+ (url: string, prefix?: string, acceptOverride?: string) =>
290
+ urlMutation.mutateAsync({
291
+ url,
292
+ prefix,
293
+ accept: acceptOverride,
294
+ activityId: activeActivityIdRef.current
295
+ }),
296
+ [urlMutation]
297
+ )
298
+
299
+ const validate = React.useCallback(
300
+ (files: File[]) => {
301
+ const result = validateFiles(files, validationConfig)
302
+ return {
303
+ valid: result.valid,
304
+ errors: result.errors.map((e) => (e.filename ? `${e.filename}: ${e.error}` : e.error))
305
+ }
306
+ },
307
+ [validationConfig]
308
+ )
309
+
310
+ const isUploading = progress.some((p) => p.progress < 100)
311
+
312
+ return {
313
+ mutation,
314
+ urlMutation,
315
+ progress,
316
+ isUploading,
317
+ upload,
318
+ uploadFromUrl,
319
+ validate
320
+ }
321
+ }
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+
3
+ import { getUsers } from '@admin/actions/users'
4
+ import type { UsersPage } from '@admin/types/auth'
5
+ import { useQuery } from '@tanstack/react-query'
6
+
7
+ export function useUsers() {
8
+ return useQuery<UsersPage>({
9
+ queryKey: ['users'],
10
+ queryFn: () => getUsers()
11
+ })
12
+ }
@@ -0,0 +1,58 @@
1
+ import { sendPasswordResetEmail } from '@admin/actions/email'
2
+ import db from '@admin/db'
3
+ import * as schema from '@admin/db/schema'
4
+ import { betterAuth } from 'better-auth'
5
+ import { drizzleAdapter } from 'better-auth/adapters/drizzle'
6
+ import { toNextJsHandler } from 'better-auth/next-js'
7
+
8
+ export { toNextJsHandler }
9
+
10
+ export const auth = betterAuth({
11
+ secret: process.env.BETTERSTART_AUTH_SECRET,
12
+ baseURL: process.env.BETTERSTART_AUTH_URL,
13
+ basePath: process.env.BETTERSTART_AUTH_BASE_PATH || '/api/admin/auth',
14
+ database: drizzleAdapter(db, {
15
+ provider: 'pg',
16
+ schema: {
17
+ user: schema.user,
18
+ session: schema.session,
19
+ account: schema.account,
20
+ verification: schema.verification
21
+ }
22
+ }),
23
+ emailAndPassword: {
24
+ enabled: true,
25
+ minPasswordLength: 8,
26
+ revokeSessionsOnPasswordReset: true,
27
+ sendResetPassword: async ({
28
+ user,
29
+ url
30
+ }: {
31
+ user: { email: string; name: string }
32
+ url: string
33
+ }) => {
34
+ await sendPasswordResetEmail({ user, url })
35
+ }
36
+ },
37
+ session: {
38
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
39
+ updateAge: 60 * 60 * 24, // 1 day
40
+ cookieCache: {
41
+ enabled: true,
42
+ maxAge: 15 * 60 // 15 minutes — avoids DB round-trip for session validation
43
+ }
44
+ },
45
+ user: {
46
+ additionalFields: {
47
+ role: {
48
+ type: 'string',
49
+ required: false,
50
+ defaultValue: 'member',
51
+ input: false
52
+ }
53
+ }
54
+ }
55
+ })
56
+
57
+ export type Session = typeof auth.$Infer.Session
58
+ export type User = typeof auth.$Infer.Session.user
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+
3
+ import { createAuthClient } from 'better-auth/react'
4
+
5
+ export const authClient = createAuthClient({
6
+ baseURL:
7
+ process.env.NEXT_PUBLIC_BETTERSTART_AUTH_URL ||
8
+ (typeof window !== 'undefined' ? window.location.origin : ''),
9
+ basePath: '/api/admin/auth'
10
+ })
11
+
12
+ export const { signIn, signUp, signOut, useSession } = authClient
@@ -0,0 +1,46 @@
1
+ import { UserRole } from '@admin/types/auth'
2
+ import { getAdminPostLoginPath, isUserRole } from '@admin/utils/auth/roles'
3
+ import { headers } from 'next/headers'
4
+ import { redirect } from 'next/navigation'
5
+ import { cache } from 'react'
6
+ import { auth, type User } from './auth'
7
+
8
+ export { UserRole } from '@admin/types/auth'
9
+
10
+ interface GetSessionOptions {
11
+ disableCookieCache?: boolean
12
+ }
13
+
14
+ /**
15
+ * Get the current session from Better Auth.
16
+ * Wrapped with React.cache to deduplicate within a single request.
17
+ */
18
+ export const getSession = cache(async (options: GetSessionOptions = {}) => {
19
+ const session = await auth.api.getSession({
20
+ query: options.disableCookieCache ? { disableCookieCache: true } : undefined,
21
+ headers: await headers()
22
+ })
23
+ return session
24
+ })
25
+
26
+ /**
27
+ * Require the user to have one of the specified roles.
28
+ * Redirects to /admin/login if unauthenticated and to /admin/profile if authenticated
29
+ * without the required Admin role.
30
+ */
31
+ export async function requireRole(allowedRoles: readonly UserRole[]): Promise<User> {
32
+ const session = await getSession({ disableCookieCache: true })
33
+
34
+ if (!session?.user) {
35
+ redirect('/admin/login')
36
+ }
37
+
38
+ const user = session.user
39
+ const role = isUserRole(user.role) ? user.role : UserRole.MEMBER
40
+
41
+ if (!allowedRoles.includes(role)) {
42
+ redirect(getAdminPostLoginPath(role))
43
+ }
44
+
45
+ return user
46
+ }
@@ -0,0 +1,72 @@
1
+ import { sendEmail } from '@admin/actions/email'
2
+ import type { SendEmailResult } from '@admin/actions/email/types'
3
+ import {
4
+ type FormEmailDeliveryInput,
5
+ isValidEmail,
6
+ normalizeEmailList,
7
+ renderNotificationEmail,
8
+ renderThankYouEmail
9
+ } from '@admin/utils/email/form-delivery'
10
+
11
+ export type { FormEmailDeliveryInput } from '@admin/utils/email/form-delivery'
12
+
13
+ interface QueuedFormEmail {
14
+ label: string
15
+ to: string | string[]
16
+ subject: string
17
+ html: string
18
+ }
19
+
20
+ async function sendQueuedEmail(email: QueuedFormEmail): Promise<{
21
+ label: string
22
+ result: SendEmailResult
23
+ }> {
24
+ const result = await sendEmail({
25
+ to: email.to,
26
+ subject: email.subject,
27
+ html: email.html
28
+ })
29
+
30
+ return {
31
+ label: email.label,
32
+ result
33
+ }
34
+ }
35
+
36
+ export async function sendFormEmails(input: FormEmailDeliveryInput): Promise<void> {
37
+ const notificationEmails = normalizeEmailList(input.notificationEmails)
38
+ const submitterEmail = input.submitterEmail?.trim()
39
+ const queuedEmails: QueuedFormEmail[] = []
40
+
41
+ if (notificationEmails.length > 0) {
42
+ queuedEmails.push({
43
+ label: 'form notification',
44
+ to: notificationEmails,
45
+ subject: `New ${input.formLabel} submission`,
46
+ html: renderNotificationEmail(input)
47
+ })
48
+ }
49
+
50
+ if (submitterEmail && isValidEmail(submitterEmail)) {
51
+ queuedEmails.push({
52
+ label: 'form submitter confirmation',
53
+ to: submitterEmail,
54
+ subject: `We received your ${input.formLabel} submission`,
55
+ html: renderThankYouEmail(input)
56
+ })
57
+ }
58
+
59
+ if (queuedEmails.length === 0) {
60
+ return
61
+ }
62
+
63
+ const results = await Promise.all(queuedEmails.map(sendQueuedEmail))
64
+ const failures = results.filter(({ result }) => !result.delivered)
65
+
66
+ if (failures.length > 0) {
67
+ console.error(
68
+ `Failed to send ${failures.length} ${input.formName} form email(s):`,
69
+ failures.map(({ label, result }) => `${label}: ${result.reason ?? 'unknown error'}`)
70
+ )
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ export { isEmailDeliveryConfigured } from './is-email-delivery-configured'
2
+ export { getEmailProvider } from './provider'
3
+ export { sendEmail } from './send-email'
4
+ export { sendPasswordResetEmail } from './send-password-reset-email'
@@ -0,0 +1,5 @@
1
+ import { getEmailProvider } from './provider'
2
+
3
+ export function isEmailDeliveryConfigured(): boolean {
4
+ return getEmailProvider().isConfigured()
5
+ }
@@ -0,0 +1,24 @@
1
+ import type {
2
+ AdminEmailProvider,
3
+ PasswordResetEmailInput,
4
+ SendEmailInput,
5
+ SendEmailResult
6
+ } from '@admin/actions/email/types'
7
+
8
+ const NOT_CONFIGURED_REASON = 'Email delivery is not configured.'
9
+
10
+ async function notDelivered(
11
+ _input: SendEmailInput | PasswordResetEmailInput
12
+ ): Promise<SendEmailResult> {
13
+ return {
14
+ delivered: false,
15
+ reason: NOT_CONFIGURED_REASON
16
+ }
17
+ }
18
+
19
+ export const noneEmailProvider: AdminEmailProvider = {
20
+ id: 'none',
21
+ isConfigured: () => false,
22
+ sendEmail: notDelivered,
23
+ sendPasswordResetEmail: notDelivered
24
+ }
@@ -0,0 +1,6 @@
1
+ import { noneEmailProvider } from '@admin/actions/email/none'
2
+ import type { AdminEmailProvider } from '@admin/actions/email/types'
3
+
4
+ export function getEmailProvider(): AdminEmailProvider {
5
+ return noneEmailProvider
6
+ }
@@ -0,0 +1,6 @@
1
+ import type { SendEmailInput, SendEmailResult } from '@admin/actions/email/types'
2
+ import { getEmailProvider } from './provider'
3
+
4
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
5
+ return getEmailProvider().sendEmail(input)
6
+ }
@@ -0,0 +1,10 @@
1
+ import type { PasswordResetEmailInput } from '@admin/actions/email/types'
2
+ import { getEmailProvider } from './provider'
3
+
4
+ export async function sendPasswordResetEmail(input: PasswordResetEmailInput): Promise<void> {
5
+ const result = await getEmailProvider().sendPasswordResetEmail(input)
6
+
7
+ if (!result.delivered) {
8
+ throw new Error(result.reason ?? 'Email delivery is not configured.')
9
+ }
10
+ }
@@ -0,0 +1,25 @@
1
+ export interface SendEmailInput {
2
+ to: string | string[]
3
+ subject: string
4
+ html: string
5
+ }
6
+
7
+ export interface SendEmailResult {
8
+ delivered: boolean
9
+ reason?: string
10
+ }
11
+
12
+ export interface PasswordResetEmailInput {
13
+ user: {
14
+ email: string
15
+ name: string
16
+ }
17
+ url: string
18
+ }
19
+
20
+ export interface AdminEmailProvider {
21
+ id: string
22
+ isConfigured: () => boolean
23
+ sendEmail: (input: SendEmailInput) => Promise<SendEmailResult>
24
+ sendPasswordResetEmail: (input: PasswordResetEmailInput) => Promise<SendEmailResult>
25
+ }
@@ -0,0 +1,48 @@
1
+ 'use server'
2
+
3
+ import db from '@admin/db'
4
+ import { entityVersions, user } from '@admin/db/schema'
5
+ import { and, desc, eq } from 'drizzle-orm'
6
+ import { alias } from 'drizzle-orm/pg-core'
7
+ import { cacheLife, cacheTag } from 'next/cache'
8
+ import type { EntityVersionWithAuthor } from './types'
9
+ import { entityVersionsCacheTags } from './types'
10
+
11
+ const createdByUser = alias(user, 'entity_version_created_by_user')
12
+
13
+ export async function getEntityVersions(
14
+ entityType: string,
15
+ entityId: string
16
+ ): Promise<EntityVersionWithAuthor[]> {
17
+ 'use cache'
18
+ cacheLife('max')
19
+ cacheTag(entityVersionsCacheTags.for(entityType, entityId))
20
+
21
+ try {
22
+ const rows = await db
23
+ .select({
24
+ id: entityVersions.id,
25
+ entityType: entityVersions.entityType,
26
+ entityId: entityVersions.entityId,
27
+ version: entityVersions.version,
28
+ data: entityVersions.data,
29
+ createdAt: entityVersions.createdAt,
30
+ createdBy: entityVersions.createdBy,
31
+ createdByUser: {
32
+ id: createdByUser.id,
33
+ name: createdByUser.name,
34
+ email: createdByUser.email,
35
+ image: createdByUser.image
36
+ }
37
+ })
38
+ .from(entityVersions)
39
+ .leftJoin(createdByUser, eq(entityVersions.createdBy, createdByUser.id))
40
+ .where(and(eq(entityVersions.entityType, entityType), eq(entityVersions.entityId, entityId)))
41
+ .orderBy(desc(entityVersions.version), desc(entityVersions.createdAt))
42
+
43
+ return rows as EntityVersionWithAuthor[]
44
+ } catch (error) {
45
+ console.error('Error fetching entity versions:', error)
46
+ return []
47
+ }
48
+ }
@@ -0,0 +1,10 @@
1
+ export { getEntityVersions } from './get-entity-versions'
2
+ export { createEntityVersion } from './internal-create-entity-version'
3
+ export { deleteEntityVersions } from './internal-delete-entity-versions'
4
+ export type {
5
+ EntityVersion,
6
+ EntityVersionAuthor,
7
+ EntityVersionData,
8
+ EntityVersionWithAuthor
9
+ } from './types'
10
+ export { entityVersionsCacheTags } from './types'