create-nextblock 0.16.3 → 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.
- package/CLAUDE.md +22 -0
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/package.json +1 -1
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.swcrc +30 -30
- package/templates/nextblock-template/CLAUDE.md +26 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +270 -205
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +10388 -9315
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
- package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +442 -441
- package/templates/nextblock-template/app/article/[slug]/page.utils.ts +4 -1
- package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
- package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +3 -40
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -136
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/pages/page.tsx +273 -273
- package/templates/nextblock-template/app/cms/posts/page.tsx +251 -251
- package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +19 -0
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +14 -2
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
- package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
- package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
- package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
- package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
- package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
- package/templates/nextblock-template/components/Header.tsx +38 -38
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
- package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
- package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +11 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
- package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +11 -0
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -0
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -135
- package/templates/nextblock-template/lib/cms/payments-reminder.ts +105 -105
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +56 -54
- package/templates/nextblock-template/lib/media/youtube.ts +99 -99
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/seo/page-audit-context.tsx +3 -3
- package/templates/nextblock-template/lib/seo/page-document.test.ts +34 -0
- package/templates/nextblock-template/lib/seo/page-document.ts +3 -462
- package/templates/nextblock-template/lib/seo/robots-txt.ts +5 -4
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +50 -10
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
- package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
- package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
- package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
- package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
- package/templates/nextblock-template/lib/zod-config.ts +5 -5
- package/templates/nextblock-template/next-env.d.ts +1 -0
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/public/images/cortex_post.webp +0 -0
- package/templates/nextblock-template/public/images/update_nextblock.webp +0 -0
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
- 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
|
+
}
|