create-nextblock 0.12.8 → 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/next.config.js +6 -0
- 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
|
@@ -18,10 +18,10 @@ export type ImageBlockContent = {
|
|
|
18
18
|
height: number | null;
|
|
19
19
|
blur_data_url: string | null;
|
|
20
20
|
};
|
|
21
|
-
import { ImageIcon, X as XIcon } from 'lucide-react';
|
|
22
|
-
import MediaPickerDialog from "../../media/components/MediaPickerDialog"; // Import the upload form
|
|
23
|
-
import { BlockEditorProps } from '../components/BlockEditorModal';
|
|
24
|
-
import { resolveMediaUrl } from '../../../../lib/media/resolveMediaUrl';
|
|
21
|
+
import { ImageIcon, X as XIcon } from 'lucide-react';
|
|
22
|
+
import MediaPickerDialog from "../../media/components/MediaPickerDialog"; // Import the upload form
|
|
23
|
+
import { BlockEditorProps } from '../components/BlockEditorModal';
|
|
24
|
+
import { resolveMediaUrl } from '../../../../lib/media/resolveMediaUrl';
|
|
25
25
|
|
|
26
26
|
export default function ImageBlockEditor({ content, onChange }: BlockEditorProps<Partial<ImageBlockContent>>) {
|
|
27
27
|
const [selectedMediaObjectKey, setSelectedMediaObjectKey] = useState<string | null | undefined>(content.object_key);
|
|
@@ -83,22 +83,22 @@ export default function ImageBlockEditor({ content, onChange }: BlockEditorProps
|
|
|
83
83
|
setSelectedMediaObjectKey(null);
|
|
84
84
|
onChange({ media_id: null, object_key: null, alt_text: "", caption: "", width: null, height: null, blur_data_url: null });
|
|
85
85
|
};
|
|
86
|
-
|
|
87
|
-
const displayObjectKey = content.object_key || selectedMediaObjectKey;
|
|
88
|
-
const displayImageUrl = resolveMediaUrl(displayObjectKey);
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div className="space-y-3 p-3 border-t mt-2">
|
|
92
|
-
<Label>Image</Label>
|
|
93
|
-
<div className="mt-1 p-3 border rounded-md bg-muted/30 min-h-[120px] flex flex-col items-center justify-center">
|
|
94
|
-
{isLoadingMediaDetails && <p>Loading image details...</p>}
|
|
95
|
-
{!isLoadingMediaDetails && displayImageUrl && typeof content.width === 'number' && typeof content.height === 'number' && content.width > 0 && content.height > 0 ? (
|
|
96
|
-
<div className="relative group inline-block" style={{ maxWidth: content.width, maxHeight: 200 }}> {/* Max height for editor preview consistency */}
|
|
97
|
-
<Image
|
|
98
|
-
src={displayImageUrl}
|
|
99
|
-
alt={content.alt_text || "Selected image"}
|
|
100
|
-
width={content.width}
|
|
101
|
-
height={content.height}
|
|
86
|
+
|
|
87
|
+
const displayObjectKey = content.object_key || selectedMediaObjectKey;
|
|
88
|
+
const displayImageUrl = resolveMediaUrl(displayObjectKey);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="space-y-3 p-3 border-t mt-2">
|
|
92
|
+
<Label>Image</Label>
|
|
93
|
+
<div className="mt-1 p-3 border rounded-md bg-muted/30 min-h-[120px] flex flex-col items-center justify-center">
|
|
94
|
+
{isLoadingMediaDetails && <p>Loading image details...</p>}
|
|
95
|
+
{!isLoadingMediaDetails && displayImageUrl && typeof content.width === 'number' && typeof content.height === 'number' && content.width > 0 && content.height > 0 ? (
|
|
96
|
+
<div className="relative group inline-block" style={{ maxWidth: content.width, maxHeight: 200 }}> {/* Max height for editor preview consistency */}
|
|
97
|
+
<Image
|
|
98
|
+
src={displayImageUrl}
|
|
99
|
+
alt={content.alt_text || "Selected image"}
|
|
100
|
+
width={content.width}
|
|
101
|
+
height={content.height}
|
|
102
102
|
className="rounded-md object-contain" // Removed max-h-40, relying on width/height and parent max-height
|
|
103
103
|
style={{ maxHeight: '200px' }} // Ensure image does not exceed this height in preview
|
|
104
104
|
placeholder={content.blur_data_url ? "blur" : "empty"}
|
|
@@ -108,15 +108,15 @@ export default function ImageBlockEditor({ content, onChange }: BlockEditorProps
|
|
|
108
108
|
type="button" variant="destructive" size="icon"
|
|
109
109
|
className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity h-6 w-6"
|
|
110
110
|
onClick={handleRemoveImage} title="Remove Image"
|
|
111
|
-
> <XIcon className="h-3 w-3" /> </Button>
|
|
112
|
-
</div>
|
|
113
|
-
) : !isLoadingMediaDetails && displayImageUrl ? ( // Fallback if width/height are missing but key exists
|
|
114
|
-
<div className="relative group inline-block">
|
|
115
|
-
<Image
|
|
116
|
-
src={displayImageUrl}
|
|
117
|
-
alt={content.alt_text || "Selected image"}
|
|
118
|
-
width={300}
|
|
119
|
-
height={200}
|
|
111
|
+
> <XIcon className="h-3 w-3" /> </Button>
|
|
112
|
+
</div>
|
|
113
|
+
) : !isLoadingMediaDetails && displayImageUrl ? ( // Fallback if width/height are missing but key exists
|
|
114
|
+
<div className="relative group inline-block">
|
|
115
|
+
<Image
|
|
116
|
+
src={displayImageUrl}
|
|
117
|
+
alt={content.alt_text || "Selected image"}
|
|
118
|
+
width={300}
|
|
119
|
+
height={200}
|
|
120
120
|
className="rounded-md object-contain max-h-40 block"
|
|
121
121
|
style={{ maxHeight: '200px' }}
|
|
122
122
|
/>
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
// app/cms/blocks/editors/TextBlockEditor.tsx
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
-
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
|
-
import dynamic from 'next/dynamic';
|
|
6
|
-
import type { Extensions } from '@tiptap/core';
|
|
7
|
-
import MediaPickerDialog from '../../media/components/MediaPickerDialog';
|
|
4
|
+
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import dynamic from 'next/dynamic';
|
|
6
|
+
import type { Extensions } from '@tiptap/core';
|
|
7
|
+
import MediaPickerDialog from '../../media/components/MediaPickerDialog';
|
|
8
8
|
import { Label } from '@nextblock-cms/ui';
|
|
9
9
|
import { BlockEditorProps } from '../components/BlockEditorModal';
|
|
10
|
-
import { resolveMediaUrl } from '../../../../lib/media/resolveMediaUrl';
|
|
11
|
-
import { useCortexAiActive } from '../../components/CortexAiActiveContext';
|
|
12
|
-
import {
|
|
13
|
-
createDynamicCustomBlockExtensions,
|
|
14
|
-
type DynamicCustomBlockEditorDefinition,
|
|
15
|
-
} from '../../../../lib/editor/dynamic-extensions';
|
|
10
|
+
import { resolveMediaUrl } from '../../../../lib/media/resolveMediaUrl';
|
|
11
|
+
import { useCortexAiActive } from '../../components/CortexAiActiveContext';
|
|
12
|
+
import {
|
|
13
|
+
createDynamicCustomBlockExtensions,
|
|
14
|
+
type DynamicCustomBlockEditorDefinition,
|
|
15
|
+
} from '../../../../lib/editor/dynamic-extensions';
|
|
16
16
|
|
|
17
17
|
// Props expected by NotionEditor
|
|
18
18
|
type NotionEditorProps = {
|
|
19
19
|
content: string;
|
|
20
20
|
onChange: (html: string) => void;
|
|
21
|
-
openImagePicker?: () => Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>;
|
|
22
|
-
className?: string;
|
|
23
|
-
showAiPrompt?: boolean;
|
|
24
|
-
dynamicExtensions?: Extensions;
|
|
25
|
-
};
|
|
21
|
+
openImagePicker?: () => Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>;
|
|
22
|
+
className?: string;
|
|
23
|
+
showAiPrompt?: boolean;
|
|
24
|
+
dynamicExtensions?: Extensions;
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
// Use the alias that resolves in your repo; if you mapped @nextblock-cms/editor, swap it here.
|
|
28
28
|
const NotionEditor = dynamic<NotionEditorProps>(
|
|
@@ -40,91 +40,91 @@ export default function TextBlockEditor({
|
|
|
40
40
|
className,
|
|
41
41
|
}: BlockEditorProps<Partial<TextBlockContent>>) {
|
|
42
42
|
const labelId = useId();
|
|
43
|
-
const [pickerOpen, setPickerOpen] = useState(false);
|
|
44
|
-
const [dynamicDefinitions, setDynamicDefinitions] = useState<DynamicCustomBlockEditorDefinition[]>([]);
|
|
45
|
-
const [dynamicDefinitionsLoaded, setDynamicDefinitionsLoaded] = useState(false);
|
|
46
|
-
const isCortexAiActive = useCortexAiActive();
|
|
47
|
-
const resolverRef = useRef<null | ((v: any) => void)>(null);
|
|
43
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
44
|
+
const [dynamicDefinitions, setDynamicDefinitions] = useState<DynamicCustomBlockEditorDefinition[]>([]);
|
|
45
|
+
const [dynamicDefinitionsLoaded, setDynamicDefinitionsLoaded] = useState(false);
|
|
46
|
+
const isCortexAiActive = useCortexAiActive();
|
|
47
|
+
const resolverRef = useRef<null | ((v: any) => void)>(null);
|
|
48
48
|
const openImagePicker = useCallback(() => {
|
|
49
49
|
setPickerOpen(true);
|
|
50
50
|
return new Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>((resolve) => {
|
|
51
51
|
resolverRef.current = resolve;
|
|
52
|
-
});
|
|
53
|
-
}, []);
|
|
54
|
-
const dynamicExtensions = useMemo(
|
|
55
|
-
() => createDynamicCustomBlockExtensions(dynamicDefinitions),
|
|
56
|
-
[dynamicDefinitions]
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
let isActive = true;
|
|
61
|
-
|
|
62
|
-
async function loadDynamicDefinitions() {
|
|
63
|
-
try {
|
|
64
|
-
const response = await fetch('/api/custom-blocks/editor-definitions', {
|
|
65
|
-
cache: 'no-store',
|
|
66
|
-
method: 'GET',
|
|
67
|
-
});
|
|
68
|
-
const payload = (await response.json()) as {
|
|
69
|
-
definitions?: DynamicCustomBlockEditorDefinition[];
|
|
70
|
-
error?: string;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (!isActive) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!response.ok) {
|
|
78
|
-
console.error(
|
|
79
|
-
'[TextBlockEditor] Failed to load custom block editor definitions:',
|
|
80
|
-
payload.error
|
|
81
|
-
);
|
|
82
|
-
setDynamicDefinitions([]);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
setDynamicDefinitions(payload.definitions ?? []);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (isActive) {
|
|
89
|
-
console.error('[TextBlockEditor] Failed to load custom block editor definitions:', error);
|
|
90
|
-
setDynamicDefinitions([]);
|
|
91
|
-
}
|
|
92
|
-
} finally {
|
|
93
|
-
if (isActive) {
|
|
94
|
-
setDynamicDefinitionsLoaded(true);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
void loadDynamicDefinitions();
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
isActive = false;
|
|
103
|
-
};
|
|
104
|
-
}, []);
|
|
105
|
-
|
|
106
|
-
return (
|
|
52
|
+
});
|
|
53
|
+
}, []);
|
|
54
|
+
const dynamicExtensions = useMemo(
|
|
55
|
+
() => createDynamicCustomBlockExtensions(dynamicDefinitions),
|
|
56
|
+
[dynamicDefinitions]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
let isActive = true;
|
|
61
|
+
|
|
62
|
+
async function loadDynamicDefinitions() {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch('/api/custom-blocks/editor-definitions', {
|
|
65
|
+
cache: 'no-store',
|
|
66
|
+
method: 'GET',
|
|
67
|
+
});
|
|
68
|
+
const payload = (await response.json()) as {
|
|
69
|
+
definitions?: DynamicCustomBlockEditorDefinition[];
|
|
70
|
+
error?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!isActive) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
console.error(
|
|
79
|
+
'[TextBlockEditor] Failed to load custom block editor definitions:',
|
|
80
|
+
payload.error
|
|
81
|
+
);
|
|
82
|
+
setDynamicDefinitions([]);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setDynamicDefinitions(payload.definitions ?? []);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (isActive) {
|
|
89
|
+
console.error('[TextBlockEditor] Failed to load custom block editor definitions:', error);
|
|
90
|
+
setDynamicDefinitions([]);
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
if (isActive) {
|
|
94
|
+
setDynamicDefinitionsLoaded(true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void loadDynamicDefinitions();
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
isActive = false;
|
|
103
|
+
};
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
107
|
<div className="h-full flex flex-col">
|
|
108
108
|
<Label htmlFor={labelId} className="sr-only">
|
|
109
109
|
Text Content
|
|
110
110
|
</Label>
|
|
111
111
|
|
|
112
112
|
<div id={labelId} role="group" aria-labelledby={labelId} className="flex-1 min-h-0 flex flex-col">
|
|
113
|
-
{dynamicDefinitionsLoaded ? (
|
|
114
|
-
<NotionEditor
|
|
115
|
-
key={dynamicDefinitions.map((definition) => definition.id).join('|') || 'static'}
|
|
116
|
-
content={content?.html_content ?? ''}
|
|
117
|
-
dynamicExtensions={dynamicExtensions}
|
|
118
|
-
onChange={(html) => onChange({ html_content: html })}
|
|
119
|
-
openImagePicker={openImagePicker}
|
|
120
|
-
className={className}
|
|
121
|
-
showAiPrompt={isCortexAiActive}
|
|
122
|
-
/>
|
|
123
|
-
) : (
|
|
124
|
-
<div className="flex min-h-[500px] flex-1 items-center justify-center rounded-lg border bg-background text-sm text-muted-foreground">
|
|
125
|
-
Loading editor...
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
113
|
+
{dynamicDefinitionsLoaded ? (
|
|
114
|
+
<NotionEditor
|
|
115
|
+
key={dynamicDefinitions.map((definition) => definition.id).join('|') || 'static'}
|
|
116
|
+
content={content?.html_content ?? ''}
|
|
117
|
+
dynamicExtensions={dynamicExtensions}
|
|
118
|
+
onChange={(html) => onChange({ html_content: html })}
|
|
119
|
+
openImagePicker={openImagePicker}
|
|
120
|
+
className={className}
|
|
121
|
+
showAiPrompt={isCortexAiActive}
|
|
122
|
+
/>
|
|
123
|
+
) : (
|
|
124
|
+
<div className="flex min-h-[500px] flex-1 items-center justify-center rounded-lg border bg-background text-sm text-muted-foreground">
|
|
125
|
+
Loading editor...
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
128
|
|
|
129
129
|
{/* Hidden controlled MediaPickerDialog for image selection */}
|
|
130
130
|
<div className="sr-only" aria-hidden>
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import { Check, ExternalLink, Github, Loader2 } from 'lucide-react';
|
|
6
|
-
import { Button } from '@nextblock-cms/ui';
|
|
7
|
-
import { startGithubConnect, pollGithubConnect } from './github-connect-actions';
|
|
8
|
-
|
|
9
|
-
type Phase = 'idle' | 'starting' | 'awaiting' | 'installed' | 'error';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* One-click "Connect GitHub" via the OAuth device flow. On authorization, the server
|
|
13
|
-
* installs the upstream-sync workflow into the repo (the file Vercel's clone strips).
|
|
14
|
-
* No PAT, no env config — the public client id is baked into the app.
|
|
15
|
-
*/
|
|
16
|
-
export default function ConnectGitHubButton() {
|
|
17
|
-
const router = useRouter();
|
|
18
|
-
const [phase, setPhase] = useState<Phase>('idle');
|
|
19
|
-
const [userCode, setUserCode] = useState('');
|
|
20
|
-
const [verificationUri, setVerificationUri] = useState('https://github.com/login/device');
|
|
21
|
-
const [error, setError] = useState('');
|
|
22
|
-
|
|
23
|
-
// Cancel any in-flight polling when the component unmounts.
|
|
24
|
-
const activeRef = useRef(false);
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
activeRef.current = true;
|
|
27
|
-
return () => {
|
|
28
|
-
activeRef.current = false;
|
|
29
|
-
};
|
|
30
|
-
}, []);
|
|
31
|
-
|
|
32
|
-
const poll = useCallback(async (intervalMs: number) => {
|
|
33
|
-
if (!activeRef.current) return;
|
|
34
|
-
const result = await pollGithubConnect();
|
|
35
|
-
if (!activeRef.current) return;
|
|
36
|
-
|
|
37
|
-
if (result.status === 'installed') {
|
|
38
|
-
setPhase('installed');
|
|
39
|
-
// Re-render the dashboard so the onboarding step picks up the now-active workflow
|
|
40
|
-
// (the step then flips to done and this control is replaced).
|
|
41
|
-
router.refresh();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (result.status === 'error') {
|
|
45
|
-
setError(result.error);
|
|
46
|
-
setPhase('error');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
// pending — back off a little on slow_down, then poll again.
|
|
50
|
-
const next = result.slowDown ? intervalMs + 5000 : intervalMs;
|
|
51
|
-
setTimeout(() => void poll(next), next);
|
|
52
|
-
}, [router]);
|
|
53
|
-
|
|
54
|
-
const connect = useCallback(async () => {
|
|
55
|
-
setError('');
|
|
56
|
-
setPhase('starting');
|
|
57
|
-
const res = await startGithubConnect();
|
|
58
|
-
if (!activeRef.current) return;
|
|
59
|
-
if (!res.ok || !res.userCode) {
|
|
60
|
-
setError(res.error ?? 'Could not start GitHub connect.');
|
|
61
|
-
setPhase('error');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
setUserCode(res.userCode);
|
|
65
|
-
if (res.verificationUri) setVerificationUri(res.verificationUri);
|
|
66
|
-
setPhase('awaiting');
|
|
67
|
-
const intervalMs = Math.max((res.interval ?? 5) + 1, 5) * 1000;
|
|
68
|
-
setTimeout(() => void poll(intervalMs), intervalMs);
|
|
69
|
-
}, [poll]);
|
|
70
|
-
|
|
71
|
-
if (phase === 'installed') {
|
|
72
|
-
return (
|
|
73
|
-
<div className="flex items-center gap-1.5 text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
|
74
|
-
<Check className="h-4 w-4" />
|
|
75
|
-
Connected — workflow installed
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (phase === 'awaiting') {
|
|
81
|
-
return (
|
|
82
|
-
<div className="flex flex-col items-end gap-1.5 text-right">
|
|
83
|
-
<Button asChild size="sm" variant="outline">
|
|
84
|
-
<a href={verificationUri} target="_blank" rel="noopener noreferrer">
|
|
85
|
-
Authorize on GitHub
|
|
86
|
-
<ExternalLink className="ml-1 h-3.5 w-3.5" />
|
|
87
|
-
</a>
|
|
88
|
-
</Button>
|
|
89
|
-
<p className="text-xs text-muted-foreground">
|
|
90
|
-
Enter code{' '}
|
|
91
|
-
<span className="font-mono font-semibold tracking-wider text-foreground">{userCode}</span>
|
|
92
|
-
</p>
|
|
93
|
-
<p className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
94
|
-
<Loader2 className="h-3 w-3 animate-spin" />
|
|
95
|
-
Waiting for authorization…
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<div className="flex flex-col items-end gap-1">
|
|
103
|
-
<Button
|
|
104
|
-
size="sm"
|
|
105
|
-
variant="outline"
|
|
106
|
-
onClick={() => void connect()}
|
|
107
|
-
disabled={phase === 'starting'}
|
|
108
|
-
className="shrink-0"
|
|
109
|
-
>
|
|
110
|
-
{phase === 'starting' ? (
|
|
111
|
-
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
|
|
112
|
-
) : (
|
|
113
|
-
<Github className="mr-1 h-3.5 w-3.5" />
|
|
114
|
-
)}
|
|
115
|
-
{phase === 'starting' ? 'Starting…' : 'Connect GitHub'}
|
|
116
|
-
</Button>
|
|
117
|
-
{phase === 'error' && (
|
|
118
|
-
<p className="max-w-[16rem] text-right text-xs text-red-600 dark:text-red-400">{error}</p>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { Check, ExternalLink, Github, Loader2 } from 'lucide-react';
|
|
6
|
+
import { Button } from '@nextblock-cms/ui';
|
|
7
|
+
import { startGithubConnect, pollGithubConnect } from './github-connect-actions';
|
|
8
|
+
|
|
9
|
+
type Phase = 'idle' | 'starting' | 'awaiting' | 'installed' | 'error';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One-click "Connect GitHub" via the OAuth device flow. On authorization, the server
|
|
13
|
+
* installs the upstream-sync workflow into the repo (the file Vercel's clone strips).
|
|
14
|
+
* No PAT, no env config — the public client id is baked into the app.
|
|
15
|
+
*/
|
|
16
|
+
export default function ConnectGitHubButton() {
|
|
17
|
+
const router = useRouter();
|
|
18
|
+
const [phase, setPhase] = useState<Phase>('idle');
|
|
19
|
+
const [userCode, setUserCode] = useState('');
|
|
20
|
+
const [verificationUri, setVerificationUri] = useState('https://github.com/login/device');
|
|
21
|
+
const [error, setError] = useState('');
|
|
22
|
+
|
|
23
|
+
// Cancel any in-flight polling when the component unmounts.
|
|
24
|
+
const activeRef = useRef(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
activeRef.current = true;
|
|
27
|
+
return () => {
|
|
28
|
+
activeRef.current = false;
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const poll = useCallback(async (intervalMs: number) => {
|
|
33
|
+
if (!activeRef.current) return;
|
|
34
|
+
const result = await pollGithubConnect();
|
|
35
|
+
if (!activeRef.current) return;
|
|
36
|
+
|
|
37
|
+
if (result.status === 'installed') {
|
|
38
|
+
setPhase('installed');
|
|
39
|
+
// Re-render the dashboard so the onboarding step picks up the now-active workflow
|
|
40
|
+
// (the step then flips to done and this control is replaced).
|
|
41
|
+
router.refresh();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (result.status === 'error') {
|
|
45
|
+
setError(result.error);
|
|
46
|
+
setPhase('error');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// pending — back off a little on slow_down, then poll again.
|
|
50
|
+
const next = result.slowDown ? intervalMs + 5000 : intervalMs;
|
|
51
|
+
setTimeout(() => void poll(next), next);
|
|
52
|
+
}, [router]);
|
|
53
|
+
|
|
54
|
+
const connect = useCallback(async () => {
|
|
55
|
+
setError('');
|
|
56
|
+
setPhase('starting');
|
|
57
|
+
const res = await startGithubConnect();
|
|
58
|
+
if (!activeRef.current) return;
|
|
59
|
+
if (!res.ok || !res.userCode) {
|
|
60
|
+
setError(res.error ?? 'Could not start GitHub connect.');
|
|
61
|
+
setPhase('error');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setUserCode(res.userCode);
|
|
65
|
+
if (res.verificationUri) setVerificationUri(res.verificationUri);
|
|
66
|
+
setPhase('awaiting');
|
|
67
|
+
const intervalMs = Math.max((res.interval ?? 5) + 1, 5) * 1000;
|
|
68
|
+
setTimeout(() => void poll(intervalMs), intervalMs);
|
|
69
|
+
}, [poll]);
|
|
70
|
+
|
|
71
|
+
if (phase === 'installed') {
|
|
72
|
+
return (
|
|
73
|
+
<div className="flex items-center gap-1.5 text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
|
74
|
+
<Check className="h-4 w-4" />
|
|
75
|
+
Connected — workflow installed
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (phase === 'awaiting') {
|
|
81
|
+
return (
|
|
82
|
+
<div className="flex flex-col items-end gap-1.5 text-right">
|
|
83
|
+
<Button asChild size="sm" variant="outline">
|
|
84
|
+
<a href={verificationUri} target="_blank" rel="noopener noreferrer">
|
|
85
|
+
Authorize on GitHub
|
|
86
|
+
<ExternalLink className="ml-1 h-3.5 w-3.5" />
|
|
87
|
+
</a>
|
|
88
|
+
</Button>
|
|
89
|
+
<p className="text-xs text-muted-foreground">
|
|
90
|
+
Enter code{' '}
|
|
91
|
+
<span className="font-mono font-semibold tracking-wider text-foreground">{userCode}</span>
|
|
92
|
+
</p>
|
|
93
|
+
<p className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
94
|
+
<Loader2 className="h-3 w-3 animate-spin" />
|
|
95
|
+
Waiting for authorization…
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex flex-col items-end gap-1">
|
|
103
|
+
<Button
|
|
104
|
+
size="sm"
|
|
105
|
+
variant="outline"
|
|
106
|
+
onClick={() => void connect()}
|
|
107
|
+
disabled={phase === 'starting'}
|
|
108
|
+
className="shrink-0"
|
|
109
|
+
>
|
|
110
|
+
{phase === 'starting' ? (
|
|
111
|
+
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
|
|
112
|
+
) : (
|
|
113
|
+
<Github className="mr-1 h-3.5 w-3.5" />
|
|
114
|
+
)}
|
|
115
|
+
{phase === 'starting' ? 'Starting…' : 'Connect GitHub'}
|
|
116
|
+
</Button>
|
|
117
|
+
{phase === 'error' && (
|
|
118
|
+
<p className="max-w-[16rem] text-right text-xs text-red-600 dark:text-red-400">{error}</p>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
-
|
|
5
|
-
export type CortexAiContentType = "page" | "post" | "product";
|
|
6
|
-
|
|
7
|
-
export type CortexAiPageContext = {
|
|
8
|
-
contentType: CortexAiContentType;
|
|
9
|
-
currentEditor?: {
|
|
10
|
-
blockId?: number | string | null;
|
|
11
|
-
blockType?: string | null;
|
|
12
|
-
field?: string | null;
|
|
13
|
-
};
|
|
14
|
-
entityId: number | string;
|
|
15
|
-
languageId?: number | null;
|
|
16
|
-
slug?: string | null;
|
|
17
|
-
title?: string | null;
|
|
18
|
-
translationGroupId?: string | null;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type CortexAiPageContextValue = {
|
|
22
|
-
pageContext: CortexAiPageContext | null;
|
|
23
|
-
setPageContext: (context: CortexAiPageContext | null) => void;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const CortexAiPageContextStore = createContext<CortexAiPageContextValue | null>(null);
|
|
27
|
-
|
|
28
|
-
export function CortexAiPageContextProvider({ children }: { children: React.ReactNode }) {
|
|
29
|
-
const [pageContext, setPageContext] = useState<CortexAiPageContext | null>(null);
|
|
30
|
-
const value = useMemo(() => ({ pageContext, setPageContext }), [pageContext]);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<CortexAiPageContextStore.Provider value={value}>
|
|
34
|
-
{children}
|
|
35
|
-
</CortexAiPageContextStore.Provider>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function useCortexAiPageContext() {
|
|
40
|
-
return useContext(CortexAiPageContextStore);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function CortexAiPageContextRegistrar({
|
|
44
|
-
context,
|
|
45
|
-
}: {
|
|
46
|
-
context: CortexAiPageContext;
|
|
47
|
-
}) {
|
|
48
|
-
const store = useCortexAiPageContext();
|
|
49
|
-
const setPageContext = store?.setPageContext;
|
|
50
|
-
const serializedContext = JSON.stringify(context);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
setPageContext?.(context);
|
|
54
|
-
return () => setPageContext?.(null);
|
|
55
|
-
}, [serializedContext, setPageContext]);
|
|
56
|
-
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export type CortexAiContentType = "page" | "post" | "product";
|
|
6
|
+
|
|
7
|
+
export type CortexAiPageContext = {
|
|
8
|
+
contentType: CortexAiContentType;
|
|
9
|
+
currentEditor?: {
|
|
10
|
+
blockId?: number | string | null;
|
|
11
|
+
blockType?: string | null;
|
|
12
|
+
field?: string | null;
|
|
13
|
+
};
|
|
14
|
+
entityId: number | string;
|
|
15
|
+
languageId?: number | null;
|
|
16
|
+
slug?: string | null;
|
|
17
|
+
title?: string | null;
|
|
18
|
+
translationGroupId?: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type CortexAiPageContextValue = {
|
|
22
|
+
pageContext: CortexAiPageContext | null;
|
|
23
|
+
setPageContext: (context: CortexAiPageContext | null) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const CortexAiPageContextStore = createContext<CortexAiPageContextValue | null>(null);
|
|
27
|
+
|
|
28
|
+
export function CortexAiPageContextProvider({ children }: { children: React.ReactNode }) {
|
|
29
|
+
const [pageContext, setPageContext] = useState<CortexAiPageContext | null>(null);
|
|
30
|
+
const value = useMemo(() => ({ pageContext, setPageContext }), [pageContext]);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<CortexAiPageContextStore.Provider value={value}>
|
|
34
|
+
{children}
|
|
35
|
+
</CortexAiPageContextStore.Provider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useCortexAiPageContext() {
|
|
40
|
+
return useContext(CortexAiPageContextStore);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function CortexAiPageContextRegistrar({
|
|
44
|
+
context,
|
|
45
|
+
}: {
|
|
46
|
+
context: CortexAiPageContext;
|
|
47
|
+
}) {
|
|
48
|
+
const store = useCortexAiPageContext();
|
|
49
|
+
const setPageContext = store?.setPageContext;
|
|
50
|
+
const serializedContext = JSON.stringify(context);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setPageContext?.(context);
|
|
54
|
+
return () => setPageContext?.(null);
|
|
55
|
+
}, [serializedContext, setPageContext]);
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|