create-nextblock 0.12.9 → 0.12.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.
- package/docker-template/.dockerignore +23 -23
- 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/libs/db/tsconfig.lib.json +3 -3
- package/libs/editor/tsconfig.lib.json +3 -3
- package/libs/ui/tsconfig.lib.json +3 -3
- package/libs/utils/tsconfig.json +3 -3
- package/package.json +1 -1
- package/project.json +19 -19
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.dockerignore +23 -23
- package/templates/nextblock-template/README.md +34 -34
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
- package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +141 -141
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -91
- package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +123 -123
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/interactions.test.ts +301 -301
- package/templates/nextblock-template/app/actions/interactions.ts +373 -373
- package/templates/nextblock-template/app/actions/postActions.ts +146 -146
- 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/ai/global-agent/route.ts +964 -964
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- 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 +3490 -3490
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5597 -5597
- 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/media/record/route.ts +160 -160
- package/templates/nextblock-template/app/api/search/route.ts +43 -43
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -244
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- 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/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/BlockComposer.tsx +1522 -1522
- 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/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +431 -391
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -408
- package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -51
- package/templates/nextblock-template/app/cms/layout.tsx +73 -73
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +149 -149
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -101
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/pages/page.tsx +18 -17
- package/templates/nextblock-template/app/cms/posts/page.tsx +19 -18
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/products/page.tsx +39 -27
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
- 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/revisions/service.ts +19 -19
- package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
- 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/actions.ts +93 -93
- package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -410
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +246 -246
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -80
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
- package/templates/nextblock-template/app/cms/settings/email/actions.ts +64 -64
- package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +181 -181
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/google-analytics/actions.ts +60 -60
- package/templates/nextblock-template/app/cms/settings/google-analytics/components/GoogleAnalyticsForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +16 -16
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
- 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/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
- package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +52 -52
- package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +148 -148
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/actions.ts +44 -44
- package/templates/nextblock-template/app/cms/settings/registration/components/RegistrationForm.tsx +65 -65
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/actions.ts +284 -284
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +522 -522
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
- package/templates/nextblock-template/app/cms/users/actions.ts +222 -222
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +44 -44
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +253 -253
- package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -73
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
- package/templates/nextblock-template/app/profile/account-data.ts +37 -37
- package/templates/nextblock-template/app/profile/account-links.ts +22 -22
- package/templates/nextblock-template/app/profile/account-types.ts +11 -11
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -79
- package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -32
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- package/templates/nextblock-template/app/profile/page.tsx +42 -42
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
- 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/sitemap.ts +130 -130
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/GitHubLoginButton.tsx +15 -15
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -369
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -419
- 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/PostCardSkeleton.tsx +12 -12
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.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 +464 -464
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
- 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/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -1319
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
- package/templates/nextblock-template/docs/README.md +39 -39
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12505 -12505
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
- package/templates/nextblock-template/index.d.ts +7 -7
- 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/auth/twoFactor.ts +208 -208
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +6 -6
- package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -18
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +100 -77
- package/templates/nextblock-template/lib/cms-transfer/csv.ts +403 -399
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/config/email-settings.ts +217 -217
- 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-relation-registry.ts +256 -256
- 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/full-backup/server.ts +743 -743
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- 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/search/server.ts +585 -585
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- 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 +37 -37
- 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/site-url.ts +48 -48
- 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/check-upstream.ts +441 -441
- 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/package.json +1 -1
- package/templates/nextblock-template/postcss.config.js +6 -6
- package/templates/nextblock-template/scripts/backup.js +115 -115
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/restore.js +385 -385
- 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/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
- package/templates/nextblock-template/tsconfig.tsbuildinfo +0 -1
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import { headers } from 'next/headers';
|
|
4
|
-
import { redirect } from 'next/navigation';
|
|
5
|
-
import { createClient } from '@nextblock-cms/db/server';
|
|
6
|
-
import {
|
|
7
|
-
createEmailChallenge,
|
|
8
|
-
issueTwoFactorVerifiedCookie,
|
|
9
|
-
verifyEmailChallenge,
|
|
10
|
-
} from '../../../lib/auth/twoFactor';
|
|
11
|
-
import { issueTrustedDevice } from '../../../lib/auth/trustedDevices';
|
|
12
|
-
import {
|
|
13
|
-
REMEMBER_INTENT_COOKIE,
|
|
14
|
-
clearCookie,
|
|
15
|
-
getCookieValue,
|
|
16
|
-
} from '../../../lib/auth/cookies';
|
|
17
|
-
import { sendTwoFactorCodeEmail } from '../../actions/twoFactorEmail';
|
|
18
|
-
|
|
19
|
-
function safeRedirect(path?: string): string {
|
|
20
|
-
return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** If the user opted into "remember this device" at login, mint the trust now. */
|
|
24
|
-
async function maybeIssueTrustedDevice(userId: string): Promise<void> {
|
|
25
|
-
const intent = await getCookieValue(REMEMBER_INTENT_COOKIE);
|
|
26
|
-
if (intent !== '1') return;
|
|
27
|
-
const userAgent = (await headers()).get('user-agent');
|
|
28
|
-
await issueTrustedDevice(userId, userAgent);
|
|
29
|
-
await clearCookie(REMEMBER_INTENT_COOKIE);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function verifyTotpChallenge(formData: FormData) {
|
|
33
|
-
const code = (formData.get('code')?.toString() ?? '').trim();
|
|
34
|
-
const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
|
|
35
|
-
const supabase = createClient();
|
|
36
|
-
|
|
37
|
-
const {
|
|
38
|
-
data: { user },
|
|
39
|
-
} = await supabase.auth.getUser();
|
|
40
|
-
if (!user) return { error: 'Your session expired. Please sign in again.' };
|
|
41
|
-
if (!/^\d{6}$/.test(code)) return { error: 'Enter the 6-digit code from your app.' };
|
|
42
|
-
|
|
43
|
-
const { data: factors } = await supabase.auth.mfa.listFactors();
|
|
44
|
-
const factor = factors?.totp?.find((f) => f.status === 'verified');
|
|
45
|
-
if (!factor) return { error: 'No authenticator is set up for this account.' };
|
|
46
|
-
|
|
47
|
-
const { data: challenge, error: challengeError } = await supabase.auth.mfa.challenge({
|
|
48
|
-
factorId: factor.id,
|
|
49
|
-
});
|
|
50
|
-
if (challengeError || !challenge) return { error: 'Could not start verification.' };
|
|
51
|
-
|
|
52
|
-
const { error: verifyError } = await supabase.auth.mfa.verify({
|
|
53
|
-
factorId: factor.id,
|
|
54
|
-
challengeId: challenge.id,
|
|
55
|
-
code,
|
|
56
|
-
});
|
|
57
|
-
if (verifyError) return { error: 'That code was not valid. Please try again.' };
|
|
58
|
-
|
|
59
|
-
await maybeIssueTrustedDevice(user.id);
|
|
60
|
-
redirect(redirectTo);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function verifyEmailCode(formData: FormData) {
|
|
64
|
-
const code = (formData.get('code')?.toString() ?? '').trim();
|
|
65
|
-
const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
|
|
66
|
-
const supabase = createClient();
|
|
67
|
-
|
|
68
|
-
const {
|
|
69
|
-
data: { user },
|
|
70
|
-
} = await supabase.auth.getUser();
|
|
71
|
-
if (!user) return { error: 'Your session expired. Please sign in again.' };
|
|
72
|
-
|
|
73
|
-
const ok = await verifyEmailChallenge(user.id, code);
|
|
74
|
-
if (!ok) return { error: 'That code was incorrect or expired. Request a new one.' };
|
|
75
|
-
|
|
76
|
-
await issueTwoFactorVerifiedCookie(user.id);
|
|
77
|
-
await maybeIssueTrustedDevice(user.id);
|
|
78
|
-
redirect(redirectTo);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function resendEmailCode() {
|
|
82
|
-
const supabase = createClient();
|
|
83
|
-
const {
|
|
84
|
-
data: { user },
|
|
85
|
-
} = await supabase.auth.getUser();
|
|
86
|
-
if (!user?.email) return { error: 'No email address is associated with your account.' };
|
|
87
|
-
|
|
88
|
-
const code = await createEmailChallenge(user.id);
|
|
89
|
-
await sendTwoFactorCodeEmail(user.email, code);
|
|
90
|
-
return { success: true, message: `A new code is on its way to ${user.email}.` };
|
|
91
|
-
}
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
import { redirect } from 'next/navigation';
|
|
5
|
+
import { createClient } from '@nextblock-cms/db/server';
|
|
6
|
+
import {
|
|
7
|
+
createEmailChallenge,
|
|
8
|
+
issueTwoFactorVerifiedCookie,
|
|
9
|
+
verifyEmailChallenge,
|
|
10
|
+
} from '../../../lib/auth/twoFactor';
|
|
11
|
+
import { issueTrustedDevice } from '../../../lib/auth/trustedDevices';
|
|
12
|
+
import {
|
|
13
|
+
REMEMBER_INTENT_COOKIE,
|
|
14
|
+
clearCookie,
|
|
15
|
+
getCookieValue,
|
|
16
|
+
} from '../../../lib/auth/cookies';
|
|
17
|
+
import { sendTwoFactorCodeEmail } from '../../actions/twoFactorEmail';
|
|
18
|
+
|
|
19
|
+
function safeRedirect(path?: string): string {
|
|
20
|
+
return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** If the user opted into "remember this device" at login, mint the trust now. */
|
|
24
|
+
async function maybeIssueTrustedDevice(userId: string): Promise<void> {
|
|
25
|
+
const intent = await getCookieValue(REMEMBER_INTENT_COOKIE);
|
|
26
|
+
if (intent !== '1') return;
|
|
27
|
+
const userAgent = (await headers()).get('user-agent');
|
|
28
|
+
await issueTrustedDevice(userId, userAgent);
|
|
29
|
+
await clearCookie(REMEMBER_INTENT_COOKIE);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function verifyTotpChallenge(formData: FormData) {
|
|
33
|
+
const code = (formData.get('code')?.toString() ?? '').trim();
|
|
34
|
+
const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
|
|
35
|
+
const supabase = createClient();
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
data: { user },
|
|
39
|
+
} = await supabase.auth.getUser();
|
|
40
|
+
if (!user) return { error: 'Your session expired. Please sign in again.' };
|
|
41
|
+
if (!/^\d{6}$/.test(code)) return { error: 'Enter the 6-digit code from your app.' };
|
|
42
|
+
|
|
43
|
+
const { data: factors } = await supabase.auth.mfa.listFactors();
|
|
44
|
+
const factor = factors?.totp?.find((f) => f.status === 'verified');
|
|
45
|
+
if (!factor) return { error: 'No authenticator is set up for this account.' };
|
|
46
|
+
|
|
47
|
+
const { data: challenge, error: challengeError } = await supabase.auth.mfa.challenge({
|
|
48
|
+
factorId: factor.id,
|
|
49
|
+
});
|
|
50
|
+
if (challengeError || !challenge) return { error: 'Could not start verification.' };
|
|
51
|
+
|
|
52
|
+
const { error: verifyError } = await supabase.auth.mfa.verify({
|
|
53
|
+
factorId: factor.id,
|
|
54
|
+
challengeId: challenge.id,
|
|
55
|
+
code,
|
|
56
|
+
});
|
|
57
|
+
if (verifyError) return { error: 'That code was not valid. Please try again.' };
|
|
58
|
+
|
|
59
|
+
await maybeIssueTrustedDevice(user.id);
|
|
60
|
+
redirect(redirectTo);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function verifyEmailCode(formData: FormData) {
|
|
64
|
+
const code = (formData.get('code')?.toString() ?? '').trim();
|
|
65
|
+
const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
|
|
66
|
+
const supabase = createClient();
|
|
67
|
+
|
|
68
|
+
const {
|
|
69
|
+
data: { user },
|
|
70
|
+
} = await supabase.auth.getUser();
|
|
71
|
+
if (!user) return { error: 'Your session expired. Please sign in again.' };
|
|
72
|
+
|
|
73
|
+
const ok = await verifyEmailChallenge(user.id, code);
|
|
74
|
+
if (!ok) return { error: 'That code was incorrect or expired. Request a new one.' };
|
|
75
|
+
|
|
76
|
+
await issueTwoFactorVerifiedCookie(user.id);
|
|
77
|
+
await maybeIssueTrustedDevice(user.id);
|
|
78
|
+
redirect(redirectTo);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function resendEmailCode() {
|
|
82
|
+
const supabase = createClient();
|
|
83
|
+
const {
|
|
84
|
+
data: { user },
|
|
85
|
+
} = await supabase.auth.getUser();
|
|
86
|
+
if (!user?.email) return { error: 'No email address is associated with your account.' };
|
|
87
|
+
|
|
88
|
+
const code = await createEmailChallenge(user.id);
|
|
89
|
+
await sendTwoFactorCodeEmail(user.email, code);
|
|
90
|
+
return { success: true, message: `A new code is on its way to ${user.email}.` };
|
|
91
|
+
}
|
package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx
CHANGED
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState, useTransition } from 'react';
|
|
4
|
-
import { unstable_rethrow } from 'next/navigation';
|
|
5
|
-
import { Alert, AlertDescription, Button, Input, Label, Spinner } from '@nextblock-cms/ui';
|
|
6
|
-
import { resendEmailCode, verifyEmailCode, verifyTotpChallenge } from '../actions';
|
|
7
|
-
|
|
8
|
-
interface TwoFactorFormProps {
|
|
9
|
-
type: 'totp' | 'email';
|
|
10
|
-
email: string;
|
|
11
|
-
redirectTo: string;
|
|
12
|
-
pendingEmailCode: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function TwoFactorForm({
|
|
16
|
-
type,
|
|
17
|
-
email,
|
|
18
|
-
redirectTo,
|
|
19
|
-
pendingEmailCode,
|
|
20
|
-
}: TwoFactorFormProps) {
|
|
21
|
-
const [isPending, startTransition] = useTransition();
|
|
22
|
-
const [code, setCode] = useState('');
|
|
23
|
-
const [error, setError] = useState<string | null>(null);
|
|
24
|
-
const [info, setInfo] = useState<string | null>(
|
|
25
|
-
type === 'email' && pendingEmailCode ? `Enter the code we sent to ${email}.` : null,
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const submit = () => {
|
|
29
|
-
setError(null);
|
|
30
|
-
const formData = new FormData();
|
|
31
|
-
formData.append('code', code);
|
|
32
|
-
formData.append('redirect_to', redirectTo);
|
|
33
|
-
startTransition(async () => {
|
|
34
|
-
try {
|
|
35
|
-
const action = type === 'totp' ? verifyTotpChallenge : verifyEmailCode;
|
|
36
|
-
const result = await action(formData);
|
|
37
|
-
// A successful action redirects server-side; only failures return here.
|
|
38
|
-
if (result?.error) setError(result.error);
|
|
39
|
-
} catch (err) {
|
|
40
|
-
// A successful verify ends in redirect(), which Next signals by throwing a
|
|
41
|
-
// NEXT_REDIRECT control-flow error. Let Next handle it (perform the navigation)
|
|
42
|
-
// instead of surfacing it as a red error flash; only real errors fall through.
|
|
43
|
-
unstable_rethrow(err);
|
|
44
|
-
setError(err instanceof Error ? err.message : 'Verification failed.');
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const resend = () => {
|
|
50
|
-
setError(null);
|
|
51
|
-
setInfo(null);
|
|
52
|
-
startTransition(async () => {
|
|
53
|
-
try {
|
|
54
|
-
const result = await resendEmailCode();
|
|
55
|
-
if (result?.error) setError(result.error);
|
|
56
|
-
else if (result?.message) setInfo(result.message);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
setError(err instanceof Error ? err.message : 'Could not send a code.');
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className="rounded-xl border bg-card p-6 shadow-sm">
|
|
65
|
-
<h1 className="text-xl font-semibold">Two-step verification</h1>
|
|
66
|
-
<p className="mt-1 text-sm text-muted-foreground">
|
|
67
|
-
{type === 'totp'
|
|
68
|
-
? 'Enter the 6-digit code from your authenticator app to finish signing in.'
|
|
69
|
-
: `For your security, enter the 6-digit code sent to ${email || 'your email'}.`}
|
|
70
|
-
</p>
|
|
71
|
-
|
|
72
|
-
<form
|
|
73
|
-
className="mt-6 space-y-4"
|
|
74
|
-
onSubmit={(e) => {
|
|
75
|
-
e.preventDefault();
|
|
76
|
-
submit();
|
|
77
|
-
}}
|
|
78
|
-
>
|
|
79
|
-
<div className="space-y-2">
|
|
80
|
-
<Label htmlFor="code">Verification code</Label>
|
|
81
|
-
<Input
|
|
82
|
-
id="code"
|
|
83
|
-
inputMode="numeric"
|
|
84
|
-
autoComplete="one-time-code"
|
|
85
|
-
autoFocus
|
|
86
|
-
maxLength={6}
|
|
87
|
-
value={code}
|
|
88
|
-
onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
|
|
89
|
-
placeholder="000000"
|
|
90
|
-
className="tracking-[0.5em] text-center text-lg"
|
|
91
|
-
/>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{error && (
|
|
95
|
-
<Alert variant="destructive" className="py-2 px-4">
|
|
96
|
-
<AlertDescription>{error}</AlertDescription>
|
|
97
|
-
</Alert>
|
|
98
|
-
)}
|
|
99
|
-
{info && !error && (
|
|
100
|
-
<Alert variant="success" className="py-2 px-4">
|
|
101
|
-
<AlertDescription>{info}</AlertDescription>
|
|
102
|
-
</Alert>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
<Button type="submit" disabled={isPending || code.length !== 6} className="w-full">
|
|
106
|
-
{isPending ? <Spinner className="mr-2 h-4 w-4 animate-spin" /> : null}
|
|
107
|
-
Verify & continue
|
|
108
|
-
</Button>
|
|
109
|
-
</form>
|
|
110
|
-
|
|
111
|
-
{type === 'email' && (
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
onClick={resend}
|
|
115
|
-
disabled={isPending}
|
|
116
|
-
className="mt-4 w-full text-center text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:opacity-50"
|
|
117
|
-
>
|
|
118
|
-
{pendingEmailCode ? 'Resend code' : 'Send me a code'}
|
|
119
|
-
</button>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useTransition } from 'react';
|
|
4
|
+
import { unstable_rethrow } from 'next/navigation';
|
|
5
|
+
import { Alert, AlertDescription, Button, Input, Label, Spinner } from '@nextblock-cms/ui';
|
|
6
|
+
import { resendEmailCode, verifyEmailCode, verifyTotpChallenge } from '../actions';
|
|
7
|
+
|
|
8
|
+
interface TwoFactorFormProps {
|
|
9
|
+
type: 'totp' | 'email';
|
|
10
|
+
email: string;
|
|
11
|
+
redirectTo: string;
|
|
12
|
+
pendingEmailCode: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function TwoFactorForm({
|
|
16
|
+
type,
|
|
17
|
+
email,
|
|
18
|
+
redirectTo,
|
|
19
|
+
pendingEmailCode,
|
|
20
|
+
}: TwoFactorFormProps) {
|
|
21
|
+
const [isPending, startTransition] = useTransition();
|
|
22
|
+
const [code, setCode] = useState('');
|
|
23
|
+
const [error, setError] = useState<string | null>(null);
|
|
24
|
+
const [info, setInfo] = useState<string | null>(
|
|
25
|
+
type === 'email' && pendingEmailCode ? `Enter the code we sent to ${email}.` : null,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const submit = () => {
|
|
29
|
+
setError(null);
|
|
30
|
+
const formData = new FormData();
|
|
31
|
+
formData.append('code', code);
|
|
32
|
+
formData.append('redirect_to', redirectTo);
|
|
33
|
+
startTransition(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const action = type === 'totp' ? verifyTotpChallenge : verifyEmailCode;
|
|
36
|
+
const result = await action(formData);
|
|
37
|
+
// A successful action redirects server-side; only failures return here.
|
|
38
|
+
if (result?.error) setError(result.error);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// A successful verify ends in redirect(), which Next signals by throwing a
|
|
41
|
+
// NEXT_REDIRECT control-flow error. Let Next handle it (perform the navigation)
|
|
42
|
+
// instead of surfacing it as a red error flash; only real errors fall through.
|
|
43
|
+
unstable_rethrow(err);
|
|
44
|
+
setError(err instanceof Error ? err.message : 'Verification failed.');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const resend = () => {
|
|
50
|
+
setError(null);
|
|
51
|
+
setInfo(null);
|
|
52
|
+
startTransition(async () => {
|
|
53
|
+
try {
|
|
54
|
+
const result = await resendEmailCode();
|
|
55
|
+
if (result?.error) setError(result.error);
|
|
56
|
+
else if (result?.message) setInfo(result.message);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
setError(err instanceof Error ? err.message : 'Could not send a code.');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="rounded-xl border bg-card p-6 shadow-sm">
|
|
65
|
+
<h1 className="text-xl font-semibold">Two-step verification</h1>
|
|
66
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
67
|
+
{type === 'totp'
|
|
68
|
+
? 'Enter the 6-digit code from your authenticator app to finish signing in.'
|
|
69
|
+
: `For your security, enter the 6-digit code sent to ${email || 'your email'}.`}
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
<form
|
|
73
|
+
className="mt-6 space-y-4"
|
|
74
|
+
onSubmit={(e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
submit();
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<div className="space-y-2">
|
|
80
|
+
<Label htmlFor="code">Verification code</Label>
|
|
81
|
+
<Input
|
|
82
|
+
id="code"
|
|
83
|
+
inputMode="numeric"
|
|
84
|
+
autoComplete="one-time-code"
|
|
85
|
+
autoFocus
|
|
86
|
+
maxLength={6}
|
|
87
|
+
value={code}
|
|
88
|
+
onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
|
|
89
|
+
placeholder="000000"
|
|
90
|
+
className="tracking-[0.5em] text-center text-lg"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{error && (
|
|
95
|
+
<Alert variant="destructive" className="py-2 px-4">
|
|
96
|
+
<AlertDescription>{error}</AlertDescription>
|
|
97
|
+
</Alert>
|
|
98
|
+
)}
|
|
99
|
+
{info && !error && (
|
|
100
|
+
<Alert variant="success" className="py-2 px-4">
|
|
101
|
+
<AlertDescription>{info}</AlertDescription>
|
|
102
|
+
</Alert>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
<Button type="submit" disabled={isPending || code.length !== 6} className="w-full">
|
|
106
|
+
{isPending ? <Spinner className="mr-2 h-4 w-4 animate-spin" /> : null}
|
|
107
|
+
Verify & continue
|
|
108
|
+
</Button>
|
|
109
|
+
</form>
|
|
110
|
+
|
|
111
|
+
{type === 'email' && (
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
onClick={resend}
|
|
115
|
+
disabled={isPending}
|
|
116
|
+
className="mt-4 w-full text-center text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:opacity-50"
|
|
117
|
+
>
|
|
118
|
+
{pendingEmailCode ? 'Resend code' : 'Send me a code'}
|
|
119
|
+
</button>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -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,17 +1,17 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Toaster } from "react-hot-toast";
|
|
4
|
-
|
|
5
|
-
export function ToasterProvider() {
|
|
6
|
-
return (
|
|
7
|
-
<Toaster
|
|
8
|
-
position="top-right"
|
|
9
|
-
toastOptions={{
|
|
10
|
-
style: { fontSize: 14 },
|
|
11
|
-
success: { iconTheme: { primary: '#16a34a', secondary: 'white' } },
|
|
12
|
-
error: { iconTheme: { primary: '#dc2626', secondary: 'white' } },
|
|
13
|
-
}}
|
|
14
|
-
/>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toaster } from "react-hot-toast";
|
|
4
|
+
|
|
5
|
+
export function ToasterProvider() {
|
|
6
|
+
return (
|
|
7
|
+
<Toaster
|
|
8
|
+
position="top-right"
|
|
9
|
+
toastOptions={{
|
|
10
|
+
style: { fontSize: 14 },
|
|
11
|
+
success: { iconTheme: { primary: '#16a34a', secondary: 'white' } },
|
|
12
|
+
error: { iconTheme: { primary: '#dc2626', secondary: 'white' } },
|
|
13
|
+
}}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import { getPageDataBySlug } from './page.utils';
|
|
4
|
-
|
|
5
|
-
export async function getPublishedPageForLocale(slug: string, localeCode: string) {
|
|
6
|
-
return getPageDataBySlug(slug, localeCode);
|
|
7
|
-
}
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { getPageDataBySlug } from './page.utils';
|
|
4
|
+
|
|
5
|
+
export async function getPublishedPageForLocale(slug: string, localeCode: string) {
|
|
6
|
+
return getPageDataBySlug(slug, localeCode);
|
|
7
|
+
}
|