create-nextblock 0.16.1 → 0.16.3

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 (264) hide show
  1. package/docker-template/.dockerignore +1 -1
  2. package/libs/db/tsconfig.lib.json +3 -3
  3. package/libs/editor/tsconfig.lib.json +3 -3
  4. package/libs/ui/tsconfig.lib.json +3 -3
  5. package/libs/utils/tsconfig.json +3 -3
  6. package/package.json +50 -50
  7. package/project.json +19 -19
  8. package/templates/nextblock-template/.dockerignore +1 -1
  9. package/templates/nextblock-template/.swcrc +30 -30
  10. package/templates/nextblock-template/AGENTS.md +9 -9
  11. package/templates/nextblock-template/CLAUDE.md +1 -1
  12. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  13. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  14. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
  15. package/templates/nextblock-template/app/ToasterProvider.tsx +26 -26
  16. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  17. package/templates/nextblock-template/app/actions/contactSellerActions.test.ts +280 -280
  18. package/templates/nextblock-template/app/actions/contactSellerActions.ts +222 -222
  19. package/templates/nextblock-template/app/actions/email-retry.test.ts +62 -62
  20. package/templates/nextblock-template/app/actions/email.ts +241 -241
  21. package/templates/nextblock-template/app/actions/formActions.ts +245 -245
  22. package/templates/nextblock-template/app/actions/interactions.ts +489 -489
  23. package/templates/nextblock-template/app/actions/threadActions.ts +166 -166
  24. package/templates/nextblock-template/app/actions/visibilityActions.ts +210 -210
  25. package/templates/nextblock-template/app/api/ai/seo/alt-text/route.ts +221 -221
  26. package/templates/nextblock-template/app/api/ai/seo/metadata/route.ts +186 -186
  27. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
  28. package/templates/nextblock-template/app/api/checkout/route.ts +162 -162
  29. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +4 -4
  30. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +4542 -2904
  31. package/templates/nextblock-template/app/api/mcp/route.ts +415 -415
  32. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  33. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  34. package/templates/nextblock-template/app/api/view/route.ts +114 -114
  35. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  36. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  37. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  38. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  39. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  40. package/templates/nextblock-template/app/checkout/page.tsx +57 -57
  41. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +558 -558
  42. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  43. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  44. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -304
  45. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +406 -406
  46. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
  47. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  48. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  49. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +254 -254
  50. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  51. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
  52. package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +40 -0
  53. package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -0
  54. package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
  55. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
  56. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  57. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  58. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  59. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  60. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
  61. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
  62. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
  63. package/templates/nextblock-template/app/cms/layout.tsx +101 -101
  64. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  65. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  66. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  67. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -661
  68. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -404
  69. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -333
  70. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -87
  71. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -37
  72. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  73. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +649 -649
  74. package/templates/nextblock-template/app/cms/pages/page.tsx +273 -227
  75. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +2 -0
  76. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +624 -618
  77. package/templates/nextblock-template/app/cms/posts/page.tsx +251 -194
  78. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
  79. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  80. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  81. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  82. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  83. package/templates/nextblock-template/app/cms/revisions/actions.ts +332 -332
  84. package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
  85. package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
  86. package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
  87. package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
  88. package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
  89. package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
  90. package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
  91. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  92. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  93. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -227
  94. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  95. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  96. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  97. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
  98. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
  99. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
  100. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
  101. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  102. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  103. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  104. package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -514
  105. package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -529
  106. package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -57
  107. package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -448
  108. package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -93
  109. package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -46
  110. package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -47
  111. package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
  112. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  113. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  114. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  115. package/templates/nextblock-template/app/layout.tsx +671 -671
  116. package/templates/nextblock-template/app/lib/seo.ts +319 -319
  117. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  118. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  119. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -502
  120. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  121. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  122. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  123. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  124. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  125. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  126. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  127. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  128. package/templates/nextblock-template/app/providers.tsx +96 -96
  129. package/templates/nextblock-template/app/robots.ts +123 -123
  130. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -164
  131. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -57
  132. package/templates/nextblock-template/app/thread/layout.tsx +15 -15
  133. package/templates/nextblock-template/app/thread/page.tsx +98 -98
  134. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  135. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  136. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  137. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  138. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  139. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  140. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -312
  141. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  142. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  143. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -188
  144. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  145. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  146. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  147. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  148. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  149. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  150. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  151. package/templates/nextblock-template/components/Header.tsx +38 -38
  152. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  153. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  154. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -378
  155. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -426
  156. package/templates/nextblock-template/components/SiteScripts.tsx +56 -56
  157. package/templates/nextblock-template/components/StaffReplies.tsx +102 -102
  158. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  159. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  160. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  161. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  162. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  163. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  164. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  165. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -18
  166. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -20
  167. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -25
  168. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -385
  169. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  170. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -157
  171. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -34
  172. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -612
  173. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  174. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  175. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  176. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  177. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -32
  178. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  179. package/templates/nextblock-template/components/form-message.tsx +32 -32
  180. package/templates/nextblock-template/components/seo/GenerateMetaButton.tsx +137 -137
  181. package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +251 -244
  182. package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -749
  183. package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -195
  184. package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -144
  185. package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -243
  186. package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -110
  187. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  188. package/templates/nextblock-template/components/theme-icon.tsx +78 -78
  189. package/templates/nextblock-template/components/theme-switcher.tsx +85 -85
  190. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  191. package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -44
  192. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  193. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +77 -77
  194. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -372
  195. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -309
  196. package/templates/nextblock-template/docs/README.md +42 -42
  197. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12506 -12506
  198. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  199. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  200. package/templates/nextblock-template/index.d.ts +7 -7
  201. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  202. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +134 -134
  203. package/templates/nextblock-template/lib/blocks/blockColors.ts +175 -175
  204. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -761
  205. package/templates/nextblock-template/lib/blocks/inlineScriptNonce.ts +20 -20
  206. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -64
  207. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -0
  208. package/templates/nextblock-template/lib/cms/payments-reminder.ts +30 -23
  209. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -42
  210. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -162
  211. package/templates/nextblock-template/lib/config/email-settings.ts +323 -323
  212. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -57
  213. package/templates/nextblock-template/lib/cortex-ai/alt-text-request.ts +86 -86
  214. package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -60
  215. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -59
  216. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -39
  217. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -70
  218. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -93
  219. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -157
  220. package/templates/nextblock-template/lib/messages/threads.ts +579 -579
  221. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  222. package/templates/nextblock-template/lib/publishing/viewUrl.ts +26 -26
  223. package/templates/nextblock-template/lib/search/types.ts +27 -27
  224. package/templates/nextblock-template/lib/seo/alt-text-write-back.test.ts +154 -154
  225. package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -109
  226. package/templates/nextblock-template/lib/seo/block-content.ts +123 -123
  227. package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -242
  228. package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -204
  229. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +157 -140
  230. package/templates/nextblock-template/lib/seo/page-document.test.ts +384 -350
  231. package/templates/nextblock-template/lib/seo/page-document.ts +463 -412
  232. package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -479
  233. package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -466
  234. package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -102
  235. package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -41
  236. package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -370
  237. package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -510
  238. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +37 -22
  239. package/templates/nextblock-template/lib/site-scripts/revisions.ts +71 -71
  240. package/templates/nextblock-template/lib/site-scripts/types.ts +46 -46
  241. package/templates/nextblock-template/lib/site-url.test.ts +89 -89
  242. package/templates/nextblock-template/lib/site-url.ts +102 -102
  243. package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -124
  244. package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -31
  245. package/templates/nextblock-template/lib/themes/tokens.ts +143 -143
  246. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  247. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  248. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  249. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  250. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  251. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  252. package/templates/nextblock-template/next-env.d.ts +1 -2
  253. package/templates/nextblock-template/package.json +1 -1
  254. package/templates/nextblock-template/postcss.config.js +6 -6
  255. package/templates/nextblock-template/scripts/backup.js +115 -115
  256. package/templates/nextblock-template/scripts/restore.js +385 -385
  257. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  258. package/templates/nextblock-template/tailwind.config.js +25 -25
  259. package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
  260. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  261. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  262. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
  263. package/templates/nextblock-template/tools/update.mjs +1303 -1303
  264. package/tsconfig.base.json +3 -3
