create-nextblock 0.13.9 → 0.13.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/page.tsx +46 -46
- 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/postActions.ts +146 -146
- package/templates/nextblock-template/app/actions/productGridActions.ts +40 -40
- 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/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/ecommerce/product-picker/route.ts +151 -151
- 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 +5749 -5609
- 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/BlockEditorArea.tsx +10 -1
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +5 -1
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +55 -24
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/components/MultiEntityPicker.tsx +263 -251
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- 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/EcommerceActiveContext.tsx +27 -27
- 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/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/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/interactions/EmailRecipientsInput.tsx +189 -189
- 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/navigation/components/NavigationMenuDnd.tsx +3 -3
- 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/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/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
- 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/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/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -188
- 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/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- 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/components/CreateUserForm.tsx +217 -217
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/cms/users/new/page.tsx +44 -44
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- 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-links.ts +22 -22
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- 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/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- 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/ProductGridClient.tsx +114 -114
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +8 -1
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +29 -23
- 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/media/YouTubeFacade.tsx +69 -0
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -0
- 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 +471 -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/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/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/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-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +78 -78
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +21 -1
- package/templates/nextblock-template/lib/blocks/product-grid-data.ts +210 -210
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- 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/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-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/i18n/country-languages.ts +247 -247
- package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -197
- package/templates/nextblock-template/lib/i18n/detection.ts +192 -192
- 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/media/youtube.ts +81 -0
- 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/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 +87 -82
- 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-env.d.ts +1 -1
- 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/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
// app/cms/users/components/CreateUserForm.tsx
|
|
2
|
-
"use client";
|
|
3
|
-
|
|
4
|
-
import React, { useState, useTransition } from "react";
|
|
5
|
-
import { useRouter } from "next/navigation";
|
|
6
|
-
import { Button } from "@nextblock-cms/ui";
|
|
7
|
-
import { Input } from "@nextblock-cms/ui";
|
|
8
|
-
import { Label } from "@nextblock-cms/ui";
|
|
9
|
-
import { Checkbox } from "@nextblock-cms/ui";
|
|
10
|
-
import {
|
|
11
|
-
Select,
|
|
12
|
-
SelectContent,
|
|
13
|
-
SelectItem,
|
|
14
|
-
SelectTrigger,
|
|
15
|
-
SelectValue,
|
|
16
|
-
} from "@nextblock-cms/ui";
|
|
17
|
-
import { Alert, AlertTitle, AlertDescription, Spinner } from "@nextblock-cms/ui";
|
|
18
|
-
import { Eye, EyeOff, RefreshCw } from "lucide-react";
|
|
19
|
-
import type { Database } from "@nextblock-cms/db";
|
|
20
|
-
import { createUser } from "../actions";
|
|
21
|
-
|
|
22
|
-
type UserRole = Database["public"]["Enums"]["user_role"];
|
|
23
|
-
|
|
24
|
-
function generatePassword(length = 16): string {
|
|
25
|
-
const charset =
|
|
26
|
-
"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*";
|
|
27
|
-
const values = new Uint32Array(length);
|
|
28
|
-
crypto.getRandomValues(values);
|
|
29
|
-
let result = "";
|
|
30
|
-
for (let i = 0; i < length; i++) {
|
|
31
|
-
result += charset[values[i] % charset.length];
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default function CreateUserForm() {
|
|
37
|
-
const router = useRouter();
|
|
38
|
-
const [isPending, startTransition] = useTransition();
|
|
39
|
-
|
|
40
|
-
const [email, setEmail] = useState("");
|
|
41
|
-
const [password, setPassword] = useState("");
|
|
42
|
-
const [fullName, setFullName] = useState("");
|
|
43
|
-
const [role, setRole] = useState<UserRole>("USER");
|
|
44
|
-
const [emailConfirm, setEmailConfirm] = useState(true);
|
|
45
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
46
|
-
|
|
47
|
-
// Only errors surface here — the action redirects on success.
|
|
48
|
-
const [formError, setFormError] = useState<string | null>(null);
|
|
49
|
-
|
|
50
|
-
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
51
|
-
event.preventDefault();
|
|
52
|
-
setFormError(null);
|
|
53
|
-
|
|
54
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
55
|
-
setFormError("Enter a valid email address.");
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (password.length < 8) {
|
|
59
|
-
setFormError("Password must be at least 8 characters.");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const formData = new FormData();
|
|
64
|
-
formData.set("email", email);
|
|
65
|
-
formData.set("password", password);
|
|
66
|
-
formData.set("full_name", fullName);
|
|
67
|
-
formData.set("role", role);
|
|
68
|
-
formData.set("email_confirm", emailConfirm ? "true" : "false");
|
|
69
|
-
|
|
70
|
-
startTransition(async () => {
|
|
71
|
-
try {
|
|
72
|
-
const result = await createUser(formData);
|
|
73
|
-
// On success the action redirects; only errors return here.
|
|
74
|
-
if (result?.error) {
|
|
75
|
-
setFormError(result.error);
|
|
76
|
-
}
|
|
77
|
-
} catch {
|
|
78
|
-
setFormError("Something went wrong creating the user. Please try again.");
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<form onSubmit={handleSubmit} className="space-y-6">
|
|
85
|
-
{formError && (
|
|
86
|
-
<Alert variant="destructive">
|
|
87
|
-
<AlertTitle>Error</AlertTitle>
|
|
88
|
-
<AlertDescription>{formError}</AlertDescription>
|
|
89
|
-
</Alert>
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
<div>
|
|
93
|
-
<Label htmlFor="email">Email</Label>
|
|
94
|
-
<Input
|
|
95
|
-
id="email"
|
|
96
|
-
name="email"
|
|
97
|
-
type="email"
|
|
98
|
-
value={email}
|
|
99
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
100
|
-
required
|
|
101
|
-
autoComplete="off"
|
|
102
|
-
className="mt-1"
|
|
103
|
-
placeholder="user@example.com"
|
|
104
|
-
/>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div>
|
|
108
|
-
<Label htmlFor="full_name">Full Name</Label>
|
|
109
|
-
<Input
|
|
110
|
-
id="full_name"
|
|
111
|
-
name="full_name"
|
|
112
|
-
value={fullName}
|
|
113
|
-
onChange={(e) => setFullName(e.target.value)}
|
|
114
|
-
className="mt-1"
|
|
115
|
-
placeholder="Jane Doe"
|
|
116
|
-
/>
|
|
117
|
-
<p className="text-xs text-muted-foreground mt-1">Optional. Can be edited later.</p>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<div>
|
|
121
|
-
<Label htmlFor="password">Password</Label>
|
|
122
|
-
<div className="flex gap-2 mt-1">
|
|
123
|
-
<div className="relative flex-1">
|
|
124
|
-
<Input
|
|
125
|
-
id="password"
|
|
126
|
-
name="password"
|
|
127
|
-
type={showPassword ? "text" : "password"}
|
|
128
|
-
value={password}
|
|
129
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
130
|
-
required
|
|
131
|
-
minLength={8}
|
|
132
|
-
autoComplete="new-password"
|
|
133
|
-
className="pr-10"
|
|
134
|
-
placeholder="At least 8 characters"
|
|
135
|
-
/>
|
|
136
|
-
<button
|
|
137
|
-
type="button"
|
|
138
|
-
onClick={() => setShowPassword((v) => !v)}
|
|
139
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
140
|
-
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
141
|
-
>
|
|
142
|
-
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
143
|
-
</button>
|
|
144
|
-
</div>
|
|
145
|
-
<Button
|
|
146
|
-
type="button"
|
|
147
|
-
variant="outline"
|
|
148
|
-
onClick={() => {
|
|
149
|
-
setPassword(generatePassword());
|
|
150
|
-
setShowPassword(true);
|
|
151
|
-
}}
|
|
152
|
-
>
|
|
153
|
-
<RefreshCw className="mr-2 h-4 w-4" /> Generate
|
|
154
|
-
</Button>
|
|
155
|
-
</div>
|
|
156
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
157
|
-
Share this password with the user securely. They can change it later from their profile.
|
|
158
|
-
</p>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
<div>
|
|
162
|
-
<Label htmlFor="role">Role</Label>
|
|
163
|
-
<Select value={role} onValueChange={(val: UserRole) => setRole(val)}>
|
|
164
|
-
<SelectTrigger id="role" className="mt-1">
|
|
165
|
-
<SelectValue placeholder="Select role" />
|
|
166
|
-
</SelectTrigger>
|
|
167
|
-
<SelectContent>
|
|
168
|
-
<SelectItem value="USER">User</SelectItem>
|
|
169
|
-
<SelectItem value="WRITER">Writer</SelectItem>
|
|
170
|
-
<SelectItem value="ADMIN">Admin</SelectItem>
|
|
171
|
-
</SelectContent>
|
|
172
|
-
</Select>
|
|
173
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
174
|
-
Writers and Admins can access the CMS. Users have public-site access only.
|
|
175
|
-
</p>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
|
-
<div className="flex items-start space-x-2 pt-2">
|
|
179
|
-
<Checkbox
|
|
180
|
-
id="email_confirm"
|
|
181
|
-
checked={emailConfirm}
|
|
182
|
-
onCheckedChange={(checked) => setEmailConfirm(checked as boolean)}
|
|
183
|
-
className="mt-0.5"
|
|
184
|
-
/>
|
|
185
|
-
<div>
|
|
186
|
-
<Label htmlFor="email_confirm" className="font-normal leading-none">
|
|
187
|
-
Mark email as confirmed
|
|
188
|
-
</Label>
|
|
189
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
190
|
-
The user can sign in immediately without a verification email. Uncheck only if your
|
|
191
|
-
project sends confirmation emails and you want the user to verify first.
|
|
192
|
-
</p>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
<div className="flex justify-end space-x-3 pt-4">
|
|
197
|
-
<Button
|
|
198
|
-
type="button"
|
|
199
|
-
variant="outline"
|
|
200
|
-
onClick={() => router.push("/cms/users")}
|
|
201
|
-
disabled={isPending}
|
|
202
|
-
>
|
|
203
|
-
Cancel
|
|
204
|
-
</Button>
|
|
205
|
-
<Button type="submit" disabled={isPending}>
|
|
206
|
-
{isPending ? (
|
|
207
|
-
<>
|
|
208
|
-
<Spinner className="mr-2 h-4 w-4" /> Creating...
|
|
209
|
-
</>
|
|
210
|
-
) : (
|
|
211
|
-
"Create User"
|
|
212
|
-
)}
|
|
213
|
-
</Button>
|
|
214
|
-
</div>
|
|
215
|
-
</form>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
1
|
+
// app/cms/users/components/CreateUserForm.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import React, { useState, useTransition } from "react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { Button } from "@nextblock-cms/ui";
|
|
7
|
+
import { Input } from "@nextblock-cms/ui";
|
|
8
|
+
import { Label } from "@nextblock-cms/ui";
|
|
9
|
+
import { Checkbox } from "@nextblock-cms/ui";
|
|
10
|
+
import {
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectTrigger,
|
|
15
|
+
SelectValue,
|
|
16
|
+
} from "@nextblock-cms/ui";
|
|
17
|
+
import { Alert, AlertTitle, AlertDescription, Spinner } from "@nextblock-cms/ui";
|
|
18
|
+
import { Eye, EyeOff, RefreshCw } from "lucide-react";
|
|
19
|
+
import type { Database } from "@nextblock-cms/db";
|
|
20
|
+
import { createUser } from "../actions";
|
|
21
|
+
|
|
22
|
+
type UserRole = Database["public"]["Enums"]["user_role"];
|
|
23
|
+
|
|
24
|
+
function generatePassword(length = 16): string {
|
|
25
|
+
const charset =
|
|
26
|
+
"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*";
|
|
27
|
+
const values = new Uint32Array(length);
|
|
28
|
+
crypto.getRandomValues(values);
|
|
29
|
+
let result = "";
|
|
30
|
+
for (let i = 0; i < length; i++) {
|
|
31
|
+
result += charset[values[i] % charset.length];
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function CreateUserForm() {
|
|
37
|
+
const router = useRouter();
|
|
38
|
+
const [isPending, startTransition] = useTransition();
|
|
39
|
+
|
|
40
|
+
const [email, setEmail] = useState("");
|
|
41
|
+
const [password, setPassword] = useState("");
|
|
42
|
+
const [fullName, setFullName] = useState("");
|
|
43
|
+
const [role, setRole] = useState<UserRole>("USER");
|
|
44
|
+
const [emailConfirm, setEmailConfirm] = useState(true);
|
|
45
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
46
|
+
|
|
47
|
+
// Only errors surface here — the action redirects on success.
|
|
48
|
+
const [formError, setFormError] = useState<string | null>(null);
|
|
49
|
+
|
|
50
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
setFormError(null);
|
|
53
|
+
|
|
54
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
55
|
+
setFormError("Enter a valid email address.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (password.length < 8) {
|
|
59
|
+
setFormError("Password must be at least 8 characters.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const formData = new FormData();
|
|
64
|
+
formData.set("email", email);
|
|
65
|
+
formData.set("password", password);
|
|
66
|
+
formData.set("full_name", fullName);
|
|
67
|
+
formData.set("role", role);
|
|
68
|
+
formData.set("email_confirm", emailConfirm ? "true" : "false");
|
|
69
|
+
|
|
70
|
+
startTransition(async () => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await createUser(formData);
|
|
73
|
+
// On success the action redirects; only errors return here.
|
|
74
|
+
if (result?.error) {
|
|
75
|
+
setFormError(result.error);
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
setFormError("Something went wrong creating the user. Please try again.");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
85
|
+
{formError && (
|
|
86
|
+
<Alert variant="destructive">
|
|
87
|
+
<AlertTitle>Error</AlertTitle>
|
|
88
|
+
<AlertDescription>{formError}</AlertDescription>
|
|
89
|
+
</Alert>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<div>
|
|
93
|
+
<Label htmlFor="email">Email</Label>
|
|
94
|
+
<Input
|
|
95
|
+
id="email"
|
|
96
|
+
name="email"
|
|
97
|
+
type="email"
|
|
98
|
+
value={email}
|
|
99
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
100
|
+
required
|
|
101
|
+
autoComplete="off"
|
|
102
|
+
className="mt-1"
|
|
103
|
+
placeholder="user@example.com"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div>
|
|
108
|
+
<Label htmlFor="full_name">Full Name</Label>
|
|
109
|
+
<Input
|
|
110
|
+
id="full_name"
|
|
111
|
+
name="full_name"
|
|
112
|
+
value={fullName}
|
|
113
|
+
onChange={(e) => setFullName(e.target.value)}
|
|
114
|
+
className="mt-1"
|
|
115
|
+
placeholder="Jane Doe"
|
|
116
|
+
/>
|
|
117
|
+
<p className="text-xs text-muted-foreground mt-1">Optional. Can be edited later.</p>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div>
|
|
121
|
+
<Label htmlFor="password">Password</Label>
|
|
122
|
+
<div className="flex gap-2 mt-1">
|
|
123
|
+
<div className="relative flex-1">
|
|
124
|
+
<Input
|
|
125
|
+
id="password"
|
|
126
|
+
name="password"
|
|
127
|
+
type={showPassword ? "text" : "password"}
|
|
128
|
+
value={password}
|
|
129
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
130
|
+
required
|
|
131
|
+
minLength={8}
|
|
132
|
+
autoComplete="new-password"
|
|
133
|
+
className="pr-10"
|
|
134
|
+
placeholder="At least 8 characters"
|
|
135
|
+
/>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => setShowPassword((v) => !v)}
|
|
139
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
140
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
141
|
+
>
|
|
142
|
+
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
<Button
|
|
146
|
+
type="button"
|
|
147
|
+
variant="outline"
|
|
148
|
+
onClick={() => {
|
|
149
|
+
setPassword(generatePassword());
|
|
150
|
+
setShowPassword(true);
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<RefreshCw className="mr-2 h-4 w-4" /> Generate
|
|
154
|
+
</Button>
|
|
155
|
+
</div>
|
|
156
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
157
|
+
Share this password with the user securely. They can change it later from their profile.
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div>
|
|
162
|
+
<Label htmlFor="role">Role</Label>
|
|
163
|
+
<Select value={role} onValueChange={(val: UserRole) => setRole(val)}>
|
|
164
|
+
<SelectTrigger id="role" className="mt-1">
|
|
165
|
+
<SelectValue placeholder="Select role" />
|
|
166
|
+
</SelectTrigger>
|
|
167
|
+
<SelectContent>
|
|
168
|
+
<SelectItem value="USER">User</SelectItem>
|
|
169
|
+
<SelectItem value="WRITER">Writer</SelectItem>
|
|
170
|
+
<SelectItem value="ADMIN">Admin</SelectItem>
|
|
171
|
+
</SelectContent>
|
|
172
|
+
</Select>
|
|
173
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
174
|
+
Writers and Admins can access the CMS. Users have public-site access only.
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className="flex items-start space-x-2 pt-2">
|
|
179
|
+
<Checkbox
|
|
180
|
+
id="email_confirm"
|
|
181
|
+
checked={emailConfirm}
|
|
182
|
+
onCheckedChange={(checked) => setEmailConfirm(checked as boolean)}
|
|
183
|
+
className="mt-0.5"
|
|
184
|
+
/>
|
|
185
|
+
<div>
|
|
186
|
+
<Label htmlFor="email_confirm" className="font-normal leading-none">
|
|
187
|
+
Mark email as confirmed
|
|
188
|
+
</Label>
|
|
189
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
190
|
+
The user can sign in immediately without a verification email. Uncheck only if your
|
|
191
|
+
project sends confirmation emails and you want the user to verify first.
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div className="flex justify-end space-x-3 pt-4">
|
|
197
|
+
<Button
|
|
198
|
+
type="button"
|
|
199
|
+
variant="outline"
|
|
200
|
+
onClick={() => router.push("/cms/users")}
|
|
201
|
+
disabled={isPending}
|
|
202
|
+
>
|
|
203
|
+
Cancel
|
|
204
|
+
</Button>
|
|
205
|
+
<Button type="submit" disabled={isPending}>
|
|
206
|
+
{isPending ? (
|
|
207
|
+
<>
|
|
208
|
+
<Spinner className="mr-2 h-4 w-4" /> Creating...
|
|
209
|
+
</>
|
|
210
|
+
) : (
|
|
211
|
+
"Create User"
|
|
212
|
+
)}
|
|
213
|
+
</Button>
|
|
214
|
+
</div>
|
|
215
|
+
</form>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
// app/cms/users/new/page.tsx
|
|
2
|
-
import Link from "next/link";
|
|
3
|
-
import { ArrowLeft } from "lucide-react";
|
|
4
|
-
import { Button } from "@nextblock-cms/ui";
|
|
5
|
-
import { createClient } from "@nextblock-cms/db/server";
|
|
6
|
-
import CreateUserForm from "../components/CreateUserForm";
|
|
7
|
-
|
|
8
|
-
export default async function NewUserPage() {
|
|
9
|
-
const supabase = createClient();
|
|
10
|
-
const { data: { user: currentAdmin } } = await supabase.auth.getUser();
|
|
11
|
-
|
|
12
|
-
if (!currentAdmin) {
|
|
13
|
-
return <p>Access Denied. Not authenticated.</p>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// User management is admin-only (mirrors the users list page). The CMS layout already
|
|
17
|
-
// gates ADMIN/WRITER; this blocks writers and direct-access attempts.
|
|
18
|
-
const { data: adminProfile } = await supabase
|
|
19
|
-
.from("profiles")
|
|
20
|
-
.select("role")
|
|
21
|
-
.eq("id", currentAdmin.id)
|
|
22
|
-
.single();
|
|
23
|
-
if (adminProfile?.role !== "ADMIN") {
|
|
24
|
-
return <p>Access Denied. Admin privileges required.</p>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="max-w-xl mx-auto">
|
|
29
|
-
<div className="flex items-center gap-3 mb-6">
|
|
30
|
-
<Button variant="outline" size="icon" aria-label="Back to users" asChild>
|
|
31
|
-
<Link href="/cms/users">
|
|
32
|
-
<ArrowLeft className="h-4 w-4" />
|
|
33
|
-
</Link>
|
|
34
|
-
</Button>
|
|
35
|
-
<h1 className="text-2xl font-bold">Create User</h1>
|
|
36
|
-
</div>
|
|
37
|
-
<p className="text-sm text-muted-foreground mb-6">
|
|
38
|
-
Create an account directly. After creating, you can fill in the profile, addresses,
|
|
39
|
-
and avatar on the next screen.
|
|
40
|
-
</p>
|
|
41
|
-
<CreateUserForm />
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
1
|
+
// app/cms/users/new/page.tsx
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { ArrowLeft } from "lucide-react";
|
|
4
|
+
import { Button } from "@nextblock-cms/ui";
|
|
5
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
6
|
+
import CreateUserForm from "../components/CreateUserForm";
|
|
7
|
+
|
|
8
|
+
export default async function NewUserPage() {
|
|
9
|
+
const supabase = createClient();
|
|
10
|
+
const { data: { user: currentAdmin } } = await supabase.auth.getUser();
|
|
11
|
+
|
|
12
|
+
if (!currentAdmin) {
|
|
13
|
+
return <p>Access Denied. Not authenticated.</p>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// User management is admin-only (mirrors the users list page). The CMS layout already
|
|
17
|
+
// gates ADMIN/WRITER; this blocks writers and direct-access attempts.
|
|
18
|
+
const { data: adminProfile } = await supabase
|
|
19
|
+
.from("profiles")
|
|
20
|
+
.select("role")
|
|
21
|
+
.eq("id", currentAdmin.id)
|
|
22
|
+
.single();
|
|
23
|
+
if (adminProfile?.role !== "ADMIN") {
|
|
24
|
+
return <p>Access Denied. Admin privileges required.</p>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="max-w-xl mx-auto">
|
|
29
|
+
<div className="flex items-center gap-3 mb-6">
|
|
30
|
+
<Button variant="outline" size="icon" aria-label="Back to users" asChild>
|
|
31
|
+
<Link href="/cms/users">
|
|
32
|
+
<ArrowLeft className="h-4 w-4" />
|
|
33
|
+
</Link>
|
|
34
|
+
</Button>
|
|
35
|
+
<h1 className="text-2xl font-bold">Create User</h1>
|
|
36
|
+
</div>
|
|
37
|
+
<p className="text-sm text-muted-foreground mb-6">
|
|
38
|
+
Create an account directly. After creating, you can fill in the profile, addresses,
|
|
39
|
+
and avatar on the next screen.
|
|
40
|
+
</p>
|
|
41
|
+
<CreateUserForm />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|