create-nextblock 0.16.4 → 0.17.0

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 (196) hide show
  1. package/CLAUDE.md +22 -0
  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/package.json +1 -1
  8. package/templates/nextblock-template/.browserslistrc +11 -11
  9. package/templates/nextblock-template/.swcrc +30 -30
  10. package/templates/nextblock-template/CLAUDE.md +26 -0
  11. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
  14. package/templates/nextblock-template/app/actions/consent.ts +57 -57
  15. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  16. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  17. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  18. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  19. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
  20. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  21. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  22. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  23. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +270 -205
  24. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +8096 -7198
  25. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  26. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  27. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  28. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  29. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  30. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  31. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  32. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +442 -441
  33. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +4 -1
  34. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  35. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  36. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  37. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  38. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  39. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  40. package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +3 -3
  41. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  42. package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -136
  43. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  44. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  45. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  46. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  47. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  48. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  49. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  50. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  51. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  52. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  53. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  54. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  55. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  56. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  57. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  58. package/templates/nextblock-template/app/cms/pages/page.tsx +273 -273
  59. package/templates/nextblock-template/app/cms/posts/page.tsx +251 -251
  60. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  61. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  62. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  63. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  64. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  65. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  66. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  67. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  68. package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
  69. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  70. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  71. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  72. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  73. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  74. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  75. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  76. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  77. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  78. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  79. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  80. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  81. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  82. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  83. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  84. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  85. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  86. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  87. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  88. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  89. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  90. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  91. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  92. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  93. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  94. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  95. package/templates/nextblock-template/components/Header.tsx +38 -38
  96. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  97. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  98. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  99. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  100. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  101. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  102. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  103. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +11 -0
  104. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  105. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  106. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  107. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  108. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
  109. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
  110. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  111. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  112. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  113. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  114. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
  115. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  116. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  117. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  118. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  119. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  120. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  121. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +11 -0
  122. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  123. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -0
  124. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  125. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  126. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  127. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  128. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  129. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  130. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  131. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -135
  132. package/templates/nextblock-template/lib/cms/payments-reminder.ts +105 -105
  133. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  134. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  135. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  136. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  137. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  138. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  139. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  140. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  141. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  142. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  143. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  144. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  145. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  146. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  147. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  148. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  149. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  150. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  151. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +56 -54
  152. package/templates/nextblock-template/lib/media/youtube.ts +99 -99
  153. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  154. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  155. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  156. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  157. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  158. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  159. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  160. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  161. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  162. package/templates/nextblock-template/lib/seo/page-document.ts +4 -4
  163. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  164. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  165. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  166. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +42 -12
  167. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  168. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  169. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  170. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  171. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  172. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  173. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  174. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  175. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  176. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  177. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  178. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  179. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  180. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  181. package/templates/nextblock-template/next-env.d.ts +1 -0
  182. package/templates/nextblock-template/package.json +1 -1
  183. package/templates/nextblock-template/public/images/cortex_post.webp +0 -0
  184. package/templates/nextblock-template/public/images/update_nextblock.webp +0 -0
  185. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  186. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  187. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  188. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  189. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  190. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  191. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  192. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  193. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  194. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  195. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  196. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
@@ -1,11 +1,11 @@
1
- chrome >= 109
2
- edge >= 109
3
- firefox >= 115
4
- safari >= 15.4
5
- ios_saf >= 15.4
6
- and_chr >= 109
7
- and_ff >= 115
8
- samsung >= 21
9
- opera >= 95
10
- not dead
11
- not op_mini all
1
+ chrome >= 109
2
+ edge >= 109
3
+ firefox >= 115
4
+ safari >= 15.4
5
+ ios_saf >= 15.4
6
+ and_chr >= 109
7
+ and_ff >= 115
8
+ samsung >= 21
9
+ opera >= 95
10
+ not dead
11
+ not op_mini all
@@ -1,30 +1,30 @@
1
- {
2
- "jsc": {
3
- "target": "es2022",
4
- "parser": {
5
- "syntax": "typescript",
6
- "decorators": true,
7
- "dynamicImport": true
8
- },
9
- "transform": {
10
- "decoratorMetadata": true,
11
- "legacyDecorator": true
12
- },
13
- "keepClassNames": true,
14
- "externalHelpers": true,
15
- "loose": true
16
- },
17
- "module": {
18
- "type": "commonjs"
19
- },
20
- "sourceMaps": true,
21
- "exclude": [
22
- "jest.config.ts",
23
- ".*\\.spec.tsx?$",
24
- ".*\\.test.tsx?$",
25
- "./src/jest-setup.ts$",
26
- "./**/jest-setup.ts$",
27
- ".*.js$",
28
- ".*.d.ts$"
29
- ]
30
- }
1
+ {
2
+ "jsc": {
3
+ "target": "es2022",
4
+ "parser": {
5
+ "syntax": "typescript",
6
+ "decorators": true,
7
+ "dynamicImport": true
8
+ },
9
+ "transform": {
10
+ "decoratorMetadata": true,
11
+ "legacyDecorator": true
12
+ },
13
+ "keepClassNames": true,
14
+ "externalHelpers": true,
15
+ "loose": true
16
+ },
17
+ "module": {
18
+ "type": "commonjs"
19
+ },
20
+ "sourceMaps": true,
21
+ "exclude": [
22
+ "jest.config.ts",
23
+ ".*\\.spec.tsx?$",
24
+ ".*\\.test.tsx?$",
25
+ "./src/jest-setup.ts$",
26
+ "./**/jest-setup.ts$",
27
+ ".*.js$",
28
+ ".*.d.ts$"
29
+ ]
30
+ }
@@ -1 +1,27 @@
1
1
  @AGENTS.md
