create-nextblock 0.12.9 → 0.12.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docker-template/.dockerignore +23 -23
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/libs/db/tsconfig.lib.json +3 -3
- package/libs/editor/tsconfig.lib.json +3 -3
- package/libs/ui/tsconfig.lib.json +3 -3
- package/libs/utils/tsconfig.json +3 -3
- package/package.json +1 -1
- package/project.json +19 -19
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.dockerignore +23 -23
- package/templates/nextblock-template/README.md +34 -34
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
- package/templates/nextblock-template/app/(auth-pages)/sign-up/SignUpForm.tsx +141 -141
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -91
- package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +123 -123
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/interactions.test.ts +301 -301
- package/templates/nextblock-template/app/actions/interactions.ts +373 -373
- package/templates/nextblock-template/app/actions/postActions.ts +146 -146
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/ai/global-agent/route.ts +964 -964
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5597 -5597
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
- package/templates/nextblock-template/app/api/search/route.ts +43 -43
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -244
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -1522
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +431 -391
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -408
- package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -51
- package/templates/nextblock-template/app/cms/layout.tsx +73 -73
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +149 -149
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -101
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/pages/page.tsx +18 -17
- package/templates/nextblock-template/app/cms/posts/page.tsx +19 -18
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/products/page.tsx +39 -27
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
- package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -93
- package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -410
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +246 -246
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -80
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
- package/templates/nextblock-template/app/cms/settings/email/actions.ts +64 -64
- package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +181 -181
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/google-analytics/actions.ts +60 -60
- package/templates/nextblock-template/app/cms/settings/google-analytics/components/GoogleAnalyticsForm.tsx +129 -129
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +16 -16
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
- package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +52 -52
- package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +148 -148
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/actions.ts +44 -44
- package/templates/nextblock-template/app/cms/settings/registration/components/RegistrationForm.tsx +65 -65
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/actions.ts +284 -284
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +522 -522
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
- package/templates/nextblock-template/app/cms/users/actions.ts +222 -222
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +44 -44
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +253 -253
- package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -73
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
- package/templates/nextblock-template/app/profile/account-data.ts +37 -37
- package/templates/nextblock-template/app/profile/account-links.ts +22 -22
- package/templates/nextblock-template/app/profile/account-types.ts +11 -11
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -79
- package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -32
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- package/templates/nextblock-template/app/profile/page.tsx +42 -42
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/sitemap.ts +130 -130
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/GitHubLoginButton.tsx +15 -15
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -369
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -419
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -464
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -1319
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
- package/templates/nextblock-template/docs/README.md +39 -39
- package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12505 -12505
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
- package/templates/nextblock-template/index.d.ts +7 -7
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/auth/twoFactor.ts +208 -208
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +6 -6
- package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -18
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +100 -77
- package/templates/nextblock-template/lib/cms-transfer/csv.ts +403 -399
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/config/email-settings.ts +217 -217
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -256
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/search/server.ts +585 -585
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +37 -37
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/site-url.ts +48 -48
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/postcss.config.js +6 -6
- package/templates/nextblock-template/scripts/backup.js +115 -115
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/restore.js +385 -385
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
- package/templates/nextblock-template/tsconfig.tsbuildinfo +0 -1
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import { Alert, AlertDescription } from '@nextblock-cms/ui';
|
|
2
|
-
|
|
3
|
-
import { getLanguages } from '../languages/actions';
|
|
4
|
-
import { getTranslations } from './actions';
|
|
5
|
-
import { ExtraTranslationsWorkspace } from './ExtraTranslationsWorkspace';
|
|
6
|
-
|
|
7
|
-
type Language = NonNullable<Awaited<ReturnType<typeof getLanguages>>['data']>[number];
|
|
8
|
-
|
|
9
|
-
interface ExtraTranslationsPageProps {
|
|
10
|
-
searchParams?: Promise<{
|
|
11
|
-
language?: string;
|
|
12
|
-
status?: string;
|
|
13
|
-
success?: string;
|
|
14
|
-
}>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getInitialLanguageCode(languages: Language[], requestedLanguageCode?: string) {
|
|
18
|
-
if (!languages.length) {
|
|
19
|
-
return 'en';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const trimmedRequestedCode = requestedLanguageCode?.trim().toLowerCase();
|
|
23
|
-
|
|
24
|
-
if (trimmedRequestedCode) {
|
|
25
|
-
const matchedLanguage = languages.find(
|
|
26
|
-
(language) => language.code.toLowerCase() === trimmedRequestedCode
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if (matchedLanguage) {
|
|
30
|
-
return matchedLanguage.code;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const firstNonEnglishLanguage = languages.find(
|
|
35
|
-
(language) => language.code.toLowerCase() !== 'en'
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return firstNonEnglishLanguage?.code ?? languages[0]?.code ?? 'en';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getInitialStatusFilter(
|
|
42
|
-
requestedStatus?: string,
|
|
43
|
-
requestedLanguageCode?: string
|
|
44
|
-
) {
|
|
45
|
-
if (
|
|
46
|
-
requestedStatus === 'all' ||
|
|
47
|
-
requestedStatus === 'missing' ||
|
|
48
|
-
requestedStatus === 'translated'
|
|
49
|
-
) {
|
|
50
|
-
return requestedStatus;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return requestedLanguageCode ? 'missing' : 'all';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default async function ExtraTranslationsPage(
|
|
57
|
-
props: ExtraTranslationsPageProps
|
|
58
|
-
) {
|
|
59
|
-
const searchParams = await props.searchParams;
|
|
60
|
-
const [translations, languagesResult] = await Promise.all([
|
|
61
|
-
getTranslations(),
|
|
62
|
-
getLanguages(),
|
|
63
|
-
]);
|
|
64
|
-
const languages = languagesResult.data ?? [];
|
|
65
|
-
const initialLanguageCode = getInitialLanguageCode(
|
|
66
|
-
languages,
|
|
67
|
-
searchParams?.language
|
|
68
|
-
);
|
|
69
|
-
const initialStatusFilter = getInitialStatusFilter(
|
|
70
|
-
searchParams?.status,
|
|
71
|
-
searchParams?.language
|
|
72
|
-
);
|
|
73
|
-
const successMessage = searchParams?.success
|
|
74
|
-
? decodeURIComponent(searchParams.success)
|
|
75
|
-
: null;
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className="space-y-4 p-6">
|
|
79
|
-
{successMessage ? (
|
|
80
|
-
<Alert variant="success">
|
|
81
|
-
<AlertDescription>{successMessage}</AlertDescription>
|
|
82
|
-
</Alert>
|
|
83
|
-
) : null}
|
|
84
|
-
|
|
85
|
-
<ExtraTranslationsWorkspace
|
|
86
|
-
initialTranslations={translations}
|
|
87
|
-
languages={languages}
|
|
88
|
-
initialLanguageCode={initialLanguageCode}
|
|
89
|
-
initialStatusFilter={initialStatusFilter}
|
|
90
|
-
/>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
1
|
+
import { Alert, AlertDescription } from '@nextblock-cms/ui';
|
|
2
|
+
|
|
3
|
+
import { getLanguages } from '../languages/actions';
|
|
4
|
+
import { getTranslations } from './actions';
|
|
5
|
+
import { ExtraTranslationsWorkspace } from './ExtraTranslationsWorkspace';
|
|
6
|
+
|
|
7
|
+
type Language = NonNullable<Awaited<ReturnType<typeof getLanguages>>['data']>[number];
|
|
8
|
+
|
|
9
|
+
interface ExtraTranslationsPageProps {
|
|
10
|
+
searchParams?: Promise<{
|
|
11
|
+
language?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
success?: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getInitialLanguageCode(languages: Language[], requestedLanguageCode?: string) {
|
|
18
|
+
if (!languages.length) {
|
|
19
|
+
return 'en';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const trimmedRequestedCode = requestedLanguageCode?.trim().toLowerCase();
|
|
23
|
+
|
|
24
|
+
if (trimmedRequestedCode) {
|
|
25
|
+
const matchedLanguage = languages.find(
|
|
26
|
+
(language) => language.code.toLowerCase() === trimmedRequestedCode
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (matchedLanguage) {
|
|
30
|
+
return matchedLanguage.code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const firstNonEnglishLanguage = languages.find(
|
|
35
|
+
(language) => language.code.toLowerCase() !== 'en'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return firstNonEnglishLanguage?.code ?? languages[0]?.code ?? 'en';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getInitialStatusFilter(
|
|
42
|
+
requestedStatus?: string,
|
|
43
|
+
requestedLanguageCode?: string
|
|
44
|
+
) {
|
|
45
|
+
if (
|
|
46
|
+
requestedStatus === 'all' ||
|
|
47
|
+
requestedStatus === 'missing' ||
|
|
48
|
+
requestedStatus === 'translated'
|
|
49
|
+
) {
|
|
50
|
+
return requestedStatus;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return requestedLanguageCode ? 'missing' : 'all';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default async function ExtraTranslationsPage(
|
|
57
|
+
props: ExtraTranslationsPageProps
|
|
58
|
+
) {
|
|
59
|
+
const searchParams = await props.searchParams;
|
|
60
|
+
const [translations, languagesResult] = await Promise.all([
|
|
61
|
+
getTranslations(),
|
|
62
|
+
getLanguages(),
|
|
63
|
+
]);
|
|
64
|
+
const languages = languagesResult.data ?? [];
|
|
65
|
+
const initialLanguageCode = getInitialLanguageCode(
|
|
66
|
+
languages,
|
|
67
|
+
searchParams?.language
|
|
68
|
+
);
|
|
69
|
+
const initialStatusFilter = getInitialStatusFilter(
|
|
70
|
+
searchParams?.status,
|
|
71
|
+
searchParams?.language
|
|
72
|
+
);
|
|
73
|
+
const successMessage = searchParams?.success
|
|
74
|
+
? decodeURIComponent(searchParams.success)
|
|
75
|
+
: null;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="space-y-4 p-6">
|
|
79
|
+
{successMessage ? (
|
|
80
|
+
<Alert variant="success">
|
|
81
|
+
<AlertDescription>{successMessage}</AlertDescription>
|
|
82
|
+
</Alert>
|
|
83
|
+
) : null}
|
|
84
|
+
|
|
85
|
+
<ExtraTranslationsWorkspace
|
|
86
|
+
initialTranslations={translations}
|
|
87
|
+
languages={languages}
|
|
88
|
+
initialLanguageCode={initialLanguageCode}
|
|
89
|
+
initialStatusFilter={initialStatusFilter}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import { createClient } from '@nextblock-cms/db/server';
|
|
4
|
-
import { revalidatePath } from 'next/cache';
|
|
5
|
-
import {
|
|
6
|
-
getPrivacySettings as readPrivacySettings,
|
|
7
|
-
mergePrivacySettings,
|
|
8
|
-
} from '../../../../lib/privacy/settings';
|
|
9
|
-
import type { PrivacySettings } from '../../../../lib/privacy/types';
|
|
10
|
-
|
|
11
|
-
export interface GoogleAnalyticsSettings {
|
|
12
|
-
gtm_id: string;
|
|
13
|
-
ga_measurement_id: string;
|
|
14
|
-
custom_scripts: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function getGoogleAnalyticsSettings(): Promise<GoogleAnalyticsSettings> {
|
|
18
|
-
const settings = await readPrivacySettings();
|
|
19
|
-
return {
|
|
20
|
-
gtm_id: settings.gtm_id,
|
|
21
|
-
ga_measurement_id: settings.ga_measurement_id,
|
|
22
|
-
custom_scripts: settings.custom_scripts,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function assertAdmin(): Promise<void> {
|
|
27
|
-
const supabase = createClient();
|
|
28
|
-
const {
|
|
29
|
-
data: { user },
|
|
30
|
-
} = await supabase.auth.getUser();
|
|
31
|
-
if (!user) {
|
|
32
|
-
throw new Error('You must be logged in to update settings.');
|
|
33
|
-
}
|
|
34
|
-
const { data: profile } = await supabase
|
|
35
|
-
.from('profiles')
|
|
36
|
-
.select('role')
|
|
37
|
-
.eq('id', user.id)
|
|
38
|
-
.single();
|
|
39
|
-
if (!profile || profile.role !== 'ADMIN') {
|
|
40
|
-
throw new Error('You do not have permission to perform this action.');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function updateGoogleAnalyticsSettings(formData: FormData) {
|
|
45
|
-
await assertAdmin();
|
|
46
|
-
|
|
47
|
-
// Only the analytics fields are touched; mergePrivacySettings preserves the
|
|
48
|
-
// banner/corporate fields owned by the Privacy & Consent page.
|
|
49
|
-
const patch: Partial<PrivacySettings> = {
|
|
50
|
-
gtm_id: (formData.get('gtm_id')?.toString() ?? '').trim(),
|
|
51
|
-
ga_measurement_id: (formData.get('ga_measurement_id')?.toString() ?? '').trim(),
|
|
52
|
-
custom_scripts: formData.get('custom_scripts')?.toString() ?? '',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
await mergePrivacySettings(patch);
|
|
56
|
-
// The analytics guard (GTM/GA4 + custom scripts) lives in the root layout.
|
|
57
|
-
revalidatePath('/', 'layout');
|
|
58
|
-
|
|
59
|
-
return { success: true, message: 'Google Analytics settings saved.' };
|
|
60
|
-
}
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { createClient } from '@nextblock-cms/db/server';
|
|
4
|
+
import { revalidatePath } from 'next/cache';
|
|
5
|
+
import {
|
|
6
|
+
getPrivacySettings as readPrivacySettings,
|
|
7
|
+
mergePrivacySettings,
|
|
8
|
+
} from '../../../../lib/privacy/settings';
|
|
9
|
+
import type { PrivacySettings } from '../../../../lib/privacy/types';
|
|
10
|
+
|
|
11
|
+
export interface GoogleAnalyticsSettings {
|
|
12
|
+
gtm_id: string;
|
|
13
|
+
ga_measurement_id: string;
|
|
14
|
+
custom_scripts: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getGoogleAnalyticsSettings(): Promise<GoogleAnalyticsSettings> {
|
|
18
|
+
const settings = await readPrivacySettings();
|
|
19
|
+
return {
|
|
20
|
+
gtm_id: settings.gtm_id,
|
|
21
|
+
ga_measurement_id: settings.ga_measurement_id,
|
|
22
|
+
custom_scripts: settings.custom_scripts,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function assertAdmin(): Promise<void> {
|
|
27
|
+
const supabase = createClient();
|
|
28
|
+
const {
|
|
29
|
+
data: { user },
|
|
30
|
+
} = await supabase.auth.getUser();
|
|
31
|
+
if (!user) {
|
|
32
|
+
throw new Error('You must be logged in to update settings.');
|
|
33
|
+
}
|
|
34
|
+
const { data: profile } = await supabase
|
|
35
|
+
.from('profiles')
|
|
36
|
+
.select('role')
|
|
37
|
+
.eq('id', user.id)
|
|
38
|
+
.single();
|
|
39
|
+
if (!profile || profile.role !== 'ADMIN') {
|
|
40
|
+
throw new Error('You do not have permission to perform this action.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function updateGoogleAnalyticsSettings(formData: FormData) {
|
|
45
|
+
await assertAdmin();
|
|
46
|
+
|
|
47
|
+
// Only the analytics fields are touched; mergePrivacySettings preserves the
|
|
48
|
+
// banner/corporate fields owned by the Privacy & Consent page.
|
|
49
|
+
const patch: Partial<PrivacySettings> = {
|
|
50
|
+
gtm_id: (formData.get('gtm_id')?.toString() ?? '').trim(),
|
|
51
|
+
ga_measurement_id: (formData.get('ga_measurement_id')?.toString() ?? '').trim(),
|
|
52
|
+
custom_scripts: formData.get('custom_scripts')?.toString() ?? '',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await mergePrivacySettings(patch);
|
|
56
|
+
// The analytics guard (GTM/GA4 + custom scripts) lives in the root layout.
|
|
57
|
+
revalidatePath('/', 'layout');
|
|
58
|
+
|
|
59
|
+
return { success: true, message: 'Google Analytics settings saved.' };
|
|
60
|
+
}
|
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useRef, useState, useTransition } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Alert,
|
|
6
|
-
AlertDescription,
|
|
7
|
-
Button,
|
|
8
|
-
Input,
|
|
9
|
-
Label,
|
|
10
|
-
Spinner,
|
|
11
|
-
Textarea,
|
|
12
|
-
} from '@nextblock-cms/ui';
|
|
13
|
-
import { Message } from '../../../../../components/form-message';
|
|
14
|
-
import { useHotkeys } from '../../../../../hooks/use-hotkeys';
|
|
15
|
-
import {
|
|
16
|
-
updateGoogleAnalyticsSettings,
|
|
17
|
-
type GoogleAnalyticsSettings,
|
|
18
|
-
} from '../actions';
|
|
19
|
-
|
|
20
|
-
interface GoogleAnalyticsFormProps {
|
|
21
|
-
initialSettings: GoogleAnalyticsSettings;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default function GoogleAnalyticsForm({ initialSettings }: GoogleAnalyticsFormProps) {
|
|
25
|
-
const [isPending, startTransition] = useTransition();
|
|
26
|
-
const [message, setMessage] = useState<Message | null>(null);
|
|
27
|
-
|
|
28
|
-
const [gtmId, setGtmId] = useState(initialSettings.gtm_id);
|
|
29
|
-
const [gaId, setGaId] = useState(initialSettings.ga_measurement_id);
|
|
30
|
-
const [customScripts, setCustomScripts] = useState(initialSettings.custom_scripts);
|
|
31
|
-
|
|
32
|
-
const formRef = useRef<HTMLFormElement>(null);
|
|
33
|
-
useHotkeys('ctrl+s', () => formRef.current?.requestSubmit());
|
|
34
|
-
|
|
35
|
-
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
36
|
-
event.preventDefault();
|
|
37
|
-
setMessage(null);
|
|
38
|
-
|
|
39
|
-
const formData = new FormData();
|
|
40
|
-
formData.append('gtm_id', gtmId);
|
|
41
|
-
formData.append('ga_measurement_id', gaId);
|
|
42
|
-
formData.append('custom_scripts', customScripts);
|
|
43
|
-
|
|
44
|
-
startTransition(async () => {
|
|
45
|
-
try {
|
|
46
|
-
const result = await updateGoogleAnalyticsSettings(formData);
|
|
47
|
-
setMessage({ success: result.message });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
setMessage({
|
|
50
|
-
error: error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<form ref={formRef} onSubmit={handleSubmit} className="space-y-8">
|
|
58
|
-
<section className="space-y-4">
|
|
59
|
-
<div>
|
|
60
|
-
<h3 className="text-sm font-semibold">Analytics & tracking</h3>
|
|
61
|
-
<p className="text-xs text-slate-500">
|
|
62
|
-
These load <strong>only</strong> after a visitor consents to analytics in the
|
|
63
|
-
Law 25 banner (managed under Privacy & Consent), so the default page weight
|
|
64
|
-
stays at zero tracking bytes.
|
|
65
|
-
</p>
|
|
66
|
-
</div>
|
|
67
|
-
<div className="space-y-2">
|
|
68
|
-
<Label htmlFor="gtm_id">Google Tag Manager ID</Label>
|
|
69
|
-
<Input
|
|
70
|
-
id="gtm_id"
|
|
71
|
-
value={gtmId}
|
|
72
|
-
onChange={(e) => setGtmId(e.target.value)}
|
|
73
|
-
placeholder="GTM-XXXXXXX"
|
|
74
|
-
/>
|
|
75
|
-
</div>
|
|
76
|
-
<div className="space-y-2">
|
|
77
|
-
<Label htmlFor="ga_measurement_id">GA4 Measurement ID</Label>
|
|
78
|
-
<Input
|
|
79
|
-
id="ga_measurement_id"
|
|
80
|
-
value={gaId}
|
|
81
|
-
onChange={(e) => setGaId(e.target.value)}
|
|
82
|
-
placeholder="G-XXXXXXXXXX"
|
|
83
|
-
/>
|
|
84
|
-
<p className="text-xs text-slate-500">
|
|
85
|
-
Loads Google Analytics 4 directly via <code>gtag</code>. If you also configure
|
|
86
|
-
GA4 inside your GTM container, set only one of these to avoid double-counting.
|
|
87
|
-
</p>
|
|
88
|
-
</div>
|
|
89
|
-
<div className="space-y-2">
|
|
90
|
-
<Label htmlFor="custom_scripts">Custom consented scripts (optional)</Label>
|
|
91
|
-
<Textarea
|
|
92
|
-
id="custom_scripts"
|
|
93
|
-
value={customScripts}
|
|
94
|
-
onChange={(e) => setCustomScripts(e.target.value)}
|
|
95
|
-
placeholder="<!-- Additional marketing/analytics <script> tags, injected only after consent -->"
|
|
96
|
-
rows={4}
|
|
97
|
-
className="font-mono text-xs"
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
</section>
|
|
101
|
-
|
|
102
|
-
<div className="flex items-center gap-4">
|
|
103
|
-
<Button type="submit" disabled={isPending}>
|
|
104
|
-
{isPending ? (
|
|
105
|
-
<>
|
|
106
|
-
<Spinner className="mr-2 h-4 w-4 animate-spin" /> Saving...
|
|
107
|
-
</>
|
|
108
|
-
) : (
|
|
109
|
-
'Save Google Analytics Settings'
|
|
110
|
-
)}
|
|
111
|
-
</Button>
|
|
112
|
-
{message && (
|
|
113
|
-
<Alert
|
|
114
|
-
variant={'success' in message ? 'success' : 'destructive'}
|
|
115
|
-
className="py-2 px-4 w-auto inline-flex items-center"
|
|
116
|
-
>
|
|
117
|
-
<AlertDescription>
|
|
118
|
-
{'success' in message
|
|
119
|
-
? message.success
|
|
120
|
-
: 'error' in message
|
|
121
|
-
? message.error
|
|
122
|
-
: ''}
|
|
123
|
-
</AlertDescription>
|
|
124
|
-
</Alert>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
</form>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRef, useState, useTransition } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertDescription,
|
|
7
|
+
Button,
|
|
8
|
+
Input,
|
|
9
|
+
Label,
|
|
10
|
+
Spinner,
|
|
11
|
+
Textarea,
|
|
12
|
+
} from '@nextblock-cms/ui';
|
|
13
|
+
import { Message } from '../../../../../components/form-message';
|
|
14
|
+
import { useHotkeys } from '../../../../../hooks/use-hotkeys';
|
|
15
|
+
import {
|
|
16
|
+
updateGoogleAnalyticsSettings,
|
|
17
|
+
type GoogleAnalyticsSettings,
|
|
18
|
+
} from '../actions';
|
|
19
|
+
|
|
20
|
+
interface GoogleAnalyticsFormProps {
|
|
21
|
+
initialSettings: GoogleAnalyticsSettings;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function GoogleAnalyticsForm({ initialSettings }: GoogleAnalyticsFormProps) {
|
|
25
|
+
const [isPending, startTransition] = useTransition();
|
|
26
|
+
const [message, setMessage] = useState<Message | null>(null);
|
|
27
|
+
|
|
28
|
+
const [gtmId, setGtmId] = useState(initialSettings.gtm_id);
|
|
29
|
+
const [gaId, setGaId] = useState(initialSettings.ga_measurement_id);
|
|
30
|
+
const [customScripts, setCustomScripts] = useState(initialSettings.custom_scripts);
|
|
31
|
+
|
|
32
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
33
|
+
useHotkeys('ctrl+s', () => formRef.current?.requestSubmit());
|
|
34
|
+
|
|
35
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
setMessage(null);
|
|
38
|
+
|
|
39
|
+
const formData = new FormData();
|
|
40
|
+
formData.append('gtm_id', gtmId);
|
|
41
|
+
formData.append('ga_measurement_id', gaId);
|
|
42
|
+
formData.append('custom_scripts', customScripts);
|
|
43
|
+
|
|
44
|
+
startTransition(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await updateGoogleAnalyticsSettings(formData);
|
|
47
|
+
setMessage({ success: result.message });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
setMessage({
|
|
50
|
+
error: error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<form ref={formRef} onSubmit={handleSubmit} className="space-y-8">
|
|
58
|
+
<section className="space-y-4">
|
|
59
|
+
<div>
|
|
60
|
+
<h3 className="text-sm font-semibold">Analytics & tracking</h3>
|
|
61
|
+
<p className="text-xs text-slate-500">
|
|
62
|
+
These load <strong>only</strong> after a visitor consents to analytics in the
|
|
63
|
+
Law 25 banner (managed under Privacy & Consent), so the default page weight
|
|
64
|
+
stays at zero tracking bytes.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="space-y-2">
|
|
68
|
+
<Label htmlFor="gtm_id">Google Tag Manager ID</Label>
|
|
69
|
+
<Input
|
|
70
|
+
id="gtm_id"
|
|
71
|
+
value={gtmId}
|
|
72
|
+
onChange={(e) => setGtmId(e.target.value)}
|
|
73
|
+
placeholder="GTM-XXXXXXX"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
<Label htmlFor="ga_measurement_id">GA4 Measurement ID</Label>
|
|
78
|
+
<Input
|
|
79
|
+
id="ga_measurement_id"
|
|
80
|
+
value={gaId}
|
|
81
|
+
onChange={(e) => setGaId(e.target.value)}
|
|
82
|
+
placeholder="G-XXXXXXXXXX"
|
|
83
|
+
/>
|
|
84
|
+
<p className="text-xs text-slate-500">
|
|
85
|
+
Loads Google Analytics 4 directly via <code>gtag</code>. If you also configure
|
|
86
|
+
GA4 inside your GTM container, set only one of these to avoid double-counting.
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<Label htmlFor="custom_scripts">Custom consented scripts (optional)</Label>
|
|
91
|
+
<Textarea
|
|
92
|
+
id="custom_scripts"
|
|
93
|
+
value={customScripts}
|
|
94
|
+
onChange={(e) => setCustomScripts(e.target.value)}
|
|
95
|
+
placeholder="<!-- Additional marketing/analytics <script> tags, injected only after consent -->"
|
|
96
|
+
rows={4}
|
|
97
|
+
className="font-mono text-xs"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<div className="flex items-center gap-4">
|
|
103
|
+
<Button type="submit" disabled={isPending}>
|
|
104
|
+
{isPending ? (
|
|
105
|
+
<>
|
|
106
|
+
<Spinner className="mr-2 h-4 w-4 animate-spin" /> Saving...
|
|
107
|
+
</>
|
|
108
|
+
) : (
|
|
109
|
+
'Save Google Analytics Settings'
|
|
110
|
+
)}
|
|
111
|
+
</Button>
|
|
112
|
+
{message && (
|
|
113
|
+
<Alert
|
|
114
|
+
variant={'success' in message ? 'success' : 'destructive'}
|
|
115
|
+
className="py-2 px-4 w-auto inline-flex items-center"
|
|
116
|
+
>
|
|
117
|
+
<AlertDescription>
|
|
118
|
+
{'success' in message
|
|
119
|
+
? message.success
|
|
120
|
+
: 'error' in message
|
|
121
|
+
? message.error
|
|
122
|
+
: ''}
|
|
123
|
+
</AlertDescription>
|
|
124
|
+
</Alert>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</form>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
// app/cms/settings/google-analytics/page.tsx
|
|
2
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
-
import { getGoogleAnalyticsSettings } from './actions';
|
|
4
|
-
import GoogleAnalyticsForm from './components/GoogleAnalyticsForm';
|
|
5
|
-
|
|
6
|
-
export default async function GoogleAnalyticsSettingsPage() {
|
|
7
|
-
const settings = await getGoogleAnalyticsSettings();
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div className="max-w-4xl mx-auto">
|
|
11
|
-
<Card>
|
|
12
|
-
<CardHeader>
|
|
13
|
-
<CardTitle>Google Analytics</CardTitle>
|
|
14
|
-
<CardDescription>
|
|
15
|
-
Configure Google Tag Manager and Google Analytics 4. These tags load only
|
|
16
|
-
after a visitor accepts analytics in the Law 25 consent banner (managed under
|
|
17
|
-
Privacy & Consent), so the default page weight stays at zero tracking bytes.
|
|
18
|
-
</CardDescription>
|
|
19
|
-
</CardHeader>
|
|
20
|
-
<CardContent>
|
|
21
|
-
<GoogleAnalyticsForm initialSettings={settings} />
|
|
22
|
-
</CardContent>
|
|
23
|
-
</Card>
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
1
|
+
// app/cms/settings/google-analytics/page.tsx
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
+
import { getGoogleAnalyticsSettings } from './actions';
|
|
4
|
+
import GoogleAnalyticsForm from './components/GoogleAnalyticsForm';
|
|
5
|
+
|
|
6
|
+
export default async function GoogleAnalyticsSettingsPage() {
|
|
7
|
+
const settings = await getGoogleAnalyticsSettings();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="max-w-4xl mx-auto">
|
|
11
|
+
<Card>
|
|
12
|
+
<CardHeader>
|
|
13
|
+
<CardTitle>Google Analytics</CardTitle>
|
|
14
|
+
<CardDescription>
|
|
15
|
+
Configure Google Tag Manager and Google Analytics 4. These tags load only
|
|
16
|
+
after a visitor accepts analytics in the Law 25 consent banner (managed under
|
|
17
|
+
Privacy & Consent), so the default page weight stays at zero tracking bytes.
|
|
18
|
+
</CardDescription>
|
|
19
|
+
</CardHeader>
|
|
20
|
+
<CardContent>
|
|
21
|
+
<GoogleAnalyticsForm initialSettings={settings} />
|
|
22
|
+
</CardContent>
|
|
23
|
+
</Card>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -121,28 +121,28 @@ export async function createLanguage(formData: FormData) {
|
|
|
121
121
|
.select()
|
|
122
122
|
.single();
|
|
123
123
|
|
|
124
|
-
if (error) {
|
|
125
|
-
console.error("Error creating language:", error);
|
|
126
|
-
if (error.code === '23505') { // Unique violation
|
|
127
|
-
if (error.message.includes('languages_code_key')) {
|
|
128
|
-
return { error: `Language code '${languageData.code}' already exists.` };
|
|
124
|
+
if (error) {
|
|
125
|
+
console.error("Error creating language:", error);
|
|
126
|
+
if (error.code === '23505') { // Unique violation
|
|
127
|
+
if (error.message.includes('languages_code_key')) {
|
|
128
|
+
return { error: `Language code '${languageData.code}' already exists.` };
|
|
129
129
|
}
|
|
130
130
|
if (error.message.includes('ensure_single_default_language_idx')) {
|
|
131
131
|
return { error: `Cannot set this language as default. Another language is already default, or an error occurred unsetting it.` };
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
return { error: `Failed to create language: ${error.message}` };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
revalidatePath("/cms/settings/languages");
|
|
138
|
-
revalidatePath("/"); // Revalidate home page as language switcher might change
|
|
139
|
-
revalidatePath("/cms/settings/extra-translations");
|
|
140
|
-
if (data?.id) {
|
|
141
|
-
redirect(`/cms/settings/extra-translations?language=${encodeURIComponent(data.code)}&success=${encodeURIComponent("Language created successfully. Please fill the extra translations.")}`);
|
|
142
|
-
} else {
|
|
143
|
-
redirect(`/cms/settings/extra-translations?success=${encodeURIComponent("Language created successfully. Please fill the extra translations.")}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
revalidatePath("/cms/settings/languages");
|
|
138
|
+
revalidatePath("/"); // Revalidate home page as language switcher might change
|
|
139
|
+
revalidatePath("/cms/settings/extra-translations");
|
|
140
|
+
if (data?.id) {
|
|
141
|
+
redirect(`/cms/settings/extra-translations?language=${encodeURIComponent(data.code)}&success=${encodeURIComponent("Language created successfully. Please fill the extra translations.")}`);
|
|
142
|
+
} else {
|
|
143
|
+
redirect(`/cms/settings/extra-translations?success=${encodeURIComponent("Language created successfully. Please fill the extra translations.")}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
146
|
|
|
147
147
|
export async function updateLanguage(languageId: number, formData: FormData) {
|
|
148
148
|
const supabase = createClient();
|