create-nextblock 0.16.1 → 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 (258) 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/sandboxResetSql.ts +3092 -3092
  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 -558
  41. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  42. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  43. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -304
  44. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +406 -406
  45. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
  46. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  47. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  48. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +254 -254
  49. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  50. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
  51. package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
  52. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
  53. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  54. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  55. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  56. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  57. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
  58. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
  59. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
  60. package/templates/nextblock-template/app/cms/layout.tsx +101 -101
  61. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  62. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  63. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  64. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -661
  65. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -404
  66. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -333
  67. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -87
  68. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -37
  69. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  70. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +649 -649
  71. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +618 -618
  72. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
  73. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  74. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  75. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  76. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  77. package/templates/nextblock-template/app/cms/revisions/actions.ts +332 -332
  78. package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
  79. package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
  80. package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
  81. package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
  82. package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
  83. package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
  84. package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
  85. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  86. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  87. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -227
  88. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  89. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  90. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  91. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
  92. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
  93. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
  94. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
  95. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  96. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  97. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  98. package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -514
  99. package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -529
  100. package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -57
  101. package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -448
  102. package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -93
  103. package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -46
  104. package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -47
  105. package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
  106. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  107. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  108. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  109. package/templates/nextblock-template/app/layout.tsx +671 -671
  110. package/templates/nextblock-template/app/lib/seo.ts +319 -319
  111. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  112. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  113. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -502
  114. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  115. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  116. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  117. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  118. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  119. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  120. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  121. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  122. package/templates/nextblock-template/app/providers.tsx +96 -96
  123. package/templates/nextblock-template/app/robots.ts +123 -123
  124. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -164
  125. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -57
  126. package/templates/nextblock-template/app/thread/layout.tsx +15 -15
  127. package/templates/nextblock-template/app/thread/page.tsx +98 -98
  128. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  129. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  130. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  131. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  132. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  133. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  134. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -312
  135. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  136. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  137. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -188
  138. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  139. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  140. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  141. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  142. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  143. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  144. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  145. package/templates/nextblock-template/components/Header.tsx +38 -38
  146. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  147. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  148. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -378
  149. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -426
  150. package/templates/nextblock-template/components/SiteScripts.tsx +56 -56
  151. package/templates/nextblock-template/components/StaffReplies.tsx +102 -102
  152. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  153. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  154. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  155. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  156. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  157. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  158. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  159. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -18
  160. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -20
  161. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -25
  162. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -385
  163. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  164. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -157
  165. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -34
  166. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -612
  167. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  168. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  169. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  170. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  171. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -32
  172. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  173. package/templates/nextblock-template/components/form-message.tsx +32 -32
  174. package/templates/nextblock-template/components/seo/GenerateMetaButton.tsx +137 -137
  175. package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +244 -244
  176. package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -749
  177. package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -195
  178. package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -144
  179. package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -243
  180. package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -110
  181. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  182. package/templates/nextblock-template/components/theme-icon.tsx +78 -78
  183. package/templates/nextblock-template/components/theme-switcher.tsx +85 -85
  184. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  185. package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -44
  186. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  187. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +77 -77
  188. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -372
  189. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -309
  190. package/templates/nextblock-template/docs/README.md +42 -42
  191. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12506 -12506
  192. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  193. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  194. package/templates/nextblock-template/index.d.ts +7 -7
  195. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  196. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +134 -134
  197. package/templates/nextblock-template/lib/blocks/blockColors.ts +175 -175
  198. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -761
  199. package/templates/nextblock-template/lib/blocks/inlineScriptNonce.ts +20 -20
  200. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -64
  201. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -0
  202. package/templates/nextblock-template/lib/cms/payments-reminder.ts +30 -23
  203. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -42
  204. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -162
  205. package/templates/nextblock-template/lib/config/email-settings.ts +323 -323
  206. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -57
  207. package/templates/nextblock-template/lib/cortex-ai/alt-text-request.ts +86 -86
  208. package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -60
  209. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -59
  210. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -39
  211. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -70
  212. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -93
  213. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -157
  214. package/templates/nextblock-template/lib/messages/threads.ts +579 -579
  215. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  216. package/templates/nextblock-template/lib/publishing/viewUrl.ts +26 -26
  217. package/templates/nextblock-template/lib/search/types.ts +27 -27
  218. package/templates/nextblock-template/lib/seo/alt-text-write-back.test.ts +154 -154
  219. package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -109
  220. package/templates/nextblock-template/lib/seo/block-content.ts +123 -123
  221. package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -242
  222. package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -204
  223. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +140 -140
  224. package/templates/nextblock-template/lib/seo/page-document.test.ts +350 -350
  225. package/templates/nextblock-template/lib/seo/page-document.ts +412 -412
  226. package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -479
  227. package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -466
  228. package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -102
  229. package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -41
  230. package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -370
  231. package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -510
  232. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +177 -177
  233. package/templates/nextblock-template/lib/site-scripts/revisions.ts +71 -71
  234. package/templates/nextblock-template/lib/site-scripts/types.ts +46 -46
  235. package/templates/nextblock-template/lib/site-url.test.ts +89 -89
  236. package/templates/nextblock-template/lib/site-url.ts +102 -102
  237. package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -124
  238. package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -31
  239. package/templates/nextblock-template/lib/themes/tokens.ts +143 -143
  240. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  241. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  242. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  243. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  244. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  245. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  246. package/templates/nextblock-template/next-env.d.ts +1 -2
  247. package/templates/nextblock-template/package.json +1 -1
  248. package/templates/nextblock-template/postcss.config.js +6 -6
  249. package/templates/nextblock-template/scripts/backup.js +115 -115
  250. package/templates/nextblock-template/scripts/restore.js +385 -385
  251. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  252. package/templates/nextblock-template/tailwind.config.js +25 -25
  253. package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
  254. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  255. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  256. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
  257. package/templates/nextblock-template/tools/update.mjs +1303 -1303
  258. 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
+ };