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,110 +1,110 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { Share2 } from 'lucide-react';
5
- import {
6
- Button,
7
- Dialog,
8
- DialogContent,
9
- DialogDescription,
10
- DialogHeader,
11
- DialogTitle,
12
- DialogTrigger,
13
- } from '@nextblock-cms/ui';
14
- import SocialPreview from './SocialPreview';
15
-
16
- /**
17
- * The share-card rehearsal, moved behind a button.
18
- *
19
- * WHY A MODAL. `SocialPreview` renders three platform tabs above a 1.91:1 card that is up
20
- * to 520px wide, so inline it costs roughly four hundred vertical pixels in the middle of
21
- * a page/post settings form that is already long enough to scroll. That is a poor trade for
22
- * something an author consults once while wording a description and then never looks at
23
- * again for the rest of the session. Behind a trigger it costs one line, and the moment it
24
- * IS wanted it gets the whole viewport instead of a squeezed column.
25
- *
26
- * WHY THE PRESENTATIONAL COMPONENT STAYS UNTOUCHED. `SocialPreview` knows how three feeds
27
- * clip a headline and nothing else — no open state, no trigger, no dialog. Keeping that
28
- * split means the same preview can still be dropped inline anywhere it earns the space
29
- * (a future site-wide SEO screen, a publish-confirmation step) without having to be
30
- * unwrapped from a modal first. This component owns the disclosure; that one owns the
31
- * drawing.
32
- *
33
- * WHY IT STAYS LIVE WHILE OPEN. The props below are the caller's live form state, so every
34
- * keystroke in the meta fields behind the dialog repaints the card — the same behaviour the
35
- * inline version had. The dialog is a React portal, not a snapshot, so nothing is frozen at
36
- * open time and there is no "reopen to refresh" trap.
37
- *
38
- * NOTHING HERE PERSISTS. This component holds exactly one piece of state — whether the
39
- * modal is open — and it is read by nobody but Radix. It contributes no form field, no
40
- * FormData entry, and no input to the caller's autosave diff.
41
- */
42
- interface SocialPreviewDialogProps {
43
- description: string;
44
- imageUrl?: string | null;
45
- siteName?: string | null;
46
- title: string;
47
- /** Optional override for the trigger label, for surfaces that word it differently. */
48
- triggerLabel?: string;
49
- url: string;
50
- }
51
-
52
- export default function SocialPreviewDialog({
53
- description,
54
- imageUrl,
55
- siteName,
56
- title,
57
- triggerLabel = 'Preview share card',
58
- url,
59
- }: SocialPreviewDialogProps) {
60
- const [isOpen, setIsOpen] = useState(false);
61
-
62
- return (
63
- <Dialog onOpenChange={setIsOpen} open={isOpen}>
64
- {/* `asChild` so the trigger is the house `Button` rather than a bare Radix button, and
65
- `type="button"` explicitly: this renders inside the page/post `<form>`, where a
66
- button that inherits the default `submit` type would save the record instead of
67
- opening a preview. The visible text is the accessible name, and Radix leaves the
68
- element a real focusable button, so keyboard and pointer reach it identically. */}
69
- <DialogTrigger asChild>
70
- <Button className="h-7 px-2 text-[11px]" size="sm" type="button" variant="outline">
71
- <Share2 aria-hidden="true" className="mr-1.5 h-3.5 w-3.5" />
72
- {triggerLabel}
73
- </Button>
74
- </DialogTrigger>
75
-
76
- {/* The card is tall and the platform switcher sits above it, so the panel is capped at
77
- 85% of the viewport and the BODY scrolls rather than the dialog growing past the
78
- screen edge — on a laptop in a browser with devtools open, an uncapped panel would
79
- push its own close button out of reach. `overflow-hidden` on the shell keeps the
80
- rounded corners from being cut by the scrolling child. */}
81
- <DialogContent className="flex max-h-[85vh] flex-col gap-0 overflow-hidden sm:max-w-[600px]">
82
- {/* `pr-8` reserves room for the close affordance the dialog draws at top-right. */}
83
- <DialogHeader className="flex-shrink-0 pb-4 pr-8">
84
- <DialogTitle>Share preview</DialogTitle>
85
- <DialogDescription>
86
- How this link is likely to appear when it is shared. Edit the meta title and
87
- description behind this dialog and the card updates as you type.
88
- </DialogDescription>
89
- </DialogHeader>
90
-
91
- <div className="flex-grow overflow-y-auto py-1">
92
- <SocialPreview
93
- description={description}
94
- imageUrl={imageUrl}
95
- siteName={siteName}
96
- title={title}
97
- url={url}
98
- />
99
- </div>
100
-
101
- {/* Stated once here rather than repeated on each tab: the numbers in the preview are
102
- tuned to observed rendering, and every platform reserves the right to reflow. */}
103
- <p className="flex-shrink-0 pt-3 text-[11px] leading-snug text-muted-foreground">
104
- Truncation is approximate — each platform reflows link previews on its own terms —
105
- so treat a card that clips here as a prompt to shorten the copy, not a guarantee.
106
- </p>
107
- </DialogContent>
108
- </Dialog>
109
- );
110
- }
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Share2 } from 'lucide-react';
5
+ import {
6
+ Button,
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ } from '@nextblock-cms/ui';
14
+ import SocialPreview from './SocialPreview';
15
+
16
+ /**
17
+ * The share-card rehearsal, moved behind a button.
18
+ *
19
+ * WHY A MODAL. `SocialPreview` renders three platform tabs above a 1.91:1 card that is up
20
+ * to 520px wide, so inline it costs roughly four hundred vertical pixels in the middle of
21
+ * a page/post settings form that is already long enough to scroll. That is a poor trade for
22
+ * something an author consults once while wording a description and then never looks at
23
+ * again for the rest of the session. Behind a trigger it costs one line, and the moment it
24
+ * IS wanted it gets the whole viewport instead of a squeezed column.
25
+ *
26
+ * WHY THE PRESENTATIONAL COMPONENT STAYS UNTOUCHED. `SocialPreview` knows how three feeds
27
+ * clip a headline and nothing else — no open state, no trigger, no dialog. Keeping that
28
+ * split means the same preview can still be dropped inline anywhere it earns the space
29
+ * (a future site-wide SEO screen, a publish-confirmation step) without having to be
30
+ * unwrapped from a modal first. This component owns the disclosure; that one owns the
31
+ * drawing.
32
+ *
33
+ * WHY IT STAYS LIVE WHILE OPEN. The props below are the caller's live form state, so every
34
+ * keystroke in the meta fields behind the dialog repaints the card — the same behaviour the
35
+ * inline version had. The dialog is a React portal, not a snapshot, so nothing is frozen at
36
+ * open time and there is no "reopen to refresh" trap.
37
+ *
38
+ * NOTHING HERE PERSISTS. This component holds exactly one piece of state — whether the
39
+ * modal is open — and it is read by nobody but Radix. It contributes no form field, no
40
+ * FormData entry, and no input to the caller's autosave diff.
41
+ */
42
+ interface SocialPreviewDialogProps {
43
+ description: string;
44
+ imageUrl?: string | null;
45
+ siteName?: string | null;
46
+ title: string;
47
+ /** Optional override for the trigger label, for surfaces that word it differently. */
48
+ triggerLabel?: string;
49
+ url: string;
50
+ }
51
+
52
+ export default function SocialPreviewDialog({
53
+ description,
54
+ imageUrl,
55
+ siteName,
56
+ title,
57
+ triggerLabel = 'Preview share card',
58
+ url,
59
+ }: SocialPreviewDialogProps) {
60
+ const [isOpen, setIsOpen] = useState(false);
61
+
62
+ return (
63
+ <Dialog onOpenChange={setIsOpen} open={isOpen}>
64
+ {/* `asChild` so the trigger is the house `Button` rather than a bare Radix button, and
65
+ `type="button"` explicitly: this renders inside the page/post `<form>`, where a
66
+ button that inherits the default `submit` type would save the record instead of
67
+ opening a preview. The visible text is the accessible name, and Radix leaves the
68
+ element a real focusable button, so keyboard and pointer reach it identically. */}
69
+ <DialogTrigger asChild>
70
+ <Button className="h-7 px-2 text-[11px]" size="sm" type="button" variant="outline">
71
+ <Share2 aria-hidden="true" className="mr-1.5 h-3.5 w-3.5" />
72
+ {triggerLabel}
73
+ </Button>
74
+ </DialogTrigger>
75
+
76
+ {/* The card is tall and the platform switcher sits above it, so the panel is capped at
77
+ 85% of the viewport and the BODY scrolls rather than the dialog growing past the
78
+ screen edge — on a laptop in a browser with devtools open, an uncapped panel would
79
+ push its own close button out of reach. `overflow-hidden` on the shell keeps the
80
+ rounded corners from being cut by the scrolling child. */}
81
+ <DialogContent className="flex max-h-[85vh] flex-col gap-0 overflow-hidden sm:max-w-[600px]">
82
+ {/* `pr-8` reserves room for the close affordance the dialog draws at top-right. */}
83
+ <DialogHeader className="flex-shrink-0 pb-4 pr-8">
84
+ <DialogTitle>Share preview</DialogTitle>
85
+ <DialogDescription>
86
+ How this link is likely to appear when it is shared. Edit the meta title and
87
+ description behind this dialog and the card updates as you type.
88
+ </DialogDescription>
89
+ </DialogHeader>
90
+
91
+ <div className="flex-grow overflow-y-auto py-1">
92
+ <SocialPreview
93
+ description={description}
94
+ imageUrl={imageUrl}
95
+ siteName={siteName}
96
+ title={title}
97
+ url={url}
98
+ />
99
+ </div>
100
+
101
+ {/* Stated once here rather than repeated on each tab: the numbers in the preview are
102
+ tuned to observed rendering, and every platform reserves the right to reflow. */}
103
+ <p className="flex-shrink-0 pt-3 text-[11px] leading-snug text-muted-foreground">
104
+ Truncation is approximate — each platform reflows link previews on its own terms —
105
+ so treat a card that clips here as a prompt to shorten the copy, not a guarantee.
106
+ </p>
107
+ </DialogContent>
108
+ </Dialog>
109
+ );
110
+ }
@@ -1,23 +1,23 @@
1
- "use client";
2
-
3
- import { Button } from "@nextblock-cms/ui";
4
- import { type ComponentProps } from "react";
5
- import { useFormStatus } from "react-dom";
6
-
7
- type Props = ComponentProps<typeof Button> & {
8
- pendingText?: string;
9
- };
10
-
11
- export function SubmitButton({
12
- children,
13
- pendingText = "Submitting...",
14
- ...props
15
- }: Props) {
16
- const { pending } = useFormStatus();
17
-
18
- return (
19
- <Button type="submit" aria-disabled={pending} {...props}>
20
- {pending ? pendingText : children}
21
- </Button>
22
- );
23
- }
1
+ "use client";
2
+
3
+ import { Button } from "@nextblock-cms/ui";
4
+ import { type ComponentProps } from "react";
5
+ import { useFormStatus } from "react-dom";
6
+
7
+ type Props = ComponentProps<typeof Button> & {
8
+ pendingText?: string;
9
+ };
10
+
11
+ export function SubmitButton({
12
+ children,
13
+ pendingText = "Submitting...",
14
+ ...props
15
+ }: Props) {
16
+ const { pending } = useFormStatus();
17
+
18
+ return (
19
+ <Button type="submit" aria-disabled={pending} {...props}>
20
+ {pending ? pendingText : children}
21
+ </Button>
22
+ );
23
+ }
@@ -1,78 +1,78 @@
1
- "use client";
2
-
3
- import {
4
- Aperture,
5
- Atom,
6
- Blend,
7
- Brush,
8
- Cloud,
9
- Contrast,
10
- Droplet,
11
- Feather,
12
- Flame,
13
- Gem,
14
- Ghost,
15
- Leaf,
16
- Moon,
17
- Mountain,
18
- Palette,
19
- Rocket,
20
- Snowflake,
21
- Sparkles,
22
- Star,
23
- Sun,
24
- Sunrise,
25
- Sunset,
26
- Waves,
27
- Zap,
28
- type LucideIcon,
29
- } from "lucide-react";
30
-
31
- /**
32
- * Curated icon set offered to themes.
33
- *
34
- * A fixed map rather than a dynamic lucide lookup: the icon name comes from the
35
- * database, and importing lucide dynamically by string would either pull the
36
- * whole icon set into the bundle or break tree-shaking.
37
- */
38
- export const THEME_ICONS: Record<string, LucideIcon> = {
39
- Sun,
40
- Moon,
41
- Zap,
42
- Sparkles,
43
- Palette,
44
- Flame,
45
- Droplet,
46
- Leaf,
47
- Star,
48
- Cloud,
49
- Snowflake,
50
- Waves,
51
- Mountain,
52
- Sunrise,
53
- Sunset,
54
- Contrast,
55
- Blend,
56
- Brush,
57
- Aperture,
58
- Atom,
59
- Feather,
60
- Gem,
61
- Ghost,
62
- Rocket,
63
- };
64
-
65
- export const THEME_ICON_NAMES = Object.keys(THEME_ICONS);
66
-
67
- export function ThemeIcon({
68
- name,
69
- size = 16,
70
- className,
71
- }: {
72
- name: string;
73
- size?: number;
74
- className?: string;
75
- }) {
76
- const Icon = THEME_ICONS[name] ?? Palette;
77
- return <Icon size={size} className={className} />;
78
- }
1
+ "use client";
2
+
3
+ import {
4
+ Aperture,
5
+ Atom,
6
+ Blend,
7
+ Brush,
8
+ Cloud,
9
+ Contrast,
10
+ Droplet,
11
+ Feather,
12
+ Flame,
13
+ Gem,
14
+ Ghost,
15
+ Leaf,
16
+ Moon,
17
+ Mountain,
18
+ Palette,
19
+ Rocket,
20
+ Snowflake,
21
+ Sparkles,
22
+ Star,
23
+ Sun,
24
+ Sunrise,
25
+ Sunset,
26
+ Waves,
27
+ Zap,
28
+ type LucideIcon,
29
+ } from "lucide-react";
30
+
31
+ /**
32
+ * Curated icon set offered to themes.
33
+ *
34
+ * A fixed map rather than a dynamic lucide lookup: the icon name comes from the
35
+ * database, and importing lucide dynamically by string would either pull the
36
+ * whole icon set into the bundle or break tree-shaking.
37
+ */
38
+ export const THEME_ICONS: Record<string, LucideIcon> = {
39
+ Sun,
40
+ Moon,
41
+ Zap,
42
+ Sparkles,
43
+ Palette,
44
+ Flame,
45
+ Droplet,
46
+ Leaf,
47
+ Star,
48
+ Cloud,
49
+ Snowflake,
50
+ Waves,
51
+ Mountain,
52
+ Sunrise,
53
+ Sunset,
54
+ Contrast,
55
+ Blend,
56
+ Brush,
57
+ Aperture,
58
+ Atom,
59
+ Feather,
60
+ Gem,
61
+ Ghost,
62
+ Rocket,
63
+ };
64
+
65
+ export const THEME_ICON_NAMES = Object.keys(THEME_ICONS);
66
+
67
+ export function ThemeIcon({
68
+ name,
69
+ size = 16,
70
+ className,
71
+ }: {
72
+ name: string;
73
+ size?: number;
74
+ className?: string;
75
+ }) {
76
+ const Icon = THEME_ICONS[name] ?? Palette;
77
+ return <Icon size={size} className={className} />;
78
+ }
@@ -1,85 +1,85 @@
1
- "use client";
2
-
3
- import { Button } from "@nextblock-cms/ui";
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuRadioGroup,
8
- DropdownMenuRadioItem,
9
- DropdownMenuTrigger,
10
- } from "@nextblock-cms/ui";
11
- import { Laptop } from "lucide-react";
12
- import { useTheme } from "next-themes";
13
- import { useEffect, useState } from "react";
14
- import { useTranslations } from "@nextblock-cms/utils";
15
- import { useThemeCatalog } from "../context/ThemeCatalogContext";
16
- import { ThemeIcon } from "./theme-icon";
17
-
18
- const ICON_SIZE = 16;
19
-
20
- const ThemeSwitcher = () => {
21
- const [mounted, setMounted] = useState(false);
22
- const { theme, setTheme } = useTheme();
23
- const { t } = useTranslations();
24
- const themes = useThemeCatalog();
25
-
26
- // useEffect only runs on the client, so now we can safely show the UI
27
- useEffect(() => {
28
- setMounted(true);
29
- }, []);
30
-
31
- if (!mounted) {
32
- return null;
33
- }
34
-
35
- const active = themes.find((entry) => entry.slug === theme);
36
-
37
- /**
38
- * Built-in slugs keep their existing translation keys so no copy is lost;
39
- * admin-created themes fall back to the name stored on the row.
40
- */
41
- const labelFor = (slug: string, name: string) => {
42
- const builtin: Record<string, string> = {
43
- light: "theme_light",
44
- dark: "theme_dark",
45
- vibrant: "theme_vibrant",
46
- };
47
- const key = builtin[slug];
48
- if (!key) return name;
49
- const translated = t(key);
50
- return translated === key ? name : translated;
51
- };
52
-
53
- return (
54
- <DropdownMenu>
55
- <DropdownMenuTrigger asChild>
56
- <Button variant="ghost" size={"sm"} aria-label={t('theme_switcher')}>
57
- {active ? (
58
- <ThemeIcon name={active.icon} size={ICON_SIZE} className="text-muted-foreground" />
59
- ) : (
60
- <Laptop size={ICON_SIZE} className="text-muted-foreground" />
61
- )}
62
- </Button>
63
- </DropdownMenuTrigger>
64
- <DropdownMenuContent className="w-content" align="start">
65
- <DropdownMenuRadioGroup
66
- value={theme}
67
- onValueChange={(e) => setTheme(e)}
68
- >
69
- {themes.map((entry) => (
70
- <DropdownMenuRadioItem key={entry.slug} className="flex gap-2" value={entry.slug}>
71
- <ThemeIcon name={entry.icon} size={ICON_SIZE} className="text-muted-foreground" />{" "}
72
- <span>{labelFor(entry.slug, entry.name)}</span>
73
- </DropdownMenuRadioItem>
74
- ))}
75
- <DropdownMenuRadioItem className="flex gap-2" value="system">
76
- <Laptop size={ICON_SIZE} className="text-muted-foreground" />{" "}
77
- <span>{t('theme_system')}</span>
78
- </DropdownMenuRadioItem>
79
- </DropdownMenuRadioGroup>
80
- </DropdownMenuContent>
81
- </DropdownMenu>
82
- );
83
- };
84
-
85
- export { ThemeSwitcher };
1
+ "use client";
2
+
3
+ import { Button } from "@nextblock-cms/ui";
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuRadioGroup,
8
+ DropdownMenuRadioItem,
9
+ DropdownMenuTrigger,
10
+ } from "@nextblock-cms/ui";
11
+ import { Laptop } from "lucide-react";
12
+ import { useTheme } from "next-themes";
13
+ import { useEffect, useState } from "react";
14
+ import { useTranslations } from "@nextblock-cms/utils";
15
+ import { useThemeCatalog } from "../context/ThemeCatalogContext";
16
+ import { ThemeIcon } from "./theme-icon";
17
+
18
+ const ICON_SIZE = 16;
19
+
20
+ const ThemeSwitcher = () => {
21
+ const [mounted, setMounted] = useState(false);
22
+ const { theme, setTheme } = useTheme();
23
+ const { t } = useTranslations();
24
+ const themes = useThemeCatalog();
25
+
26
+ // useEffect only runs on the client, so now we can safely show the UI
27
+ useEffect(() => {
28
+ setMounted(true);
29
+ }, []);
30
+
31
+ if (!mounted) {
32
+ return null;
33
+ }
34
+
35
+ const active = themes.find((entry) => entry.slug === theme);
36
+
37
+ /**
38
+ * Built-in slugs keep their existing translation keys so no copy is lost;
39
+ * admin-created themes fall back to the name stored on the row.
40
+ */
41
+ const labelFor = (slug: string, name: string) => {
42
+ const builtin: Record<string, string> = {
43
+ light: "theme_light",
44
+ dark: "theme_dark",
45
+ vibrant: "theme_vibrant",
46
+ };
47
+ const key = builtin[slug];
48
+ if (!key) return name;
49
+ const translated = t(key);
50
+ return translated === key ? name : translated;
51
+ };
52
+
53
+ return (
54
+ <DropdownMenu>
55
+ <DropdownMenuTrigger asChild>
56
+ <Button variant="ghost" size={"sm"} aria-label={t('theme_switcher')}>
57
+ {active ? (
58
+ <ThemeIcon name={active.icon} size={ICON_SIZE} className="text-muted-foreground" />
59
+ ) : (
60
+ <Laptop size={ICON_SIZE} className="text-muted-foreground" />
61
+ )}
62
+ </Button>
63
+ </DropdownMenuTrigger>
64
+ <DropdownMenuContent className="w-content" align="start">
65
+ <DropdownMenuRadioGroup
66
+ value={theme}
67
+ onValueChange={(e) => setTheme(e)}
68
+ >
69
+ {themes.map((entry) => (
70
+ <DropdownMenuRadioItem key={entry.slug} className="flex gap-2" value={entry.slug}>
71
+ <ThemeIcon name={entry.icon} size={ICON_SIZE} className="text-muted-foreground" />{" "}
72
+ <span>{labelFor(entry.slug, entry.name)}</span>
73
+ </DropdownMenuRadioItem>
74
+ ))}
75
+ <DropdownMenuRadioItem className="flex gap-2" value="system">
76
+ <Laptop size={ICON_SIZE} className="text-muted-foreground" />{" "}
77
+ <span>{t('theme_system')}</span>
78
+ </DropdownMenuRadioItem>
79
+ </DropdownMenuRadioGroup>
80
+ </DropdownMenuContent>
81
+ </DropdownMenu>
82
+ );
83
+ };
84
+
85
+ export { ThemeSwitcher };
@@ -1,10 +1,10 @@
1
1
  // context/AuthContext.tsx
