create-nextblock 0.14.1 → 0.14.4

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 (263) hide show
  1. package/docker-template/.dockerignore +23 -23
  2. package/docker-template/.env.docker.example +69 -69
  3. package/docker-template/docker/db/init/99-jwt.sql +6 -6
  4. package/docker-template/docker/db/init/99-roles.sql +25 -25
  5. package/docker-template/docker/kong/kong.yml +112 -112
  6. package/docker-template/scripts/docker-setup.mjs +310 -310
  7. package/libs/db/tsconfig.lib.json +3 -3
  8. package/libs/editor/tsconfig.lib.json +3 -3
  9. package/libs/ui/tsconfig.lib.json +3 -3
  10. package/libs/utils/tsconfig.json +3 -3
  11. package/package.json +1 -1
  12. package/project.json +19 -19
  13. package/templates/nextblock-template/.browserslistrc +11 -11
  14. package/templates/nextblock-template/.dockerignore +23 -23
  15. package/templates/nextblock-template/README.md +34 -34
  16. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  17. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  18. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
  19. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
  20. package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
  21. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  22. package/templates/nextblock-template/app/actions/consent.ts +57 -57
  23. package/templates/nextblock-template/app/actions/postActions.ts +146 -146
  24. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  25. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  26. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  27. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  28. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  29. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  30. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  31. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
  32. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5881 -5623
  33. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  34. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  35. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  36. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  37. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  38. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  39. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  40. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
  41. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  42. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  43. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +2 -2
  44. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +17 -22
  45. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  46. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +13 -19
  47. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  48. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  49. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +45 -34
  50. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +4 -2
  51. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
  52. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  53. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  54. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  55. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  56. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  57. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  58. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  59. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  60. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  61. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  62. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  63. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  64. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  65. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  66. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  67. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  68. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  69. package/templates/nextblock-template/app/cms/layout.tsx +73 -73
  70. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  71. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  72. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  73. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  74. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  75. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  76. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  77. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  78. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  79. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  80. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  81. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  82. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  83. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  84. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
  85. package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
  86. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  87. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  88. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  89. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +688 -688
  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/page.tsx +28 -28
  93. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  94. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  95. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -0
  96. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -0
  97. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -24
  98. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -0
  99. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  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/components/DeleteLogoButton.tsx +21 -21
  103. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +23 -21
  104. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  105. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  106. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  107. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
  108. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  109. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  110. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +9 -7
  111. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  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 +49 -0
  116. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  117. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  118. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  119. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  120. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  121. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  122. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  123. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  124. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  125. package/templates/nextblock-template/app/providers.tsx +16 -4
  126. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  127. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  128. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  129. package/templates/nextblock-template/app/sitemap.ts +130 -130
  130. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  131. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  132. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  133. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  134. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  135. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  136. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  137. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  138. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  139. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  140. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  141. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  142. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  143. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  144. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  145. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
  146. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +7 -10
  147. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  148. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  149. package/templates/nextblock-template/components/form-message.tsx +32 -32
  150. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
  151. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
  152. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  153. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  154. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  155. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  156. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
  157. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  158. package/templates/nextblock-template/components/theme-icon.tsx +78 -0
  159. package/templates/nextblock-template/components/theme-switcher.tsx +77 -82
  160. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  161. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  162. package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -0
  163. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  164. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  165. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  166. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  167. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  168. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  169. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +279 -202
  170. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
  171. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
  172. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  173. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  174. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
  175. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
  176. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
  177. package/templates/nextblock-template/docs/README.md +39 -39
  178. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  179. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  180. package/templates/nextblock-template/index.d.ts +7 -7
  181. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  182. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  183. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  184. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  185. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  186. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  187. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +114 -0
  188. package/templates/nextblock-template/lib/blocks/blockColors.ts +132 -0
  189. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +17 -1
  190. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  191. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
  192. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  193. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  194. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  195. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  196. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  197. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  198. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  199. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  200. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  201. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  202. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  203. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  204. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  205. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  206. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  207. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  208. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  209. package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
  210. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  211. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
  212. package/templates/nextblock-template/lib/media/youtube.ts +99 -99
  213. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  214. package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
  215. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  216. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  217. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  218. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  219. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  220. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  221. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  222. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  223. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  224. package/templates/nextblock-template/lib/search/types.ts +27 -27
  225. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  226. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  227. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  228. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +15 -10
  229. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  230. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  231. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  232. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  233. package/templates/nextblock-template/lib/site-url.ts +48 -48
  234. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  235. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  236. package/templates/nextblock-template/lib/themes/buildThemeCss.test.ts +163 -0
  237. package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -0
  238. package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -0
  239. package/templates/nextblock-template/lib/themes/tokens.ts +143 -0
  240. package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
  241. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  242. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  243. package/templates/nextblock-template/next-env.d.ts +2 -2
  244. package/templates/nextblock-template/package.json +1 -1
  245. package/templates/nextblock-template/postcss.config.js +6 -6
  246. package/templates/nextblock-template/scripts/backup.js +115 -115
  247. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  248. package/templates/nextblock-template/scripts/restore.js +385 -385
  249. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  250. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  251. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  252. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  253. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  254. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  255. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  256. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  257. package/templates/nextblock-template/scripts/verify-site-themes.ts +63 -0
  258. package/templates/nextblock-template/tailwind.config.js +25 -25
  259. package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
  260. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  261. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  262. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
  263. package/tsconfig.base.json +3 -3