@@ -1,93 +1,93 @@
1
- /**
2
- * The snake_case ↔ camelCase boundary for managed redirects.
3
- *
4
- * The database speaks `source_path` / `is_active`; the pure SEO engine in
5
- * `@nextblock-cms/utils/seo` speaks `sourcePath` / `isActive`. Exactly one place is
6
- * allowed to know both vocabularies, and this is it — every other module on this
7
- * screen sees one shape or the other and never has to guess which.
8
- *
9
- * These helpers are pure and live outside `actions.ts` deliberately: that file is
10
- * `'use server'`, where every export must be an async server action, so a synchronous
11
- * mapper cannot be exported from there. Keeping them here also means the client
12
- * components can use the identical mapping the actions use, which is what lets the
13
- * browser run the real `validateRedirectRule` against the real rule set before a
14
- * request is ever made.
15
- */
16
-
17
- import type { RedirectRule, RedirectStatusCode } from '@nextblock-cms/utils/seo';
18
-
19
- import { mapRedirectRow, type CmsRedirectRow } from '../../../../lib/seo/redirect-store';
20
-
21
- /**
22
- * A row of `public.cms_redirects` as this screen sees it.
23
- *
24
- * WHY IT IS AN ALIAS AND NOT A SECOND HAND-WRITTEN INTERFACE. The row shape was
25
- * already transcribed from migration 00000000000030's DDL in `lib/seo/redirect-store.ts`,
26
- * for the proxy's benefit, and for the same reason it had to be hand-written there:
27
- * `libs/db`'s generated `Database` type comes from `npm run db:types` against a live
28
- * project, that migration has not been applied yet, and so
29
- * `Database['public']['Tables']['cms_redirects']` does not compile today. Restating
30
- * the seven columns here would give the repo two hand-written descriptions of one
31
- * table that could drift apart in a rename; aliasing gives it one. When the migration
32
- * lands and the types are regenerated, there is a single interface to delete.
33
- */
34
- export type CmsRedirect = CmsRedirectRow;
35
-
36
- /**
37
- * What the add/edit form collects, in the engine's vocabulary.
38
- *
39
- * `statusCode` is `RedirectStatusCode` rather than `number` so a widened value cannot
40
- * reach the database and trip the table's CHECK constraint as an opaque Postgres
41
- * error; the server re-validates it with Zod regardless, because a type says nothing
42
- * about what actually arrived over the wire.
43
- */
44
- export interface RedirectInput {
45
- destinationPath: string;
46
- isActive: boolean;
47
- sourcePath: string;
48
- statusCode: RedirectStatusCode;
49
- }
50
-
51
- /**
52
- * Maps stored rows onto the engine's rule shape, dropping any row too malformed to
53
- * be a rule at all.
54
- *
55
- * Reuses `mapRedirectRow`, the proxy's mapper, so the admin screen's idea of the
56
- * current rule set is byte-for-byte the proxy's idea of it. That agreement is what
57
- * makes client-side validation trustworthy: a duplicate or a loop the admin screen
58
- * refuses is a duplicate or a loop the proxy would actually have hit.
59
- *
60
- * Unlike the proxy's query this one is given inactive rows too, and that is
61
- * intentional — `validateRedirectRule` counts a paused rule when checking for
62
- * duplicates and loops, so re-enabling one cannot introduce a conflict that
63
- * validation already waved through.
64
- */
65
- export function toRedirectRules(rows: CmsRedirect[]): RedirectRule[] {
66
- const rules: RedirectRule[] = [];
67
-
68
- for (const row of rows) {
69
- const rule = mapRedirectRow(row);
70
- if (rule !== null) {
71
- rules.push(rule);
72
- }
73
- }
74
-
75
- return rules;
76
- }
77
-
78
- /**
79
- * Maps one stored row onto the form's input shape, for the inline editor.
80
- *
81
- * Falls back to 301 for an out-of-range `status_code` for the same reason
82
- * `mapRedirectRow` does: the column is constrained to 301/302, so the fallback never
83
- * fires in practice, and defaulting to permanent keeps a rule the operator can see in
84
- * the list from silently becoming un-editable.
85
- */
86
- export function toRedirectInput(row: CmsRedirect): RedirectInput {
87
- return {
88
- destinationPath: row.destination_path,
89
- isActive: row.is_active,
90
- sourcePath: row.source_path,
91
- statusCode: row.status_code === 302 ? 302 : 301,
92
- };
93
- }
1
+ /**
2
+ * The snake_case ↔ camelCase boundary for managed redirects.
3
+ *
4
+ * The database speaks `source_path` / `is_active`; the pure SEO engine in
5
+ * `@nextblock-cms/utils/seo` speaks `sourcePath` / `isActive`. Exactly one place is
6
+ * allowed to know both vocabularies, and this is it — every other module on this
7
+ * screen sees one shape or the other and never has to guess which.
8
+ *
9
+ * These helpers are pure and live outside `actions.ts` deliberately: that file is
10
+ * `'use server'`, where every export must be an async server action, so a synchronous
11
+ * mapper cannot be exported from there. Keeping them here also means the client
12
+ * components can use the identical mapping the actions use, which is what lets the
13
+ * browser run the real `validateRedirectRule` against the real rule set before a
14
+ * request is ever made.
15
+ */
16
+
17
+ import type { RedirectRule, RedirectStatusCode } from '@nextblock-cms/utils/seo';
18
+
19
+ import { mapRedirectRow, type CmsRedirectRow } from '../../../../lib/seo/redirect-store';
20
+
21
+ /**
22
+ * A row of `public.cms_redirects` as this screen sees it.
23
+ *
24
+ * WHY IT IS AN ALIAS AND NOT A SECOND HAND-WRITTEN INTERFACE. The row shape was
25
+ * already transcribed from migration 00000000000030's DDL in `lib/seo/redirect-store.ts`,
26
+ * for the proxy's benefit, and for the same reason it had to be hand-written there:
27
+ * `libs/db`'s generated `Database` type comes from `npm run db:types` against a live
28
+ * project, that migration has not been applied yet, and so
29
+ * `Database['public']['Tables']['cms_redirects']` does not compile today. Restating
30
+ * the seven columns here would give the repo two hand-written descriptions of one
31
+ * table that could drift apart in a rename; aliasing gives it one. When the migration
32
+ * lands and the types are regenerated, there is a single interface to delete.
33
+ */
34
+ export type CmsRedirect = CmsRedirectRow;
35
+
36
+ /**
37
+ * What the add/edit form collects, in the engine's vocabulary.
38
+ *
39
+ * `statusCode` is `RedirectStatusCode` rather than `number` so a widened value cannot
40
+ * reach the database and trip the table's CHECK constraint as an opaque Postgres
41
+ * error; the server re-validates it with Zod regardless, because a type says nothing
42
+ * about what actually arrived over the wire.
43
+ */
44
+ export interface RedirectInput {
45
+ destinationPath: string;
46
+ isActive: boolean;
47
+ sourcePath: string;
48
+ statusCode: RedirectStatusCode;
49
+ }
50
+
51
+ /**
52
+ * Maps stored rows onto the engine's rule shape, dropping any row too malformed to
53
+ * be a rule at all.
54
+ *
55
+ * Reuses `mapRedirectRow`, the proxy's mapper, so the admin screen's idea of the
56
+ * current rule set is byte-for-byte the proxy's idea of it. That agreement is what
57
+ * makes client-side validation trustworthy: a duplicate or a loop the admin screen
58
+ * refuses is a duplicate or a loop the proxy would actually have hit.
59
+ *
60
+ * Unlike the proxy's query this one is given inactive rows too, and that is
61
+ * intentional — `validateRedirectRule` counts a paused rule when checking for
62
+ * duplicates and loops, so re-enabling one cannot introduce a conflict that
63
+ * validation already waved through.
64
+ */
65
+ export function toRedirectRules(rows: CmsRedirect[]): RedirectRule[] {
66
+ const rules: RedirectRule[] = [];
67
+
68
+ for (const row of rows) {
69
+ const rule = mapRedirectRow(row);
70
+ if (rule !== null) {
71
+ rules.push(rule);
72
+ }
73
+ }
74
+
75
+ return rules;
76
+ }
77
+
78
+ /**
79
+ * Maps one stored row onto the form's input shape, for the inline editor.
80
+ *
81
+ * Falls back to 301 for an out-of-range `status_code` for the same reason
82
+ * `mapRedirectRow` does: the column is constrained to 301/302, so the fallback never
83
+ * fires in practice, and defaulting to permanent keeps a rule the operator can see in
84
+ * the list from silently becoming un-editable.
85
+ */
86
+ export function toRedirectInput(row: CmsRedirect): RedirectInput {
87
+ return {
88
+ destinationPath: row.destination_path,
89
+ isActive: row.is_active,
90
+ sourcePath: row.source_path,
91
+ statusCode: row.status_code === 302 ? 302 : 301,
92
+ };
93
+ }
@@ -1,46 +1,46 @@
1
- // app/cms/settings/seo/page.tsx
2
- import { hasResolvedSiteUrl, resolveSiteUrl } from '../../../../lib/site-url';
3
- import { getRedirects, getRobotsSettings } from './actions';
4
- import { requireAdminSupabaseClient } from './require-admin';
5
- import { SeoSettingsClient } from './SeoSettingsClient';
6
-
7
- /**
8
- * The SEO operations screen: which URLs moved, and what crawlers are allowed to do.
9
- *
10
- * ADMIN-only, and gated here as well as in every action and in RLS. The gate is
11
- * repeated rather than delegated because the three layers protect different things:
12
- * this one stops a WRITER from ever seeing the screen (a redirect list is a map of a
13
- * site's history, and the robots block is a map of what it is hiding), the actions
14
- * stop a hand-rolled request that never loaded the page, and RLS stops everything
15
- * else. `requireAdminSupabaseClient` throws, which is the right behaviour for a
16
- * Server Component: an unauthorised visitor gets the error boundary instead of a
17
- * half-rendered admin screen.
18
- *
19
- * The two reads run concurrently because they are independent — one hits
20
- * `cms_redirects`, the other one row of `site_settings` — and running them in series
21
- * would add a whole round trip to a screen that is already behind an auth check.
22
- */
23
- export default async function SeoSettingsPage() {
24
- await requireAdminSupabaseClient();
25
-
26
- const [redirects, robotsSettings] = await Promise.all([getRedirects(), getRobotsSettings()]);
27
-
28
- // Resolved on the server, where the deployment's env actually lives, and handed to
29
- // the preview as data. The client component must never try to work this out for
30
- // itself: `NEXT_PUBLIC_URL` is inlined at build time and the Vercel fallbacks are
31
- // server-only, so a browser-side guess would quietly show a different sitemap URL
32
- // from the one crawlers are given.
33
- const siteUrl = resolveSiteUrl();
34
-
35
- return (
36
- <div className="mx-auto max-w-5xl space-y-6">
37
- <SeoSettingsClient
38
- initialRedirects={redirects}
39
- initialRobotsSettings={robotsSettings}
40
- isSandbox={process.env.NEXT_PUBLIC_IS_SANDBOX === 'true'}
41
- isSiteUrlConfigured={hasResolvedSiteUrl()}
42
- sitemapUrl={`${siteUrl}/sitemap.xml`}
43
- />
44
- </div>
45
- );
46
- }
1
+ // app/cms/settings/seo/page.tsx
2
+ import { hasResolvedSiteUrl, resolveSiteUrl } from '../../../../lib/site-url';
3
+ import { getRedirects, getRobotsSettings } from './actions';
4
+ import { requireAdminSupabaseClient } from './require-admin';
5
+ import { SeoSettingsClient } from './SeoSettingsClient';
6
+
7
+ /**
8
+ * The SEO operations screen: which URLs moved, and what crawlers are allowed to do.
9
+ *
10
+ * ADMIN-only, and gated here as well as in every action and in RLS. The gate is
11
+ * repeated rather than delegated because the three layers protect different things:
12
+ * this one stops a WRITER from ever seeing the screen (a redirect list is a map of a
13
+ * site's history, and the robots block is a map of what it is hiding), the actions
14
+ * stop a hand-rolled request that never loaded the page, and RLS stops everything
15
+ * else. `requireAdminSupabaseClient` throws, which is the right behaviour for a
16
+ * Server Component: an unauthorised visitor gets the error boundary instead of a
17
+ * half-rendered admin screen.
18
+ *
19
+ * The two reads run concurrently because they are independent — one hits
20
+ * `cms_redirects`, the other one row of `site_settings` — and running them in series
21
+ * would add a whole round trip to a screen that is already behind an auth check.
22
+ */
23
+ export default async function SeoSettingsPage() {
24
+ await requireAdminSupabaseClient();
25
+
26
+ const [redirects, robotsSettings] = await Promise.all([getRedirects(), getRobotsSettings()]);
27
+
28
+ // Resolved on the server, where the deployment's env actually lives, and handed to
29
+ // the preview as data. The client component must never try to work this out for
30
+ // itself: `NEXT_PUBLIC_URL` is inlined at build time and the Vercel fallbacks are
31
+ // server-only, so a browser-side guess would quietly show a different sitemap URL
32
+ // from the one crawlers are given.
33
+ const siteUrl = resolveSiteUrl();
34
+
35
+ return (
36
+ <div className="mx-auto max-w-5xl space-y-6">
37
+ <SeoSettingsClient
38
+ initialRedirects={redirects}
39
+ initialRobotsSettings={robotsSettings}
40
+ isSandbox={process.env.NEXT_PUBLIC_IS_SANDBOX === 'true'}
41
+ isSiteUrlConfigured={hasResolvedSiteUrl()}
42
+ sitemapUrl={`${siteUrl}/sitemap.xml`}
43
+ />
44
+ </div>
45
+ );
46
+ }
@@ -1,47 +1,47 @@
1
- import { createClient } from '@nextblock-cms/db/server';
2
-
3
- /**
4
- * Admin gate shared by the SEO settings server actions and by the page itself.
5
- *
6
- * Lives outside `actions.ts` because that file is `'use server'`, where every export
7
- * must itself be a valid server action — a helper that returns a Supabase client
8
- * cannot be exported from there. Keeping one copy matters more than the file count:
9
- * this is the check standing between a WRITER and the redirect table, and a redirect
10
- * rule is an open-redirect surface. Anyone who can write one can point every visitor
11
- * of `/checkout` at a phishing clone of the site, with the real domain in the
12
- * referrer, without touching a single page or post. The same reasoning is why
13
- * migration 00000000000030 grants writes on `cms_redirects` to ADMIN only and leaves
14
- * WRITER out entirely — the RLS policy is the boundary, and this is the friendly
15
- * error that keeps an operator from meeting it as an unexplained empty list.
16
- *
17
- * It throws rather than returning a result because its two kinds of caller want
18
- * opposite things. A Server Component wants the throw: an unauthorised visitor gets
19
- * the error boundary instead of a half-rendered admin screen. A server action called
20
- * from a client component wants a message it can render, so those callers catch the
21
- * throw and return it as data — see `requireAdminOrMessage` in `actions.ts` and the
22
- * note on `SettingsActionResult` about Next replacing uncaught Server Action error
23
- * messages with a generic string in production.
24
- */
25
- export async function requireAdminSupabaseClient() {
26
- const supabase = createClient();
27
- const {
28
- data: { user },
29
- error: userError,
30
- } = await supabase.auth.getUser();
31
-
32
- if (userError || !user) {
33
- throw new Error('You must be logged in to manage SEO settings.');
34
- }
35
-
36
- const { data: profile, error: profileError } = await supabase
37
- .from('profiles')
38
- .select('role')
39
- .eq('id', user.id)
40
- .single();
41
-
42
- if (profileError || !profile || profile.role !== 'ADMIN') {
43
- throw new Error('You do not have permission to manage SEO settings.');
44
- }
45
-
46
- return { supabase, userId: user.id };
47
- }
1
+ import { createClient } from '@nextblock-cms/db/server';
2
+
3
+ /**
4
+ * Admin gate shared by the SEO settings server actions and by the page itself.
5
+ *
6
+ * Lives outside `actions.ts` because that file is `'use server'`, where every export
7
+ * must itself be a valid server action — a helper that returns a Supabase client
8
+ * cannot be exported from there. Keeping one copy matters more than the file count:
9
+ * this is the check standing between a WRITER and the redirect table, and a redirect
10
+ * rule is an open-redirect surface. Anyone who can write one can point every visitor
11
+ * of `/checkout` at a phishing clone of the site, with the real domain in the
12
+ * referrer, without touching a single page or post. The same reasoning is why
13
+ * migration 00000000000030 grants writes on `cms_redirects` to ADMIN only and leaves
14
+ * WRITER out entirely — the RLS policy is the boundary, and this is the friendly
15
+ * error that keeps an operator from meeting it as an unexplained empty list.
16
+ *
17
+ * It throws rather than returning a result because its two kinds of caller want
18
+ * opposite things. A Server Component wants the throw: an unauthorised visitor gets
19
+ * the error boundary instead of a half-rendered admin screen. A server action called
20
+ * from a client component wants a message it can render, so those callers catch the
21
+ * throw and return it as data — see `requireAdminOrMessage` in `actions.ts` and the
22
+ * note on `SettingsActionResult` about Next replacing uncaught Server Action error
23
+ * messages with a generic string in production.
24
+ */
25
+ export async function requireAdminSupabaseClient() {
26
+ const supabase = createClient();
27
+ const {
28
+ data: { user },
29
+ error: userError,
30
+ } = await supabase.auth.getUser();
31
+
32
+ if (userError || !user) {
33
+ throw new Error('You must be logged in to manage SEO settings.');
34
+ }
35
+
36
+ const { data: profile, error: profileError } = await supabase
37
+ .from('profiles')
38
+ .select('role')
39
+ .eq('id', user.id)
40
+ .single();
41
+
42
+ if (profileError || !profile || profile.role !== 'ADMIN') {
43
+ throw new Error('You do not have permission to manage SEO settings.');
44
+ }
45
+
46
+ return { supabase, userId: user.id };
47
+ }
@@ -1,51 +1,51 @@
1
- // app/cms/settings/site-scripts/page.tsx
2
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
3
-
4
- import { getSiteScriptRevisions, getSiteScripts } from './actions';
5
- import SiteScriptManager from './components/SiteScriptManager';
6
-
7
- export default async function SiteScriptsSettingsPage() {
8
- const [scripts, revisions] = await Promise.all([getSiteScripts(), getSiteScriptRevisions()]);
9
-
10
- return (
11
- <div className="mx-auto max-w-5xl space-y-6">
12
- <Card>
13
- <CardHeader>
14
- <CardTitle>Site Scripts</CardTitle>
15
- <CardDescription>
16
- JavaScript that runs on every page of the public site — animation helpers, chat widgets,
17
- third-party embeds. Each script is injected with the page&apos;s Content-Security-Policy
18
- nonce, so it runs without weakening the site&apos;s security headers. Disabled scripts are
19
- never sent to visitors. Every change is logged and can be rolled back from the history
20
- below.
21
- </CardDescription>
22
- </CardHeader>
23
- <CardContent>
24
- <SiteScriptManager initialRevisions={revisions} initialScripts={scripts} />
25
- </CardContent>
26
- </Card>
27
-
28
- <Card>
29
- <CardHeader>
30
- <CardTitle>Where else code can live</CardTitle>
31
- </CardHeader>
32
- <CardContent className="space-y-2 text-sm text-muted-foreground">
33
- <p>
34
- <strong className="text-foreground">One page only?</strong> A rich-text block accepts an
35
- inline <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> directly, so
36
- page-specific effects do not need to live here.
37
- </p>
38
- <p>
39
- <strong className="text-foreground">Site-wide styling?</strong> Use Themes &amp; CSS for
40
- palettes and global stylesheets.
41
- </p>
42
- <p>
43
- <strong className="text-foreground">Marketing or analytics tags?</strong> Put those under
44
- Google Analytics instead — scripts there only fire once a visitor accepts cookies, which
45
- is what consent law requires. Scripts on this page run unconditionally.
46
- </p>
47
- </CardContent>
48
- </Card>
49
- </div>
50
- );
51
- }
1
+ // app/cms/settings/site-scripts/page.tsx
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
3
+
4
+ import { getSiteScriptRevisions, getSiteScripts } from './actions';
5
+ import SiteScriptManager from './components/SiteScriptManager';
6
+
7
+ export default async function SiteScriptsSettingsPage() {
8
+ const [scripts, revisions] = await Promise.all([getSiteScripts(), getSiteScriptRevisions()]);
9
+
10
+ return (
11
+ <div className="mx-auto max-w-5xl space-y-6">
12
+ <Card>
13
+ <CardHeader>
14
+ <CardTitle>Site Scripts</CardTitle>
15
+ <CardDescription>
16
+ JavaScript that runs on every page of the public site — animation helpers, chat widgets,
17
+ third-party embeds. Each script is injected with the page&apos;s Content-Security-Policy
18
+ nonce, so it runs without weakening the site&apos;s security headers. Disabled scripts are
19
+ never sent to visitors. Every change is logged and can be rolled back from the history
20
+ below.
21
+ </CardDescription>
22
+ </CardHeader>
23
+ <CardContent>
24
+ <SiteScriptManager initialRevisions={revisions} initialScripts={scripts} />
25
+ </CardContent>
26
+ </Card>
27
+
28
+ <Card>
29
+ <CardHeader>
30
+ <CardTitle>Where else code can live</CardTitle>
31
+ </CardHeader>
32
+ <CardContent className="space-y-2 text-sm text-muted-foreground">
33
+ <p>
34
+ <strong className="text-foreground">One page only?</strong> A rich-text block accepts an
35
+ inline <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> directly, so
36
+ page-specific effects do not need to live here.
37
+ </p>
38
+ <p>
39
+ <strong className="text-foreground">Site-wide styling?</strong> Use Themes &amp; CSS for
40
+ palettes and global stylesheets.
41
+ </p>
42
+ <p>
43
+ <strong className="text-foreground">Marketing or analytics tags?</strong> Put those under
44
+ Google Analytics instead — scripts there only fire once a visitor accepts cookies, which
45
+ is what consent law requires. Scripts on this page run unconditionally.
46
+ </p>
47
+ </CardContent>
48
+ </Card>
49
+ </div>
50
+ );
51
+ }
@@ -1,21 +1,21 @@
1
- import { redirect } from 'next/navigation';
2
- import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
- import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
4
-
5
- export const metadata = {
6
- title: 'Tax Settings | NextBlock™ CMS',
7
- };
8
-
9
- export default async function TaxesPageWrapper({
10
- searchParams,
11
- }: {
12
- searchParams?: Promise<{ success?: string }>;
13
- }) {
14
- const isOnline = await verifyPackageOnline('ecommerce');
15
-
16
- if (!isOnline) {
17
- redirect('/cms/settings/packages');
18
- }
19
-
20
- return <TaxesPageUI searchParams={await searchParams} />;
21
- }
1
+ import { redirect } from 'next/navigation';
2
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
+ import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
4
+
5
+ export const metadata = {
6
+ title: 'Tax Settings | NextBlock™ CMS',
7
+ };
8
+
9
+ export default async function TaxesPageWrapper({
10
+ searchParams,
11
+ }: {
12
+ searchParams?: Promise<{ success?: string }>;
13
+ }) {
14
+ const isOnline = await verifyPackageOnline('ecommerce');
15
+
16
+ if (!isOnline) {
17
+ redirect('/cms/settings/packages');
18
+ }
19
+
20
+ return <TaxesPageUI searchParams={await searchParams} />;
21
+ }
@@ -1,20 +1,20 @@
1
- import { redirect } from 'next/navigation';
2
- import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
- import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
4
-
5
- export default async function ShippingPage({
6
- searchParams,
7
- }: {
8
- searchParams: Promise<{ success?: string }>;
9
- }) {
10
- const [isOnline, resolvedSearchParams] = await Promise.all([
11
- verifyPackageOnline('ecommerce'),
12
- searchParams,
13
- ]);
14
-
15
- if (!isOnline) {
16
- redirect('/cms/settings/packages');
17
- }
18
-
19
- return <ShippingPageUI searchParams={resolvedSearchParams} />;
20
- }
1
+ import { redirect } from 'next/navigation';
2
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
+ import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
4
+
5
+ export default async function ShippingPage({
6
+ searchParams,
7
+ }: {
8
+ searchParams: Promise<{ success?: string }>;
9
+ }) {
10
+ const [isOnline, resolvedSearchParams] = await Promise.all([
11
+ verifyPackageOnline('ecommerce'),
12
+ searchParams,
13
+ ]);
14
+
15
+ if (!isOnline) {
16
+ redirect('/cms/settings/packages');
17
+ }
18
+
19
+ return <ShippingPageUI searchParams={resolvedSearchParams} />;
20
+ }
@@ -7,17 +7,17 @@ import { Trash2, ShieldAlert } from "lucide-react";
7
7
  import { deleteUserAndProfile } from "../actions";
8
8
  import { ConfirmationModal } from '../../components/ConfirmationModal';
9
9
 
10
- export function DeleteUserButtonClient({
11
- userId,
12
- userEmail,
13
- currentAdminId,
14
- }: {
15
- userId: string;
16
- userEmail?: string;
17
- currentAdminId?: string;
18
- }) {
19
- void userEmail;
20
- const [isPending, startTransition] = useTransition();
10
+ export function DeleteUserButtonClient({
11
+ userId,
12
+ userEmail,
13
+ currentAdminId,
14
+ }: {
15
+ userId: string;
16
+ userEmail?: string;
17
+ currentAdminId?: string;
18
+ }) {
19
+ void userEmail;
20
+ const [isPending, startTransition] = useTransition();
21
21
  const [isModalOpen, setIsModalOpen] = useState(false);
22
22
 
23
23
  const handleDelete = () => {
@@ -68,4 +68,4 @@ export function DeleteUserButtonClient({
68
68
  />
69
69
  </>
70
70
  );
71
- }
71
+ }