create-nextblock 0.13.10 → 0.13.11

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 (255) 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/productGridActions.ts +40 -40
  25. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  26. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  27. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  28. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  29. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  30. package/templates/nextblock-template/app/api/cms/ecommerce/product-picker/route.ts +151 -151
  31. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  32. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  33. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5749 -5609
  35. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  36. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  37. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  38. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  39. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  40. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  41. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  42. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
  43. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  44. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  45. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  46. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  47. package/templates/nextblock-template/app/cms/blocks/components/MultiEntityPicker.tsx +263 -263
  48. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  49. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
  50. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  51. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  52. package/templates/nextblock-template/app/cms/components/EcommerceActiveContext.tsx +27 -27
  53. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  54. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  55. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  56. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  57. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  58. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  59. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  60. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  61. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  62. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  63. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  64. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  65. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  66. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  67. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  68. package/templates/nextblock-template/app/cms/interactions/EmailRecipientsInput.tsx +189 -189
  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 +497 -497
  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/google-analytics/page.tsx +26 -26
  96. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -188
  97. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  98. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  99. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  100. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
  101. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  102. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  103. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  104. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
  105. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  106. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  107. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  108. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  109. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  110. package/templates/nextblock-template/app/cms/users/components/CreateUserForm.tsx +217 -217
  111. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  112. package/templates/nextblock-template/app/cms/users/new/page.tsx +44 -44
  113. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  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/setup/SetupWizard.tsx +678 -678
  123. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  124. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  125. package/templates/nextblock-template/app/sitemap.ts +130 -130
  126. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  127. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  128. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  129. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  130. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  131. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  132. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  133. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  134. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  135. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  136. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  137. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  138. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  139. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  140. package/templates/nextblock-template/components/blocks/ProductGridClient.tsx +114 -114
  141. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  142. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +8 -1
  143. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
  144. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +29 -23
  145. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  146. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  147. package/templates/nextblock-template/components/form-message.tsx +32 -32
  148. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +69 -0
  149. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -0
  150. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  151. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  152. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  153. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  154. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -464
  155. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  156. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  157. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  158. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  159. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  160. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  161. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  162. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  163. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  164. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  165. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
  166. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
  167. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
  168. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  169. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  170. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
  171. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
  172. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
  173. package/templates/nextblock-template/docs/README.md +39 -39
  174. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  175. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  176. package/templates/nextblock-template/index.d.ts +7 -7
  177. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  178. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  179. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  180. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  181. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  182. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +78 -78
  183. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  184. package/templates/nextblock-template/lib/blocks/product-grid-data.ts +210 -210
  185. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  186. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
  187. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  188. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  189. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  190. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  191. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  192. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  193. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  194. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  195. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  196. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  197. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  198. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  199. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  200. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  201. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  202. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  203. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  204. package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
  205. package/templates/nextblock-template/lib/i18n/country-languages.ts +247 -247
  206. package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -197
  207. package/templates/nextblock-template/lib/i18n/detection.ts +192 -192
  208. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  209. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
  210. package/templates/nextblock-template/lib/media/youtube.ts +81 -0
  211. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  212. package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
  213. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  214. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  215. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  216. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  217. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  218. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  219. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  220. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  221. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  222. package/templates/nextblock-template/lib/search/types.ts +27 -27
  223. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  224. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  225. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  226. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -82
  227. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  228. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  229. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  230. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  231. package/templates/nextblock-template/lib/site-url.ts +48 -48
  232. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  233. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  234. package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
  235. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  236. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  237. package/templates/nextblock-template/package.json +1 -1
  238. package/templates/nextblock-template/postcss.config.js +6 -6
  239. package/templates/nextblock-template/scripts/backup.js +115 -115
  240. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  241. package/templates/nextblock-template/scripts/restore.js +385 -385
  242. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  243. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  244. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  245. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  246. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  247. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  248. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  249. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  250. package/templates/nextblock-template/tailwind.config.js +25 -25
  251. package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
  252. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  253. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  254. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
  255. package/tsconfig.base.json +3 -3
