create-nextblock 0.12.9 → 0.12.12
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 -158
- 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/[id]/edit/page.tsx +12 -0
- 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,36 +1,34 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import type { Database } from "@nextblock-cms/db";
|
|
5
|
-
import {
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogContent,
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import type { Database } from "@nextblock-cms/db";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
8
|
DialogHeader,
|
|
9
9
|
DialogTitle,
|
|
10
10
|
DialogTrigger,
|
|
11
|
-
DialogFooter,
|
|
12
|
-
DialogClose,
|
|
13
11
|
Button,
|
|
14
12
|
Input,
|
|
15
13
|
Separator,
|
|
16
|
-
} from "@nextblock-cms/ui";
|
|
17
|
-
import { Search, CheckCircle } from "lucide-react";
|
|
18
|
-
import Image from "next/image";
|
|
19
|
-
import MediaUploadForm from "./MediaUploadForm";
|
|
20
|
-
import { resolveMediaUrl } from "../../../../lib/media/resolveMediaUrl";
|
|
21
|
-
|
|
22
|
-
type Media = Database["public"]["Tables"]["media"]["Row"];
|
|
23
|
-
|
|
24
|
-
const MEDIA_REQUEST_TIMEOUT_MS = 8000;
|
|
25
|
-
const MEDIA_LIBRARY_LIMIT = 50;
|
|
26
|
-
|
|
27
|
-
function resolveMediaPreviewPath(media: Media) {
|
|
28
|
-
return media.file_path || media.object_key || null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function resolveMediaPreviewSrc(path: string) {
|
|
32
|
-
return resolveMediaUrl(path);
|
|
33
|
-
}
|
|
14
|
+
} from "@nextblock-cms/ui";
|
|
15
|
+
import { Search, CheckCircle } from "lucide-react";
|
|
16
|
+
import Image from "next/image";
|
|
17
|
+
import MediaUploadForm from "./MediaUploadForm";
|
|
18
|
+
import { resolveMediaUrl } from "../../../../lib/media/resolveMediaUrl";
|
|
19
|
+
|
|
20
|
+
type Media = Database["public"]["Tables"]["media"]["Row"];
|
|
21
|
+
|
|
22
|
+
const MEDIA_REQUEST_TIMEOUT_MS = 8000;
|
|
23
|
+
const MEDIA_LIBRARY_LIMIT = 50;
|
|
24
|
+
|
|
25
|
+
function resolveMediaPreviewPath(media: Media) {
|
|
26
|
+
return media.file_path || media.object_key || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveMediaPreviewSrc(path: string) {
|
|
30
|
+
return resolveMediaUrl(path);
|
|
31
|
+
}
|
|
34
32
|
|
|
35
33
|
interface MediaPickerDialogProps {
|
|
36
34
|
triggerLabel?: string;
|
|
@@ -62,76 +60,76 @@ export default function MediaPickerDialog({
|
|
|
62
60
|
if (!isControlled) setInternalOpen(v);
|
|
63
61
|
onOpenChange?.(v);
|
|
64
62
|
};
|
|
65
|
-
const [searchTerm, setSearchTerm] = useState("");
|
|
66
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
67
|
-
const [loadError, setLoadError] = useState<string | null>(null);
|
|
68
|
-
const [items, setItems] = useState<Media[]>([]);
|
|
69
|
-
const requestIdRef = useRef(0);
|
|
70
|
-
|
|
71
|
-
const fetchLibrary = useCallback(async () => {
|
|
72
|
-
const requestId = ++requestIdRef.current;
|
|
73
|
-
setIsLoading(true);
|
|
74
|
-
setLoadError(null);
|
|
75
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const controller = new AbortController();
|
|
79
|
-
timeoutId = setTimeout(
|
|
80
|
-
() => controller.abort(new Error("Media library request timed out.")),
|
|
81
|
-
MEDIA_REQUEST_TIMEOUT_MS
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const params = new URLSearchParams({
|
|
85
|
-
limit: MEDIA_LIBRARY_LIMIT.toString(),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (searchTerm.trim()) {
|
|
89
|
-
params.set("q", searchTerm.trim());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const response = await fetch(`/api/media/library?${params.toString()}`, {
|
|
93
|
-
method: "GET",
|
|
94
|
-
cache: "no-store",
|
|
95
|
-
signal: controller.signal,
|
|
96
|
-
});
|
|
97
|
-
clearTimeout(timeoutId);
|
|
98
|
-
|
|
99
|
-
const payload = (await response.json()) as {
|
|
100
|
-
items?: Media[];
|
|
101
|
-
error?: string;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
if (requestId !== requestIdRef.current) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
console.error("Error fetching media library:", payload.error);
|
|
110
|
-
setLoadError(payload.error || "We couldn't load the media library. Please retry.");
|
|
111
|
-
} else {
|
|
112
|
-
setItems(payload.items || []);
|
|
113
|
-
}
|
|
114
|
-
} catch (error: any) {
|
|
115
|
-
if (requestId !== requestIdRef.current) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
console.error("Error fetching media library:", error);
|
|
120
|
-
|
|
121
|
-
if (error?.name === "AbortError" || error?.message === "Media library request timed out.") {
|
|
122
|
-
setLoadError("The media library took too long to respond. Please retry.");
|
|
123
|
-
} else {
|
|
124
|
-
setLoadError("We couldn't load the media library. Please retry.");
|
|
125
|
-
}
|
|
126
|
-
} finally {
|
|
127
|
-
if (timeoutId) {
|
|
128
|
-
clearTimeout(timeoutId);
|
|
129
|
-
}
|
|
130
|
-
if (requestId === requestIdRef.current) {
|
|
131
|
-
setIsLoading(false);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}, [searchTerm]);
|
|
63
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
64
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
65
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
66
|
+
const [items, setItems] = useState<Media[]>([]);
|
|
67
|
+
const requestIdRef = useRef(0);
|
|
68
|
+
|
|
69
|
+
const fetchLibrary = useCallback(async () => {
|
|
70
|
+
const requestId = ++requestIdRef.current;
|
|
71
|
+
setIsLoading(true);
|
|
72
|
+
setLoadError(null);
|
|
73
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
timeoutId = setTimeout(
|
|
78
|
+
() => controller.abort(new Error("Media library request timed out.")),
|
|
79
|
+
MEDIA_REQUEST_TIMEOUT_MS
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const params = new URLSearchParams({
|
|
83
|
+
limit: MEDIA_LIBRARY_LIMIT.toString(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (searchTerm.trim()) {
|
|
87
|
+
params.set("q", searchTerm.trim());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const response = await fetch(`/api/media/library?${params.toString()}`, {
|
|
91
|
+
method: "GET",
|
|
92
|
+
cache: "no-store",
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
});
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
|
|
97
|
+
const payload = (await response.json()) as {
|
|
98
|
+
items?: Media[];
|
|
99
|
+
error?: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (requestId !== requestIdRef.current) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.error("Error fetching media library:", payload.error);
|
|
108
|
+
setLoadError(payload.error || "We couldn't load the media library. Please retry.");
|
|
109
|
+
} else {
|
|
110
|
+
setItems(payload.items || []);
|
|
111
|
+
}
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
if (requestId !== requestIdRef.current) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.error("Error fetching media library:", error);
|
|
118
|
+
|
|
119
|
+
if (error?.name === "AbortError" || error?.message === "Media library request timed out.") {
|
|
120
|
+
setLoadError("The media library took too long to respond. Please retry.");
|
|
121
|
+
} else {
|
|
122
|
+
setLoadError("We couldn't load the media library. Please retry.");
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
if (timeoutId) {
|
|
126
|
+
clearTimeout(timeoutId);
|
|
127
|
+
}
|
|
128
|
+
if (requestId === requestIdRef.current) {
|
|
129
|
+
setIsLoading(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [searchTerm]);
|
|
135
133
|
|
|
136
134
|
useEffect(() => {
|
|
137
135
|
if (isOpen) fetchLibrary();
|
|
@@ -189,70 +187,63 @@ export default function MediaPickerDialog({
|
|
|
189
187
|
/>
|
|
190
188
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
191
189
|
</div>
|
|
192
|
-
{isLoading && filtered.length === 0 ? (
|
|
193
|
-
<div className="flex-grow flex items-center justify-center">
|
|
194
|
-
<p>Loading media...</p>
|
|
195
|
-
</div>
|
|
196
|
-
) : loadError && filtered.length === 0 ? (
|
|
197
|
-
<div className="flex-grow flex flex-col items-center justify-center gap-3 text-center">
|
|
198
|
-
<p className="max-w-sm text-sm text-muted-foreground">{loadError}</p>
|
|
199
|
-
<Button type="button" variant="outline" size="sm" onClick={() => void fetchLibrary()}>
|
|
200
|
-
Retry
|
|
201
|
-
</Button>
|
|
202
|
-
</div>
|
|
203
|
-
) : filtered.length === 0 ? (
|
|
204
|
-
<div className="flex-grow flex items-center justify-center">
|
|
205
|
-
<p>No media found.</p>
|
|
190
|
+
{isLoading && filtered.length === 0 ? (
|
|
191
|
+
<div className="flex-grow flex items-center justify-center">
|
|
192
|
+
<p>Loading media...</p>
|
|
193
|
+
</div>
|
|
194
|
+
) : loadError && filtered.length === 0 ? (
|
|
195
|
+
<div className="flex-grow flex flex-col items-center justify-center gap-3 text-center">
|
|
196
|
+
<p className="max-w-sm text-sm text-muted-foreground">{loadError}</p>
|
|
197
|
+
<Button type="button" variant="outline" size="sm" onClick={() => void fetchLibrary()}>
|
|
198
|
+
Retry
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
) : filtered.length === 0 ? (
|
|
202
|
+
<div className="flex-grow flex items-center justify-center">
|
|
203
|
+
<p>No media found.</p>
|
|
206
204
|
</div>
|
|
207
205
|
) : (
|
|
208
|
-
<div className="flex flex-wrap gap-3 overflow-y-auto min-h-0 pr-2 pb-2">
|
|
209
|
-
{filtered.map((media: Media) => {
|
|
210
|
-
const previewPath = resolveMediaPreviewPath(media);
|
|
211
|
-
const previewSrc = previewPath ? resolveMediaPreviewSrc(previewPath) : null;
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<button
|
|
215
|
-
key={media.id}
|
|
216
|
-
type="button"
|
|
217
|
-
className="relative aspect-square border rounded-md overflow-hidden group focus:outline-none focus:ring-2 focus:ring-primary min-w-0 w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6"
|
|
218
|
-
onClick={() => handleSelect(media)}
|
|
219
|
-
>
|
|
220
|
-
{media.file_type?.startsWith("image/") && previewSrc ? (
|
|
221
|
-
<>
|
|
222
|
-
<Image
|
|
223
|
-
src={previewSrc}
|
|
224
|
-
alt={media.description || media.file_name || "Media library image"}
|
|
225
|
-
fill
|
|
226
|
-
className="absolute inset-0 w-full h-full object-cover"
|
|
227
|
-
placeholder={media.blur_data_url ? "blur" : "empty"}
|
|
228
|
-
blurDataURL={media.blur_data_url || undefined}
|
|
229
|
-
sizes="(max-width: 639px) 33vw, (max-width: 767px) 25vw, (max-width: 1023px) 20vw, 17vw"
|
|
230
|
-
/>
|
|
231
|
-
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity flex items-center justify-center">
|
|
232
|
-
<CheckCircle className="h-8 w-8 text-white" />
|
|
233
|
-
</div>
|
|
234
|
-
</>
|
|
235
|
-
) : (
|
|
236
|
-
<div className="absolute inset-0 flex items-center justify-center text-xs text-muted-foreground p-1 text-center">
|
|
237
|
-
Preview unavailable
|
|
238
|
-
</div>
|
|
239
|
-
)}
|
|
240
|
-
<p className="absolute bottom-0 left-0 right-0 bg-black/50 text-white text-xs p-1 truncate text-center">
|
|
241
|
-
{media.file_name}
|
|
242
|
-
</p>
|
|
243
|
-
</button>
|
|
244
|
-
);
|
|
245
|
-
})}
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
206
|
+
<div className="flex flex-wrap gap-3 overflow-y-auto min-h-0 pr-2 pb-2">
|
|
207
|
+
{filtered.map((media: Media) => {
|
|
208
|
+
const previewPath = resolveMediaPreviewPath(media);
|
|
209
|
+
const previewSrc = previewPath ? resolveMediaPreviewSrc(previewPath) : null;
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<button
|
|
213
|
+
key={media.id}
|
|
214
|
+
type="button"
|
|
215
|
+
className="relative aspect-square border rounded-md overflow-hidden group focus:outline-none focus:ring-2 focus:ring-primary min-w-0 w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6"
|
|
216
|
+
onClick={() => handleSelect(media)}
|
|
217
|
+
>
|
|
218
|
+
{media.file_type?.startsWith("image/") && previewSrc ? (
|
|
219
|
+
<>
|
|
220
|
+
<Image
|
|
221
|
+
src={previewSrc}
|
|
222
|
+
alt={media.description || media.file_name || "Media library image"}
|
|
223
|
+
fill
|
|
224
|
+
className="absolute inset-0 w-full h-full object-cover"
|
|
225
|
+
placeholder={media.blur_data_url ? "blur" : "empty"}
|
|
226
|
+
blurDataURL={media.blur_data_url || undefined}
|
|
227
|
+
sizes="(max-width: 639px) 33vw, (max-width: 767px) 25vw, (max-width: 1023px) 20vw, 17vw"
|
|
228
|
+
/>
|
|
229
|
+
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 group-focus:opacity-100 transition-opacity flex items-center justify-center">
|
|
230
|
+
<CheckCircle className="h-8 w-8 text-white" />
|
|
231
|
+
</div>
|
|
232
|
+
</>
|
|
233
|
+
) : (
|
|
234
|
+
<div className="absolute inset-0 flex items-center justify-center text-xs text-muted-foreground p-1 text-center">
|
|
235
|
+
Preview unavailable
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
<p className="absolute bottom-0 left-0 right-0 bg-black/50 text-white text-xs p-1 truncate text-center">
|
|
239
|
+
{media.file_name}
|
|
240
|
+
</p>
|
|
241
|
+
</button>
|
|
242
|
+
);
|
|
243
|
+
})}
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
248
246
|
</div>
|
|
249
|
-
<DialogFooter className="mt-auto pt-4">
|
|
250
|
-
<DialogClose asChild>
|
|
251
|
-
<Button type="button" variant="outline">
|
|
252
|
-
Close
|
|
253
|
-
</Button>
|
|
254
|
-
</DialogClose>
|
|
255
|
-
</DialogFooter>
|
|
256
247
|
</DialogContent>
|
|
257
248
|
</Dialog>
|
|
258
249
|
);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// app/cms/media/components/MediaUploadForm.tsx
|
|
2
|
-
"use client";
|
|
3
|
-
|
|
4
|
-
import React, { useState, useRef, useTransition, useEffect } from "react";
|
|
5
|
-
import Image from "next/image";
|
|
6
|
-
import { useRouter } from "next/navigation";
|
|
7
|
-
import { Button } from "@nextblock-cms/ui";
|
|
8
|
-
import { Spinner, Alert, AlertDescription } from "@nextblock-cms/ui";
|
|
9
|
-
import { Input } from "@nextblock-cms/ui";
|
|
10
|
-
import { Label } from "@nextblock-cms/ui";
|
|
11
|
-
import { Progress } from "@nextblock-cms/ui"; // Assuming you have this shadcn/ui component
|
|
12
|
-
import { UploadCloud, XCircle, CheckCircle2 } from "lucide-react";
|
|
13
|
-
import type { Database } from "@nextblock-cms/db"; // Import Media type
|
|
14
|
-
|
|
15
|
-
type Media = Database['public']['Tables']['media']['Row'];
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import React, { useState, useRef, useTransition, useEffect } from "react";
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
7
|
+
import { Button } from "@nextblock-cms/ui";
|
|
8
|
+
import { Spinner, Alert, AlertDescription } from "@nextblock-cms/ui";
|
|
9
|
+
import { Input } from "@nextblock-cms/ui";
|
|
10
|
+
import { Label } from "@nextblock-cms/ui";
|
|
11
|
+
import { Progress } from "@nextblock-cms/ui"; // Assuming you have this shadcn/ui component
|
|
12
|
+
import { UploadCloud, XCircle, CheckCircle2 } from "lucide-react";
|
|
13
|
+
import type { Database } from "@nextblock-cms/db"; // Import Media type
|
|
14
|
+
|
|
15
|
+
type Media = Database['public']['Tables']['media']['Row'];
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
interface MediaUploadFormProps {
|
|
@@ -25,10 +25,10 @@ interface MediaUploadFormProps {
|
|
|
25
25
|
|
|
26
26
|
import { useUploadFolder } from "../UploadFolderContext";
|
|
27
27
|
|
|
28
|
-
export default function MediaUploadForm({ onUploadSuccess, returnJustData, defaultFolder }: MediaUploadFormProps) {
|
|
29
|
-
const router = useRouter();
|
|
30
|
-
const [isPending, startTransition] = useTransition();
|
|
31
|
-
const [file, setFile] = useState<File | null>(null);
|
|
28
|
+
export default function MediaUploadForm({ onUploadSuccess, returnJustData, defaultFolder }: MediaUploadFormProps) {
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const [isPending, startTransition] = useTransition();
|
|
31
|
+
const [file, setFile] = useState<File | null>(null);
|
|
32
32
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null); // For image preview
|
|
33
33
|
const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null); // For image dimensions
|
|
34
34
|
const [uploadProgress, setUploadProgress] = useState(0);
|
|
@@ -228,86 +228,86 @@ export default function MediaUploadForm({ onUploadSuccess, returnJustData, defau
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// 4. Record media in Supabase with variant info
|
|
231
|
-
const finalMediaPayload = {
|
|
232
|
-
...mediaDataPayload,
|
|
233
|
-
r2OriginalKey: objectKey,
|
|
234
|
-
r2Variants: processData.processedVariants || [],
|
|
235
|
-
originalImageDetails: processData.originalImage,
|
|
236
|
-
blurDataUrl: processData.blurDataURL || null,
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const recordResponse = await fetch('/api/media/record', {
|
|
240
|
-
method: 'POST',
|
|
241
|
-
headers: {
|
|
242
|
-
'Content-Type': 'application/json',
|
|
243
|
-
},
|
|
244
|
-
body: JSON.stringify(finalMediaPayload),
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const recordResult = await recordResponse.json();
|
|
248
|
-
|
|
249
|
-
if (!recordResponse.ok) {
|
|
250
|
-
throw new Error(recordResult?.error || 'Media record action failed.');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const handleSuccess = (newMedia?: Media) => {
|
|
254
|
-
setUploadStatus("success");
|
|
255
|
-
if (newMedia) onUploadSuccess?.(newMedia);
|
|
256
|
-
// Reset form state
|
|
257
|
-
resetFileSelection();
|
|
258
|
-
if (processingStatus !== "processed_error") {
|
|
259
|
-
setProcessingStatus("idle");
|
|
260
|
-
}
|
|
261
|
-
if (!returnJustData) {
|
|
262
|
-
router.refresh();
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
if (recordResult?.success && recordResult.data) {
|
|
267
|
-
handleSuccess(recordResult.data);
|
|
268
|
-
} else {
|
|
269
|
-
throw new Error(recordResult?.error || "Media record action failed.");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
} catch (err: unknown) {
|
|
273
|
-
console.error("Upload process error:", err);
|
|
274
|
-
setUploadStatus("error");
|
|
275
|
-
setErrorMessage(err instanceof Error ? err.message : "An unknown error occurred during upload.");
|
|
276
|
-
setUploadProgress(0);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const handleFolderKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
282
|
-
if (event.key !== "Enter") {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
event.preventDefault();
|
|
287
|
-
event.stopPropagation();
|
|
288
|
-
|
|
289
|
-
if (file) {
|
|
290
|
-
void performUpload();
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<div className="p-6 border rounded-lg shadow-sm bg-card mb-6">
|
|
296
|
-
<div role="group" aria-label="Upload new media" className="space-y-4">
|
|
297
|
-
<div>
|
|
298
|
-
<Label htmlFor="media-file" className="text-base font-medium">Upload New Media</Label>
|
|
299
|
-
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
231
|
+
const finalMediaPayload = {
|
|
232
|
+
...mediaDataPayload,
|
|
233
|
+
r2OriginalKey: objectKey,
|
|
234
|
+
r2Variants: processData.processedVariants || [],
|
|
235
|
+
originalImageDetails: processData.originalImage,
|
|
236
|
+
blurDataUrl: processData.blurDataURL || null,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const recordResponse = await fetch('/api/media/record', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify(finalMediaPayload),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const recordResult = await recordResponse.json();
|
|
248
|
+
|
|
249
|
+
if (!recordResponse.ok) {
|
|
250
|
+
throw new Error(recordResult?.error || 'Media record action failed.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const handleSuccess = (newMedia?: Media) => {
|
|
254
|
+
setUploadStatus("success");
|
|
255
|
+
if (newMedia) onUploadSuccess?.(newMedia);
|
|
256
|
+
// Reset form state
|
|
257
|
+
resetFileSelection();
|
|
258
|
+
if (processingStatus !== "processed_error") {
|
|
259
|
+
setProcessingStatus("idle");
|
|
260
|
+
}
|
|
261
|
+
if (!returnJustData) {
|
|
262
|
+
router.refresh();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (recordResult?.success && recordResult.data) {
|
|
267
|
+
handleSuccess(recordResult.data);
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error(recordResult?.error || "Media record action failed.");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} catch (err: unknown) {
|
|
273
|
+
console.error("Upload process error:", err);
|
|
274
|
+
setUploadStatus("error");
|
|
275
|
+
setErrorMessage(err instanceof Error ? err.message : "An unknown error occurred during upload.");
|
|
276
|
+
setUploadProgress(0);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const handleFolderKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
282
|
+
if (event.key !== "Enter") {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
event.stopPropagation();
|
|
288
|
+
|
|
289
|
+
if (file) {
|
|
290
|
+
void performUpload();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<div className="p-6 border rounded-lg shadow-sm bg-card mb-6">
|
|
296
|
+
<div role="group" aria-label="Upload new media" className="space-y-4">
|
|
297
|
+
<div>
|
|
298
|
+
<Label htmlFor="media-file" className="text-base font-medium">Upload New Media</Label>
|
|
299
|
+
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
300
300
|
<div>
|
|
301
301
|
<Label htmlFor="media-folder" className="text-sm">Folder (e.g., uploads/images/)</Label>
|
|
302
302
|
<Input
|
|
303
|
-
id="media-folder"
|
|
304
|
-
placeholder="uploads/"
|
|
305
|
-
value={folder}
|
|
306
|
-
onChange={(e) => setFolder(e.target.value)}
|
|
307
|
-
onKeyDown={handleFolderKeyDown}
|
|
308
|
-
/>
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
303
|
+
id="media-folder"
|
|
304
|
+
placeholder="uploads/"
|
|
305
|
+
value={folder}
|
|
306
|
+
onChange={(e) => setFolder(e.target.value)}
|
|
307
|
+
onKeyDown={handleFolderKeyDown}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
311
|
<div className="mt-2 flex items-center justify-center w-full">
|
|
312
312
|
<label
|
|
313
313
|
htmlFor="media-file-input"
|
|
@@ -377,9 +377,9 @@ export default function MediaUploadForm({ onUploadSuccess, returnJustData, defau
|
|
|
377
377
|
</>
|
|
378
378
|
) : (
|
|
379
379
|
"Upload File"
|
|
380
|
-
)}
|
|
381
|
-
</Button>
|
|
382
|
-
</div>
|
|
383
|
-
</div>
|
|
384
|
-
);
|
|
385
|
-
}
|
|
380
|
+
)}
|
|
381
|
+
</Button>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
@@ -176,9 +176,9 @@ interface NavigationMenuDndProps {
|
|
|
176
176
|
initialItems: NavigationItem[];
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
export default function NavigationMenuDnd({ menuKey, languageCode, initialItems }: NavigationMenuDndProps) {
|
|
180
|
-
void menuKey;
|
|
181
|
-
// Prevent SSR/hydration mismatches from dnd-kit by rendering on client only
|
|
179
|
+
export default function NavigationMenuDnd({ menuKey, languageCode, initialItems }: NavigationMenuDndProps) {
|
|
180
|
+
void menuKey;
|
|
181
|
+
// Prevent SSR/hydration mismatches from dnd-kit by rendering on client only
|
|
182
182
|
// Keep hooks order stable; move early return after hooks
|
|
183
183
|
const [isMounted, setIsMounted] = useState(false);
|
|
184
184
|
useEffect(() => { setIsMounted(true); }, []);
|
|
@@ -27,13 +27,13 @@ import {
|
|
|
27
27
|
DropdownMenuSeparator,
|
|
28
28
|
} from "@nextblock-cms/ui";
|
|
29
29
|
// Server action `deletePage` is used by DeletePageButtonClient
|
|
30
|
-
import type { Database } from "@nextblock-cms/db";
|
|
31
|
-
import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
|
|
32
|
-
|
|
33
|
-
type Page = Database["public"]["Tables"]["pages"]["Row"];
|
|
34
|
-
import LanguageFilterSelect from "../components/LanguageFilterSelect";
|
|
35
|
-
import DeletePageButtonClient from "./components/DeletePageButtonClient"; // Import the client component
|
|
36
|
-
import { ContentTransferControls } from "../import-export/ContentTransferControls";
|
|
30
|
+
import type { Database } from "@nextblock-cms/db";
|
|
31
|
+
import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
|
|
32
|
+
|
|
33
|
+
type Page = Database["public"]["Tables"]["pages"]["Row"];
|
|
34
|
+
import LanguageFilterSelect from "../components/LanguageFilterSelect";
|
|
35
|
+
import DeletePageButtonClient from "./components/DeletePageButtonClient"; // Import the client component
|
|
36
|
+
import { ContentTransferControls } from "../import-export/ContentTransferControls";
|
|
37
37
|
|
|
38
38
|
async function getPagesWithDetails(
|
|
39
39
|
filterLanguageId?: number
|
|
@@ -96,16 +96,17 @@ export default async function CmsPagesListPage(props: CmsPagesListPageProps) {
|
|
|
96
96
|
return (
|
|
97
97
|
<div className="w-full">
|
|
98
98
|
<div className="flex justify-between items-center mb-6 flex-wrap gap-4">
|
|
99
|
-
<h1 className="text-2xl font-semibold">Manage Pages</h1>
|
|
100
|
-
<div className="flex items-center gap-3">
|
|
101
|
-
<ContentTransferControls
|
|
102
|
-
contentType="pages"
|
|
103
|
-
label="Pages"
|
|
104
|
-
languageId={filterLangId}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
<h1 className="text-2xl font-semibold">Manage Pages</h1>
|
|
100
|
+
<div className="flex items-center gap-3">
|
|
101
|
+
<ContentTransferControls
|
|
102
|
+
contentType="pages"
|
|
103
|
+
label="Pages"
|
|
104
|
+
languageId={filterLangId}
|
|
105
|
+
hasContent={pagesWithDetails.length > 0}
|
|
106
|
+
/>
|
|
107
|
+
<LanguageFilterSelect
|
|
108
|
+
allLanguages={allLanguages}
|
|
109
|
+
currentFilterLangId={filterLangId}
|
|
109
110
|
basePath="/cms/pages"
|
|
110
111
|
/>
|
|
111
112
|
<Button variant="default" asChild>
|