@@ -1,222 +1,222 @@
1
- import 'server-only';
2
- // Derives the dashboard onboarding checklist live from configuration state. Every check
3
- // reads public/service-role data only, so it works for both ADMIN and WRITER dashboards.
4
- import { createClient } from '@nextblock-cms/db/server';
5
- import { getStoreConfigStatus } from '@nextblock-cms/ecommerce/server';
6
- import { getEmailPublicSettings } from '../config/email-settings';
7
- import { getPrivacySettings } from '../privacy/settings';
8
- import { detectChannel } from '../setup/env-status';
9
- import { getSystemConfiguration } from '../setup/system-config';
10
- import { selfActionsSettingsUrl } from '../updates/repo-identity';
11
- import { isGithubConnectAvailable } from '../updates/github-device';
12
-
13
- export type OnboardingStep = {
14
- key: string;
15
- title: string;
16
- description: string;
17
- href: string;
18
- done: boolean;
19
- optional: boolean;
20
- /** When true, render the CTA as an external link (new tab) instead of an in-app route. */
21
- isExternal?: boolean;
22
- /** When true, render the device-flow "Connect GitHub" control instead of a link. */
23
- connectGithub?: boolean;
24
- };
25
-
26
- export type OnboardingStatus = {
27
- steps: OnboardingStep[];
28
- completed: number;
29
- total: number;
30
- dismissed: boolean;
31
- };
32
-
33
- // Seeded defaults (libs/db baseline seed 00000000000003 + migration 00000000000004). The
34
- // branding/copyright steps count as "done" only when the value has been customized away from
35
- // these — a fresh install ships with the seeds, so a plain presence check would mark every
36
- // step complete immediately.
37
- const SEEDED_SITE_TITLE = 'NextBlock™ CMS';
38
- // Every object_key a fresh install may ship as the DEFAULT logo — the original baseline WebP
39
- // and the migration-00000000000004 email-safe PNG (which repoints the seeded default). A logo
40
- // counts as customized only when it is none of these; a new default swap must be added here or
41
- // branding falsely reads as done out of the box. Mirrors BUNDLED_PUBLIC_MEDIA_KEYS in
42
- // lib/media/resolveMediaUrl.ts.
43
- const SEEDED_LOGO_OBJECT_KEYS = new Set<string>([
44
- 'images/nextblock-logo-small.webp',
45
- 'images/nextblock-logo-button-tiny.png',
46
- ]);
47
- const SEEDED_COPYRIGHT: Record<string, string> = {
48
- en: '© {year} Nextblock CMS. All rights reserved.',
49
- fr: '© {year} Nextblock CMS. Tous droits réservés.',
50
- };
51
-
52
- /** True when at least one language's copyright text is set AND differs from the seed. */
53
- function hasCustomCopyright(value: unknown): boolean {
54
- if (!value || typeof value !== 'object') return false;
55
- return Object.entries(value as Record<string, unknown>).some(([lang, v]) => {
56
- if (typeof v !== 'string') return false;
57
- const trimmed = v.trim();
58
- return trimmed.length > 0 && trimmed !== (SEEDED_COPYRIGHT[lang] ?? '');
59
- });
60
- }
61
-
62
- /** Pull the active logo's media object_key from the (to-one) embedded relation. */
63
- function extractLogoObjectKey(logoRow: unknown): string | null {
64
- if (!logoRow || typeof logoRow !== 'object') return null;
65
- const media = (logoRow as { media?: unknown }).media;
66
- const record = Array.isArray(media) ? media[0] : media;
67
- if (!record || typeof record !== 'object') return null;
68
- const key = (record as { object_key?: unknown }).object_key;
69
- return typeof key === 'string' ? key : null;
70
- }
71
-
72
- export async function getOnboardingStatus(opts: {
73
- isEcommerceActive: boolean;
74
- }): Promise<OnboardingStatus> {
75
- const supabase = createClient();
76
-
77
- const [{ data: settingRows }, { data: logoRow }, emailPublic, privacy] = await Promise.all([
78
- supabase
79
- .from('site_settings')
80
- .select('key, value')
81
- .in('key', ['footer_copyright', 'bot_protection_public', 'onboarding_state', 'site_title']),
82
- supabase
83
- .from('logos')
84
- .select('media:media_id(object_key)')
85
- .order('created_at', { ascending: false })
86
- .limit(1)
87
- .maybeSingle(),
88
- getEmailPublicSettings(),
89
- getPrivacySettings(),
90
- ]);
91
-
92
- const rows = new Map((settingRows ?? []).map((r) => [r.key, r.value]));
93
- const botPublic = (rows.get('bot_protection_public') ?? {}) as Record<string, unknown>;
94
- const onboardingState = (rows.get('onboarding_state') ?? {}) as Record<string, unknown>;
95
-
96
- const siteTitleRaw = rows.get('site_title');
97
- const siteTitle = typeof siteTitleRaw === 'string' ? siteTitleRaw.trim() : '';
98
- const siteTitleCustomized = siteTitle.length > 0 && siteTitle !== SEEDED_SITE_TITLE;
99
- const logoObjectKey = extractLogoObjectKey(logoRow);
100
- const logoCustomized = Boolean(logoObjectKey) && !SEEDED_LOGO_OBJECT_KEYS.has(logoObjectKey ?? '');
101
-
102
- // Branding is "done" once the user renames the site or uploads their own logo — not merely
103
- // because the seeded NextBlock title/logo exist.
104
- const brandingDone = siteTitleCustomized || logoCustomized;
105
- const copyrightDone = hasCustomCopyright(rows.get('footer_copyright'));
106
- const emailDone = Boolean(emailPublic.host) || Boolean(process.env['SMTP_HOST']);
107
- const analyticsDone = Boolean(privacy.gtm_id);
108
- const botProvider = typeof botPublic['provider'] === 'string' ? (botPublic['provider'] as string) : 'none';
109
- const botDone = botProvider !== 'none' && botProvider !== '';
110
-
111
- const steps: OnboardingStep[] = [
112
- {
113
- key: 'admin',
114
- title: 'Create your admin account',
115
- description: 'Your administrator account is set up.',
116
- href: '/cms/users',
117
- done: true,
118
- optional: false,
119
- },
120
- {
121
- key: 'branding',
122
- title: 'Add your branding',
123
- description: 'Upload your logo and set your site identity.',
124
- href: '/cms/settings/logos',
125
- done: brandingDone,
126
- optional: false,
127
- },
128
- {
129
- key: 'copyright',
130
- title: 'Set your copyright / footer',
131
- description: 'Configure the footer copyright shown across your site.',
132
- href: '/cms/settings/copyright',
133
- done: copyrightDone,
134
- optional: false,
135
- },
136
- {
137
- key: 'email',
138
- title: 'Configure email (SMTP)',
139
- description: 'Enable verification emails, password resets, and notifications.',
140
- href: '/cms/settings/email',
141
- done: emailDone,
142
- optional: false,
143
- },
144
- {
145
- key: 'analytics',
146
- title: 'Connect analytics',
147
- description: 'Add a Google Tag Manager ID for consent-gated analytics.',
148
- href: '/cms/settings/google-analytics',
149
- done: analyticsDone,
150
- optional: true,
151
- },
152
- {
153
- key: 'bot',
154
- title: 'Enable bot protection',
155
- description: 'Protect your sign-up and sign-in forms with a CAPTCHA.',
156
- href: '/cms/settings/bot-protection',
157
- done: botDone,
158
- optional: true,
159
- },
160
- ];
161
-
162
- if (opts.isEcommerceActive) {
163
- let paymentsDone = false;
164
- try {
165
- const storeStatus = await getStoreConfigStatus();
166
- paymentsDone = storeStatus.stripe.hasKeys || storeStatus.freemius.hasKeys;
167
- } catch {
168
- paymentsDone = false;
169
- }
170
- // Insert Payments before the optional steps.
171
- steps.splice(4, 0, {
172
- key: 'payments',
173
- title: 'Set up payment providers',
174
- description: 'Add your Stripe and/or Freemius API keys to accept payments.',
175
- href: '/cms/payments',
176
- done: paymentsDone,
177
- optional: false,
178
- });
179
- }
180
-
181
- // Git-backed (Vercel 1-click / fork) installs: remind the operator to enable GitHub
182
- // Actions so the upstream sync workflow can run. "done" flips once the background poll
183
- // (maybeRefreshUpstreamStatus) has seen the workflow run at least once.
184
- if (detectChannel() === 'vercel') {
185
- let actionsActive = false;
186
- try {
187
- const config = await getSystemConfiguration();
188
- const upstream = config.settings?.['upstream_status'] as
189
- | { actions_active?: boolean }
190
- | undefined;
191
- actionsActive = upstream?.actions_active === true;
192
- } catch {
193
- actionsActive = false;
194
- }
195
- const canConnect = !actionsActive && isGithubConnectAvailable();
196
- steps.push({
197
- key: 'github-actions',
198
- title: 'Automatic updates (GitHub Actions)',
199
- description: actionsActive
200
- ? 'The daily upstream-sync workflow is active for your repository.'
201
- : canConnect
202
- ? 'Connect GitHub to install the upstream-sync workflow into your repo — Vercel’s 1-click deploy can’t copy it automatically.'
203
- : 'Vercel deploys have GitHub Actions on by default — this completes once GitHub registers the sync workflow on your default branch. A manually forked repo needs Actions enabled under Settings → Actions.',
204
- href:
205
- selfActionsSettingsUrl() ??
206
- 'https://github.com/nextblock-cms/nextblock/blob/HEAD/docs/13-STAYING-UP-TO-DATE.md',
207
- done: actionsActive,
208
- optional: true,
209
- isExternal: true,
210
- connectGithub: canConnect,
211
- });
212
- }
213
-
214
- const completed = steps.filter((s) => s.done).length;
215
-
216
- return {
217
- steps,
218
- completed,
219
- total: steps.length,
220
- dismissed: onboardingState['dismissed'] === true,
221
- };
222
- }
1
+ import 'server-only';
2
+ // Derives the dashboard onboarding checklist live from configuration state. Every check
3
+ // reads public/service-role data only, so it works for both ADMIN and WRITER dashboards.
4
+ import { createClient } from '@nextblock-cms/db/server';
5
+ import { getStoreConfigStatus } from '@nextblock-cms/ecommerce/server';
6
+ import { getEmailPublicSettings } from '../config/email-settings';
7
+ import { getPrivacySettings } from '../privacy/settings';
8
+ import { detectChannel } from '../setup/env-status';
9
+ import { getSystemConfiguration } from '../setup/system-config';
10
+ import { selfActionsSettingsUrl } from '../updates/repo-identity';
11
+ import { isGithubConnectAvailable } from '../updates/github-device';
12
+
13
+ export type OnboardingStep = {
14
+ key: string;
15
+ title: string;
16
+ description: string;
17
+ href: string;
18
+ done: boolean;
19
+ optional: boolean;
20
+ /** When true, render the CTA as an external link (new tab) instead of an in-app route. */
21
+ isExternal?: boolean;
22
+ /** When true, render the device-flow "Connect GitHub" control instead of a link. */
23
+ connectGithub?: boolean;
24
+ };
25
+
26
+ export type OnboardingStatus = {
27
+ steps: OnboardingStep[];
28
+ completed: number;
29
+ total: number;
30
+ dismissed: boolean;
31
+ };
32
+
33
+ // Seeded defaults (libs/db baseline seed 00000000000003 + migration 00000000000004). The
34
+ // branding/copyright steps count as "done" only when the value has been customized away from
35
+ // these — a fresh install ships with the seeds, so a plain presence check would mark every
36
+ // step complete immediately.
37
+ const SEEDED_SITE_TITLE = 'NextBlock™ CMS';
38
+ // Every object_key a fresh install may ship as the DEFAULT logo — the original baseline WebP
39
+ // and the migration-00000000000004 email-safe PNG (which repoints the seeded default). A logo
40
+ // counts as customized only when it is none of these; a new default swap must be added here or
41
+ // branding falsely reads as done out of the box. Mirrors BUNDLED_PUBLIC_MEDIA_KEYS in
42
+ // lib/media/resolveMediaUrl.ts.
43
+ const SEEDED_LOGO_OBJECT_KEYS = new Set<string>([
44
+ 'images/nextblock-logo-small.webp',
45
+ 'images/nextblock-logo-button-tiny.png',
46
+ ]);
47
+ const SEEDED_COPYRIGHT: Record<string, string> = {
48
+ en: '© {year} Nextblock CMS. All rights reserved.',
49
+ fr: '© {year} Nextblock CMS. Tous droits réservés.',
50
+ };
51
+
52
+ /** True when at least one language's copyright text is set AND differs from the seed. */
53
+ function hasCustomCopyright(value: unknown): boolean {
54
+ if (!value || typeof value !== 'object') return false;
55
+ return Object.entries(value as Record<string, unknown>).some(([lang, v]) => {
56
+ if (typeof v !== 'string') return false;
57
+ const trimmed = v.trim();
58
+ return trimmed.length > 0 && trimmed !== (SEEDED_COPYRIGHT[lang] ?? '');
59
+ });
60
+ }
61
+
62
+ /** Pull the active logo's media object_key from the (to-one) embedded relation. */
63
+ function extractLogoObjectKey(logoRow: unknown): string | null {
64
+ if (!logoRow || typeof logoRow !== 'object') return null;
65
+ const media = (logoRow as { media?: unknown }).media;
66
+ const record = Array.isArray(media) ? media[0] : media;
67
+ if (!record || typeof record !== 'object') return null;
68
+ const key = (record as { object_key?: unknown }).object_key;
69
+ return typeof key === 'string' ? key : null;
70
+ }
71
+
72
+ export async function getOnboardingStatus(opts: {
73
+ isEcommerceActive: boolean;
74
+ }): Promise<OnboardingStatus> {
75
+ const supabase = createClient();
76
+
77
+ const [{ data: settingRows }, { data: logoRow }, emailPublic, privacy] = await Promise.all([
78
+ supabase
79
+ .from('site_settings')
80
+ .select('key, value')
81
+ .in('key', ['footer_copyright', 'bot_protection_public', 'onboarding_state', 'site_title']),
82
+ supabase
83
+ .from('logos')
84
+ .select('media:media_id(object_key)')
85
+ .order('created_at', { ascending: false })
86
+ .limit(1)
87
+ .maybeSingle(),
88
+ getEmailPublicSettings(),
89
+ getPrivacySettings(),
90
+ ]);
91
+
92
+ const rows = new Map((settingRows ?? []).map((r) => [r.key, r.value]));
93
+ const botPublic = (rows.get('bot_protection_public') ?? {}) as Record<string, unknown>;
94
+ const onboardingState = (rows.get('onboarding_state') ?? {}) as Record<string, unknown>;
95
+
96
+ const siteTitleRaw = rows.get('site_title');
97
+ const siteTitle = typeof siteTitleRaw === 'string' ? siteTitleRaw.trim() : '';
98
+ const siteTitleCustomized = siteTitle.length > 0 && siteTitle !== SEEDED_SITE_TITLE;
99
+ const logoObjectKey = extractLogoObjectKey(logoRow);
100
+ const logoCustomized = Boolean(logoObjectKey) && !SEEDED_LOGO_OBJECT_KEYS.has(logoObjectKey ?? '');
101
+
102
+ // Branding is "done" once the user renames the site or uploads their own logo — not merely
103
+ // because the seeded NextBlock title/logo exist.
104
+ const brandingDone = siteTitleCustomized || logoCustomized;
105
+ const copyrightDone = hasCustomCopyright(rows.get('footer_copyright'));
106
+ const emailDone = Boolean(emailPublic.host) || Boolean(process.env['SMTP_HOST']);
107
+ const analyticsDone = Boolean(privacy.gtm_id);
108
+ const botProvider = typeof botPublic['provider'] === 'string' ? (botPublic['provider'] as string) : 'none';
109
+ const botDone = botProvider !== 'none' && botProvider !== '';
110
+
111
+ const steps: OnboardingStep[] = [
112
+ {
113
+ key: 'admin',
114
+ title: 'Create your admin account',
115
+ description: 'Your administrator account is set up.',
116
+ href: '/cms/users',
117
+ done: true,
118
+ optional: false,
119
+ },
120
+ {
121
+ key: 'branding',
122
+ title: 'Add your branding',
123
+ description: 'Upload your logo and set your site identity.',
124
+ href: '/cms/settings/logos',
125
+ done: brandingDone,
126
+ optional: false,
127
+ },
128
+ {
129
+ key: 'copyright',
130
+ title: 'Set your copyright / footer',
131
+ description: 'Configure the footer copyright shown across your site.',
132
+ href: '/cms/settings/copyright',
133
+ done: copyrightDone,
134
+ optional: false,
135
+ },
136
+ {
137
+ key: 'email',
138
+ title: 'Configure email (SMTP)',
139
+ description: 'Enable verification emails, password resets, and notifications.',
140
+ href: '/cms/settings/email',
141
+ done: emailDone,
142
+ optional: false,
143
+ },
144
+ {
145
+ key: 'analytics',
146
+ title: 'Connect analytics',
147
+ description: 'Add a Google Tag Manager ID for consent-gated analytics.',
148
+ href: '/cms/settings/google-analytics',
149
+ done: analyticsDone,
150
+ optional: true,
151
+ },
152
+ {
153
+ key: 'bot',
154
+ title: 'Enable bot protection',
155
+ description: 'Protect your sign-up and sign-in forms with a CAPTCHA.',
156
+ href: '/cms/settings/bot-protection',
157
+ done: botDone,
158
+ optional: true,
159
+ },
160
+ ];
161
+
162
+ if (opts.isEcommerceActive) {
163
+ let paymentsDone = false;
164
+ try {
165
+ const storeStatus = await getStoreConfigStatus();
166
+ paymentsDone = storeStatus.stripe.hasKeys || storeStatus.freemius.hasKeys;
167
+ } catch {
168
+ paymentsDone = false;
169
+ }
170
+ // Insert Payments before the optional steps.
171
+ steps.splice(4, 0, {
172
+ key: 'payments',
173
+ title: 'Set up payment providers',
174
+ description: 'Add your Stripe and/or Freemius API keys to accept payments.',
175
+ href: '/cms/payments',
176
+ done: paymentsDone,
177
+ optional: false,
178
+ });
179
+ }
180
+
181
+ // Git-backed (Vercel 1-click / fork) installs: remind the operator to enable GitHub
182
+ // Actions so the upstream sync workflow can run. "done" flips once the background poll
183
+ // (maybeRefreshUpstreamStatus) has seen the workflow run at least once.
184
+ if (detectChannel() === 'vercel') {
185
+ let actionsActive = false;
186
+ try {
187
+ const config = await getSystemConfiguration();
188
+ const upstream = config.settings?.['upstream_status'] as
189
+ | { actions_active?: boolean }
190
+ | undefined;
191
+ actionsActive = upstream?.actions_active === true;
192
+ } catch {
193
+ actionsActive = false;
194
+ }
195
+ const canConnect = !actionsActive && isGithubConnectAvailable();
196
+ steps.push({
197
+ key: 'github-actions',
198
+ title: 'Automatic updates (GitHub Actions)',
199
+ description: actionsActive
200
+ ? 'The daily upstream-sync workflow is active for your repository.'
201
+ : canConnect
202
+ ? 'Connect GitHub to install the upstream-sync workflow into your repo — Vercel’s 1-click deploy can’t copy it automatically.'
203
+ : 'Vercel deploys have GitHub Actions on by default — this completes once GitHub registers the sync workflow on your default branch. A manually forked repo needs Actions enabled under Settings → Actions.',
204
+ href:
205
+ selfActionsSettingsUrl() ??
206
+ 'https://github.com/nextblock-cms/nextblock/blob/HEAD/docs/13-STAYING-UP-TO-DATE.md',
207
+ done: actionsActive,
208
+ optional: true,
209
+ isExternal: true,
210
+ connectGithub: canConnect,
211
+ });
212
+ }
213
+
214
+ const completed = steps.filter((s) => s.done).length;
215
+
216
+ return {
217
+ steps,
218
+ completed,
219
+ total: steps.length,
220
+ dismissed: onboardingState['dismissed'] === true,
221
+ };
222
+ }
@@ -1,60 +1,60 @@
1
- import type { Database } from '@nextblock-cms/db';
2
-
3
- type BlockRow = Database['public']['Tables']['blocks']['Row'];
4
-
5
- const WORDS_PER_MINUTE = 200;
6
-
7
- function extractPlainTextFromHtml(html: string) {
8
- return html
9
- .replace(/<style[\s\S]*?<\/style>/gi, ' ')
10
- .replace(/<script[\s\S]*?<\/script>/gi, ' ')
11
- .replace(/<[^>]+>/g, ' ')
12
- .replace(/&nbsp;/g, ' ')
13
- .replace(/&amp;/g, '&')
14
- .replace(/\s+/g, ' ')
15
- .trim();
16
- }
17
-
18
- function countWords(text: string) {
19
- if (!text) {
20
- return 0;
21
- }
22
-
23
- return text.split(' ').length;
24
- }
25
-
26
- export function estimateReadTimeMinutesFromHtmlFragments(htmlFragments: string[]) {
27
- const words = htmlFragments.reduce((total, html) => {
28
- const plainText = extractPlainTextFromHtml(html);
29
- return total + countWords(plainText);
30
- }, 0);
31
-
32
- return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
33
- }
34
-
35
- export function estimateReadTimeMinutesFromBlocks(
36
- blocks: Array<Pick<BlockRow, 'block_type' | 'content'>> | undefined
37
- ) {
38
- if (!blocks || blocks.length === 0) {
39
- return 1;
40
- }
41
-
42
- const htmlFragments = blocks.reduce<string[]>((fragments, block) => {
43
- if (block.block_type !== 'text') {
44
- return fragments;
45
- }
46
-
47
- const html =
48
- typeof block.content === 'object' && block.content && 'html_content' in block.content
49
- ? String((block.content as { html_content?: string }).html_content ?? '')
50
- : '';
51
-
52
- if (html) {
53
- fragments.push(html);
54
- }
55
-
56
- return fragments;
57
- }, []);
58
-
59
- return estimateReadTimeMinutesFromHtmlFragments(htmlFragments);
60
- }
1
+ import type { Database } from '@nextblock-cms/db';
2
+
3
+ type BlockRow = Database['public']['Tables']['blocks']['Row'];
4
+
5
+ const WORDS_PER_MINUTE = 200;
6
+
7
+ function extractPlainTextFromHtml(html: string) {
8
+ return html
9
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
10
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
11
+ .replace(/<[^>]+>/g, ' ')
12
+ .replace(/&nbsp;/g, ' ')
13
+ .replace(/&amp;/g, '&')
14
+ .replace(/\s+/g, ' ')
15
+ .trim();
16
+ }
17
+
18
+ function countWords(text: string) {
19
+ if (!text) {
20
+ return 0;
21
+ }
22
+
23
+ return text.split(' ').length;
24
+ }
25
+
26
+ export function estimateReadTimeMinutesFromHtmlFragments(htmlFragments: string[]) {
27
+ const words = htmlFragments.reduce((total, html) => {
28
+ const plainText = extractPlainTextFromHtml(html);
29
+ return total + countWords(plainText);
30
+ }, 0);
31
+
32
+ return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
33
+ }
34
+
35
+ export function estimateReadTimeMinutesFromBlocks(
36
+ blocks: Array<Pick<BlockRow, 'block_type' | 'content'>> | undefined
37
+ ) {
38
+ if (!blocks || blocks.length === 0) {
39
+ return 1;
40
+ }
41
+
42
+ const htmlFragments = blocks.reduce<string[]>((fragments, block) => {
43
+ if (block.block_type !== 'text') {
44
+ return fragments;
45
+ }
46
+
47
+ const html =
48
+ typeof block.content === 'object' && block.content && 'html_content' in block.content
49
+ ? String((block.content as { html_content?: string }).html_content ?? '')
50
+ : '';
51
+
52
+ if (html) {
53
+ fragments.push(html);
54
+ }
55
+
56
+ return fragments;
57
+ }, []);
58
+
59
+ return estimateReadTimeMinutesFromHtmlFragments(htmlFragments);
60
+ }