create-nextblock 0.15.10 → 0.16.2

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 (266) 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 -0
  26. package/templates/nextblock-template/app/api/ai/seo/metadata/route.ts +186 -0
  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/sandboxResetSql.ts +3092 -2900
  30. package/templates/nextblock-template/app/api/mcp/route.ts +415 -415
  31. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  32. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  33. package/templates/nextblock-template/app/api/view/route.ts +114 -114
  34. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  35. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  36. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  37. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  38. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  39. package/templates/nextblock-template/app/checkout/page.tsx +57 -57
  40. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +558 -552
  41. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +27 -0
  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 +180 -3
  46. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +171 -6
  47. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
  48. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  49. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  50. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +12 -3
  51. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  52. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
  53. package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
  54. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
  55. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  56. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  57. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  58. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  59. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
  60. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
  61. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
  62. package/templates/nextblock-template/app/cms/layout.tsx +101 -101
  63. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  64. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  65. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +177 -2
  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/[id]/edit/EditPageClient.tsx +28 -0
  74. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +248 -5
  75. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +33 -0
  76. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +240 -5
  77. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
  78. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  79. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  80. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  81. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  82. package/templates/nextblock-template/app/cms/revisions/actions.ts +332 -332
  83. package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
  84. package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
  85. package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
  86. package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
  87. package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
  88. package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
  89. package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
  90. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  91. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  92. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -227
  93. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  94. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  95. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  96. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
  97. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
  98. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
  99. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
  100. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  101. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  102. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  103. package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -0
  104. package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -0
  105. package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -0
  106. package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -0
  107. package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -0
  108. package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -0
  109. package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -0
  110. package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
  111. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  112. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  113. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  114. package/templates/nextblock-template/app/layout.tsx +671 -671
  115. package/templates/nextblock-template/app/lib/seo.ts +319 -319
  116. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  117. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  118. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -502
  119. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  120. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  121. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  122. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  123. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  124. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  125. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  126. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  127. package/templates/nextblock-template/app/providers.tsx +96 -96
  128. package/templates/nextblock-template/app/robots.ts +123 -0
  129. package/templates/nextblock-template/app/sitemap.ts +1 -1
  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 -0
  181. package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +244 -0
  182. package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -0
  183. package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -0
  184. package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -0
  185. package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -0
  186. package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -0
  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 -0
  214. package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -0
  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 -0
  225. package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -0
  226. package/templates/nextblock-template/lib/seo/block-content.ts +123 -0
  227. package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -0
  228. package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -0
  229. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +140 -0
  230. package/templates/nextblock-template/lib/seo/page-document.test.ts +350 -0
  231. package/templates/nextblock-template/lib/seo/page-document.ts +412 -0
  232. package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -0
  233. package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -0
  234. package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -0
  235. package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -0
  236. package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -0
  237. package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -0
  238. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +177 -167
  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 +0 -1
  253. package/templates/nextblock-template/package.json +1 -1
  254. package/templates/nextblock-template/postcss.config.js +6 -6
  255. package/templates/nextblock-template/proxy.ts +240 -20
  256. package/templates/nextblock-template/scripts/backup.js +115 -115
  257. package/templates/nextblock-template/scripts/restore.js +385 -385
  258. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  259. package/templates/nextblock-template/tailwind.config.js +25 -25
  260. package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
  261. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  262. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  263. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
  264. package/templates/nextblock-template/tools/update.mjs +1303 -1303
  265. package/tsconfig.base.json +3 -3
  266. package/templates/nextblock-template/app/robots.txt/route.ts +0 -32
@@ -0,0 +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,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
+ };
@@ -1,44 +1,44 @@
1
- "use client";
2
-
3
- import { createContext, useContext, useMemo, type ReactNode } from "react";
4
-
5
- /** The switcher-facing shape of a row in `site_themes`. */
6
- export interface ThemeCatalogEntry {
7
- slug: string;
8
- name: string;
9
- /** lucide-react icon name. */
10
- icon: string;
11
- colorScheme: "light" | "dark";
12
- }
13
-
14
- /**
15
- * Themes are read once on the server in app/layout.tsx and handed to the client
16
- * through this context, so the switcher never fetches and never hardcodes a
17
- * list. Falls back to the palette shipped in libs/ui/src/styles/theme.css when
18
- * the table is empty or unreachable.
19
- */
20
- export const FALLBACK_THEME_CATALOG: ThemeCatalogEntry[] = [
21
- { slug: "light", name: "Light", icon: "Sun", colorScheme: "light" },
22
- { slug: "dark", name: "Dark", icon: "Moon", colorScheme: "dark" },
23
- { slug: "vibrant", name: "Vibrant", icon: "Zap", colorScheme: "dark" },
24
- ];
25
-
26
- const ThemeCatalogContext = createContext<ThemeCatalogEntry[]>(FALLBACK_THEME_CATALOG);
27
-
28
- export function ThemeCatalogProvider({
29
- themes,
30
- children,
31
- }: {
32
- themes?: ThemeCatalogEntry[];
33
- children: ReactNode;
34
- }) {
35
- const value = useMemo(
36
- () => (Array.isArray(themes) && themes.length > 0 ? themes : FALLBACK_THEME_CATALOG),
37
- [themes],
38
- );
39
- return <ThemeCatalogContext.Provider value={value}>{children}</ThemeCatalogContext.Provider>;
40
- }
41
-
42
- export function useThemeCatalog(): ThemeCatalogEntry[] {
43
- return useContext(ThemeCatalogContext);
44
- }
1
+ "use client";
2
+
3
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
4
+
5
+ /** The switcher-facing shape of a row in `site_themes`. */
6
+ export interface ThemeCatalogEntry {
7
+ slug: string;
8
+ name: string;
9
+ /** lucide-react icon name. */
10
+ icon: string;
11
+ colorScheme: "light" | "dark";
12
+ }
13
+
14
+ /**
15
+ * Themes are read once on the server in app/layout.tsx and handed to the client
16
+ * through this context, so the switcher never fetches and never hardcodes a
17
+ * list. Falls back to the palette shipped in libs/ui/src/styles/theme.css when
18
+ * the table is empty or unreachable.
19
+ */
20
+ export const FALLBACK_THEME_CATALOG: ThemeCatalogEntry[] = [
21
+ { slug: "light", name: "Light", icon: "Sun", colorScheme: "light" },
22
+ { slug: "dark", name: "Dark", icon: "Moon", colorScheme: "dark" },
23
+ { slug: "vibrant", name: "Vibrant", icon: "Zap", colorScheme: "dark" },
24
+ ];
25
+
26
+ const ThemeCatalogContext = createContext<ThemeCatalogEntry[]>(FALLBACK_THEME_CATALOG);
27
+
28
+ export function ThemeCatalogProvider({
29
+ themes,
30
+ children,
31
+ }: {
32
+ themes?: ThemeCatalogEntry[];
33
+ children: ReactNode;
34
+ }) {
35
+ const value = useMemo(
36
+ () => (Array.isArray(themes) && themes.length > 0 ? themes : FALLBACK_THEME_CATALOG),
37
+ [themes],
38
+ );
39
+ return <ThemeCatalogContext.Provider value={value}>{children}</ThemeCatalogContext.Provider>;
40
+ }
41
+
42
+ export function useThemeCatalog(): ThemeCatalogEntry[] {
43
+ return useContext(ThemeCatalogContext);
44
+ }