create-nextblock 0.16.1 → 0.16.2
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/sandboxResetSql.ts +3092 -3092
- 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/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/posts/components/PostForm.tsx +618 -618
- 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 +244 -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 +140 -140
- package/templates/nextblock-template/lib/seo/page-document.test.ts +350 -350
- package/templates/nextblock-template/lib/seo/page-document.ts +412 -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 +177 -177
- 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,93 +1,93 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The snake_case ↔ camelCase boundary for managed redirects.
|
|
3
|
-
*
|
|
4
|
-
* The database speaks `source_path` / `is_active`; the pure SEO engine in
|
|
5
|
-
* `@nextblock-cms/utils/seo` speaks `sourcePath` / `isActive`. Exactly one place is
|
|
6
|
-
* allowed to know both vocabularies, and this is it — every other module on this
|
|
7
|
-
* screen sees one shape or the other and never has to guess which.
|
|
8
|
-
*
|
|
9
|
-
* These helpers are pure and live outside `actions.ts` deliberately: that file is
|
|
10
|
-
* `'use server'`, where every export must be an async server action, so a synchronous
|
|
11
|
-
* mapper cannot be exported from there. Keeping them here also means the client
|
|
12
|
-
* components can use the identical mapping the actions use, which is what lets the
|
|
13
|
-
* browser run the real `validateRedirectRule` against the real rule set before a
|
|
14
|
-
* request is ever made.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { RedirectRule, RedirectStatusCode } from '@nextblock-cms/utils/seo';
|
|
18
|
-
|
|
19
|
-
import { mapRedirectRow, type CmsRedirectRow } from '../../../../lib/seo/redirect-store';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A row of `public.cms_redirects` as this screen sees it.
|
|
23
|
-
*
|
|
24
|
-
* WHY IT IS AN ALIAS AND NOT A SECOND HAND-WRITTEN INTERFACE. The row shape was
|
|
25
|
-
* already transcribed from migration 00000000000030's DDL in `lib/seo/redirect-store.ts`,
|
|
26
|
-
* for the proxy's benefit, and for the same reason it had to be hand-written there:
|
|
27
|
-
* `libs/db`'s generated `Database` type comes from `npm run db:types` against a live
|
|
28
|
-
* project, that migration has not been applied yet, and so
|
|
29
|
-
* `Database['public']['Tables']['cms_redirects']` does not compile today. Restating
|
|
30
|
-
* the seven columns here would give the repo two hand-written descriptions of one
|
|
31
|
-
* table that could drift apart in a rename; aliasing gives it one. When the migration
|
|
32
|
-
* lands and the types are regenerated, there is a single interface to delete.
|
|
33
|
-
*/
|
|
34
|
-
export type CmsRedirect = CmsRedirectRow;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* What the add/edit form collects, in the engine's vocabulary.
|
|
38
|
-
*
|
|
39
|
-
* `statusCode` is `RedirectStatusCode` rather than `number` so a widened value cannot
|
|
40
|
-
* reach the database and trip the table's CHECK constraint as an opaque Postgres
|
|
41
|
-
* error; the server re-validates it with Zod regardless, because a type says nothing
|
|
42
|
-
* about what actually arrived over the wire.
|
|
43
|
-
*/
|
|
44
|
-
export interface RedirectInput {
|
|
45
|
-
destinationPath: string;
|
|
46
|
-
isActive: boolean;
|
|
47
|
-
sourcePath: string;
|
|
48
|
-
statusCode: RedirectStatusCode;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Maps stored rows onto the engine's rule shape, dropping any row too malformed to
|
|
53
|
-
* be a rule at all.
|
|
54
|
-
*
|
|
55
|
-
* Reuses `mapRedirectRow`, the proxy's mapper, so the admin screen's idea of the
|
|
56
|
-
* current rule set is byte-for-byte the proxy's idea of it. That agreement is what
|
|
57
|
-
* makes client-side validation trustworthy: a duplicate or a loop the admin screen
|
|
58
|
-
* refuses is a duplicate or a loop the proxy would actually have hit.
|
|
59
|
-
*
|
|
60
|
-
* Unlike the proxy's query this one is given inactive rows too, and that is
|
|
61
|
-
* intentional — `validateRedirectRule` counts a paused rule when checking for
|
|
62
|
-
* duplicates and loops, so re-enabling one cannot introduce a conflict that
|
|
63
|
-
* validation already waved through.
|
|
64
|
-
*/
|
|
65
|
-
export function toRedirectRules(rows: CmsRedirect[]): RedirectRule[] {
|
|
66
|
-
const rules: RedirectRule[] = [];
|
|
67
|
-
|
|
68
|
-
for (const row of rows) {
|
|
69
|
-
const rule = mapRedirectRow(row);
|
|
70
|
-
if (rule !== null) {
|
|
71
|
-
rules.push(rule);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return rules;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Maps one stored row onto the form's input shape, for the inline editor.
|
|
80
|
-
*
|
|
81
|
-
* Falls back to 301 for an out-of-range `status_code` for the same reason
|
|
82
|
-
* `mapRedirectRow` does: the column is constrained to 301/302, so the fallback never
|
|
83
|
-
* fires in practice, and defaulting to permanent keeps a rule the operator can see in
|
|
84
|
-
* the list from silently becoming un-editable.
|
|
85
|
-
*/
|
|
86
|
-
export function toRedirectInput(row: CmsRedirect): RedirectInput {
|
|
87
|
-
return {
|
|
88
|
-
destinationPath: row.destination_path,
|
|
89
|
-
isActive: row.is_active,
|
|
90
|
-
sourcePath: row.source_path,
|
|
91
|
-
statusCode: row.status_code === 302 ? 302 : 301,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The snake_case ↔ camelCase boundary for managed redirects.
|
|
3
|
+
*
|
|
4
|
+
* The database speaks `source_path` / `is_active`; the pure SEO engine in
|
|
5
|
+
* `@nextblock-cms/utils/seo` speaks `sourcePath` / `isActive`. Exactly one place is
|
|
6
|
+
* allowed to know both vocabularies, and this is it — every other module on this
|
|
7
|
+
* screen sees one shape or the other and never has to guess which.
|
|
8
|
+
*
|
|
9
|
+
* These helpers are pure and live outside `actions.ts` deliberately: that file is
|
|
10
|
+
* `'use server'`, where every export must be an async server action, so a synchronous
|
|
11
|
+
* mapper cannot be exported from there. Keeping them here also means the client
|
|
12
|
+
* components can use the identical mapping the actions use, which is what lets the
|
|
13
|
+
* browser run the real `validateRedirectRule` against the real rule set before a
|
|
14
|
+
* request is ever made.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { RedirectRule, RedirectStatusCode } from '@nextblock-cms/utils/seo';
|
|
18
|
+
|
|
19
|
+
import { mapRedirectRow, type CmsRedirectRow } from '../../../../lib/seo/redirect-store';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A row of `public.cms_redirects` as this screen sees it.
|
|
23
|
+
*
|
|
24
|
+
* WHY IT IS AN ALIAS AND NOT A SECOND HAND-WRITTEN INTERFACE. The row shape was
|
|
25
|
+
* already transcribed from migration 00000000000030's DDL in `lib/seo/redirect-store.ts`,
|
|
26
|
+
* for the proxy's benefit, and for the same reason it had to be hand-written there:
|
|
27
|
+
* `libs/db`'s generated `Database` type comes from `npm run db:types` against a live
|
|
28
|
+
* project, that migration has not been applied yet, and so
|
|
29
|
+
* `Database['public']['Tables']['cms_redirects']` does not compile today. Restating
|
|
30
|
+
* the seven columns here would give the repo two hand-written descriptions of one
|
|
31
|
+
* table that could drift apart in a rename; aliasing gives it one. When the migration
|
|
32
|
+
* lands and the types are regenerated, there is a single interface to delete.
|
|
33
|
+
*/
|
|
34
|
+
export type CmsRedirect = CmsRedirectRow;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What the add/edit form collects, in the engine's vocabulary.
|
|
38
|
+
*
|
|
39
|
+
* `statusCode` is `RedirectStatusCode` rather than `number` so a widened value cannot
|
|
40
|
+
* reach the database and trip the table's CHECK constraint as an opaque Postgres
|
|
41
|
+
* error; the server re-validates it with Zod regardless, because a type says nothing
|
|
42
|
+
* about what actually arrived over the wire.
|
|
43
|
+
*/
|
|
44
|
+
export interface RedirectInput {
|
|
45
|
+
destinationPath: string;
|
|
46
|
+
isActive: boolean;
|
|
47
|
+
sourcePath: string;
|
|
48
|
+
statusCode: RedirectStatusCode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Maps stored rows onto the engine's rule shape, dropping any row too malformed to
|
|
53
|
+
* be a rule at all.
|
|
54
|
+
*
|
|
55
|
+
* Reuses `mapRedirectRow`, the proxy's mapper, so the admin screen's idea of the
|
|
56
|
+
* current rule set is byte-for-byte the proxy's idea of it. That agreement is what
|
|
57
|
+
* makes client-side validation trustworthy: a duplicate or a loop the admin screen
|
|
58
|
+
* refuses is a duplicate or a loop the proxy would actually have hit.
|
|
59
|
+
*
|
|
60
|
+
* Unlike the proxy's query this one is given inactive rows too, and that is
|
|
61
|
+
* intentional — `validateRedirectRule` counts a paused rule when checking for
|
|
62
|
+
* duplicates and loops, so re-enabling one cannot introduce a conflict that
|
|
63
|
+
* validation already waved through.
|
|
64
|
+
*/
|
|
65
|
+
export function toRedirectRules(rows: CmsRedirect[]): RedirectRule[] {
|
|
66
|
+
const rules: RedirectRule[] = [];
|
|
67
|
+
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
const rule = mapRedirectRow(row);
|
|
70
|
+
if (rule !== null) {
|
|
71
|
+
rules.push(rule);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return rules;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Maps one stored row onto the form's input shape, for the inline editor.
|
|
80
|
+
*
|
|
81
|
+
* Falls back to 301 for an out-of-range `status_code` for the same reason
|
|
82
|
+
* `mapRedirectRow` does: the column is constrained to 301/302, so the fallback never
|
|
83
|
+
* fires in practice, and defaulting to permanent keeps a rule the operator can see in
|
|
84
|
+
* the list from silently becoming un-editable.
|
|
85
|
+
*/
|
|
86
|
+
export function toRedirectInput(row: CmsRedirect): RedirectInput {
|
|
87
|
+
return {
|
|
88
|
+
destinationPath: row.destination_path,
|
|
89
|
+
isActive: row.is_active,
|
|
90
|
+
sourcePath: row.source_path,
|
|
91
|
+
statusCode: row.status_code === 302 ? 302 : 301,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
// app/cms/settings/seo/page.tsx
|
|
2
|
-
import { hasResolvedSiteUrl, resolveSiteUrl } from '../../../../lib/site-url';
|
|
3
|
-
import { getRedirects, getRobotsSettings } from './actions';
|
|
4
|
-
import { requireAdminSupabaseClient } from './require-admin';
|
|
5
|
-
import { SeoSettingsClient } from './SeoSettingsClient';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The SEO operations screen: which URLs moved, and what crawlers are allowed to do.
|
|
9
|
-
*
|
|
10
|
-
* ADMIN-only, and gated here as well as in every action and in RLS. The gate is
|
|
11
|
-
* repeated rather than delegated because the three layers protect different things:
|
|
12
|
-
* this one stops a WRITER from ever seeing the screen (a redirect list is a map of a
|
|
13
|
-
* site's history, and the robots block is a map of what it is hiding), the actions
|
|
14
|
-
* stop a hand-rolled request that never loaded the page, and RLS stops everything
|
|
15
|
-
* else. `requireAdminSupabaseClient` throws, which is the right behaviour for a
|
|
16
|
-
* Server Component: an unauthorised visitor gets the error boundary instead of a
|
|
17
|
-
* half-rendered admin screen.
|
|
18
|
-
*
|
|
19
|
-
* The two reads run concurrently because they are independent — one hits
|
|
20
|
-
* `cms_redirects`, the other one row of `site_settings` — and running them in series
|
|
21
|
-
* would add a whole round trip to a screen that is already behind an auth check.
|
|
22
|
-
*/
|
|
23
|
-
export default async function SeoSettingsPage() {
|
|
24
|
-
await requireAdminSupabaseClient();
|
|
25
|
-
|
|
26
|
-
const [redirects, robotsSettings] = await Promise.all([getRedirects(), getRobotsSettings()]);
|
|
27
|
-
|
|
28
|
-
// Resolved on the server, where the deployment's env actually lives, and handed to
|
|
29
|
-
// the preview as data. The client component must never try to work this out for
|
|
30
|
-
// itself: `NEXT_PUBLIC_URL` is inlined at build time and the Vercel fallbacks are
|
|
31
|
-
// server-only, so a browser-side guess would quietly show a different sitemap URL
|
|
32
|
-
// from the one crawlers are given.
|
|
33
|
-
const siteUrl = resolveSiteUrl();
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className="mx-auto max-w-5xl space-y-6">
|
|
37
|
-
<SeoSettingsClient
|
|
38
|
-
initialRedirects={redirects}
|
|
39
|
-
initialRobotsSettings={robotsSettings}
|
|
40
|
-
isSandbox={process.env.NEXT_PUBLIC_IS_SANDBOX === 'true'}
|
|
41
|
-
isSiteUrlConfigured={hasResolvedSiteUrl()}
|
|
42
|
-
sitemapUrl={`${siteUrl}/sitemap.xml`}
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
1
|
+
// app/cms/settings/seo/page.tsx
|
|
2
|
+
import { hasResolvedSiteUrl, resolveSiteUrl } from '../../../../lib/site-url';
|
|
3
|
+
import { getRedirects, getRobotsSettings } from './actions';
|
|
4
|
+
import { requireAdminSupabaseClient } from './require-admin';
|
|
5
|
+
import { SeoSettingsClient } from './SeoSettingsClient';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The SEO operations screen: which URLs moved, and what crawlers are allowed to do.
|
|
9
|
+
*
|
|
10
|
+
* ADMIN-only, and gated here as well as in every action and in RLS. The gate is
|
|
11
|
+
* repeated rather than delegated because the three layers protect different things:
|
|
12
|
+
* this one stops a WRITER from ever seeing the screen (a redirect list is a map of a
|
|
13
|
+
* site's history, and the robots block is a map of what it is hiding), the actions
|
|
14
|
+
* stop a hand-rolled request that never loaded the page, and RLS stops everything
|
|
15
|
+
* else. `requireAdminSupabaseClient` throws, which is the right behaviour for a
|
|
16
|
+
* Server Component: an unauthorised visitor gets the error boundary instead of a
|
|
17
|
+
* half-rendered admin screen.
|
|
18
|
+
*
|
|
19
|
+
* The two reads run concurrently because they are independent — one hits
|
|
20
|
+
* `cms_redirects`, the other one row of `site_settings` — and running them in series
|
|
21
|
+
* would add a whole round trip to a screen that is already behind an auth check.
|
|
22
|
+
*/
|
|
23
|
+
export default async function SeoSettingsPage() {
|
|
24
|
+
await requireAdminSupabaseClient();
|
|
25
|
+
|
|
26
|
+
const [redirects, robotsSettings] = await Promise.all([getRedirects(), getRobotsSettings()]);
|
|
27
|
+
|
|
28
|
+
// Resolved on the server, where the deployment's env actually lives, and handed to
|
|
29
|
+
// the preview as data. The client component must never try to work this out for
|
|
30
|
+
// itself: `NEXT_PUBLIC_URL` is inlined at build time and the Vercel fallbacks are
|
|
31
|
+
// server-only, so a browser-side guess would quietly show a different sitemap URL
|
|
32
|
+
// from the one crawlers are given.
|
|
33
|
+
const siteUrl = resolveSiteUrl();
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="mx-auto max-w-5xl space-y-6">
|
|
37
|
+
<SeoSettingsClient
|
|
38
|
+
initialRedirects={redirects}
|
|
39
|
+
initialRobotsSettings={robotsSettings}
|
|
40
|
+
isSandbox={process.env.NEXT_PUBLIC_IS_SANDBOX === 'true'}
|
|
41
|
+
isSiteUrlConfigured={hasResolvedSiteUrl()}
|
|
42
|
+
sitemapUrl={`${siteUrl}/sitemap.xml`}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { createClient } from '@nextblock-cms/db/server';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Admin gate shared by the SEO settings server actions and by the page itself.
|
|
5
|
-
*
|
|
6
|
-
* Lives outside `actions.ts` because that file is `'use server'`, where every export
|
|
7
|
-
* must itself be a valid server action — a helper that returns a Supabase client
|
|
8
|
-
* cannot be exported from there. Keeping one copy matters more than the file count:
|
|
9
|
-
* this is the check standing between a WRITER and the redirect table, and a redirect
|
|
10
|
-
* rule is an open-redirect surface. Anyone who can write one can point every visitor
|
|
11
|
-
* of `/checkout` at a phishing clone of the site, with the real domain in the
|
|
12
|
-
* referrer, without touching a single page or post. The same reasoning is why
|
|
13
|
-
* migration 00000000000030 grants writes on `cms_redirects` to ADMIN only and leaves
|
|
14
|
-
* WRITER out entirely — the RLS policy is the boundary, and this is the friendly
|
|
15
|
-
* error that keeps an operator from meeting it as an unexplained empty list.
|
|
16
|
-
*
|
|
17
|
-
* It throws rather than returning a result because its two kinds of caller want
|
|
18
|
-
* opposite things. A Server Component wants the throw: an unauthorised visitor gets
|
|
19
|
-
* the error boundary instead of a half-rendered admin screen. A server action called
|
|
20
|
-
* from a client component wants a message it can render, so those callers catch the
|
|
21
|
-
* throw and return it as data — see `requireAdminOrMessage` in `actions.ts` and the
|
|
22
|
-
* note on `SettingsActionResult` about Next replacing uncaught Server Action error
|
|
23
|
-
* messages with a generic string in production.
|
|
24
|
-
*/
|
|
25
|
-
export async function requireAdminSupabaseClient() {
|
|
26
|
-
const supabase = createClient();
|
|
27
|
-
const {
|
|
28
|
-
data: { user },
|
|
29
|
-
error: userError,
|
|
30
|
-
} = await supabase.auth.getUser();
|
|
31
|
-
|
|
32
|
-
if (userError || !user) {
|
|
33
|
-
throw new Error('You must be logged in to manage SEO settings.');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const { data: profile, error: profileError } = await supabase
|
|
37
|
-
.from('profiles')
|
|
38
|
-
.select('role')
|
|
39
|
-
.eq('id', user.id)
|
|
40
|
-
.single();
|
|
41
|
-
|
|
42
|
-
if (profileError || !profile || profile.role !== 'ADMIN') {
|
|
43
|
-
throw new Error('You do not have permission to manage SEO settings.');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return { supabase, userId: user.id };
|
|
47
|
-
}
|
|
1
|
+
import { createClient } from '@nextblock-cms/db/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin gate shared by the SEO settings server actions and by the page itself.
|
|
5
|
+
*
|
|
6
|
+
* Lives outside `actions.ts` because that file is `'use server'`, where every export
|
|
7
|
+
* must itself be a valid server action — a helper that returns a Supabase client
|
|
8
|
+
* cannot be exported from there. Keeping one copy matters more than the file count:
|
|
9
|
+
* this is the check standing between a WRITER and the redirect table, and a redirect
|
|
10
|
+
* rule is an open-redirect surface. Anyone who can write one can point every visitor
|
|
11
|
+
* of `/checkout` at a phishing clone of the site, with the real domain in the
|
|
12
|
+
* referrer, without touching a single page or post. The same reasoning is why
|
|
13
|
+
* migration 00000000000030 grants writes on `cms_redirects` to ADMIN only and leaves
|
|
14
|
+
* WRITER out entirely — the RLS policy is the boundary, and this is the friendly
|
|
15
|
+
* error that keeps an operator from meeting it as an unexplained empty list.
|
|
16
|
+
*
|
|
17
|
+
* It throws rather than returning a result because its two kinds of caller want
|
|
18
|
+
* opposite things. A Server Component wants the throw: an unauthorised visitor gets
|
|
19
|
+
* the error boundary instead of a half-rendered admin screen. A server action called
|
|
20
|
+
* from a client component wants a message it can render, so those callers catch the
|
|
21
|
+
* throw and return it as data — see `requireAdminOrMessage` in `actions.ts` and the
|
|
22
|
+
* note on `SettingsActionResult` about Next replacing uncaught Server Action error
|
|
23
|
+
* messages with a generic string in production.
|
|
24
|
+
*/
|
|
25
|
+
export async function requireAdminSupabaseClient() {
|
|
26
|
+
const supabase = createClient();
|
|
27
|
+
const {
|
|
28
|
+
data: { user },
|
|
29
|
+
error: userError,
|
|
30
|
+
} = await supabase.auth.getUser();
|
|
31
|
+
|
|
32
|
+
if (userError || !user) {
|
|
33
|
+
throw new Error('You must be logged in to manage SEO settings.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { data: profile, error: profileError } = await supabase
|
|
37
|
+
.from('profiles')
|
|
38
|
+
.select('role')
|
|
39
|
+
.eq('id', user.id)
|
|
40
|
+
.single();
|
|
41
|
+
|
|
42
|
+
if (profileError || !profile || profile.role !== 'ADMIN') {
|
|
43
|
+
throw new Error('You do not have permission to manage SEO settings.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { supabase, userId: user.id };
|
|
47
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
// app/cms/settings/site-scripts/page.tsx
|
|
2
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
-
|
|
4
|
-
import { getSiteScriptRevisions, getSiteScripts } from './actions';
|
|
5
|
-
import SiteScriptManager from './components/SiteScriptManager';
|
|
6
|
-
|
|
7
|
-
export default async function SiteScriptsSettingsPage() {
|
|
8
|
-
const [scripts, revisions] = await Promise.all([getSiteScripts(), getSiteScriptRevisions()]);
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<div className="mx-auto max-w-5xl space-y-6">
|
|
12
|
-
<Card>
|
|
13
|
-
<CardHeader>
|
|
14
|
-
<CardTitle>Site Scripts</CardTitle>
|
|
15
|
-
<CardDescription>
|
|
16
|
-
JavaScript that runs on every page of the public site — animation helpers, chat widgets,
|
|
17
|
-
third-party embeds. Each script is injected with the page's Content-Security-Policy
|
|
18
|
-
nonce, so it runs without weakening the site's security headers. Disabled scripts are
|
|
19
|
-
never sent to visitors. Every change is logged and can be rolled back from the history
|
|
20
|
-
below.
|
|
21
|
-
</CardDescription>
|
|
22
|
-
</CardHeader>
|
|
23
|
-
<CardContent>
|
|
24
|
-
<SiteScriptManager initialRevisions={revisions} initialScripts={scripts} />
|
|
25
|
-
</CardContent>
|
|
26
|
-
</Card>
|
|
27
|
-
|
|
28
|
-
<Card>
|
|
29
|
-
<CardHeader>
|
|
30
|
-
<CardTitle>Where else code can live</CardTitle>
|
|
31
|
-
</CardHeader>
|
|
32
|
-
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
|
33
|
-
<p>
|
|
34
|
-
<strong className="text-foreground">One page only?</strong> A rich-text block accepts an
|
|
35
|
-
inline <code><style></code> and <code><script></code> directly, so
|
|
36
|
-
page-specific effects do not need to live here.
|
|
37
|
-
</p>
|
|
38
|
-
<p>
|
|
39
|
-
<strong className="text-foreground">Site-wide styling?</strong> Use Themes & CSS for
|
|
40
|
-
palettes and global stylesheets.
|
|
41
|
-
</p>
|
|
42
|
-
<p>
|
|
43
|
-
<strong className="text-foreground">Marketing or analytics tags?</strong> Put those under
|
|
44
|
-
Google Analytics instead — scripts there only fire once a visitor accepts cookies, which
|
|
45
|
-
is what consent law requires. Scripts on this page run unconditionally.
|
|
46
|
-
</p>
|
|
47
|
-
</CardContent>
|
|
48
|
-
</Card>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
1
|
+
// app/cms/settings/site-scripts/page.tsx
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
+
|
|
4
|
+
import { getSiteScriptRevisions, getSiteScripts } from './actions';
|
|
5
|
+
import SiteScriptManager from './components/SiteScriptManager';
|
|
6
|
+
|
|
7
|
+
export default async function SiteScriptsSettingsPage() {
|
|
8
|
+
const [scripts, revisions] = await Promise.all([getSiteScripts(), getSiteScriptRevisions()]);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="mx-auto max-w-5xl space-y-6">
|
|
12
|
+
<Card>
|
|
13
|
+
<CardHeader>
|
|
14
|
+
<CardTitle>Site Scripts</CardTitle>
|
|
15
|
+
<CardDescription>
|
|
16
|
+
JavaScript that runs on every page of the public site — animation helpers, chat widgets,
|
|
17
|
+
third-party embeds. Each script is injected with the page's Content-Security-Policy
|
|
18
|
+
nonce, so it runs without weakening the site's security headers. Disabled scripts are
|
|
19
|
+
never sent to visitors. Every change is logged and can be rolled back from the history
|
|
20
|
+
below.
|
|
21
|
+
</CardDescription>
|
|
22
|
+
</CardHeader>
|
|
23
|
+
<CardContent>
|
|
24
|
+
<SiteScriptManager initialRevisions={revisions} initialScripts={scripts} />
|
|
25
|
+
</CardContent>
|
|
26
|
+
</Card>
|
|
27
|
+
|
|
28
|
+
<Card>
|
|
29
|
+
<CardHeader>
|
|
30
|
+
<CardTitle>Where else code can live</CardTitle>
|
|
31
|
+
</CardHeader>
|
|
32
|
+
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
|
33
|
+
<p>
|
|
34
|
+
<strong className="text-foreground">One page only?</strong> A rich-text block accepts an
|
|
35
|
+
inline <code><style></code> and <code><script></code> directly, so
|
|
36
|
+
page-specific effects do not need to live here.
|
|
37
|
+
</p>
|
|
38
|
+
<p>
|
|
39
|
+
<strong className="text-foreground">Site-wide styling?</strong> Use Themes & CSS for
|
|
40
|
+
palettes and global stylesheets.
|
|
41
|
+
</p>
|
|
42
|
+
<p>
|
|
43
|
+
<strong className="text-foreground">Marketing or analytics tags?</strong> Put those under
|
|
44
|
+
Google Analytics instead — scripts there only fire once a visitor accepts cookies, which
|
|
45
|
+
is what consent law requires. Scripts on this page run unconditionally.
|
|
46
|
+
</p>
|
|
47
|
+
</CardContent>
|
|
48
|
+
</Card>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { redirect } from 'next/navigation';
|
|
2
|
-
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
-
import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
-
|
|
5
|
-
export const metadata = {
|
|
6
|
-
title: 'Tax Settings | NextBlock™ CMS',
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default async function TaxesPageWrapper({
|
|
10
|
-
searchParams,
|
|
11
|
-
}: {
|
|
12
|
-
searchParams?: Promise<{ success?: string }>;
|
|
13
|
-
}) {
|
|
14
|
-
const isOnline = await verifyPackageOnline('ecommerce');
|
|
15
|
-
|
|
16
|
-
if (!isOnline) {
|
|
17
|
-
redirect('/cms/settings/packages');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return <TaxesPageUI searchParams={await searchParams} />;
|
|
21
|
-
}
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
|
+
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
+
import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: 'Tax Settings | NextBlock™ CMS',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default async function TaxesPageWrapper({
|
|
10
|
+
searchParams,
|
|
11
|
+
}: {
|
|
12
|
+
searchParams?: Promise<{ success?: string }>;
|
|
13
|
+
}) {
|
|
14
|
+
const isOnline = await verifyPackageOnline('ecommerce');
|
|
15
|
+
|
|
16
|
+
if (!isOnline) {
|
|
17
|
+
redirect('/cms/settings/packages');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <TaxesPageUI searchParams={await searchParams} />;
|
|
21
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { redirect } from 'next/navigation';
|
|
2
|
-
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
-
import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
-
|
|
5
|
-
export default async function ShippingPage({
|
|
6
|
-
searchParams,
|
|
7
|
-
}: {
|
|
8
|
-
searchParams: Promise<{ success?: string }>;
|
|
9
|
-
}) {
|
|
10
|
-
const [isOnline, resolvedSearchParams] = await Promise.all([
|
|
11
|
-
verifyPackageOnline('ecommerce'),
|
|
12
|
-
searchParams,
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
if (!isOnline) {
|
|
16
|
-
redirect('/cms/settings/packages');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return <ShippingPageUI searchParams={resolvedSearchParams} />;
|
|
20
|
-
}
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
|
+
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
+
import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
+
|
|
5
|
+
export default async function ShippingPage({
|
|
6
|
+
searchParams,
|
|
7
|
+
}: {
|
|
8
|
+
searchParams: Promise<{ success?: string }>;
|
|
9
|
+
}) {
|
|
10
|
+
const [isOnline, resolvedSearchParams] = await Promise.all([
|
|
11
|
+
verifyPackageOnline('ecommerce'),
|
|
12
|
+
searchParams,
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
if (!isOnline) {
|
|
16
|
+
redirect('/cms/settings/packages');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return <ShippingPageUI searchParams={resolvedSearchParams} />;
|
|
20
|
+
}
|
|
@@ -7,17 +7,17 @@ import { Trash2, ShieldAlert } from "lucide-react";
|
|
|
7
7
|
import { deleteUserAndProfile } from "../actions";
|
|
8
8
|
import { ConfirmationModal } from '../../components/ConfirmationModal';
|
|
9
9
|
|
|
10
|
-
export function DeleteUserButtonClient({
|
|
11
|
-
userId,
|
|
12
|
-
userEmail,
|
|
13
|
-
currentAdminId,
|
|
14
|
-
}: {
|
|
15
|
-
userId: string;
|
|
16
|
-
userEmail?: string;
|
|
17
|
-
currentAdminId?: string;
|
|
18
|
-
}) {
|
|
19
|
-
void userEmail;
|
|
20
|
-
const [isPending, startTransition] = useTransition();
|
|
10
|
+
export function DeleteUserButtonClient({
|
|
11
|
+
userId,
|
|
12
|
+
userEmail,
|
|
13
|
+
currentAdminId,
|
|
14
|
+
}: {
|
|
15
|
+
userId: string;
|
|
16
|
+
userEmail?: string;
|
|
17
|
+
currentAdminId?: string;
|
|
18
|
+
}) {
|
|
19
|
+
void userEmail;
|
|
20
|
+
const [isPending, startTransition] = useTransition();
|
|
21
21
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
22
22
|
|
|
23
23
|
const handleDelete = () => {
|
|
@@ -68,4 +68,4 @@ export function DeleteUserButtonClient({
|
|
|
68
68
|
/>
|
|
69
69
|
</>
|
|
70
70
|
);
|
|
71
|
-
}
|
|
71
|
+
}
|