@@ -0,0 +1,81 @@
1
+ // Shared YouTube URL normalization.
2
+ //
3
+ // No public render path may emit a www.youtube.com iframe: the player writes
4
+ // cookies via document.cookie on load (TESTCOOKIESENABLED, LAST_RESULT_ENTRY_KEY,
5
+ // ExcludeSameSiteUnspecifiedTreatedAsLax) which Chrome logs as a DevTools "Cookie"
6
+ // issue and Lighthouse fails through the `inspector-issues` audit (Best Practices
7
+ // 26/27 = 96). Switching to youtube-nocookie.com does NOT stop those writes --
8
+ // only not loading the player does. See components/media/YouTubeFacade.tsx.
9
+
10
+ const YOUTUBE_HOST_RE = /^(?:www\.|m\.|music\.)?(?:youtube\.com|youtube-nocookie\.com)$/i;
11
+ const YOUTU_BE_RE = /^(?:www\.)?youtu\.be$/i;
12
+ const VIDEO_ID_RE = /^[A-Za-z0-9_-]{6,20}$/;
13
+
14
+ export interface ParsedYouTubeUrl {
15
+ videoId: string;
16
+ /** Extra player params carried over from the source URL (v/si/autoplay stripped). */
17
+ params: URLSearchParams;
18
+ }
19
+
20
+ /**
21
+ * Accepts every shape an author can paste or that YouTube's Share > Embed panel
22
+ * emits: watch?v= (v anywhere in the query), youtu.be/, /embed/, /shorts/,
23
+ * /live/, /v/, m. and music. subdomains, and youtube-nocookie.com/embed/.
24
+ * Returns null for non-YouTube URLs.
25
+ */
26
+ export function parseYouTubeUrl(rawUrl: string | undefined | null): ParsedYouTubeUrl | null {
27
+ if (!rawUrl) return null;
28
+ let url: URL;
29
+ try {
30
+ url = new URL(rawUrl, 'https://www.youtube.com');
31
+ } catch {
32
+ return null;
33
+ }
34
+
35
+ let videoId = '';
36
+ if (YOUTU_BE_RE.test(url.hostname)) {
37
+ videoId = url.pathname.split('/').filter(Boolean)[0] || '';
38
+ } else if (YOUTUBE_HOST_RE.test(url.hostname)) {
39
+ const segments = url.pathname.split('/').filter(Boolean);
40
+ const head = segments[0] || '';
41
+ if (head === 'watch') {
42
+ videoId = url.searchParams.get('v') || '';
43
+ } else if (head === 'embed' || head === 'shorts' || head === 'live' || head === 'v') {
44
+ videoId = segments[1] || '';
45
+ }
46
+ }
47
+ if (!VIDEO_ID_RE.test(videoId)) return null;
48
+
49
+ const params = new URLSearchParams(url.search);
50
+ params.delete('v');
51
+ params.delete('si'); // share-tracking token, not needed for playback
52
+ // Never auto-start on load: an autoplaying frame boots the player (and its
53
+ // cookie writes) during a Lighthouse run. The facade re-adds autoplay=1 only
54
+ // after a real user click.
55
+ params.delete('autoplay');
56
+ return { videoId, params };
57
+ }
58
+
59
+ export function buildNoCookieEmbedUrl(
60
+ videoId: string,
61
+ params?: URLSearchParams,
62
+ opts?: { autoplay?: boolean }
63
+ ): string {
64
+ const search = new URLSearchParams(params ? params.toString() : '');
65
+ if (opts?.autoplay) search.set('autoplay', '1');
66
+ const qs = search.toString();
67
+ return `https://www.youtube-nocookie.com/embed/${videoId}${qs ? `?${qs}` : ''}`;
68
+ }
69
+
70
+ /** Last-resort string rewrite for raw-HTML paths that cannot be parsed into nodes. */
71
+ export function rewriteYouTubeHostsInHtml(html: string): string {
72
+ if (!html) return html;
73
+ return html.replace(
74
+ /(?:https?:)?\/\/(?:www\.|m\.|music\.)?youtube\.com\/embed\//gi,
75
+ 'https://www.youtube-nocookie.com/embed/'
76
+ );
77
+ }
78
+
79
+ export function youTubePosterUrl(videoId: string, quality: 'maxres' | 'hq' = 'maxres'): string {
80
+ return `https://i.ytimg.com/vi/${videoId}/${quality === 'maxres' ? 'maxresdefault' : 'hqdefault'}.jpg`;
81
+ }
@@ -1,31 +1,31 @@
1
- 'use server';
2
-
3
- import { createClient } from '@nextblock-cms/db/server';
4
- import { revalidatePath } from 'next/cache';
5
-
6
- /** Persist the onboarding dismiss flag in the `onboarding_state` row (read-merge). */
7
- export async function setOnboardingDismissed(dismissed: boolean) {
8
- const supabase = createClient();
9
-
10
- const { data: existing } = await supabase
11
- .from('site_settings')
12
- .select('value')
13
- .eq('key', 'onboarding_state')
14
- .maybeSingle();
15
-
16
- const current =
17
- existing?.value && typeof existing.value === 'object' && !Array.isArray(existing.value)
18
- ? (existing.value as Record<string, unknown>)
19
- : {};
20
-
21
- const { error } = await supabase
22
- .from('site_settings')
23
- .upsert({ key: 'onboarding_state', value: { ...current, dismissed } });
24
-
25
- if (error) {
26
- console.error('Error updating onboarding state:', error.message);
27
- throw new Error('Failed to update onboarding state.');
28
- }
29
-
30
- revalidatePath('/cms/dashboard');
31
- }
1
+ 'use server';
2
+
3
+ import { createClient } from '@nextblock-cms/db/server';
4
+ import { revalidatePath } from 'next/cache';
5
+
6
+ /** Persist the onboarding dismiss flag in the `onboarding_state` row (read-merge). */
7
+ export async function setOnboardingDismissed(dismissed: boolean) {
8
+ const supabase = createClient();
9
+
10
+ const { data: existing } = await supabase
11
+ .from('site_settings')
12
+ .select('value')
13
+ .eq('key', 'onboarding_state')
14
+ .maybeSingle();
15
+
16
+ const current =
17
+ existing?.value && typeof existing.value === 'object' && !Array.isArray(existing.value)
18
+ ? (existing.value as Record<string, unknown>)
19
+ : {};
20
+
21
+ const { error } = await supabase
22
+ .from('site_settings')
23
+ .upsert({ key: 'onboarding_state', value: { ...current, dismissed } });
24
+
25
+ if (error) {
26
+ console.error('Error updating onboarding state:', error.message);
27
+ throw new Error('Failed to update onboarding state.');
28
+ }
29
+
30
+ revalidatePath('/cms/dashboard');
31
+ }
@@ -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
+ }