create-nextblock 0.16.1 → 0.16.3
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 +1 -1
- 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 +50 -50
- package/project.json +19 -19
- package/templates/nextblock-template/.dockerignore +1 -1
- package/templates/nextblock-template/.swcrc +30 -30
- package/templates/nextblock-template/AGENTS.md +9 -9
- package/templates/nextblock-template/CLAUDE.md +1 -1
- 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/.well-known/ucp/route.ts +16 -16
- package/templates/nextblock-template/app/ToasterProvider.tsx +26 -26
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/contactSellerActions.test.ts +280 -280
- package/templates/nextblock-template/app/actions/contactSellerActions.ts +222 -222
- package/templates/nextblock-template/app/actions/email-retry.test.ts +62 -62
- package/templates/nextblock-template/app/actions/email.ts +241 -241
- package/templates/nextblock-template/app/actions/formActions.ts +245 -245
- package/templates/nextblock-template/app/actions/interactions.ts +489 -489
- package/templates/nextblock-template/app/actions/threadActions.ts +166 -166
- package/templates/nextblock-template/app/actions/visibilityActions.ts +210 -210
- package/templates/nextblock-template/app/api/ai/seo/alt-text/route.ts +221 -221
- package/templates/nextblock-template/app/api/ai/seo/metadata/route.ts +186 -186
- package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
- package/templates/nextblock-template/app/api/checkout/route.ts +162 -162
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +4 -4
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +4542 -2904
- package/templates/nextblock-template/app/api/mcp/route.ts +415 -415
- 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/api/view/route.ts +114 -114
- 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/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
- package/templates/nextblock-template/app/checkout/page.tsx +57 -57
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +558 -558
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -304
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +406 -406
- package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
- package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +254 -254
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +40 -0
- package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -0
- package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
- package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
- 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/dashboard/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
- package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
- package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/layout.tsx +101 -101
- 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/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -661
- package/templates/nextblock-template/app/cms/messages/actions.ts +404 -404
- package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -333
- package/templates/nextblock-template/app/cms/messages/page.tsx +87 -87
- package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -37
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +649 -649
- package/templates/nextblock-template/app/cms/pages/page.tsx +273 -227
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +2 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +624 -618
- package/templates/nextblock-template/app/cms/posts/page.tsx +251 -194
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- 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/revisions/actions.ts +332 -332
- package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
- package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
- package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
- package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
- package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
- package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
- package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
- 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/components/EmailForm.tsx +227 -227
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
- package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
- package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
- package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
- 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/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -514
- package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -529
- package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -57
- package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -448
- package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -93
- package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -46
- package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -47
- package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
- 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/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/layout.tsx +671 -671
- package/templates/nextblock-template/app/lib/seo.ts +319 -319
- 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/product/[slug]/page.tsx +502 -502
- 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-links.ts +22 -22
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- 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/providers.tsx +96 -96
- package/templates/nextblock-template/app/robots.ts +123 -123
- package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -164
- package/templates/nextblock-template/app/thread/[token]/route.ts +57 -57
- package/templates/nextblock-template/app/thread/layout.tsx +15 -15
- package/templates/nextblock-template/app/thread/page.tsx +98 -98
- 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/BlockRenderer.tsx +312 -312
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
- package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -188
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
- package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
- package/templates/nextblock-template/components/Header.tsx +38 -38
- 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 +378 -378
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -426
- package/templates/nextblock-template/components/SiteScripts.tsx +56 -56
- package/templates/nextblock-template/components/StaffReplies.tsx +102 -102
- 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/PostsGridClient.tsx +48 -48
- package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- 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/CartBlockRenderer.tsx +18 -18
- package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -20
- package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -25
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -385
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
- package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -157
- package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -34
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -612
- 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/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -32
- 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/seo/GenerateMetaButton.tsx +137 -137
- package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +251 -244
- package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -749
- package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -195
- package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -144
- package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -243
- package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -110
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-icon.tsx +78 -78
- package/templates/nextblock-template/components/theme-switcher.tsx +85 -85
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -44
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +77 -77
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -372
- package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -309
- package/templates/nextblock-template/docs/README.md +42 -42
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12506 -12506
- 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/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/blockColors.test.ts +134 -134
- package/templates/nextblock-template/lib/blocks/blockColors.ts +175 -175
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -761
- package/templates/nextblock-template/lib/blocks/inlineScriptNonce.ts +20 -20
- package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -64
- package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -0
- package/templates/nextblock-template/lib/cms/payments-reminder.ts +30 -23
- package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -42
- package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -162
- package/templates/nextblock-template/lib/config/email-settings.ts +323 -323
- package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -57
- package/templates/nextblock-template/lib/cortex-ai/alt-text-request.ts +86 -86
- package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -60
- package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -59
- package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -39
- package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -70
- package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -93
- package/templates/nextblock-template/lib/messages/thread-token.ts +157 -157
- package/templates/nextblock-template/lib/messages/threads.ts +579 -579
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- package/templates/nextblock-template/lib/publishing/viewUrl.ts +26 -26
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- package/templates/nextblock-template/lib/seo/alt-text-write-back.test.ts +154 -154
- package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -109
- package/templates/nextblock-template/lib/seo/block-content.ts +123 -123
- package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -242
- package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -204
- package/templates/nextblock-template/lib/seo/page-audit-context.tsx +157 -140
- package/templates/nextblock-template/lib/seo/page-document.test.ts +384 -350
- package/templates/nextblock-template/lib/seo/page-document.ts +463 -412
- package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -479
- package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -466
- package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -102
- package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -41
- package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -370
- package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -510
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +37 -22
- package/templates/nextblock-template/lib/site-scripts/revisions.ts +71 -71
- package/templates/nextblock-template/lib/site-scripts/types.ts +46 -46
- package/templates/nextblock-template/lib/site-url.test.ts +89 -89
- package/templates/nextblock-template/lib/site-url.ts +102 -102
- package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -124
- package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -31
- package/templates/nextblock-template/lib/themes/tokens.ts +143 -143
- 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 -2
- 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/restore.js +385 -385
- package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
- package/templates/nextblock-template/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
- package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
- package/templates/nextblock-template/tools/update.mjs +1303 -1303
- package/tsconfig.base.json +3 -3
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import { createClient } from '@nextblock-cms/db/server';
|
|
4
|
-
import { encodedRedirect } from '@nextblock-cms/utils/server';
|
|
5
|
-
import { redirect } from 'next/navigation';
|
|
6
|
-
|
|
7
|
-
export async function changePasswordAction(formData: FormData) {
|
|
8
|
-
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
9
|
-
redirect('/profile');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const supabase = createClient();
|
|
13
|
-
const {
|
|
14
|
-
data: { user },
|
|
15
|
-
error: authError,
|
|
16
|
-
} = await supabase.auth.getUser();
|
|
17
|
-
|
|
18
|
-
if (authError || !user) {
|
|
19
|
-
redirect('/sign-in?redirect=%2Fprofile%2Fpassword');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const password = formData.get('password')?.toString() || '';
|
|
23
|
-
const confirmPassword =
|
|
24
|
-
formData.get('confirmPassword')?.toString() || '';
|
|
25
|
-
|
|
26
|
-
if (!password || !confirmPassword) {
|
|
27
|
-
return encodedRedirect(
|
|
28
|
-
'error',
|
|
29
|
-
'/profile/password',
|
|
30
|
-
'password_update_failed'
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (password !== confirmPassword) {
|
|
35
|
-
return encodedRedirect(
|
|
36
|
-
'error',
|
|
37
|
-
'/profile/password',
|
|
38
|
-
'passwords_do_not_match'
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const { error } = await supabase.auth.updateUser({
|
|
43
|
-
password,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (error) {
|
|
47
|
-
return encodedRedirect(
|
|
48
|
-
'error',
|
|
49
|
-
'/profile/password',
|
|
50
|
-
'password_update_failed'
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return encodedRedirect(
|
|
55
|
-
'success',
|
|
56
|
-
'/profile/password',
|
|
57
|
-
'password_updated_success'
|
|
58
|
-
);
|
|
59
|
-
}
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { createClient } from '@nextblock-cms/db/server';
|
|
4
|
+
import { encodedRedirect } from '@nextblock-cms/utils/server';
|
|
5
|
+
import { redirect } from 'next/navigation';
|
|
6
|
+
|
|
7
|
+
export async function changePasswordAction(formData: FormData) {
|
|
8
|
+
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
9
|
+
redirect('/profile');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const supabase = createClient();
|
|
13
|
+
const {
|
|
14
|
+
data: { user },
|
|
15
|
+
error: authError,
|
|
16
|
+
} = await supabase.auth.getUser();
|
|
17
|
+
|
|
18
|
+
if (authError || !user) {
|
|
19
|
+
redirect('/sign-in?redirect=%2Fprofile%2Fpassword');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const password = formData.get('password')?.toString() || '';
|
|
23
|
+
const confirmPassword =
|
|
24
|
+
formData.get('confirmPassword')?.toString() || '';
|
|
25
|
+
|
|
26
|
+
if (!password || !confirmPassword) {
|
|
27
|
+
return encodedRedirect(
|
|
28
|
+
'error',
|
|
29
|
+
'/profile/password',
|
|
30
|
+
'password_update_failed'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (password !== confirmPassword) {
|
|
35
|
+
return encodedRedirect(
|
|
36
|
+
'error',
|
|
37
|
+
'/profile/password',
|
|
38
|
+
'passwords_do_not_match'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { error } = await supabase.auth.updateUser({
|
|
43
|
+
password,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (error) {
|
|
47
|
+
return encodedRedirect(
|
|
48
|
+
'error',
|
|
49
|
+
'/profile/password',
|
|
50
|
+
'password_update_failed'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return encodedRedirect(
|
|
55
|
+
'success',
|
|
56
|
+
'/profile/password',
|
|
57
|
+
'password_updated_success'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { requireProfileAccountContext } from '../account-data';
|
|
2
|
-
import { PasswordSettingsPageClient } from './PasswordSettingsPageClient';
|
|
3
|
-
import { notFound } from 'next/navigation';
|
|
4
|
-
|
|
5
|
-
export default async function ProfilePasswordPage({
|
|
6
|
-
searchParams,
|
|
7
|
-
}: {
|
|
8
|
-
searchParams: Promise<{ success?: string; error?: string }>;
|
|
9
|
-
}) {
|
|
10
|
-
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
11
|
-
notFound();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const { profile, user } = await requireProfileAccountContext(
|
|
15
|
-
'/profile/password'
|
|
16
|
-
);
|
|
17
|
-
const params = await searchParams;
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<PasswordSettingsPageClient
|
|
21
|
-
profile={profile}
|
|
22
|
-
user={user}
|
|
23
|
-
successMessage={params.success || null}
|
|
24
|
-
errorMessage={params.error || null}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
1
|
+
import { requireProfileAccountContext } from '../account-data';
|
|
2
|
+
import { PasswordSettingsPageClient } from './PasswordSettingsPageClient';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
4
|
+
|
|
5
|
+
export default async function ProfilePasswordPage({
|
|
6
|
+
searchParams,
|
|
7
|
+
}: {
|
|
8
|
+
searchParams: Promise<{ success?: string; error?: string }>;
|
|
9
|
+
}) {
|
|
10
|
+
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
11
|
+
notFound();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { profile, user } = await requireProfileAccountContext(
|
|
15
|
+
'/profile/password'
|
|
16
|
+
);
|
|
17
|
+
const params = await searchParams;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<PasswordSettingsPageClient
|
|
21
|
+
profile={profile}
|
|
22
|
+
user={user}
|
|
23
|
+
successMessage={params.success || null}
|
|
24
|
+
errorMessage={params.error || null}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { ThemeProvider } from "next-themes";
|
|
4
|
-
|
|
5
|
-
// Suppress the React 19 warning for the <script> tag generated by next-themes in development
|
|
6
|
-
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
|
|
7
|
-
const orig = console.error;
|
|
8
|
-
console.error = (...args: any[]) => {
|
|
9
|
-
if (typeof args[0] === 'string' && args[0].includes('Encountered a script tag')) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
orig.apply(console, args);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
import { AuthProvider } from '../context/AuthContext';
|
|
16
|
-
import { LanguageProvider, useLanguage } from '../context/LanguageContext';
|
|
17
|
-
import { CurrentContentProvider } from '../context/CurrentContentContext';
|
|
18
|
-
import { ThemeCatalogProvider } from '../context/ThemeCatalogContext';
|
|
19
|
-
import { DeferredCartTranslator } from '../components/DeferredCartTranslator';
|
|
20
|
-
import { CurrencyProvider } from '@nextblock-cms/ecommerce/CurrencyProvider';
|
|
21
|
-
import { TranslationsProvider } from '@nextblock-cms/utils';
|
|
22
|
-
|
|
23
|
-
function TranslationBridge({
|
|
24
|
-
children,
|
|
25
|
-
translations,
|
|
26
|
-
}: {
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
translations: { key: string; translations: Record<string, string> }[];
|
|
29
|
-
}) {
|
|
30
|
-
const { currentLocale } = useLanguage();
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<TranslationsProvider translations={translations} lang={currentLocale}>
|
|
34
|
-
{children}
|
|
35
|
-
</TranslationsProvider>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function Providers({ children, ...props }: { children: React.ReactNode;[key: string]: any; }) {
|
|
40
|
-
const {
|
|
41
|
-
serverUser,
|
|
42
|
-
serverProfile,
|
|
43
|
-
serverLocale,
|
|
44
|
-
initialCurrencies,
|
|
45
|
-
initialCurrencyCode,
|
|
46
|
-
initialAvailableLanguages,
|
|
47
|
-
initialDefaultLanguage,
|
|
48
|
-
rememberVisitorChoice,
|
|
49
|
-
translations,
|
|
50
|
-
nonce,
|
|
51
|
-
themeSlugs,
|
|
52
|
-
initialTheme,
|
|
53
|
-
themeCatalog,
|
|
54
|
-
} = props;
|
|
55
|
-
|
|
56
|
-
// Themes come from the site_themes table. Fall back to the three that ship in
|
|
57
|
-
// libs/ui/src/styles/theme.css if the table is empty or unreachable, so the
|
|
58
|
-
// switcher never renders an empty list.
|
|
59
|
-
const resolvedThemes: string[] =
|
|
60
|
-
Array.isArray(themeSlugs) && themeSlugs.length > 0 ? themeSlugs : ['light', 'dark', 'vibrant'];
|
|
61
|
-
const resolvedDefault: string =
|
|
62
|
-
typeof initialTheme === 'string' && resolvedThemes.includes(initialTheme) ? initialTheme : 'light';
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<AuthProvider serverUser={serverUser} serverProfile={serverProfile}>
|
|
66
|
-
<LanguageProvider
|
|
67
|
-
serverLocale={serverLocale}
|
|
68
|
-
initialAvailableLanguages={initialAvailableLanguages}
|
|
69
|
-
initialDefaultLanguage={initialDefaultLanguage}
|
|
70
|
-
rememberVisitorChoice={rememberVisitorChoice}
|
|
71
|
-
>
|
|
72
|
-
<CurrencyProvider
|
|
73
|
-
initialCurrencies={initialCurrencies}
|
|
74
|
-
initialCurrencyCode={initialCurrencyCode}
|
|
75
|
-
locale={serverLocale}
|
|
76
|
-
>
|
|
77
|
-
<CurrentContentProvider>
|
|
78
|
-
<DeferredCartTranslator />
|
|
79
|
-
<TranslationBridge translations={translations}>
|
|
80
|
-
<ThemeProvider
|
|
81
|
-
attribute="class"
|
|
82
|
-
defaultTheme={resolvedDefault}
|
|
83
|
-
enableSystem
|
|
84
|
-
disableTransitionOnChange
|
|
85
|
-
nonce={nonce}
|
|
86
|
-
themes={resolvedThemes}
|
|
87
|
-
>
|
|
88
|
-
<ThemeCatalogProvider themes={themeCatalog}>{children}</ThemeCatalogProvider>
|
|
89
|
-
</ThemeProvider>
|
|
90
|
-
</TranslationBridge>
|
|
91
|
-
</CurrentContentProvider>
|
|
92
|
-
</CurrencyProvider>
|
|
93
|
-
</LanguageProvider>
|
|
94
|
-
</AuthProvider>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider } from "next-themes";
|
|
4
|
+
|
|
5
|
+
// Suppress the React 19 warning for the <script> tag generated by next-themes in development
|
|
6
|
+
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
|
|
7
|
+
const orig = console.error;
|
|
8
|
+
console.error = (...args: any[]) => {
|
|
9
|
+
if (typeof args[0] === 'string' && args[0].includes('Encountered a script tag')) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
orig.apply(console, args);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
import { AuthProvider } from '../context/AuthContext';
|
|
16
|
+
import { LanguageProvider, useLanguage } from '../context/LanguageContext';
|
|
17
|
+
import { CurrentContentProvider } from '../context/CurrentContentContext';
|
|
18
|
+
import { ThemeCatalogProvider } from '../context/ThemeCatalogContext';
|
|
19
|
+
import { DeferredCartTranslator } from '../components/DeferredCartTranslator';
|
|
20
|
+
import { CurrencyProvider } from '@nextblock-cms/ecommerce/CurrencyProvider';
|
|
21
|
+
import { TranslationsProvider } from '@nextblock-cms/utils';
|
|
22
|
+
|
|
23
|
+
function TranslationBridge({
|
|
24
|
+
children,
|
|
25
|
+
translations,
|
|
26
|
+
}: {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
translations: { key: string; translations: Record<string, string> }[];
|
|
29
|
+
}) {
|
|
30
|
+
const { currentLocale } = useLanguage();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TranslationsProvider translations={translations} lang={currentLocale}>
|
|
34
|
+
{children}
|
|
35
|
+
</TranslationsProvider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Providers({ children, ...props }: { children: React.ReactNode;[key: string]: any; }) {
|
|
40
|
+
const {
|
|
41
|
+
serverUser,
|
|
42
|
+
serverProfile,
|
|
43
|
+
serverLocale,
|
|
44
|
+
initialCurrencies,
|
|
45
|
+
initialCurrencyCode,
|
|
46
|
+
initialAvailableLanguages,
|
|
47
|
+
initialDefaultLanguage,
|
|
48
|
+
rememberVisitorChoice,
|
|
49
|
+
translations,
|
|
50
|
+
nonce,
|
|
51
|
+
themeSlugs,
|
|
52
|
+
initialTheme,
|
|
53
|
+
themeCatalog,
|
|
54
|
+
} = props;
|
|
55
|
+
|
|
56
|
+
// Themes come from the site_themes table. Fall back to the three that ship in
|
|
57
|
+
// libs/ui/src/styles/theme.css if the table is empty or unreachable, so the
|
|
58
|
+
// switcher never renders an empty list.
|
|
59
|
+
const resolvedThemes: string[] =
|
|
60
|
+
Array.isArray(themeSlugs) && themeSlugs.length > 0 ? themeSlugs : ['light', 'dark', 'vibrant'];
|
|
61
|
+
const resolvedDefault: string =
|
|
62
|
+
typeof initialTheme === 'string' && resolvedThemes.includes(initialTheme) ? initialTheme : 'light';
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<AuthProvider serverUser={serverUser} serverProfile={serverProfile}>
|
|
66
|
+
<LanguageProvider
|
|
67
|
+
serverLocale={serverLocale}
|
|
68
|
+
initialAvailableLanguages={initialAvailableLanguages}
|
|
69
|
+
initialDefaultLanguage={initialDefaultLanguage}
|
|
70
|
+
rememberVisitorChoice={rememberVisitorChoice}
|
|
71
|
+
>
|
|
72
|
+
<CurrencyProvider
|
|
73
|
+
initialCurrencies={initialCurrencies}
|
|
74
|
+
initialCurrencyCode={initialCurrencyCode}
|
|
75
|
+
locale={serverLocale}
|
|
76
|
+
>
|
|
77
|
+
<CurrentContentProvider>
|
|
78
|
+
<DeferredCartTranslator />
|
|
79
|
+
<TranslationBridge translations={translations}>
|
|
80
|
+
<ThemeProvider
|
|
81
|
+
attribute="class"
|
|
82
|
+
defaultTheme={resolvedDefault}
|
|
83
|
+
enableSystem
|
|
84
|
+
disableTransitionOnChange
|
|
85
|
+
nonce={nonce}
|
|
86
|
+
themes={resolvedThemes}
|
|
87
|
+
>
|
|
88
|
+
<ThemeCatalogProvider themes={themeCatalog}>{children}</ThemeCatalogProvider>
|
|
89
|
+
</ThemeProvider>
|
|
90
|
+
</TranslationBridge>
|
|
91
|
+
</CurrentContentProvider>
|
|
92
|
+
</CurrencyProvider>
|
|
93
|
+
</LanguageProvider>
|
|
94
|
+
</AuthProvider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import type { MetadataRoute } from 'next';
|
|
2
|
-
import { getSsgSupabaseClient } from '@nextblock-cms/db/server';
|
|
3
|
-
import { normalizeRobotsSettings, type RobotsSettings } from '@nextblock-cms/utils/seo';
|
|
4
|
-
import { buildRobotsMetadata } from '../lib/seo/robots-txt';
|
|
5
|
-
import { resolveSiteUrl, hasResolvedSiteUrl } from '../lib/site-url';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* /robots.txt, generated from the operator's stored settings.
|
|
9
|
-
*
|
|
10
|
-
* This replaces the hand-rolled `app/robots.txt/route.ts` that used to live here,
|
|
11
|
-
* and the two CANNOT coexist: Next's `normalizeMetadataRoute` rewrites the page
|
|
12
|
-
* `/robots` to `/robots.txt` and then appends `/route`, which is character for
|
|
13
|
-
* character the app path the old handler occupied. Keeping both would be a
|
|
14
|
-
* duplicate-route collision, not a fallback — so the old file was deleted in the
|
|
15
|
-
* same change that added this one. The externally visible path is unchanged, which
|
|
16
|
-
* is why `isSetupAllowlisted()` in proxy.ts still allowlists the literal
|
|
17
|
-
* '/robots.txt' and needed no edit.
|
|
18
|
-
*
|
|
19
|
-
* The move from a route handler to the metadata route is what buys the caching
|
|
20
|
-
* below. Everything else about the response — the served path, the content type, the
|
|
21
|
-
* sandbox behaviour — is identical to what shipped before.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Cache the generated file and rebuild it at most once an hour, mirroring
|
|
26
|
-
* app/sitemap.ts. Crawlers get a fast, statically-served response while a change an
|
|
27
|
-
* operator saves in /cms/settings still takes effect without a redeploy.
|
|
28
|
-
*
|
|
29
|
-
* NextBlock does not enable Next.js Cache Components, so the route-segment
|
|
30
|
-
* `revalidate` config is the idiomatic caching control here. If `cacheComponents`
|
|
31
|
-
* were turned on, the equivalent would be a `'use cache'` body paired with
|
|
32
|
-
* `cacheLife('hours')` instead of this export.
|
|
33
|
-
*/
|
|
34
|
-
export const revalidate = 3600;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The `site_settings` row the SEO screen writes, seeded by migration 30 and moved into
|
|
38
|
-
* the ADMIN-only write group by migration 31 — a WRITER could otherwise have PATCHed
|
|
39
|
-
* this row through PostgREST and de-indexed the whole site, since RLS, not the server
|
|
40
|
-
* action, is the boundary that actually holds.
|
|
41
|
-
*/
|
|
42
|
-
const ROBOTS_SETTINGS_KEY = 'seo_robots_settings';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Reads the stored robots configuration, falling back to the permissive defaults on
|
|
46
|
-
* any failure whatsoever.
|
|
47
|
-
*
|
|
48
|
-
* The anon client is the right one here, and deliberately so: `seo_robots_settings`
|
|
49
|
-
* is a non-secret key and `site_settings`' read policy is already public for
|
|
50
|
-
* non-secret keys, so nothing on this path needs the service role. Handing a
|
|
51
|
-
* service-role client to a route that anonymous crawlers hit would be a needless
|
|
52
|
-
* escalation.
|
|
53
|
-
*
|
|
54
|
-
* That read has to stay anonymous, which is why migration 31 tightened only the
|
|
55
|
-
* INSERT/UPDATE/DELETE policies and left `site_settings_read_policy` alone. Adding
|
|
56
|
-
* this key to the read policy's sensitive array would not fail loudly — the query
|
|
57
|
-
* below would simply return no row, every crawler would be served the permissive
|
|
58
|
-
* defaults, and the operator's configuration would stop applying without a single
|
|
59
|
-
* error anywhere.
|
|
60
|
-
*
|
|
61
|
-
* The failure handling matters more than it looks. A crawler that receives a 500 for
|
|
62
|
-
* robots.txt may treat the entire site as disallowed until it next succeeds, so an
|
|
63
|
-
* unreachable database — or a `cms_redirects`-era install that has not yet run
|
|
64
|
-
* `npm run db:migrate`, where this settings row does not exist — must produce a
|
|
65
|
-
* valid, permissive file rather than an error. `normalizeRobotsSettings` handles the
|
|
66
|
-
* other half of that: whatever jsonb hands back, including null, a string or a
|
|
67
|
-
* half-migrated object, becomes a complete `RobotsSettings`.
|
|
68
|
-
*/
|
|
69
|
-
async function loadRobotsSettings(): Promise<RobotsSettings> {
|
|
70
|
-
try {
|
|
71
|
-
const supabase = getSsgSupabaseClient();
|
|
72
|
-
const { data, error } = await supabase
|
|
73
|
-
.from('site_settings')
|
|
74
|
-
.select('value')
|
|
75
|
-
.eq('key', ROBOTS_SETTINGS_KEY)
|
|
76
|
-
.maybeSingle();
|
|
77
|
-
|
|
78
|
-
if (error) {
|
|
79
|
-
console.error('robots.txt: failed to read the robots settings; serving defaults.', error);
|
|
80
|
-
return normalizeRobotsSettings(undefined);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// A missing row is not an error — it is a site whose operator has never opened
|
|
84
|
-
// the SEO screen — and the defaults are exactly what that site should serve.
|
|
85
|
-
return normalizeRobotsSettings(data?.value);
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error('robots.txt: robots settings lookup threw; serving defaults.', error);
|
|
88
|
-
return normalizeRobotsSettings(undefined);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export default async function robots(): Promise<MetadataRoute.Robots> {
|
|
93
|
-
const isSandbox = process.env.NEXT_PUBLIC_IS_SANDBOX === 'true';
|
|
94
|
-
|
|
95
|
-
// The sandbox answer ignores every stored setting, so there is nothing to read.
|
|
96
|
-
// Skipping the query keeps a disposable deployment's robots.txt working even when
|
|
97
|
-
// its database is asleep or being reset by the cron job. The reasoning for why the
|
|
98
|
-
// sandbox ALLOWS crawling rather than disallowing it lives on SANDBOX_USER_AGENT_RULE
|
|
99
|
-
// in lib/seo/robots-txt.ts — it is the counter-intuitive part of this feature and
|
|
100
|
-
// that is where it is written down in full.
|
|
101
|
-
if (isSandbox) {
|
|
102
|
-
return buildRobotsMetadata(normalizeRobotsSettings(undefined), {
|
|
103
|
-
isSandbox: true,
|
|
104
|
-
sitemapUrl: null,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Explicit NEXT_PUBLIC_URL → Vercel production URL → local-dev fallback.
|
|
109
|
-
const siteUrl = resolveSiteUrl();
|
|
110
|
-
|
|
111
|
-
if (!hasResolvedSiteUrl()) {
|
|
112
|
-
console.warn(
|
|
113
|
-
'Warning: no site URL is set for robots.txt (NEXT_PUBLIC_URL / Vercel production URL). Defaulting to http://localhost:3000. Set NEXT_PUBLIC_URL for production.'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const settings = await loadRobotsSettings();
|
|
118
|
-
|
|
119
|
-
return buildRobotsMetadata(settings, {
|
|
120
|
-
isSandbox: false,
|
|
121
|
-
sitemapUrl: `${siteUrl}/sitemap.xml`,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
1
|
+
import type { MetadataRoute } from 'next';
|
|
2
|
+
import { getSsgSupabaseClient } from '@nextblock-cms/db/server';
|
|
3
|
+
import { normalizeRobotsSettings, type RobotsSettings } from '@nextblock-cms/utils/seo';
|
|
4
|
+
import { buildRobotsMetadata } from '../lib/seo/robots-txt';
|
|
5
|
+
import { resolveSiteUrl, hasResolvedSiteUrl } from '../lib/site-url';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* /robots.txt, generated from the operator's stored settings.
|
|
9
|
+
*
|
|
10
|
+
* This replaces the hand-rolled `app/robots.txt/route.ts` that used to live here,
|
|
11
|
+
* and the two CANNOT coexist: Next's `normalizeMetadataRoute` rewrites the page
|
|
12
|
+
* `/robots` to `/robots.txt` and then appends `/route`, which is character for
|
|
13
|
+
* character the app path the old handler occupied. Keeping both would be a
|
|
14
|
+
* duplicate-route collision, not a fallback — so the old file was deleted in the
|
|
15
|
+
* same change that added this one. The externally visible path is unchanged, which
|
|
16
|
+
* is why `isSetupAllowlisted()` in proxy.ts still allowlists the literal
|
|
17
|
+
* '/robots.txt' and needed no edit.
|
|
18
|
+
*
|
|
19
|
+
* The move from a route handler to the metadata route is what buys the caching
|
|
20
|
+
* below. Everything else about the response — the served path, the content type, the
|
|
21
|
+
* sandbox behaviour — is identical to what shipped before.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cache the generated file and rebuild it at most once an hour, mirroring
|
|
26
|
+
* app/sitemap.ts. Crawlers get a fast, statically-served response while a change an
|
|
27
|
+
* operator saves in /cms/settings still takes effect without a redeploy.
|
|
28
|
+
*
|
|
29
|
+
* NextBlock does not enable Next.js Cache Components, so the route-segment
|
|
30
|
+
* `revalidate` config is the idiomatic caching control here. If `cacheComponents`
|
|
31
|
+
* were turned on, the equivalent would be a `'use cache'` body paired with
|
|
32
|
+
* `cacheLife('hours')` instead of this export.
|
|
33
|
+
*/
|
|
34
|
+
export const revalidate = 3600;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The `site_settings` row the SEO screen writes, seeded by migration 30 and moved into
|
|
38
|
+
* the ADMIN-only write group by migration 31 — a WRITER could otherwise have PATCHed
|
|
39
|
+
* this row through PostgREST and de-indexed the whole site, since RLS, not the server
|
|
40
|
+
* action, is the boundary that actually holds.
|
|
41
|
+
*/
|
|
42
|
+
const ROBOTS_SETTINGS_KEY = 'seo_robots_settings';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reads the stored robots configuration, falling back to the permissive defaults on
|
|
46
|
+
* any failure whatsoever.
|
|
47
|
+
*
|
|
48
|
+
* The anon client is the right one here, and deliberately so: `seo_robots_settings`
|
|
49
|
+
* is a non-secret key and `site_settings`' read policy is already public for
|
|
50
|
+
* non-secret keys, so nothing on this path needs the service role. Handing a
|
|
51
|
+
* service-role client to a route that anonymous crawlers hit would be a needless
|
|
52
|
+
* escalation.
|
|
53
|
+
*
|
|
54
|
+
* That read has to stay anonymous, which is why migration 31 tightened only the
|
|
55
|
+
* INSERT/UPDATE/DELETE policies and left `site_settings_read_policy` alone. Adding
|
|
56
|
+
* this key to the read policy's sensitive array would not fail loudly — the query
|
|
57
|
+
* below would simply return no row, every crawler would be served the permissive
|
|
58
|
+
* defaults, and the operator's configuration would stop applying without a single
|
|
59
|
+
* error anywhere.
|
|
60
|
+
*
|
|
61
|
+
* The failure handling matters more than it looks. A crawler that receives a 500 for
|
|
62
|
+
* robots.txt may treat the entire site as disallowed until it next succeeds, so an
|
|
63
|
+
* unreachable database — or a `cms_redirects`-era install that has not yet run
|
|
64
|
+
* `npm run db:migrate`, where this settings row does not exist — must produce a
|
|
65
|
+
* valid, permissive file rather than an error. `normalizeRobotsSettings` handles the
|
|
66
|
+
* other half of that: whatever jsonb hands back, including null, a string or a
|
|
67
|
+
* half-migrated object, becomes a complete `RobotsSettings`.
|
|
68
|
+
*/
|
|
69
|
+
async function loadRobotsSettings(): Promise<RobotsSettings> {
|
|
70
|
+
try {
|
|
71
|
+
const supabase = getSsgSupabaseClient();
|
|
72
|
+
const { data, error } = await supabase
|
|
73
|
+
.from('site_settings')
|
|
74
|
+
.select('value')
|
|
75
|
+
.eq('key', ROBOTS_SETTINGS_KEY)
|
|
76
|
+
.maybeSingle();
|
|
77
|
+
|
|
78
|
+
if (error) {
|
|
79
|
+
console.error('robots.txt: failed to read the robots settings; serving defaults.', error);
|
|
80
|
+
return normalizeRobotsSettings(undefined);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A missing row is not an error — it is a site whose operator has never opened
|
|
84
|
+
// the SEO screen — and the defaults are exactly what that site should serve.
|
|
85
|
+
return normalizeRobotsSettings(data?.value);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('robots.txt: robots settings lookup threw; serving defaults.', error);
|
|
88
|
+
return normalizeRobotsSettings(undefined);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default async function robots(): Promise<MetadataRoute.Robots> {
|
|
93
|
+
const isSandbox = process.env.NEXT_PUBLIC_IS_SANDBOX === 'true';
|
|
94
|
+
|
|
95
|
+
// The sandbox answer ignores every stored setting, so there is nothing to read.
|
|
96
|
+
// Skipping the query keeps a disposable deployment's robots.txt working even when
|
|
97
|
+
// its database is asleep or being reset by the cron job. The reasoning for why the
|
|
98
|
+
// sandbox ALLOWS crawling rather than disallowing it lives on SANDBOX_USER_AGENT_RULE
|
|
99
|
+
// in lib/seo/robots-txt.ts — it is the counter-intuitive part of this feature and
|
|
100
|
+
// that is where it is written down in full.
|
|
101
|
+
if (isSandbox) {
|
|
102
|
+
return buildRobotsMetadata(normalizeRobotsSettings(undefined), {
|
|
103
|
+
isSandbox: true,
|
|
104
|
+
sitemapUrl: null,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Explicit NEXT_PUBLIC_URL → Vercel production URL → local-dev fallback.
|
|
109
|
+
const siteUrl = resolveSiteUrl();
|
|
110
|
+
|
|
111
|
+
if (!hasResolvedSiteUrl()) {
|
|
112
|
+
console.warn(
|
|
113
|
+
'Warning: no site URL is set for robots.txt (NEXT_PUBLIC_URL / Vercel production URL). Defaulting to http://localhost:3000. Set NEXT_PUBLIC_URL for production.'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const settings = await loadRobotsSettings();
|
|
118
|
+
|
|
119
|
+
return buildRobotsMetadata(settings, {
|
|
120
|
+
isSandbox: false,
|
|
121
|
+
sitemapUrl: `${siteUrl}/sitemap.xml`,
|
|
122
|
+
});
|
|
123
|
+
}
|