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
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
// app/cms/settings/security/page.tsx
|
|
2
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
-
import { getSecurityPanelData } from './actions';
|
|
4
|
-
import SecurityPanel from './components/SecurityPanel';
|
|
5
|
-
|
|
6
|
-
export default async function SecuritySettingsPage() {
|
|
7
|
-
// The sandbox/demo runs on a single shared account, so per-account security (2FA,
|
|
8
|
-
// trusted devices, policy) is intentionally disabled there.
|
|
9
|
-
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
10
|
-
return (
|
|
11
|
-
<div className="max-w-4xl mx-auto">
|
|
12
|
-
<Card>
|
|
13
|
-
<CardHeader>
|
|
14
|
-
<CardTitle>Security & 2FA</CardTitle>
|
|
15
|
-
<CardDescription>
|
|
16
|
-
Security settings are disabled in the sandbox/demo environment, which runs on a
|
|
17
|
-
shared account and resets daily. They are available in a real installation.
|
|
18
|
-
</CardDescription>
|
|
19
|
-
</CardHeader>
|
|
20
|
-
<CardContent />
|
|
21
|
-
</Card>
|
|
22
|
-
</div>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const data = await getSecurityPanelData();
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="max-w-4xl mx-auto space-y-6">
|
|
30
|
-
<SecurityPanel data={data} />
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
1
|
+
// app/cms/settings/security/page.tsx
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nextblock-cms/ui';
|
|
3
|
+
import { getSecurityPanelData } from './actions';
|
|
4
|
+
import SecurityPanel from './components/SecurityPanel';
|
|
5
|
+
|
|
6
|
+
export default async function SecuritySettingsPage() {
|
|
7
|
+
// The sandbox/demo runs on a single shared account, so per-account security (2FA,
|
|
8
|
+
// trusted devices, policy) is intentionally disabled there.
|
|
9
|
+
if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
|
|
10
|
+
return (
|
|
11
|
+
<div className="max-w-4xl mx-auto">
|
|
12
|
+
<Card>
|
|
13
|
+
<CardHeader>
|
|
14
|
+
<CardTitle>Security & 2FA</CardTitle>
|
|
15
|
+
<CardDescription>
|
|
16
|
+
Security settings are disabled in the sandbox/demo environment, which runs on a
|
|
17
|
+
shared account and resets daily. They are available in a real installation.
|
|
18
|
+
</CardDescription>
|
|
19
|
+
</CardHeader>
|
|
20
|
+
<CardContent />
|
|
21
|
+
</Card>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = await getSecurityPanelData();
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="max-w-4xl mx-auto space-y-6">
|
|
30
|
+
<SecurityPanel data={data} />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { redirect } from 'next/navigation';
|
|
2
|
-
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
-
import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
-
|
|
5
|
-
export const metadata = {
|
|
6
|
-
title: 'Tax Settings | NextBlock™ CMS',
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default async function TaxesPageWrapper({
|
|
10
|
-
searchParams,
|
|
11
|
-
}: {
|
|
12
|
-
searchParams?: Promise<{ success?: string }>;
|
|
13
|
-
}) {
|
|
14
|
-
const isOnline = await verifyPackageOnline('ecommerce');
|
|
15
|
-
|
|
16
|
-
if (!isOnline) {
|
|
17
|
-
redirect('/cms/settings/packages');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return <TaxesPageUI searchParams={await searchParams} />;
|
|
21
|
-
}
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
|
+
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
+
import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: 'Tax Settings | NextBlock™ CMS',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default async function TaxesPageWrapper({
|
|
10
|
+
searchParams,
|
|
11
|
+
}: {
|
|
12
|
+
searchParams?: Promise<{ success?: string }>;
|
|
13
|
+
}) {
|
|
14
|
+
const isOnline = await verifyPackageOnline('ecommerce');
|
|
15
|
+
|
|
16
|
+
if (!isOnline) {
|
|
17
|
+
redirect('/cms/settings/packages');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <TaxesPageUI searchParams={await searchParams} />;
|
|
21
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { redirect } from 'next/navigation';
|
|
2
|
-
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
-
import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
-
|
|
5
|
-
export default async function ShippingPage({
|
|
6
|
-
searchParams,
|
|
7
|
-
}: {
|
|
8
|
-
searchParams: Promise<{ success?: string }>;
|
|
9
|
-
}) {
|
|
10
|
-
const [isOnline, resolvedSearchParams] = await Promise.all([
|
|
11
|
-
verifyPackageOnline('ecommerce'),
|
|
12
|
-
searchParams,
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
if (!isOnline) {
|
|
16
|
-
redirect('/cms/settings/packages');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return <ShippingPageUI searchParams={resolvedSearchParams} />;
|
|
20
|
-
}
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
|
+
import { verifyPackageOnline } from '@nextblock-cms/db/server';
|
|
3
|
+
import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
|
|
4
|
+
|
|
5
|
+
export default async function ShippingPage({
|
|
6
|
+
searchParams,
|
|
7
|
+
}: {
|
|
8
|
+
searchParams: Promise<{ success?: string }>;
|
|
9
|
+
}) {
|
|
10
|
+
const [isOnline, resolvedSearchParams] = await Promise.all([
|
|
11
|
+
verifyPackageOnline('ecommerce'),
|
|
12
|
+
searchParams,
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
if (!isOnline) {
|
|
16
|
+
redirect('/cms/settings/packages');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return <ShippingPageUI searchParams={resolvedSearchParams} />;
|
|
20
|
+
}
|
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
// app/cms/users/actions.ts
|
|
2
|
-
"use server";
|
|
3
|
-
|
|
4
|
-
import { createClient } from "@nextblock-cms/db/server";
|
|
5
|
-
import { revalidatePath } from "next/cache";
|
|
6
|
-
import { redirect } from "next/navigation";
|
|
7
|
-
import type { Database } from "@nextblock-cms/db";
|
|
8
|
-
import { normalizeCustomerAddress } from "@nextblock-cms/ecommerce";
|
|
9
|
-
import { upsertDefaultUserAddresses } from "@nextblock-cms/ecommerce/server";
|
|
10
|
-
import { createClient as createSupabaseClient } from '@supabase/supabase-js';
|
|
11
|
-
import { resolveSupabaseServiceKey, resolveSupabaseUrl } from '../../../lib/setup/env-status';
|
|
12
|
-
|
|
13
|
-
type UserRole = Database['public']['Enums']['user_role'];
|
|
14
|
-
|
|
15
|
-
// Helper to check admin role using the server client
|
|
16
|
-
async function verifyAdmin(supabase: ReturnType<typeof createClient>): Promise<{ isAdmin: boolean; error?: string; userId?: string }> {
|
|
17
|
-
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
18
|
-
if (authError || !user) {
|
|
19
|
-
return { isAdmin: false, error: "Authentication required." };
|
|
20
|
-
}
|
|
21
|
-
const { data: profile, error: profileError } = await supabase
|
|
22
|
-
.from("profiles")
|
|
23
|
-
.select("role")
|
|
24
|
-
.eq("id", user.id)
|
|
25
|
-
.single();
|
|
26
|
-
|
|
27
|
-
if (profileError || !profile) {
|
|
28
|
-
return { isAdmin: false, error: "Profile not found or error fetching profile." };
|
|
29
|
-
}
|
|
30
|
-
if (profile.role !== "ADMIN") {
|
|
31
|
-
return { isAdmin: false, error: "Admin privileges required." };
|
|
32
|
-
}
|
|
33
|
-
return { isAdmin: true, userId: user.id };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type UpdateUserProfilePayload = {
|
|
37
|
-
role: UserRole;
|
|
38
|
-
full_name?: string | null;
|
|
39
|
-
avatar_url?: string | null;
|
|
40
|
-
website?: string | null;
|
|
41
|
-
github_username?: string | null;
|
|
42
|
-
phone?: string | null;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function createServiceRoleClient() {
|
|
46
|
-
const supabaseUrl = resolveSupabaseUrl();
|
|
47
|
-
const serviceRoleKey = resolveSupabaseServiceKey();
|
|
48
|
-
|
|
49
|
-
if (!supabaseUrl || !serviceRoleKey) {
|
|
50
|
-
throw new Error('Missing required environment variables');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return createSupabaseClient(supabaseUrl, serviceRoleKey, {
|
|
54
|
-
auth: {
|
|
55
|
-
autoRefreshToken: false,
|
|
56
|
-
persistSession: false,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function updateUserProfile(userIdToUpdate: string, formData: FormData) {
|
|
62
|
-
const supabase = createClient();
|
|
63
|
-
const adminCheck = await verifyAdmin(supabase);
|
|
64
|
-
if (!adminCheck.isAdmin) {
|
|
65
|
-
return { error: adminCheck.error || "Unauthorized" };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const parseAddressField = (fieldName: string) => {
|
|
69
|
-
const rawValue = formData.get(fieldName) as string | null;
|
|
70
|
-
if (!rawValue) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
return normalizeCustomerAddress(JSON.parse(rawValue));
|
|
76
|
-
} catch {
|
|
77
|
-
throw new Error(`Invalid JSON for ${fieldName}.`);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
let billingAddressJSON = null;
|
|
82
|
-
let shippingAddressJSON = null;
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
billingAddressJSON = parseAddressField("billing_address");
|
|
86
|
-
shippingAddressJSON = parseAddressField("shipping_address");
|
|
87
|
-
} catch (error: any) {
|
|
88
|
-
return { error: error.message };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (formData.get("use_billing_for_shipping") === "true") {
|
|
92
|
-
shippingAddressJSON = billingAddressJSON;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const rawFormData = {
|
|
96
|
-
role: formData.get("role") as UserRole,
|
|
97
|
-
full_name: formData.get("full_name") as string || null,
|
|
98
|
-
avatar_url: formData.get("avatar_url") as string || null,
|
|
99
|
-
website: formData.get("website") as string || null,
|
|
100
|
-
github_username: formData.get("github_username") as string || null,
|
|
101
|
-
phone: formData.get("phone") as string || null,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
if (!rawFormData.role) {
|
|
105
|
-
return { error: "Role is a required field." };
|
|
106
|
-
}
|
|
107
|
-
if (!['ADMIN', 'WRITER', 'USER'].includes(rawFormData.role)) {
|
|
108
|
-
return { error: "Invalid role specified." };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Prevent an admin from accidentally removing their own admin role if they are the only admin
|
|
112
|
-
// This is a basic check; a more robust system might count admins.
|
|
113
|
-
if (userIdToUpdate === adminCheck.userId && rawFormData.role !== 'ADMIN') {
|
|
114
|
-
const { count } = await supabase
|
|
115
|
-
.from('profiles')
|
|
116
|
-
.select('*', { count: 'exact', head: true })
|
|
117
|
-
.eq('role', 'ADMIN');
|
|
118
|
-
if (count === 1) {
|
|
119
|
-
return { error: "Cannot remove the last admin's role." };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const profileData: UpdateUserProfilePayload = {
|
|
125
|
-
role: rawFormData.role,
|
|
126
|
-
full_name: rawFormData.full_name,
|
|
127
|
-
avatar_url: rawFormData.avatar_url,
|
|
128
|
-
website: rawFormData.website,
|
|
129
|
-
github_username: rawFormData.github_username,
|
|
130
|
-
phone: rawFormData.phone,
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const adminSupabase = createServiceRoleClient();
|
|
134
|
-
|
|
135
|
-
const { error } = await adminSupabase
|
|
136
|
-
.from("profiles")
|
|
137
|
-
.update(profileData)
|
|
138
|
-
.eq("id", userIdToUpdate);
|
|
139
|
-
|
|
140
|
-
if (error) {
|
|
141
|
-
console.error("Error updating user profile:", error);
|
|
142
|
-
return { error: `Failed to update profile: ${error.message}` };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await upsertDefaultUserAddresses({
|
|
146
|
-
userId: userIdToUpdate,
|
|
147
|
-
billingAddress: billingAddressJSON,
|
|
148
|
-
shippingAddress: shippingAddressJSON,
|
|
149
|
-
client: adminSupabase,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
revalidatePath("/cms/users");
|
|
153
|
-
revalidatePath(`/cms/users/${userIdToUpdate}/edit`);
|
|
154
|
-
revalidatePath("/profile");
|
|
155
|
-
revalidatePath("/checkout");
|
|
156
|
-
redirect(`/cms/users/${userIdToUpdate}/edit?success=User profile updated successfully`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function deleteUserAndProfile(userIdToDelete: string) {
|
|
160
|
-
|
|
161
|
-
// For deleting a user, we need to use the Supabase Admin API,
|
|
162
|
-
// which requires a client initialized with the SERVICE_ROLE_KEY.
|
|
163
|
-
// This ensures the operation has the necessary privileges.
|
|
164
|
-
// IMPORTANT: Ensure SUPABASE_SERVICE_ROLE_KEY is set in your .env.local and Vercel env vars.
|
|
165
|
-
const supabaseAdmin = createClient(
|
|
166
|
-
// Re-create client with service role. This is a common pattern.
|
|
167
|
-
// Ensure your createClient function can be called without args to use env vars,
|
|
168
|
-
// or pass them explicitly if needed. The one from the template should work.
|
|
169
|
-
// If your createClient is specific to user context (cookies), you might need a separate
|
|
170
|
-
// admin client factory. For now, assuming `createClient()` can make a service client
|
|
171
|
-
// if called in a server action without user cookie context, or if it defaults to service key.
|
|
172
|
-
// A safer way:
|
|
173
|
-
// const supabaseAdmin = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
|
174
|
-
// However, the template's createClient for server components/actions should handle this by not having cookie access.
|
|
175
|
-
// Let's assume for now createClient() is sufficient if it can use service role.
|
|
176
|
-
// A more explicit way for admin actions:
|
|
177
|
-
// import { createClient as createAdminClient } from '@supabase/supabase-js';
|
|
178
|
-
// const supabaseAdmin = createAdminClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const adminCheck = await verifyAdmin(supabaseAdmin); // Verify current user is admin
|
|
183
|
-
if (!adminCheck.isAdmin) {
|
|
184
|
-
return { error: adminCheck.error || "Unauthorized" };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (userIdToDelete === adminCheck.userId) {
|
|
188
|
-
return { error: "Admins cannot delete their own account through this panel." };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Use the Supabase Auth Admin API to delete the user
|
|
192
|
-
// This requires the `SERVICE_ROLE_KEY` to be configured for the Supabase client.
|
|
193
|
-
// The standard `createClient` from `utils/supabase/server` might not use the service role by default.
|
|
194
|
-
// You might need a dedicated admin client instance.
|
|
195
|
-
// For this example, we'll assume `supabase.auth.admin.deleteUser` is available and configured.
|
|
196
|
-
// If not, this part needs adjustment to use a service_role client.
|
|
197
|
-
|
|
198
|
-
// The `createClient()` from `@supabase/ssr` for server context doesn't directly expose `auth.admin`.
|
|
199
|
-
// We need to create a standard Supabase client with the service role key.
|
|
200
|
-
const { createClient: createServiceRoleClient } = await import('@supabase/supabase-js');
|
|
201
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
|
|
202
|
-
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SECRET_KEY;
|
|
203
|
-
|
|
204
|
-
if (!supabaseUrl || !serviceRoleKey) {
|
|
205
|
-
throw new Error('Missing required environment variables');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const serviceSupabase = createServiceRoleClient(supabaseUrl, serviceRoleKey);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const { error: deletionError } = await serviceSupabase.auth.admin.deleteUser(userIdToDelete);
|
|
212
|
-
|
|
213
|
-
if (deletionError) {
|
|
214
|
-
console.error("Error deleting user:", deletionError);
|
|
215
|
-
// If the profile was deleted by cascade but auth user deletion failed, this is an inconsistent state.
|
|
216
|
-
return { error: `Failed to delete user: ${deletionError.message}` };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// The `profiles` table has ON DELETE CASCADE for the user ID, so it should be deleted automatically.
|
|
220
|
-
revalidatePath("/cms/users");
|
|
221
|
-
redirect("/cms/users?success=User deleted successfully");
|
|
222
|
-
}
|
|
1
|
+
// app/cms/users/actions.ts
|
|
2
|
+
"use server";
|
|
3
|
+
|
|
4
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
5
|
+
import { revalidatePath } from "next/cache";
|
|
6
|
+
import { redirect } from "next/navigation";
|
|
7
|
+
import type { Database } from "@nextblock-cms/db";
|
|
8
|
+
import { normalizeCustomerAddress } from "@nextblock-cms/ecommerce";
|
|
9
|
+
import { upsertDefaultUserAddresses } from "@nextblock-cms/ecommerce/server";
|
|
10
|
+
import { createClient as createSupabaseClient } from '@supabase/supabase-js';
|
|
11
|
+
import { resolveSupabaseServiceKey, resolveSupabaseUrl } from '../../../lib/setup/env-status';
|
|
12
|
+
|
|
13
|
+
type UserRole = Database['public']['Enums']['user_role'];
|
|
14
|
+
|
|
15
|
+
// Helper to check admin role using the server client
|
|
16
|
+
async function verifyAdmin(supabase: ReturnType<typeof createClient>): Promise<{ isAdmin: boolean; error?: string; userId?: string }> {
|
|
17
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
18
|
+
if (authError || !user) {
|
|
19
|
+
return { isAdmin: false, error: "Authentication required." };
|
|
20
|
+
}
|
|
21
|
+
const { data: profile, error: profileError } = await supabase
|
|
22
|
+
.from("profiles")
|
|
23
|
+
.select("role")
|
|
24
|
+
.eq("id", user.id)
|
|
25
|
+
.single();
|
|
26
|
+
|
|
27
|
+
if (profileError || !profile) {
|
|
28
|
+
return { isAdmin: false, error: "Profile not found or error fetching profile." };
|
|
29
|
+
}
|
|
30
|
+
if (profile.role !== "ADMIN") {
|
|
31
|
+
return { isAdmin: false, error: "Admin privileges required." };
|
|
32
|
+
}
|
|
33
|
+
return { isAdmin: true, userId: user.id };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type UpdateUserProfilePayload = {
|
|
37
|
+
role: UserRole;
|
|
38
|
+
full_name?: string | null;
|
|
39
|
+
avatar_url?: string | null;
|
|
40
|
+
website?: string | null;
|
|
41
|
+
github_username?: string | null;
|
|
42
|
+
phone?: string | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function createServiceRoleClient() {
|
|
46
|
+
const supabaseUrl = resolveSupabaseUrl();
|
|
47
|
+
const serviceRoleKey = resolveSupabaseServiceKey();
|
|
48
|
+
|
|
49
|
+
if (!supabaseUrl || !serviceRoleKey) {
|
|
50
|
+
throw new Error('Missing required environment variables');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return createSupabaseClient(supabaseUrl, serviceRoleKey, {
|
|
54
|
+
auth: {
|
|
55
|
+
autoRefreshToken: false,
|
|
56
|
+
persistSession: false,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function updateUserProfile(userIdToUpdate: string, formData: FormData) {
|
|
62
|
+
const supabase = createClient();
|
|
63
|
+
const adminCheck = await verifyAdmin(supabase);
|
|
64
|
+
if (!adminCheck.isAdmin) {
|
|
65
|
+
return { error: adminCheck.error || "Unauthorized" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const parseAddressField = (fieldName: string) => {
|
|
69
|
+
const rawValue = formData.get(fieldName) as string | null;
|
|
70
|
+
if (!rawValue) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return normalizeCustomerAddress(JSON.parse(rawValue));
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error(`Invalid JSON for ${fieldName}.`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let billingAddressJSON = null;
|
|
82
|
+
let shippingAddressJSON = null;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
billingAddressJSON = parseAddressField("billing_address");
|
|
86
|
+
shippingAddressJSON = parseAddressField("shipping_address");
|
|
87
|
+
} catch (error: any) {
|
|
88
|
+
return { error: error.message };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (formData.get("use_billing_for_shipping") === "true") {
|
|
92
|
+
shippingAddressJSON = billingAddressJSON;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const rawFormData = {
|
|
96
|
+
role: formData.get("role") as UserRole,
|
|
97
|
+
full_name: formData.get("full_name") as string || null,
|
|
98
|
+
avatar_url: formData.get("avatar_url") as string || null,
|
|
99
|
+
website: formData.get("website") as string || null,
|
|
100
|
+
github_username: formData.get("github_username") as string || null,
|
|
101
|
+
phone: formData.get("phone") as string || null,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (!rawFormData.role) {
|
|
105
|
+
return { error: "Role is a required field." };
|
|
106
|
+
}
|
|
107
|
+
if (!['ADMIN', 'WRITER', 'USER'].includes(rawFormData.role)) {
|
|
108
|
+
return { error: "Invalid role specified." };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Prevent an admin from accidentally removing their own admin role if they are the only admin
|
|
112
|
+
// This is a basic check; a more robust system might count admins.
|
|
113
|
+
if (userIdToUpdate === adminCheck.userId && rawFormData.role !== 'ADMIN') {
|
|
114
|
+
const { count } = await supabase
|
|
115
|
+
.from('profiles')
|
|
116
|
+
.select('*', { count: 'exact', head: true })
|
|
117
|
+
.eq('role', 'ADMIN');
|
|
118
|
+
if (count === 1) {
|
|
119
|
+
return { error: "Cannot remove the last admin's role." };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
const profileData: UpdateUserProfilePayload = {
|
|
125
|
+
role: rawFormData.role,
|
|
126
|
+
full_name: rawFormData.full_name,
|
|
127
|
+
avatar_url: rawFormData.avatar_url,
|
|
128
|
+
website: rawFormData.website,
|
|
129
|
+
github_username: rawFormData.github_username,
|
|
130
|
+
phone: rawFormData.phone,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const adminSupabase = createServiceRoleClient();
|
|
134
|
+
|
|
135
|
+
const { error } = await adminSupabase
|
|
136
|
+
.from("profiles")
|
|
137
|
+
.update(profileData)
|
|
138
|
+
.eq("id", userIdToUpdate);
|
|
139
|
+
|
|
140
|
+
if (error) {
|
|
141
|
+
console.error("Error updating user profile:", error);
|
|
142
|
+
return { error: `Failed to update profile: ${error.message}` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await upsertDefaultUserAddresses({
|
|
146
|
+
userId: userIdToUpdate,
|
|
147
|
+
billingAddress: billingAddressJSON,
|
|
148
|
+
shippingAddress: shippingAddressJSON,
|
|
149
|
+
client: adminSupabase,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
revalidatePath("/cms/users");
|
|
153
|
+
revalidatePath(`/cms/users/${userIdToUpdate}/edit`);
|
|
154
|
+
revalidatePath("/profile");
|
|
155
|
+
revalidatePath("/checkout");
|
|
156
|
+
redirect(`/cms/users/${userIdToUpdate}/edit?success=User profile updated successfully`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function deleteUserAndProfile(userIdToDelete: string) {
|
|
160
|
+
|
|
161
|
+
// For deleting a user, we need to use the Supabase Admin API,
|
|
162
|
+
// which requires a client initialized with the SERVICE_ROLE_KEY.
|
|
163
|
+
// This ensures the operation has the necessary privileges.
|
|
164
|
+
// IMPORTANT: Ensure SUPABASE_SERVICE_ROLE_KEY is set in your .env.local and Vercel env vars.
|
|
165
|
+
const supabaseAdmin = createClient(
|
|
166
|
+
// Re-create client with service role. This is a common pattern.
|
|
167
|
+
// Ensure your createClient function can be called without args to use env vars,
|
|
168
|
+
// or pass them explicitly if needed. The one from the template should work.
|
|
169
|
+
// If your createClient is specific to user context (cookies), you might need a separate
|
|
170
|
+
// admin client factory. For now, assuming `createClient()` can make a service client
|
|
171
|
+
// if called in a server action without user cookie context, or if it defaults to service key.
|
|
172
|
+
// A safer way:
|
|
173
|
+
// const supabaseAdmin = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
|
174
|
+
// However, the template's createClient for server components/actions should handle this by not having cookie access.
|
|
175
|
+
// Let's assume for now createClient() is sufficient if it can use service role.
|
|
176
|
+
// A more explicit way for admin actions:
|
|
177
|
+
// import { createClient as createAdminClient } from '@supabase/supabase-js';
|
|
178
|
+
// const supabaseAdmin = createAdminClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
const adminCheck = await verifyAdmin(supabaseAdmin); // Verify current user is admin
|
|
183
|
+
if (!adminCheck.isAdmin) {
|
|
184
|
+
return { error: adminCheck.error || "Unauthorized" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (userIdToDelete === adminCheck.userId) {
|
|
188
|
+
return { error: "Admins cannot delete their own account through this panel." };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Use the Supabase Auth Admin API to delete the user
|
|
192
|
+
// This requires the `SERVICE_ROLE_KEY` to be configured for the Supabase client.
|
|
193
|
+
// The standard `createClient` from `utils/supabase/server` might not use the service role by default.
|
|
194
|
+
// You might need a dedicated admin client instance.
|
|
195
|
+
// For this example, we'll assume `supabase.auth.admin.deleteUser` is available and configured.
|
|
196
|
+
// If not, this part needs adjustment to use a service_role client.
|
|
197
|
+
|
|
198
|
+
// The `createClient()` from `@supabase/ssr` for server context doesn't directly expose `auth.admin`.
|
|
199
|
+
// We need to create a standard Supabase client with the service role key.
|
|
200
|
+
const { createClient: createServiceRoleClient } = await import('@supabase/supabase-js');
|
|
201
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
|
|
202
|
+
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SECRET_KEY;
|
|
203
|
+
|
|
204
|
+
if (!supabaseUrl || !serviceRoleKey) {
|
|
205
|
+
throw new Error('Missing required environment variables');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const serviceSupabase = createServiceRoleClient(supabaseUrl, serviceRoleKey);
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
const { error: deletionError } = await serviceSupabase.auth.admin.deleteUser(userIdToDelete);
|
|
212
|
+
|
|
213
|
+
if (deletionError) {
|
|
214
|
+
console.error("Error deleting user:", deletionError);
|
|
215
|
+
// If the profile was deleted by cascade but auth user deletion failed, this is an inconsistent state.
|
|
216
|
+
return { error: `Failed to delete user: ${deletionError.message}` };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// The `profiles` table has ON DELETE CASCADE for the user ID, so it should be deleted automatically.
|
|
220
|
+
revalidatePath("/cms/users");
|
|
221
|
+
redirect("/cms/users?success=User deleted successfully");
|
|
222
|
+
}
|
|
@@ -7,17 +7,17 @@ import { Trash2, ShieldAlert } from "lucide-react";
|
|
|
7
7
|
import { deleteUserAndProfile } from "../actions";
|
|
8
8
|
import { ConfirmationModal } from '../../components/ConfirmationModal';
|
|
9
9
|
|
|
10
|
-
export function DeleteUserButtonClient({
|
|
11
|
-
userId,
|
|
12
|
-
userEmail,
|
|
13
|
-
currentAdminId,
|
|
14
|
-
}: {
|
|
15
|
-
userId: string;
|
|
16
|
-
userEmail?: string;
|
|
17
|
-
currentAdminId?: string;
|
|
18
|
-
}) {
|
|
19
|
-
void userEmail;
|
|
20
|
-
const [isPending, startTransition] = useTransition();
|
|
10
|
+
export function DeleteUserButtonClient({
|
|
11
|
+
userId,
|
|
12
|
+
userEmail,
|
|
13
|
+
currentAdminId,
|
|
14
|
+
}: {
|
|
15
|
+
userId: string;
|
|
16
|
+
userEmail?: string;
|
|
17
|
+
currentAdminId?: string;
|
|
18
|
+
}) {
|
|
19
|
+
void userEmail;
|
|
20
|
+
const [isPending, startTransition] = useTransition();
|
|
21
21
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
22
22
|
|
|
23
23
|
const handleDelete = () => {
|
|
@@ -68,4 +68,4 @@ export function DeleteUserButtonClient({
|
|
|
68
68
|
/>
|
|
69
69
|
</>
|
|
70
70
|
);
|
|
71
|
-
}
|
|
71
|
+
}
|