create-nextblock 0.2.77 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-nextblock.js +740 -459
- package/package.json +1 -2
- package/scripts/sync-template.js +18 -1
- package/templates/nextblock-template/.browserslistrc +11 -0
- package/templates/nextblock-template/.swcrc +30 -30
- package/templates/nextblock-template/README.md +23 -114
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
- package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
- package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
- package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
- package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
- package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
- package/templates/nextblock-template/app/actions/consent.ts +57 -0
- package/templates/nextblock-template/app/actions/formActions.ts +130 -11
- package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
- package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
- package/templates/nextblock-template/app/actions/postActions.ts +146 -48
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
- package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
- package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
- package/templates/nextblock-template/app/actions.ts +67 -12
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
- package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
- package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
- package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
- package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
- package/templates/nextblock-template/app/api/draft/route.ts +93 -0
- package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
- package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
- package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
- package/templates/nextblock-template/app/api/search/route.ts +43 -0
- package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
- package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
- package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
- package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
- package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
- package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
- package/templates/nextblock-template/app/cart/page.tsx +7 -0
- package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
- package/templates/nextblock-template/app/checkout/page.tsx +52 -0
- package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
- package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
- package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
- package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
- package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
- package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
- package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
- package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
- package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
- package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
- package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
- package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
- package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
- package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
- package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
- package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
- package/templates/nextblock-template/app/cms/dashboard/page.tsx +191 -151
- package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
- package/templates/nextblock-template/app/cms/layout.tsx +29 -10
- package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
- package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
- package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
- package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
- package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
- package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
- package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
- package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
- package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
- package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
- package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
- package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
- package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
- package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
- package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
- package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
- package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
- package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
- package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
- package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
- package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
- package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
- package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
- package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
- package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
- package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
- package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
- package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
- package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
- package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
- package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
- package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
- package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
- package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
- package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
- package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
- package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
- package/templates/nextblock-template/app/globals.css +9 -0
- package/templates/nextblock-template/app/layout.tsx +372 -116
- package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
- package/templates/nextblock-template/app/lib/seo.ts +279 -0
- package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
- package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
- package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
- package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
- package/templates/nextblock-template/app/page.tsx +165 -73
- package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
- package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
- package/templates/nextblock-template/app/profile/account-data.ts +37 -0
- package/templates/nextblock-template/app/profile/account-links.ts +22 -0
- package/templates/nextblock-template/app/profile/account-types.ts +11 -0
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
- package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
- package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
- package/templates/nextblock-template/app/profile/page.tsx +51 -0
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
- package/templates/nextblock-template/app/providers.tsx +55 -17
- package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
- package/templates/nextblock-template/app/sitemap.ts +128 -0
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
- package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
- package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
- package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
- package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
- package/templates/nextblock-template/components/AppShell.tsx +154 -0
- package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
- package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
- package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
- package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
- package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
- package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
- package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
- package/templates/nextblock-template/components/Header.tsx +49 -41
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
- package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
- package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
- package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
- package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
- package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
- package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
- package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
- package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
- package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
- package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
- package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
- package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
- package/templates/nextblock-template/components/blocks/types.ts +7 -6
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.tsx +32 -26
- package/templates/nextblock-template/components/header-auth.tsx +69 -17
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
- package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
- package/templates/nextblock-template/context/AuthContext.tsx +23 -90
- package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
- package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
- package/templates/nextblock-template/context/language-rest-client.ts +31 -0
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
- package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
- package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
- package/templates/nextblock-template/docs/README.md +34 -0
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
- package/templates/nextblock-template/index.d.ts +2 -0
- package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
- package/templates/nextblock-template/lib/ai-client.ts +247 -0
- package/templates/nextblock-template/lib/ai-config.ts +81 -0
- package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
- package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
- package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
- package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
- package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
- package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
- package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
- package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
- package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
- package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
- package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
- package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
- package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
- package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
- package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
- package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
- package/templates/nextblock-template/lib/blocks/README.md +13 -670
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
- package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
- package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
- package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
- package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
- package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
- package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
- package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
- package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
- package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
- package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
- package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
- package/templates/nextblock-template/lib/privacy/types.ts +67 -0
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
- package/templates/nextblock-template/lib/promotions/server.ts +741 -0
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
- package/templates/nextblock-template/lib/search/server.ts +585 -0
- package/templates/nextblock-template/lib/search/types.ts +27 -0
- package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
- package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
- package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
- package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
- package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
- package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
- package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
- package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
- package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
- package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
- package/templates/nextblock-template/lib/zod-config.ts +5 -0
- package/templates/nextblock-template/next.config.js +190 -66
- package/templates/nextblock-template/package.json +34 -30
- package/templates/nextblock-template/proxy.ts +435 -253
- package/templates/nextblock-template/public/images/NBcover.webp +0 -0
- package/templates/nextblock-template/public/images/cap.webp +0 -0
- package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
- package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
- package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
- package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
- package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
- package/templates/nextblock-template/public/images/extensibility.webp +0 -0
- package/templates/nextblock-template/public/images/goals.webp +0 -0
- package/templates/nextblock-template/public/images/included.webp +0 -0
- package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
- package/templates/nextblock-template/public/images/pants.webp +0 -0
- package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
- package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
- package/templates/nextblock-template/tailwind.config.js +1 -0
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
- package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
- package/templates/nextblock-template/tsconfig.json +52 -66
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/types/jsdom.d.ts +6 -0
- package/templates/nextblock-template/app/force-styles.tsx +0 -31
- package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
- package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
- package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
- package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
- package/templates/nextblock-template/docs/files-structure.md +0 -426
- package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import { createClient } from "@nextblock-cms/db/server";
|
|
5
5
|
import { revalidatePath } from "next/cache";
|
|
6
|
-
import type { Database
|
|
6
|
+
import type { Database } from "@nextblock-cms/db";
|
|
7
7
|
import { getInitialContent, isValidBlockType } from "../../../lib/blocks/blockRegistry";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { getOrCreateContentDraft } from "../../../lib/visual-editing/draft-content";
|
|
9
|
+
import { getOrCreateProductDraft } from "../../../lib/visual-editing/product-drafts";
|
|
10
10
|
|
|
11
|
-
type Block = Database['public']['Tables']['blocks']['Row'];
|
|
12
11
|
type BlockType = Database['public']['Tables']['blocks']['Row']['block_type'];
|
|
13
12
|
|
|
14
13
|
// Helper to verify user can edit the parent (page/post)
|
|
@@ -16,10 +15,12 @@ async function canEditParent(
|
|
|
16
15
|
supabase: ReturnType<typeof createClient>,
|
|
17
16
|
userId: string,
|
|
18
17
|
pageId?: number | null,
|
|
19
|
-
postId?: number | null
|
|
18
|
+
postId?: number | null,
|
|
19
|
+
productId?: string | null
|
|
20
20
|
): Promise<boolean> {
|
|
21
21
|
void pageId;
|
|
22
22
|
void postId;
|
|
23
|
+
void productId;
|
|
23
24
|
const { data: profile } = await supabase
|
|
24
25
|
.from("profiles")
|
|
25
26
|
.select("role")
|
|
@@ -29,19 +30,9 @@ async function canEditParent(
|
|
|
29
30
|
if (!profile || !["ADMIN", "WRITER"].includes(profile.role)) {
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
|
-
// Further checks could be added here to see if a WRITER owns the page/post
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
interface CreateBlockPayload {
|
|
37
|
-
page_id?: number | null;
|
|
38
|
-
post_id?: number | null;
|
|
39
|
-
language_id: number;
|
|
40
|
-
block_type: BlockType;
|
|
41
|
-
content: object; // Content structure defined by block registry
|
|
42
|
-
order: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
36
|
export async function createBlockForPage(pageId: number, languageId: number, blockType: BlockType, order: number) {
|
|
46
37
|
const supabase = createClient();
|
|
47
38
|
const { data: { user } } = await supabase.auth.getUser();
|
|
@@ -62,34 +53,44 @@ export async function createBlockForPage(pageId: number, languageId: number, blo
|
|
|
62
53
|
return { error: "Failed to get initial content for block type." };
|
|
63
54
|
}
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// capture previous state for revision (before insert)
|
|
74
|
-
const previousContent = await getFullPageContent(pageId);
|
|
75
|
-
|
|
76
|
-
const { data, error } = await supabase.from("blocks").insert(payload).select().single();
|
|
77
|
-
|
|
78
|
-
if (error) {
|
|
79
|
-
console.error("Error creating block:", error);
|
|
80
|
-
return { error: `Failed to create block: ${error.message}` };
|
|
81
|
-
}
|
|
56
|
+
try {
|
|
57
|
+
const draft = await getOrCreateContentDraft(supabase, "page", pageId, user.id);
|
|
58
|
+
let newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
59
|
+
while (draft.blocks.some(b => b.id === newBlockId)) {
|
|
60
|
+
newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
61
|
+
}
|
|
82
62
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
const newBlock = {
|
|
64
|
+
id: newBlockId,
|
|
65
|
+
page_id: pageId,
|
|
66
|
+
post_id: null,
|
|
67
|
+
language_id: languageId,
|
|
68
|
+
block_type: blockType,
|
|
69
|
+
content: initialContent,
|
|
70
|
+
order: order,
|
|
71
|
+
created_at: new Date().toISOString(),
|
|
72
|
+
updated_at: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const updatedBlocks = [...draft.blocks, newBlock];
|
|
76
|
+
updatedBlocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
77
|
+
|
|
78
|
+
const { error: updateError } = await supabase
|
|
79
|
+
.from("content_drafts")
|
|
80
|
+
.update({ blocks: updatedBlocks as any })
|
|
81
|
+
.eq("id", draft.id);
|
|
82
|
+
|
|
83
|
+
if (updateError) {
|
|
84
|
+
console.error("Error creating draft block for page:", updateError);
|
|
85
|
+
return { error: `Failed to save draft block: ${updateError.message}` };
|
|
88
86
|
}
|
|
89
|
-
}
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
revalidatePath(`/cms/pages/${pageId}/edit`);
|
|
89
|
+
return { success: true, newBlock: newBlock as any };
|
|
90
|
+
} catch (err: any) {
|
|
91
|
+
console.error("Error getting draft for page blocks update:", err);
|
|
92
|
+
return { error: `Failed to load page draft: ${err.message || err}` };
|
|
93
|
+
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export async function createBlockForPost(postId: number, languageId: number, blockType: BlockType, order: number) {
|
|
@@ -112,184 +113,338 @@ export async function createBlockForPost(postId: number, languageId: number, blo
|
|
|
112
113
|
return { error: "Failed to get initial content for block type." };
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
};
|
|
116
|
+
try {
|
|
117
|
+
const draft = await getOrCreateContentDraft(supabase, "post", postId, user.id);
|
|
118
|
+
let newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
119
|
+
while (draft.blocks.some(b => b.id === newBlockId)) {
|
|
120
|
+
newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
const newBlock = {
|
|
124
|
+
id: newBlockId,
|
|
125
|
+
page_id: null,
|
|
126
|
+
post_id: postId,
|
|
127
|
+
language_id: languageId,
|
|
128
|
+
block_type: blockType,
|
|
129
|
+
content: initialContent,
|
|
130
|
+
order: order,
|
|
131
|
+
created_at: new Date().toISOString(),
|
|
132
|
+
updated_at: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const updatedBlocks = [...draft.blocks, newBlock];
|
|
136
|
+
updatedBlocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
137
|
+
|
|
138
|
+
const { error: updateError } = await supabase
|
|
139
|
+
.from("content_drafts")
|
|
140
|
+
.update({ blocks: updatedBlocks as any })
|
|
141
|
+
.eq("id", draft.id);
|
|
142
|
+
|
|
143
|
+
if (updateError) {
|
|
144
|
+
console.error("Error creating draft block for post:", updateError);
|
|
145
|
+
return { error: `Failed to save draft block: ${updateError.message}` };
|
|
146
|
+
}
|
|
125
147
|
|
|
126
|
-
|
|
148
|
+
revalidatePath(`/cms/posts/${postId}/edit`);
|
|
149
|
+
return { success: true, newBlock: newBlock as any };
|
|
150
|
+
} catch (err: any) {
|
|
151
|
+
console.error("Error getting draft for post blocks update:", err);
|
|
152
|
+
return { error: `Failed to load post draft: ${err.message || err}` };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function createBlockForProduct(productId: string, languageId: number, blockType: BlockType, order: number) {
|
|
157
|
+
const supabase = createClient();
|
|
158
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
127
159
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
return { error:
|
|
160
|
+
if (!user) return { error: "User not authenticated." };
|
|
161
|
+
if (!(await canEditParent(supabase, user.id, null, null, productId))) {
|
|
162
|
+
return { error: "Unauthorized to add blocks to this product." };
|
|
131
163
|
}
|
|
132
164
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await createPostRevision(postId, user.id, previousContent, newContent);
|
|
137
|
-
}
|
|
165
|
+
// Validate block type using registry
|
|
166
|
+
if (!isValidBlockType(blockType)) {
|
|
167
|
+
return { error: "Unknown block type." };
|
|
138
168
|
}
|
|
139
169
|
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
// Get initial content from registry
|
|
171
|
+
const initialContent = getInitialContent(blockType);
|
|
172
|
+
if (!initialContent) {
|
|
173
|
+
return { error: "Failed to get initial content for block type." };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const draft = await getOrCreateProductDraft(supabase, productId, user.id);
|
|
178
|
+
let newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
179
|
+
while (draft.blocks && draft.blocks.some(b => b.id === newBlockId)) {
|
|
180
|
+
newBlockId = -1 - Math.floor(Math.random() * 9999999);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const newBlock = {
|
|
184
|
+
id: newBlockId,
|
|
185
|
+
page_id: null,
|
|
186
|
+
post_id: null,
|
|
187
|
+
product_id: productId,
|
|
188
|
+
language_id: languageId,
|
|
189
|
+
block_type: blockType,
|
|
190
|
+
content: initialContent,
|
|
191
|
+
order: order,
|
|
192
|
+
created_at: new Date().toISOString(),
|
|
193
|
+
updated_at: new Date().toISOString(),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const updatedBlocks = [...(draft.blocks || []), newBlock];
|
|
197
|
+
updatedBlocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
198
|
+
|
|
199
|
+
const { error: updateError } = await supabase
|
|
200
|
+
.from("product_drafts")
|
|
201
|
+
.update({ blocks: updatedBlocks as any })
|
|
202
|
+
.eq("id", draft.id);
|
|
203
|
+
|
|
204
|
+
if (updateError) {
|
|
205
|
+
console.error("Error creating draft block for product:", updateError);
|
|
206
|
+
return { error: `Failed to save draft block: ${updateError.message}` };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
revalidatePath(`/cms/products/${productId}/edit`);
|
|
210
|
+
return { success: true, newBlock: newBlock as any };
|
|
211
|
+
} catch (err: any) {
|
|
212
|
+
console.error("Error getting draft for product blocks update:", err);
|
|
213
|
+
return { error: `Failed to load product draft: ${err.message || err}` };
|
|
214
|
+
}
|
|
142
215
|
}
|
|
143
216
|
|
|
144
|
-
export async function updateBlock(
|
|
217
|
+
export async function updateBlock(
|
|
218
|
+
blockId: number,
|
|
219
|
+
newContent: unknown,
|
|
220
|
+
pageId?: number | null,
|
|
221
|
+
postId?: number | null,
|
|
222
|
+
productId?: string | null
|
|
223
|
+
) {
|
|
145
224
|
const supabase = createClient();
|
|
146
225
|
const { data: { user } } = await supabase.auth.getUser();
|
|
147
226
|
|
|
148
227
|
if (!user) return { error: "User not authenticated." };
|
|
149
|
-
if (!(await canEditParent(supabase, user.id, pageId, postId))) {
|
|
228
|
+
if (!(await canEditParent(supabase, user.id, pageId, postId, productId))) {
|
|
150
229
|
return { error: "Unauthorized to update this block." };
|
|
151
230
|
}
|
|
152
231
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return { error: "Block not found." };
|
|
161
|
-
}
|
|
232
|
+
if (productId) {
|
|
233
|
+
try {
|
|
234
|
+
const draft = await getOrCreateProductDraft(supabase, productId, user.id);
|
|
235
|
+
const existingBlock = (draft.blocks || []).find(b => b.id === blockId);
|
|
236
|
+
if (!existingBlock) {
|
|
237
|
+
return { error: "Block not found in draft." };
|
|
238
|
+
}
|
|
162
239
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
240
|
+
const updatedBlocks = (draft.blocks || []).map(b =>
|
|
241
|
+
b.id === blockId
|
|
242
|
+
? { ...b, content: newContent as any, updated_at: new Date().toISOString() }
|
|
243
|
+
: b
|
|
244
|
+
);
|
|
169
245
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.select()
|
|
175
|
-
.single();
|
|
246
|
+
const { error: updateError } = await supabase
|
|
247
|
+
.from("product_drafts")
|
|
248
|
+
.update({ blocks: updatedBlocks as any })
|
|
249
|
+
.eq("id", draft.id);
|
|
176
250
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
251
|
+
if (updateError) {
|
|
252
|
+
console.error("Error updating draft block content for product:", updateError);
|
|
253
|
+
return { error: `Failed to update draft block: ${updateError.message}` };
|
|
254
|
+
}
|
|
181
255
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
return { success: true, updatedBlock: { ...existingBlock, content: newContent } as any };
|
|
257
|
+
} catch (err: any) {
|
|
258
|
+
console.error("Error getting draft for product block content update:", err);
|
|
259
|
+
return { error: `Failed to load product draft: ${err.message || err}` };
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
const parentType = pageId ? "page" : "post";
|
|
263
|
+
const parentId = pageId || postId;
|
|
264
|
+
if (!parentId) return { error: "Missing pageId or postId." };
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const draft = await getOrCreateContentDraft(supabase, parentType, parentId, user.id);
|
|
268
|
+
const existingBlock = draft.blocks.find(b => b.id === blockId);
|
|
269
|
+
if (!existingBlock) {
|
|
270
|
+
return { error: "Block not found in draft." };
|
|
188
271
|
}
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
272
|
+
|
|
273
|
+
const updatedBlocks = draft.blocks.map(b =>
|
|
274
|
+
b.id === blockId
|
|
275
|
+
? { ...b, content: newContent as any, updated_at: new Date().toISOString() }
|
|
276
|
+
: b
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const { error: updateError } = await supabase
|
|
280
|
+
.from("content_drafts")
|
|
281
|
+
.update({ blocks: updatedBlocks as any })
|
|
282
|
+
.eq("id", draft.id);
|
|
283
|
+
|
|
284
|
+
if (updateError) {
|
|
285
|
+
console.error("Error updating draft block content:", updateError);
|
|
286
|
+
return { error: `Failed to update draft block: ${updateError.message}` };
|
|
193
287
|
}
|
|
288
|
+
|
|
289
|
+
return { success: true, updatedBlock: { ...existingBlock, content: newContent } as any };
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
console.error("Error getting draft for block content update:", err);
|
|
292
|
+
return { error: `Failed to load draft: ${err.message || err}` };
|
|
194
293
|
}
|
|
195
294
|
}
|
|
196
|
-
|
|
197
|
-
return { success: true, updatedBlock: data as Block };
|
|
198
295
|
}
|
|
199
296
|
|
|
200
297
|
export async function updateMultipleBlockOrders(
|
|
201
298
|
updates: Array<{ id: number; order: number }>,
|
|
202
299
|
pageId?: number | null,
|
|
203
|
-
postId?: number | null
|
|
300
|
+
postId?: number | null,
|
|
301
|
+
productId?: string | null
|
|
204
302
|
) {
|
|
205
303
|
const supabase = createClient();
|
|
206
304
|
const { data: { user } } = await supabase.auth.getUser();
|
|
207
305
|
|
|
208
306
|
if (!user) return { error: "User not authenticated." };
|
|
209
|
-
if (!(await canEditParent(supabase, user.id, pageId, postId))) {
|
|
307
|
+
if (!(await canEditParent(supabase, user.id, pageId, postId, productId))) {
|
|
210
308
|
return { error: "Unauthorized to reorder blocks." };
|
|
211
309
|
}
|
|
212
310
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
311
|
+
if (productId) {
|
|
312
|
+
try {
|
|
313
|
+
const draft = await getOrCreateProductDraft(supabase, productId, user.id);
|
|
314
|
+
const updatedBlocks = (draft.blocks || []).map(b => {
|
|
315
|
+
const update = updates.find(u => u.id === b.id);
|
|
316
|
+
if (update) {
|
|
317
|
+
return { ...b, order: update.order, updated_at: new Date().toISOString() };
|
|
318
|
+
}
|
|
319
|
+
return b;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
updatedBlocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
323
|
+
|
|
324
|
+
const { error: updateError } = await supabase
|
|
325
|
+
.from("product_drafts")
|
|
326
|
+
.update({ blocks: updatedBlocks as any })
|
|
327
|
+
.eq("id", draft.id);
|
|
328
|
+
|
|
329
|
+
if (updateError) {
|
|
330
|
+
console.error("Error updating draft blocks order for product:", updateError);
|
|
331
|
+
return { error: `Failed to save blocks order draft: ${updateError.message}` };
|
|
332
|
+
}
|
|
221
333
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
334
|
+
revalidatePath(`/cms/products/${productId}/edit`);
|
|
335
|
+
return { success: true };
|
|
336
|
+
} catch (err: any) {
|
|
337
|
+
console.error("Error getting draft for product blocks order update:", err);
|
|
338
|
+
return { error: `Failed to load product draft: ${err.message || err}` };
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
const parentType = pageId ? "page" : "post";
|
|
342
|
+
const parentId = pageId || postId;
|
|
343
|
+
if (!parentId) return { error: "Missing pageId or postId." };
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const draft = await getOrCreateContentDraft(supabase, parentType, parentId, user.id);
|
|
347
|
+
const updatedBlocks = draft.blocks.map(b => {
|
|
348
|
+
const update = updates.find(u => u.id === b.id);
|
|
349
|
+
if (update) {
|
|
350
|
+
return { ...b, order: update.order, updated_at: new Date().toISOString() };
|
|
351
|
+
}
|
|
352
|
+
return b;
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
updatedBlocks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
356
|
+
|
|
357
|
+
const { error: updateError } = await supabase
|
|
358
|
+
.from("content_drafts")
|
|
359
|
+
.update({ blocks: updatedBlocks as any })
|
|
360
|
+
.eq("id", draft.id);
|
|
361
|
+
|
|
362
|
+
if (updateError) {
|
|
363
|
+
console.error("Error updating draft blocks order:", updateError);
|
|
364
|
+
return { error: `Failed to save blocks order draft: ${updateError.message}` };
|
|
365
|
+
}
|
|
226
366
|
|
|
227
|
-
|
|
228
|
-
|
|
367
|
+
if (pageId) revalidatePath(`/cms/pages/${pageId}/edit`);
|
|
368
|
+
if (postId) revalidatePath(`/cms/posts/${postId}/edit`);
|
|
229
369
|
|
|
230
|
-
|
|
370
|
+
return { success: true };
|
|
371
|
+
} catch (err: any) {
|
|
372
|
+
console.error("Error getting draft for blocks order update:", err);
|
|
373
|
+
return { error: `Failed to load draft: ${err.message || err}` };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
231
376
|
}
|
|
232
377
|
|
|
233
|
-
|
|
234
|
-
|
|
378
|
+
export async function deleteBlock(
|
|
379
|
+
blockId: number,
|
|
380
|
+
pageId?: number | null,
|
|
381
|
+
postId?: number | null,
|
|
382
|
+
productId?: string | null
|
|
383
|
+
) {
|
|
235
384
|
const supabase = createClient();
|
|
236
385
|
const { data: { user } } = await supabase.auth.getUser();
|
|
237
386
|
|
|
238
387
|
if (!user) return { error: "User not authenticated." };
|
|
239
|
-
if (!(await canEditParent(supabase, user.id, pageId, postId))) {
|
|
388
|
+
if (!(await canEditParent(supabase, user.id, pageId, postId, productId))) {
|
|
240
389
|
return { error: "Unauthorized to delete this block." };
|
|
241
390
|
}
|
|
242
391
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
.eq('id', blockId)
|
|
248
|
-
.single();
|
|
249
|
-
if (fetchError || !existingBlock) {
|
|
250
|
-
return { error: "Block not found." };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
let previousAggregate: Awaited<ReturnType<typeof getFullPageContent>> | Awaited<ReturnType<typeof getFullPostContent>> | null = null;
|
|
254
|
-
if (existingBlock.page_id) {
|
|
255
|
-
previousAggregate = await getFullPageContent(existingBlock.page_id);
|
|
256
|
-
} else if (existingBlock.post_id) {
|
|
257
|
-
previousAggregate = await getFullPostContent(existingBlock.post_id);
|
|
258
|
-
}
|
|
392
|
+
if (productId) {
|
|
393
|
+
try {
|
|
394
|
+
const draft = await getOrCreateProductDraft(supabase, productId, user.id);
|
|
395
|
+
const updatedBlocks = (draft.blocks || []).filter(b => b.id !== blockId);
|
|
259
396
|
|
|
260
|
-
|
|
397
|
+
const { error: updateError } = await supabase
|
|
398
|
+
.from("product_drafts")
|
|
399
|
+
.update({ blocks: updatedBlocks as any })
|
|
400
|
+
.eq("id", draft.id);
|
|
261
401
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// create revision after delete
|
|
268
|
-
if (user && previousAggregate) {
|
|
269
|
-
if (existingBlock.page_id) {
|
|
270
|
-
const nextAggregate = await getFullPageContent(existingBlock.page_id, { excludeDeletedBlockId: blockId });
|
|
271
|
-
if (nextAggregate) {
|
|
272
|
-
await createPageRevision(existingBlock.page_id, user.id, previousAggregate, nextAggregate);
|
|
273
|
-
}
|
|
274
|
-
} else if (existingBlock.post_id) {
|
|
275
|
-
const nextAggregate = await getFullPostContent(existingBlock.post_id, { excludeDeletedBlockId: blockId });
|
|
276
|
-
if (nextAggregate) {
|
|
277
|
-
await createPostRevision(existingBlock.post_id, user.id, previousAggregate as any, nextAggregate as any);
|
|
402
|
+
if (updateError) {
|
|
403
|
+
console.error("Error deleting draft block for product:", updateError);
|
|
404
|
+
return { error: `Failed to delete block from product draft: ${updateError.message}` };
|
|
278
405
|
}
|
|
406
|
+
|
|
407
|
+
revalidatePath(`/cms/products/${productId}/edit`);
|
|
408
|
+
return { success: true };
|
|
409
|
+
} catch (err: any) {
|
|
410
|
+
console.error("Error getting draft for product block deletion:", err);
|
|
411
|
+
return { error: `Failed to load product draft: ${err.message || err}` };
|
|
279
412
|
}
|
|
280
|
-
}
|
|
413
|
+
} else {
|
|
414
|
+
const parentType = pageId ? "page" : "post";
|
|
415
|
+
const parentId = pageId || postId;
|
|
416
|
+
if (!parentId) return { error: "Missing pageId or postId." };
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const draft = await getOrCreateContentDraft(supabase, parentType, parentId, user.id);
|
|
420
|
+
const updatedBlocks = draft.blocks.filter(b => b.id !== blockId);
|
|
421
|
+
|
|
422
|
+
const { error: updateError } = await supabase
|
|
423
|
+
.from("content_drafts")
|
|
424
|
+
.update({ blocks: updatedBlocks as any })
|
|
425
|
+
.eq("id", draft.id);
|
|
426
|
+
|
|
427
|
+
if (updateError) {
|
|
428
|
+
console.error("Error deleting draft block:", updateError);
|
|
429
|
+
return { error: `Failed to delete block from draft: ${updateError.message}` };
|
|
430
|
+
}
|
|
281
431
|
|
|
282
|
-
|
|
283
|
-
|
|
432
|
+
if (pageId) revalidatePath(`/cms/pages/${pageId}/edit`);
|
|
433
|
+
if (postId) revalidatePath(`/cms/posts/${postId}/edit`);
|
|
284
434
|
|
|
285
|
-
|
|
435
|
+
return { success: true };
|
|
436
|
+
} catch (err: any) {
|
|
437
|
+
console.error("Error getting draft for block deletion:", err);
|
|
438
|
+
return { error: `Failed to load draft: ${err.message || err}` };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
286
441
|
}
|
|
287
442
|
|
|
288
443
|
export async function copyBlocksFromLanguage(
|
|
289
|
-
parentId: number, // ID of the page or
|
|
290
|
-
parentType: "page" | "post",
|
|
444
|
+
parentId: number | string, // ID of the page, post, or product being edited
|
|
445
|
+
parentType: "page" | "post" | "product",
|
|
291
446
|
sourceLanguageId: number,
|
|
292
|
-
targetLanguageId: number, // Language of the
|
|
447
|
+
targetLanguageId: number, // Language of the target being edited
|
|
293
448
|
targetTranslationGroupId: string
|
|
294
449
|
) {
|
|
295
450
|
"use server";
|
|
@@ -300,13 +455,12 @@ export async function copyBlocksFromLanguage(
|
|
|
300
455
|
return { error: "User not authenticated." };
|
|
301
456
|
}
|
|
302
457
|
|
|
303
|
-
if (!(await canEditParent(supabase, user.id, parentType === "page" ? parentId : null, parentType === "post" ? parentId : null))) {
|
|
458
|
+
if (!(await canEditParent(supabase, user.id, parentType === "page" ? parentId as number : null, parentType === "post" ? parentId as number : null, parentType === "product" ? parentId as string : null))) {
|
|
304
459
|
return { error: "Unauthorized to modify blocks for this target." };
|
|
305
460
|
}
|
|
306
461
|
|
|
307
|
-
let sourceParentId: number | null = null;
|
|
462
|
+
let sourceParentId: number | string | null = null;
|
|
308
463
|
|
|
309
|
-
// 1. Fetch Source Page/Post ID
|
|
310
464
|
try {
|
|
311
465
|
if (parentType === "page") {
|
|
312
466
|
const { data: sourcePage, error: sourcePageError } = await supabase
|
|
@@ -334,6 +488,19 @@ export async function copyBlocksFromLanguage(
|
|
|
334
488
|
return { error: "Source post not found or error fetching it." };
|
|
335
489
|
}
|
|
336
490
|
sourceParentId = sourcePost.id;
|
|
491
|
+
} else if (parentType === "product") {
|
|
492
|
+
const { data: sourceProduct, error: sourceProductError } = await supabase
|
|
493
|
+
.from("products")
|
|
494
|
+
.select("id")
|
|
495
|
+
.eq("translation_group_id", targetTranslationGroupId)
|
|
496
|
+
.eq("language_id", sourceLanguageId)
|
|
497
|
+
.single();
|
|
498
|
+
|
|
499
|
+
if (sourceProductError || !sourceProduct) {
|
|
500
|
+
console.error("Error fetching source product:", sourceProductError);
|
|
501
|
+
return { error: "Source product not found or error fetching it." };
|
|
502
|
+
}
|
|
503
|
+
sourceParentId = sourceProduct.id;
|
|
337
504
|
} else {
|
|
338
505
|
return { error: "Invalid parent type specified." };
|
|
339
506
|
}
|
|
@@ -342,88 +509,110 @@ export async function copyBlocksFromLanguage(
|
|
|
342
509
|
return { error: "Could not determine source parent ID." };
|
|
343
510
|
}
|
|
344
511
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
512
|
+
let blocksToCopy: any[] = [];
|
|
513
|
+
if (parentType === "product") {
|
|
514
|
+
// 1. Check if source draft exists
|
|
515
|
+
const { data: sourceDraft } = await supabase
|
|
516
|
+
.from("product_drafts")
|
|
517
|
+
.select("blocks")
|
|
518
|
+
.eq("product_id", sourceParentId)
|
|
519
|
+
.maybeSingle();
|
|
520
|
+
|
|
521
|
+
if (sourceDraft && sourceDraft.blocks && Array.isArray(sourceDraft.blocks)) {
|
|
522
|
+
blocksToCopy = sourceDraft.blocks;
|
|
523
|
+
} else {
|
|
524
|
+
// 2. Fetch live blocks from source
|
|
525
|
+
const { data: liveBlocks, error: sourceBlocksError } = await supabase
|
|
526
|
+
.from("blocks")
|
|
527
|
+
.select("block_type, content, order")
|
|
528
|
+
.eq("product_id", sourceParentId)
|
|
529
|
+
.order("order", { ascending: true });
|
|
530
|
+
|
|
531
|
+
if (sourceBlocksError) {
|
|
532
|
+
console.error("Error fetching source blocks:", sourceBlocksError);
|
|
533
|
+
return { error: `Failed to fetch blocks from source: ${sourceBlocksError.message}` };
|
|
534
|
+
}
|
|
535
|
+
blocksToCopy = liveBlocks || [];
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
// 1. Check if source draft exists
|
|
539
|
+
const { data: sourceDraft } = await supabase
|
|
540
|
+
.from("content_drafts")
|
|
541
|
+
.select("blocks")
|
|
542
|
+
.eq("parent_type", parentType)
|
|
543
|
+
.eq("parent_id", sourceParentId)
|
|
544
|
+
.maybeSingle();
|
|
545
|
+
|
|
546
|
+
if (sourceDraft && sourceDraft.blocks && Array.isArray(sourceDraft.blocks)) {
|
|
547
|
+
blocksToCopy = sourceDraft.blocks;
|
|
548
|
+
} else {
|
|
549
|
+
// 2. Fetch live blocks from source
|
|
550
|
+
const { data: liveBlocks, error: sourceBlocksError } = await supabase
|
|
551
|
+
.from("blocks")
|
|
552
|
+
.select("block_type, content, order")
|
|
553
|
+
.eq(parentType === "page" ? "page_id" : "post_id", sourceParentId)
|
|
554
|
+
.order("order", { ascending: true });
|
|
555
|
+
|
|
556
|
+
if (sourceBlocksError) {
|
|
557
|
+
console.error("Error fetching source blocks:", sourceBlocksError);
|
|
558
|
+
return { error: `Failed to fetch blocks from source: ${sourceBlocksError.message}` };
|
|
559
|
+
}
|
|
560
|
+
blocksToCopy = liveBlocks || [];
|
|
561
|
+
}
|
|
367
562
|
}
|
|
368
563
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
order: number;
|
|
378
|
-
}) => ({
|
|
379
|
-
// id, created_at, updated_at will be set by DB
|
|
380
|
-
page_id: parentType === "page" ? parentId : null,
|
|
381
|
-
post_id: parentType === "post" ? parentId : null,
|
|
564
|
+
// 3. Generate new blocks with negative IDs for draft array
|
|
565
|
+
const copiedBlocks = blocksToCopy.map((block: any, index: number) => {
|
|
566
|
+
const newId = -1 - Math.floor(Math.random() * 9999999);
|
|
567
|
+
return {
|
|
568
|
+
id: newId,
|
|
569
|
+
page_id: null,
|
|
570
|
+
post_id: null,
|
|
571
|
+
product_id: parentType === "product" ? (parentId as string) : null,
|
|
382
572
|
language_id: targetLanguageId,
|
|
383
573
|
block_type: block.block_type,
|
|
384
|
-
content: block.content,
|
|
385
|
-
order: block.order,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
574
|
+
content: block.content,
|
|
575
|
+
order: block.order ?? index,
|
|
576
|
+
created_at: new Date().toISOString(),
|
|
577
|
+
updated_at: new Date().toISOString(),
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
if (parentType === "product") {
|
|
582
|
+
// 4. Load or create target draft and update blocks
|
|
583
|
+
const targetDraft = await getOrCreateProductDraft(supabase, parentId as string, user.id);
|
|
584
|
+
const { error: updateError } = await supabase
|
|
585
|
+
.from("product_drafts")
|
|
586
|
+
.update({ blocks: copiedBlocks as any })
|
|
587
|
+
.eq("id", targetDraft.id);
|
|
588
|
+
|
|
589
|
+
if (updateError) {
|
|
590
|
+
console.error("Error writing copied blocks to target product draft:", updateError);
|
|
591
|
+
return { error: `Failed to copy blocks to draft: ${updateError.message}` };
|
|
592
|
+
}
|
|
389
593
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
594
|
+
revalidatePath(`/cms/products/${parentId}/edit`);
|
|
595
|
+
} else {
|
|
596
|
+
// 4. Load or create target draft and update blocks
|
|
597
|
+
const targetDraft = await getOrCreateContentDraft(supabase, parentType, parentId as number, user.id);
|
|
598
|
+
const { error: updateError } = await supabase
|
|
599
|
+
.from("content_drafts")
|
|
600
|
+
.update({ blocks: copiedBlocks as any })
|
|
601
|
+
.eq("id", targetDraft.id);
|
|
602
|
+
|
|
603
|
+
if (updateError) {
|
|
604
|
+
console.error("Error writing copied blocks to target draft:", updateError);
|
|
605
|
+
return { error: `Failed to copy blocks to draft: ${updateError.message}` };
|
|
393
606
|
}
|
|
394
|
-
}
|
|
395
607
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
.eq("id", parentId)
|
|
403
|
-
.single();
|
|
404
|
-
if (pageError || !pageData) {
|
|
405
|
-
console.warn("Could not fetch target page slug for revalidation:", pageError);
|
|
406
|
-
} else {
|
|
407
|
-
targetSlug = pageData.slug;
|
|
408
|
-
if (targetSlug) revalidatePath(`/${targetSlug}`);
|
|
409
|
-
}
|
|
410
|
-
revalidatePath(`/cms/pages/${parentId}/edit`); // Revalidate edit page
|
|
411
|
-
} else if (parentType === "post") {
|
|
412
|
-
const { data: postData, error: postError } = await supabase
|
|
413
|
-
.from("posts")
|
|
414
|
-
.select("slug")
|
|
415
|
-
.eq("id", parentId)
|
|
416
|
-
.single();
|
|
417
|
-
if (postError || !postData) {
|
|
418
|
-
console.warn("Could not fetch target post slug for revalidation:", postError);
|
|
419
|
-
} else {
|
|
420
|
-
targetSlug = postData.slug;
|
|
421
|
-
if (targetSlug) revalidatePath(`/article/${targetSlug}`);
|
|
422
|
-
}
|
|
423
|
-
revalidatePath(`/cms/posts/${parentId}/edit`); // Revalidate edit page
|
|
608
|
+
// 5. Revalidation
|
|
609
|
+
if (parentType === "page") {
|
|
610
|
+
revalidatePath(`/cms/pages/${parentId}/edit`);
|
|
611
|
+
} else if (parentType === "post") {
|
|
612
|
+
revalidatePath(`/cms/posts/${parentId}/edit`);
|
|
613
|
+
}
|
|
424
614
|
}
|
|
425
615
|
|
|
426
|
-
|
|
427
616
|
return { success: true, message: "Blocks copied successfully." };
|
|
428
617
|
|
|
429
618
|
} catch (e: unknown) {
|