2
+
3
+ # apps/nextblock — the NextBlock app
4
+
5
+ Next.js 16 App Router app: public site, CMS under `app/cms`, API routes under `app/api`,
6
+ first-boot wizard under `app/setup`. Standalone projects are a copy of this directory with
7
+ `@nextblock-cms/*` installed from npm.
8
+
9
+ ## Map
10
+ - `proxy.ts` (Next 16 middleware): `/setup` gate, DB redirects, CSP nonce, `/cms` session + role
11
+ checks (`/cms/admin|users|settings` ADMIN-only); `app/cms/layout.tsx` adds 2FA.
12
+ - `lib/blocks/blockRegistry.ts` + `blockTypes.ts`: built-in block schemas and defaults.
13
+ `components/BlockRenderer.tsx` resolves `block_type` via `components/blocks/publicRendererLoaders.ts`,
14
+ then the ecommerce loaders, then custom blocks; unknown types render null unless editing.
15
+ - Block row writes (delete+insert): `app/actions/visualEditingActions.ts`, `app/cms/revisions/service.ts`,
16
+ `lib/cms-transfer/server.ts`, `lib/visual-editing/product-drafts.ts`.
17
+ - `lib/setup/env-status.ts` resolves Supabase env; keep it free of Node/Supabase imports.
18
+
19
+ ## Commands
20
+ Monorepo: `npx nx serve nextblock`, `npx nx build nextblock`, `npx vitest run apps/nextblock/<file>`.
21
+ Standalone: `npm run dev`, `npm run build`.
22
+
23
+ ## Rules
24
+ - Import `z` from `lib/zod-config.ts` (jitless; the prod CSP bans eval).
25
+ - Server modules `import 'server-only'`; set `export const runtime = 'nodejs'` on Node-only route handlers.
26
+ - Generated, never edit: `lib/setup/migrations-bundle.ts`, `app/api/cron/reset-sandbox/sandboxResetSql.ts`,
27
+ `lib/custom-block-safelist.ts`, `next-env.d.ts`, the Next block in `AGENTS.md`.
@@ -1,46 +1,46 @@
1
- import { Suspense } from "react";
2
- import { headers } from "next/headers";
3
- import { createClient } from "@nextblock-cms/db/server";
4
- import SignUpForm from "./SignUpForm";
5
-
6
- type BotProtectionProvider = 'none' | 'turnstile' | 'recaptcha';
7
-
8
- // Server wrapper: resolves the site-wide bot-protection provider + site key
9
- // (CMS → Settings → Bot Protection) and the CSP nonce, then hands them to the
10
- // interactive client form. Mirrors the read in components/BlockRenderer.tsx.
11
- export default async function SignUpPage() {
12
- let scriptNonce = '';
13
- try {
14
- scriptNonce = (await headers()).get('x-nonce') || '';
15
- } catch (e) {
16
- console.error('[Bot Protection] Error loading CSP nonce on sign-up page:', e);
17
- }
18
-
19
- let botProtection: { provider: BotProtectionProvider; siteKey: string } = {
20
- provider: 'none',
21
- siteKey: '',
22
- };
23
- try {
24
- const supabase = createClient();
25
- const { data: publicSetting } = await supabase
26
- .from('site_settings')
27
- .select('value')
28
- .eq('key', 'bot_protection_public')
29
- .maybeSingle();
30
- if (publicSetting?.value) {
31
- const publicVal = publicSetting.value as Record<string, any>;
32
- botProtection = {
33
- provider: publicVal.provider || 'none',
34
- siteKey: publicVal.siteKey || '',
35
- };
36
- }
37
- } catch (e) {
38
- console.error('[Bot Protection] Error loading settings on sign-up page:', e);
39
- }
40
-
41
- return (
42
- <Suspense fallback={null}>
43
- <SignUpForm botProtection={botProtection} scriptNonce={scriptNonce} />
44
- </Suspense>
45
- );
46
- }
1
+ import { Suspense } from "react";
2
+ import { headers } from "next/headers";
3
+ import { createClient } from "@nextblock-cms/db/server";
4
+ import SignUpForm from "./SignUpForm";
5
+
6
+ type BotProtectionProvider = 'none' | 'turnstile' | 'recaptcha';
7
+
8
+ // Server wrapper: resolves the site-wide bot-protection provider + site key
9
+ // (CMS → Settings → Bot Protection) and the CSP nonce, then hands them to the
10
+ // interactive client form. Mirrors the read in components/BlockRenderer.tsx.
11
+ export default async function SignUpPage() {
12
+ let scriptNonce = '';
13
+ try {
14
+ scriptNonce = (await headers()).get('x-nonce') || '';
15
+ } catch (e) {
16
+ console.error('[Bot Protection] Error loading CSP nonce on sign-up page:', e);
17
+ }
18
+
19
+ let botProtection: { provider: BotProtectionProvider; siteKey: string } = {
20
+ provider: 'none',
21
+ siteKey: '',
22
+ };
23
+ try {
24
+ const supabase = createClient();
25
+ const { data: publicSetting } = await supabase
26
+ .from('site_settings')
27
+ .select('value')
28
+ .eq('key', 'bot_protection_public')
29
+ .maybeSingle();
30
+ if (publicSetting?.value) {
31
+ const publicVal = publicSetting.value as Record<string, any>;
32
+ botProtection = {
33
+ provider: publicVal.provider || 'none',
34
+ siteKey: publicVal.siteKey || '',
35
+ };
36
+ }
37
+ } catch (e) {
38
+ console.error('[Bot Protection] Error loading settings on sign-up page:', e);
39
+ }
40
+
41
+ return (
42
+ <Suspense fallback={null}>
43
+ <SignUpForm botProtection={botProtection} scriptNonce={scriptNonce} />
44
+ </Suspense>
45
+ );
46
+ }
@@ -1,51 +1,51 @@
1
- import { redirect } from 'next/navigation';
2
- import { createClient } from '@nextblock-cms/db/server';
3
- import {
4
- evaluateTwoFactor,
5
- hasPendingEmailChallenge,
6
- } from '../../../lib/auth/twoFactor';
7
- import TwoFactorForm from './components/TwoFactorForm';
8
-
9
- function safeRedirect(path?: string): string {
10
- return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
11
- }
12
-
13
- export default async function TwoFactorPage({
14
- searchParams,
15
- }: {
16
- searchParams: Promise<{ redirect_to?: string }>;
17
- }) {
18
- const params = await searchParams;
19
- const redirectTo = safeRedirect(params.redirect_to);
20
-
21
- const evaluation = await evaluateTwoFactor();
22
- const userId = evaluation.userId;
23
- if (!userId) {
24
- redirect('/sign-in');
25
- }
26
- // Already satisfied (or no MFA) -> straight through.
27
- if (evaluation.status === 'satisfied' || evaluation.status === 'not_required') {
28
- redirect(redirectTo);
29
- }
30
-
31
- const type = evaluation.status === 'totp_required' ? 'totp' : 'email';
32
-
33
- const supabase = createClient();
34
- const {
35
- data: { user },
36
- } = await supabase.auth.getUser();
37
-
38
- const pendingEmailCode =
39
- type === 'email' ? await hasPendingEmailChallenge(userId) : false;
40
-
41
- return (
42
- <div className="flex-1 flex flex-col w-full max-w-md mx-auto py-10">
43
- <TwoFactorForm
44
- type={type}
45
- email={user?.email ?? ''}
46
- redirectTo={redirectTo}
47
- pendingEmailCode={pendingEmailCode}
48
- />
49
- </div>
50
- );
51
- }
1
+ import { redirect } from 'next/navigation';
2
+ import { createClient } from '@nextblock-cms/db/server';
3
+ import {
4
+ evaluateTwoFactor,
5
+ hasPendingEmailChallenge,
6
+ } from '../../../lib/auth/twoFactor';
7
+ import TwoFactorForm from './components/TwoFactorForm';
8
+
9
+ function safeRedirect(path?: string): string {
10
+ return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
11
+ }
12
+
13
+ export default async function TwoFactorPage({
14
+ searchParams,
15
+ }: {
16
+ searchParams: Promise<{ redirect_to?: string }>;
17
+ }) {
18
+ const params = await searchParams;
19
+ const redirectTo = safeRedirect(params.redirect_to);
20
+
21
+ const evaluation = await evaluateTwoFactor();
22
+ const userId = evaluation.userId;
23
+ if (!userId) {
24
+ redirect('/sign-in');
25
+ }
26
+ // Already satisfied (or no MFA) -> straight through.
27
+ if (evaluation.status === 'satisfied' || evaluation.status === 'not_required') {
28
+ redirect(redirectTo);
29
+ }
30
+
31
+ const type = evaluation.status === 'totp_required' ? 'totp' : 'email';
32
+
33
+ const supabase = createClient();
34
+ const {
35
+ data: { user },
36
+ } = await supabase.auth.getUser();
37
+
38
+ const pendingEmailCode =
39
+ type === 'email' ? await hasPendingEmailChallenge(userId) : false;
40
+
41
+ return (
42
+ <div className="flex-1 flex flex-col w-full max-w-md mx-auto py-10">
43
+ <TwoFactorForm
44
+ type={type}
45
+ email={user?.email ?? ''}
46
+ redirectTo={redirectTo}
47
+ pendingEmailCode={pendingEmailCode}
48
+ />
49
+ </div>
50
+ );
51
+ }
@@ -1,16 +1,16 @@
1
- import { NextResponse } from 'next/server';
2
- import { buildUcpProfile } from '../../lib/ucp/protocol';
3
-
4
- export const dynamic = 'force-dynamic';
5
- export const runtime = 'nodejs';
6
-
7
- export async function GET(request: Request) {
8
- const baseUrl =
9
- process.env.NEXT_PUBLIC_URL?.replace(/\/+$/, '') || new URL(request.url).origin;
10
-
11
- return NextResponse.json(buildUcpProfile(baseUrl), {
12
- headers: {
13
- 'Cache-Control': 'public, max-age=300, s-maxage=300',
14
- },
15
- });
16
- }
1
+ import { NextResponse } from 'next/server';
2
+ import { buildUcpProfile } from '../../lib/ucp/protocol';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+ export const runtime = 'nodejs';
6
+
7
+ export async function GET(request: Request) {
8
+ const baseUrl =
9
+ process.env.NEXT_PUBLIC_URL?.replace(/\/+$/, '') || new URL(request.url).origin;
10
+
11
+ return NextResponse.json(buildUcpProfile(baseUrl), {
12
+ headers: {
13
+ 'Cache-Control': 'public, max-age=300, s-maxage=300',
14
+ },
15
+ });
16
+ }
@@ -1,57 +1,57 @@
1
- 'use server';
2
-
3
- import { headers } from 'next/headers';
4
- import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
5
-
6
- /** Reduce an IP to a non-identifying form for the consent audit log. */
7
- function maskIp(ip: string | null): string | null {
8
- if (!ip) return null;
9
- const first = ip.split(',')[0]?.trim();
10
- if (!first) return null;
11
- if (first.includes(':')) {
12
- // IPv6: keep the first three hextets, drop the rest.
13
- const hextets = first.split(':').filter(Boolean);
14
- return `${hextets.slice(0, 3).join(':')}::x`;
15
- }
16
- const octets = first.split('.');
17
- if (octets.length === 4) {
18
- octets[3] = 'x';
19
- return octets.join('.');
20
- }
21
- return null;
22
- }
23
-
24
- export interface ConsentLogInput {
25
- token: string;
26
- analytics: boolean;
27
- marketing: boolean;
28
- }
29
-
30
- /**
31
- * Record a consent decision for Law 25 / PIPEDA accountability. Best-effort:
32
- * failures never block the visitor's choice from taking effect client-side.
33
- */
34
- export async function logConsentDecision(input: ConsentLogInput): Promise<{ ok: boolean }> {
35
- if (!input?.token) return { ok: false };
36
- try {
37
- const h = await headers();
38
- const ip = h.get('x-forwarded-for') ?? h.get('x-real-ip');
39
- const userAgent = h.get('user-agent');
40
-
41
- const supabase = getServiceRoleSupabaseClient();
42
- await supabase.from('privacy_consent_logs').insert({
43
- consent_token: input.token,
44
- categories: {
45
- necessary: true,
46
- analytics: Boolean(input.analytics),
47
- marketing: Boolean(input.marketing),
48
- },
49
- ip_masked: maskIp(ip),
50
- user_agent: userAgent ? userAgent.slice(0, 500) : null,
51
- });
52
- return { ok: true };
53
- } catch (error) {
54
- console.error('Failed to record consent decision:', error);
55
- return { ok: false };
56
- }
57
- }
1
+ 'use server';
2
+
3
+ import { headers } from 'next/headers';
4
+ import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
5
+
6
+ /** Reduce an IP to a non-identifying form for the consent audit log. */
7
+ function maskIp(ip: string | null): string | null {
8
+ if (!ip) return null;
9
+ const first = ip.split(',')[0]?.trim();
10
+ if (!first) return null;
11
+ if (first.includes(':')) {
12
+ // IPv6: keep the first three hextets, drop the rest.
13
+ const hextets = first.split(':').filter(Boolean);
14
+ return `${hextets.slice(0, 3).join(':')}::x`;
15
+ }
16
+ const octets = first.split('.');
17
+ if (octets.length === 4) {
18
+ octets[3] = 'x';
19
+ return octets.join('.');
20
+ }
21
+ return null;
22
+ }
23
+
24
+ export interface ConsentLogInput {
25
+ token: string;
26
+ analytics: boolean;
27
+ marketing: boolean;
28
+ }
29
+
30
+ /**
31
+ * Record a consent decision for Law 25 / PIPEDA accountability. Best-effort:
32
+ * failures never block the visitor's choice from taking effect client-side.
33
+ */
34
+ export async function logConsentDecision(input: ConsentLogInput): Promise<{ ok: boolean }> {
35
+ if (!input?.token) return { ok: false };
36
+ try {
37
+ const h = await headers();
38
+ const ip = h.get('x-forwarded-for') ?? h.get('x-real-ip');
39
+ const userAgent = h.get('user-agent');
40
+
41
+ const supabase = getServiceRoleSupabaseClient();
42
+ await supabase.from('privacy_consent_logs').insert({
43
+ consent_token: input.token,
44
+ categories: {
45
+ necessary: true,
46
+ analytics: Boolean(input.analytics),
47
+ marketing: Boolean(input.marketing),
48
+ },
49
+ ip_masked: maskIp(ip),
50
+ user_agent: userAgent ? userAgent.slice(0, 500) : null,
51
+ });
52
+ return { ok: true };
53
+ } catch (error) {
54
+ console.error('Failed to record consent decision:', error);
55
+ return { ok: false };
56
+ }
57
+ }
@@ -1,22 +1,22 @@
1
- // Shared transactional email for 2FA codes. Server-only helper (not a server
2
- // action); callable from sign-in, the challenge page, and the security panel.
3
- import { sendEmail } from './email';
4
-
5
- export async function sendTwoFactorCodeEmail(
6
- to: string,
7
- code: string,
8
- purpose = 'verify your sign-in',
9
- ): Promise<void> {
10
- await sendEmail({
11
- to,
12
- subject: 'Your verification code',
13
- text: `Your verification code is ${code}. It expires in 5 minutes. If you didn't request it, you can ignore this email.`,
14
- html: `
15
- <div style="font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;max-width:480px;margin:0 auto;padding:24px;">
16
- {{brand_header}}
17
- <p style="font-size:14px;color:#475569;">Use this code to ${purpose}:</p>
18
- <p style="font-size:32px;font-weight:700;letter-spacing:8px;margin:16px 0;color:#0f172a;">${code}</p>
19
- <p style="font-size:13px;color:#64748b;">This code expires in 5 minutes. If you didn't request it, you can safely ignore this email.</p>
20
- </div>`,
21
- });
22
- }
1
+ // Shared transactional email for 2FA codes. Server-only helper (not a server
2
+ // action); callable from sign-in, the challenge page, and the security panel.
3
+ import { sendEmail } from './email';
4
+
5
+ export async function sendTwoFactorCodeEmail(
6
+ to: string,
7
+ code: string,
8
+ purpose = 'verify your sign-in',
9
+ ): Promise<void> {
10
+ await sendEmail({
11
+ to,
12
+ subject: 'Your verification code',
13
+ text: `Your verification code is ${code}. It expires in 5 minutes. If you didn't request it, you can ignore this email.`,
14
+ html: `
15
+ <div style="font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;max-width:480px;margin:0 auto;padding:24px;">
16
+ {{brand_header}}
17
+ <p style="font-size:14px;color:#475569;">Use this code to ${purpose}:</p>
18
+ <p style="font-size:32px;font-weight:700;letter-spacing:8px;margin:16px 0;color:#0f172a;">${code}</p>
19
+ <p style="font-size:13px;color:#64748b;">This code expires in 5 minutes. If you didn't request it, you can safely ignore this email.</p>
20
+ </div>`,
21
+ });
22
+ }