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,210 +1,210 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Visibility actions — the ONLY writes that can change what the public sees.
|
|
5
|
-
*
|
|
6
|
-
* These deliberately bypass the Live Draft pipeline (`content_drafts` /
|
|
7
|
-
* `product_drafts`) entirely: they read nothing from a draft, create nothing, and
|
|
8
|
-
* leave any pending content edits exactly where they are. Publishing a page and
|
|
9
|
-
* publishing your unsaved edits are two different decisions, so they are two
|
|
10
|
-
* different code paths.
|
|
11
|
-
*
|
|
12
|
-
* Visibility is stored as the (status, published_at) pair — see
|
|
13
|
-
* `libs/utils/src/lib/publishing.ts` for how "scheduled" is derived from it.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { createClient } from "@nextblock-cms/db/server";
|
|
17
|
-
import { revalidatePath } from "next/cache";
|
|
18
|
-
import {
|
|
19
|
-
LIVE_STATUS,
|
|
20
|
-
isFutureSchedule,
|
|
21
|
-
resolveVisibilityState,
|
|
22
|
-
type PublishableType,
|
|
23
|
-
type VisibilityState,
|
|
24
|
-
} from "@nextblock-cms/utils";
|
|
25
|
-
import { getHomepageTranslationGroupId } from "../lib/homepage";
|
|
26
|
-
|
|
27
|
-
export interface VisibilityResult {
|
|
28
|
-
error?: string;
|
|
29
|
-
state?: VisibilityState;
|
|
30
|
-
publishedAt?: string | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** What the caller wants the content to become. */
|
|
34
|
-
export type VisibilityIntent =
|
|
35
|
-
| { action: "publish" }
|
|
36
|
-
| { action: "schedule"; publishedAt: string }
|
|
37
|
-
| { action: "unpublish" }
|
|
38
|
-
| { action: "archive" };
|
|
39
|
-
|
|
40
|
-
const TABLE: Record<PublishableType, "pages" | "posts" | "products"> = {
|
|
41
|
-
page: "pages",
|
|
42
|
-
post: "posts",
|
|
43
|
-
product: "products",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const CMS_LIST_PATH: Record<PublishableType, string> = {
|
|
47
|
-
page: "/cms/pages",
|
|
48
|
-
post: "/cms/posts",
|
|
49
|
-
product: "/cms/products",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function publicPath(type: PublishableType, slug: string): string {
|
|
53
|
-
if (type === "post") return `/article/${slug}`;
|
|
54
|
-
if (type === "product") return `/product/${slug}`;
|
|
55
|
-
return `/${slug}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Turn an intent into the columns to write.
|
|
60
|
-
*
|
|
61
|
-
* "Publish" always clears `published_at`. Leaving a past timestamp behind would be
|
|
62
|
-
* harmless for the public gate but would keep rendering as a stale schedule in the
|
|
63
|
-
* CMS; leaving a FUTURE one would mean "Publish now" silently did nothing.
|
|
64
|
-
*/
|
|
65
|
-
function buildUpdate(type: PublishableType, intent: VisibilityIntent) {
|
|
66
|
-
const liveStatus = LIVE_STATUS[type];
|
|
67
|
-
|
|
68
|
-
switch (intent.action) {
|
|
69
|
-
case "publish":
|
|
70
|
-
return { status: liveStatus, published_at: null };
|
|
71
|
-
case "schedule":
|
|
72
|
-
return { status: liveStatus, published_at: intent.publishedAt };
|
|
73
|
-
case "unpublish":
|
|
74
|
-
return { status: "draft", published_at: null };
|
|
75
|
-
case "archive":
|
|
76
|
-
return { status: "archived", published_at: null };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Set a page/post/product's visibility directly on its row.
|
|
82
|
-
*
|
|
83
|
-
* `id` is a number for pages and posts, a uuid string for products.
|
|
84
|
-
*/
|
|
85
|
-
export async function setContentVisibility(
|
|
86
|
-
type: PublishableType,
|
|
87
|
-
id: number | string,
|
|
88
|
-
intent: VisibilityIntent,
|
|
89
|
-
): Promise<VisibilityResult> {
|
|
90
|
-
const supabase = createClient();
|
|
91
|
-
const {
|
|
92
|
-
data: { user },
|
|
93
|
-
} = await supabase.auth.getUser();
|
|
94
|
-
if (!user) {
|
|
95
|
-
return { error: "You must be signed in to change visibility." };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const { data: profile } = await supabase
|
|
99
|
-
.from("profiles")
|
|
100
|
-
.select("role")
|
|
101
|
-
.eq("id", user.id)
|
|
102
|
-
.single();
|
|
103
|
-
if (!profile || !["ADMIN", "WRITER"].includes(profile.role)) {
|
|
104
|
-
return { error: "You do not have permission to change visibility." };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (intent.action === "schedule" && !isFutureSchedule(intent.publishedAt)) {
|
|
108
|
-
return { error: "Pick a date and time in the future, or publish now instead." };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const update = buildUpdate(type, intent);
|
|
112
|
-
const { data: row, error } = await supabase
|
|
113
|
-
.from(TABLE[type])
|
|
114
|
-
.update({ ...update, updated_at: new Date().toISOString() } as never)
|
|
115
|
-
.eq("id", id as never)
|
|
116
|
-
.select("slug, status, published_at, translation_group_id")
|
|
117
|
-
.single();
|
|
118
|
-
|
|
119
|
-
if (error || !row) {
|
|
120
|
-
return { error: error?.message || "Could not update visibility." };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const updated = row as unknown as {
|
|
124
|
-
slug: string | null;
|
|
125
|
-
status: string;
|
|
126
|
-
published_at: string | null;
|
|
127
|
-
translation_group_id: string | null;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
revalidatePath(CMS_LIST_PATH[type]);
|
|
131
|
-
revalidatePath(`${CMS_LIST_PATH[type]}/${id}/edit`);
|
|
132
|
-
|
|
133
|
-
if (updated.slug) {
|
|
134
|
-
revalidatePath(publicPath(type, updated.slug));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Every language variation of the homepage is also served at "/", whatever its
|
|
138
|
-
// slug, so taking one down (or putting it up) has to bust that cache too.
|
|
139
|
-
if (type === "page") {
|
|
140
|
-
const homepageGroupId = await getHomepageTranslationGroupId(supabase);
|
|
141
|
-
if (
|
|
142
|
-
(homepageGroupId && updated.translation_group_id === homepageGroupId) ||
|
|
143
|
-
updated.slug === "home"
|
|
144
|
-
) {
|
|
145
|
-
revalidatePath("/");
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (type === "post") {
|
|
150
|
-
revalidatePath("/articles");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
state: resolveVisibilityState({
|
|
155
|
-
status: updated.status,
|
|
156
|
-
publishedAt: updated.published_at,
|
|
157
|
-
liveStatus: LIVE_STATUS[type],
|
|
158
|
-
}),
|
|
159
|
-
publishedAt: updated.published_at,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Sibling-language versions of the same content, with their visibility state.
|
|
165
|
-
*
|
|
166
|
-
* Status is per-row, so publishing the French page does nothing for English. The
|
|
167
|
-
* publish dialog shows this list to catch the half-published translation — the
|
|
168
|
-
* single most common multilingual mistake.
|
|
169
|
-
*/
|
|
170
|
-
export interface SiblingVisibility {
|
|
171
|
-
id: number | string;
|
|
172
|
-
languageId: number;
|
|
173
|
-
title: string;
|
|
174
|
-
state: VisibilityState;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function getSiblingVisibility(
|
|
178
|
-
type: PublishableType,
|
|
179
|
-
translationGroupId: string,
|
|
180
|
-
excludeId: number | string,
|
|
181
|
-
): Promise<SiblingVisibility[]> {
|
|
182
|
-
if (!translationGroupId) return [];
|
|
183
|
-
|
|
184
|
-
const supabase = createClient();
|
|
185
|
-
const { data, error } = await supabase
|
|
186
|
-
.from(TABLE[type])
|
|
187
|
-
.select("id, title, language_id, status, published_at")
|
|
188
|
-
.eq("translation_group_id", translationGroupId);
|
|
189
|
-
|
|
190
|
-
if (error || !data) return [];
|
|
191
|
-
|
|
192
|
-
return (data as unknown as Array<{
|
|
193
|
-
id: number | string;
|
|
194
|
-
title: string;
|
|
195
|
-
language_id: number;
|
|
196
|
-
status: string;
|
|
197
|
-
published_at: string | null;
|
|
198
|
-
}>)
|
|
199
|
-
.filter((row) => String(row.id) !== String(excludeId))
|
|
200
|
-
.map((row) => ({
|
|
201
|
-
id: row.id,
|
|
202
|
-
languageId: row.language_id,
|
|
203
|
-
title: row.title,
|
|
204
|
-
state: resolveVisibilityState({
|
|
205
|
-
status: row.status,
|
|
206
|
-
publishedAt: row.published_at,
|
|
207
|
-
liveStatus: LIVE_STATUS[type],
|
|
208
|
-
}),
|
|
209
|
-
}));
|
|
210
|
-
}
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visibility actions — the ONLY writes that can change what the public sees.
|
|
5
|
+
*
|
|
6
|
+
* These deliberately bypass the Live Draft pipeline (`content_drafts` /
|
|
7
|
+
* `product_drafts`) entirely: they read nothing from a draft, create nothing, and
|
|
8
|
+
* leave any pending content edits exactly where they are. Publishing a page and
|
|
9
|
+
* publishing your unsaved edits are two different decisions, so they are two
|
|
10
|
+
* different code paths.
|
|
11
|
+
*
|
|
12
|
+
* Visibility is stored as the (status, published_at) pair — see
|
|
13
|
+
* `libs/utils/src/lib/publishing.ts` for how "scheduled" is derived from it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
17
|
+
import { revalidatePath } from "next/cache";
|
|
18
|
+
import {
|
|
19
|
+
LIVE_STATUS,
|
|
20
|
+
isFutureSchedule,
|
|
21
|
+
resolveVisibilityState,
|
|
22
|
+
type PublishableType,
|
|
23
|
+
type VisibilityState,
|
|
24
|
+
} from "@nextblock-cms/utils";
|
|
25
|
+
import { getHomepageTranslationGroupId } from "../lib/homepage";
|
|
26
|
+
|
|
27
|
+
export interface VisibilityResult {
|
|
28
|
+
error?: string;
|
|
29
|
+
state?: VisibilityState;
|
|
30
|
+
publishedAt?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What the caller wants the content to become. */
|
|
34
|
+
export type VisibilityIntent =
|
|
35
|
+
| { action: "publish" }
|
|
36
|
+
| { action: "schedule"; publishedAt: string }
|
|
37
|
+
| { action: "unpublish" }
|
|
38
|
+
| { action: "archive" };
|
|
39
|
+
|
|
40
|
+
const TABLE: Record<PublishableType, "pages" | "posts" | "products"> = {
|
|
41
|
+
page: "pages",
|
|
42
|
+
post: "posts",
|
|
43
|
+
product: "products",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CMS_LIST_PATH: Record<PublishableType, string> = {
|
|
47
|
+
page: "/cms/pages",
|
|
48
|
+
post: "/cms/posts",
|
|
49
|
+
product: "/cms/products",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function publicPath(type: PublishableType, slug: string): string {
|
|
53
|
+
if (type === "post") return `/article/${slug}`;
|
|
54
|
+
if (type === "product") return `/product/${slug}`;
|
|
55
|
+
return `/${slug}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Turn an intent into the columns to write.
|
|
60
|
+
*
|
|
61
|
+
* "Publish" always clears `published_at`. Leaving a past timestamp behind would be
|
|
62
|
+
* harmless for the public gate but would keep rendering as a stale schedule in the
|
|
63
|
+
* CMS; leaving a FUTURE one would mean "Publish now" silently did nothing.
|
|
64
|
+
*/
|
|
65
|
+
function buildUpdate(type: PublishableType, intent: VisibilityIntent) {
|
|
66
|
+
const liveStatus = LIVE_STATUS[type];
|
|
67
|
+
|
|
68
|
+
switch (intent.action) {
|
|
69
|
+
case "publish":
|
|
70
|
+
return { status: liveStatus, published_at: null };
|
|
71
|
+
case "schedule":
|
|
72
|
+
return { status: liveStatus, published_at: intent.publishedAt };
|
|
73
|
+
case "unpublish":
|
|
74
|
+
return { status: "draft", published_at: null };
|
|
75
|
+
case "archive":
|
|
76
|
+
return { status: "archived", published_at: null };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set a page/post/product's visibility directly on its row.
|
|
82
|
+
*
|
|
83
|
+
* `id` is a number for pages and posts, a uuid string for products.
|
|
84
|
+
*/
|
|
85
|
+
export async function setContentVisibility(
|
|
86
|
+
type: PublishableType,
|
|
87
|
+
id: number | string,
|
|
88
|
+
intent: VisibilityIntent,
|
|
89
|
+
): Promise<VisibilityResult> {
|
|
90
|
+
const supabase = createClient();
|
|
91
|
+
const {
|
|
92
|
+
data: { user },
|
|
93
|
+
} = await supabase.auth.getUser();
|
|
94
|
+
if (!user) {
|
|
95
|
+
return { error: "You must be signed in to change visibility." };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { data: profile } = await supabase
|
|
99
|
+
.from("profiles")
|
|
100
|
+
.select("role")
|
|
101
|
+
.eq("id", user.id)
|
|
102
|
+
.single();
|
|
103
|
+
if (!profile || !["ADMIN", "WRITER"].includes(profile.role)) {
|
|
104
|
+
return { error: "You do not have permission to change visibility." };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (intent.action === "schedule" && !isFutureSchedule(intent.publishedAt)) {
|
|
108
|
+
return { error: "Pick a date and time in the future, or publish now instead." };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const update = buildUpdate(type, intent);
|
|
112
|
+
const { data: row, error } = await supabase
|
|
113
|
+
.from(TABLE[type])
|
|
114
|
+
.update({ ...update, updated_at: new Date().toISOString() } as never)
|
|
115
|
+
.eq("id", id as never)
|
|
116
|
+
.select("slug, status, published_at, translation_group_id")
|
|
117
|
+
.single();
|
|
118
|
+
|
|
119
|
+
if (error || !row) {
|
|
120
|
+
return { error: error?.message || "Could not update visibility." };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const updated = row as unknown as {
|
|
124
|
+
slug: string | null;
|
|
125
|
+
status: string;
|
|
126
|
+
published_at: string | null;
|
|
127
|
+
translation_group_id: string | null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
revalidatePath(CMS_LIST_PATH[type]);
|
|
131
|
+
revalidatePath(`${CMS_LIST_PATH[type]}/${id}/edit`);
|
|
132
|
+
|
|
133
|
+
if (updated.slug) {
|
|
134
|
+
revalidatePath(publicPath(type, updated.slug));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Every language variation of the homepage is also served at "/", whatever its
|
|
138
|
+
// slug, so taking one down (or putting it up) has to bust that cache too.
|
|
139
|
+
if (type === "page") {
|
|
140
|
+
const homepageGroupId = await getHomepageTranslationGroupId(supabase);
|
|
141
|
+
if (
|
|
142
|
+
(homepageGroupId && updated.translation_group_id === homepageGroupId) ||
|
|
143
|
+
updated.slug === "home"
|
|
144
|
+
) {
|
|
145
|
+
revalidatePath("/");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (type === "post") {
|
|
150
|
+
revalidatePath("/articles");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
state: resolveVisibilityState({
|
|
155
|
+
status: updated.status,
|
|
156
|
+
publishedAt: updated.published_at,
|
|
157
|
+
liveStatus: LIVE_STATUS[type],
|
|
158
|
+
}),
|
|
159
|
+
publishedAt: updated.published_at,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sibling-language versions of the same content, with their visibility state.
|
|
165
|
+
*
|
|
166
|
+
* Status is per-row, so publishing the French page does nothing for English. The
|
|
167
|
+
* publish dialog shows this list to catch the half-published translation — the
|
|
168
|
+
* single most common multilingual mistake.
|
|
169
|
+
*/
|
|
170
|
+
export interface SiblingVisibility {
|
|
171
|
+
id: number | string;
|
|
172
|
+
languageId: number;
|
|
173
|
+
title: string;
|
|
174
|
+
state: VisibilityState;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function getSiblingVisibility(
|
|
178
|
+
type: PublishableType,
|
|
179
|
+
translationGroupId: string,
|
|
180
|
+
excludeId: number | string,
|
|
181
|
+
): Promise<SiblingVisibility[]> {
|
|
182
|
+
if (!translationGroupId) return [];
|
|
183
|
+
|
|
184
|
+
const supabase = createClient();
|
|
185
|
+
const { data, error } = await supabase
|
|
186
|
+
.from(TABLE[type])
|
|
187
|
+
.select("id, title, language_id, status, published_at")
|
|
188
|
+
.eq("translation_group_id", translationGroupId);
|
|
189
|
+
|
|
190
|
+
if (error || !data) return [];
|
|
191
|
+
|
|
192
|
+
return (data as unknown as Array<{
|
|
193
|
+
id: number | string;
|
|
194
|
+
title: string;
|
|
195
|
+
language_id: number;
|
|
196
|
+
status: string;
|
|
197
|
+
published_at: string | null;
|
|
198
|
+
}>)
|
|
199
|
+
.filter((row) => String(row.id) !== String(excludeId))
|
|
200
|
+
.map((row) => ({
|
|
201
|
+
id: row.id,
|
|
202
|
+
languageId: row.language_id,
|
|
203
|
+
title: row.title,
|
|
204
|
+
state: resolveVisibilityState({
|
|
205
|
+
status: row.status,
|
|
206
|
+
publishedAt: row.published_at,
|
|
207
|
+
liveStatus: LIVE_STATUS[type],
|
|
208
|
+
}),
|
|
209
|
+
}));
|
|
210
|
+
}
|