2
- 'use client';
3
-
4
- import React, { createContext, useContext, useState, useEffect, useMemo } from 'react';
5
- import type { ReactNode } from 'react';
6
- import type { User, SupabaseClient } from '@supabase/supabase-js';
7
- import type { Database } from '@nextblock-cms/db';
2
+ 'use client';
3
+
4
+ import React, { createContext, useContext, useState, useEffect, useMemo } from 'react';
5
+ import type { ReactNode } from 'react';
6
+ import type { User, SupabaseClient } from '@supabase/supabase-js';
7
+ import type { Database } from '@nextblock-cms/db';
8
8
 
9
9
  type Profile = Database['public']['Tables']['profiles']['Row'];
10
10
  type UserRole = Database['public']['Enums']['user_role'];
@@ -28,17 +28,17 @@ interface AuthContextType {
28
28
 
29
29
  const AuthContext = createContext<AuthContextType | undefined>(undefined);
30
30
 
31
- export const AuthProvider = ({ children, serverUser, serverProfile }: AuthProviderProps) => {
32
- const [user, setUser] = useState<User | null>(serverUser);
33
- const [profile, setProfile] = useState<Profile | null>(serverProfile);
34
- const [role, setRole] = useState<UserRole | null>(serverProfile?.role ?? null);
35
- const [isLoading] = useState(false);
36
-
37
- useEffect(() => {
38
- setUser(serverUser);
39
- setProfile(serverProfile);
40
- setRole(serverProfile?.role ?? null);
41
- }, [serverUser, serverProfile]);
31
+ export const AuthProvider = ({ children, serverUser, serverProfile }: AuthProviderProps) => {
32
+ const [user, setUser] = useState<User | null>(serverUser);
33
+ const [profile, setProfile] = useState<Profile | null>(serverProfile);
34
+ const [role, setRole] = useState<UserRole | null>(serverProfile?.role ?? null);
35
+ const [isLoading] = useState(false);
36
+
37
+ useEffect(() => {
38
+ setUser(serverUser);
39
+ setProfile(serverProfile);
40
+ setRole(serverProfile?.role ?? null);
41
+ }, [serverUser, serverProfile]);
42
42
 
43
43
  const isAdmin = role === 'ADMIN';
44
44
  const isWriter = role === 'WRITER';
@@ -49,11 +49,11 @@ export const AuthProvider = ({ children, serverUser, serverProfile }: AuthProvid
49
49
  profile,
50
50
  role,
51
51
  isLoading,
52
- isAdmin,
53
- isWriter,
54
- isUserRole,
55
- supabase: null,
56
- }), [user, profile, role, isLoading, isAdmin, isWriter, isUserRole]);
52
+ isAdmin,
53
+ isWriter,
54
+ isUserRole,
55
+ supabase: null,
56
+ }), [user, profile, role, isLoading, isAdmin, isWriter, isUserRole]);
57
57
 
58
58
  return (
59
59
  <AuthContext.Provider value={value}>
@@ -68,4 +68,4 @@ export const useAuth = () => {
68
68
  throw new Error('useAuth must be used within an AuthProvider');
69
69
  }
70
70
  return context;
71
- };
71
+ };