create-nextblock 0.0.1
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/bin/create-nextblock.js +997 -0
- package/package.json +25 -0
- package/scripts/sync-template.js +284 -0
- package/templates/nextblock-template/.env.example +37 -0
- package/templates/nextblock-template/.swcrc +30 -0
- package/templates/nextblock-template/README.md +194 -0
- package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
- package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
- package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
- package/templates/nextblock-template/app/actions/email.ts +31 -0
- package/templates/nextblock-template/app/actions/formActions.ts +65 -0
- package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
- package/templates/nextblock-template/app/actions/postActions.ts +80 -0
- package/templates/nextblock-template/app/actions.ts +146 -0
- package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
- package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
- package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
- package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
- package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
- package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
- package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
- package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
- package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
- package/templates/nextblock-template/app/blog/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
- package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
- package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
- package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
- package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
- package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
- package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
- package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
- package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
- package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
- package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
- package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
- package/templates/nextblock-template/app/cms/layout.tsx +10 -0
- package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
- package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
- package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
- package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
- package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
- package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
- package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
- package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
- package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
- package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
- package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
- package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
- package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
- package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
- package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
- package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
- package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
- package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
- package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
- package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
- package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
- package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
- package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
- package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
- package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
- package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
- package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
- package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
- package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
- package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
- package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
- package/templates/nextblock-template/app/favicon.ico +0 -0
- package/templates/nextblock-template/app/globals.css +401 -0
- package/templates/nextblock-template/app/layout.tsx +191 -0
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
- package/templates/nextblock-template/app/page.tsx +109 -0
- package/templates/nextblock-template/app/providers.tsx +43 -0
- package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
- package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
- package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
- package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
- package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
- package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
- package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
- package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
- package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
- package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
- package/templates/nextblock-template/components/Header.tsx +42 -0
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
- package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
- package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
- package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
- package/templates/nextblock-template/components/blocks/types.ts +8 -0
- package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
- package/templates/nextblock-template/components/form-message.tsx +26 -0
- package/templates/nextblock-template/components/header-auth.tsx +71 -0
- package/templates/nextblock-template/components/submit-button.tsx +23 -0
- package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
- package/templates/nextblock-template/context/AuthContext.tsx +138 -0
- package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
- package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
- package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
- package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
- package/templates/nextblock-template/docs/files-structure.md +426 -0
- package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
- package/templates/nextblock-template/eslint.config.mjs +28 -0
- package/templates/nextblock-template/index.d.ts +5 -0
- package/templates/nextblock-template/lib/blocks/README.md +670 -0
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
- package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
- package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
- package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
- package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
- package/templates/nextblock-template/lib/ui/badge.ts +1 -0
- package/templates/nextblock-template/lib/ui/button.ts +1 -0
- package/templates/nextblock-template/lib/ui/card.ts +1 -0
- package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
- package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
- package/templates/nextblock-template/lib/ui/input.ts +1 -0
- package/templates/nextblock-template/lib/ui/label.ts +1 -0
- package/templates/nextblock-template/lib/ui/popover.ts +1 -0
- package/templates/nextblock-template/lib/ui/progress.ts +1 -0
- package/templates/nextblock-template/lib/ui/select.ts +1 -0
- package/templates/nextblock-template/lib/ui/separator.ts +1 -0
- package/templates/nextblock-template/lib/ui/table.ts +1 -0
- package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
- package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
- package/templates/nextblock-template/lib/ui/ui.ts +1 -0
- package/templates/nextblock-template/middleware.ts +206 -0
- package/templates/nextblock-template/next-env.d.ts +6 -0
- package/templates/nextblock-template/next.config.js +99 -0
- package/templates/nextblock-template/package.json +52 -0
- package/templates/nextblock-template/postcss.config.js +6 -0
- package/templates/nextblock-template/project.json +7 -0
- package/templates/nextblock-template/public/.gitkeep +0 -0
- package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
- package/templates/nextblock-template/scripts/backup.js +53 -0
- package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
- package/templates/nextblock-template/tailwind.config.ts +19 -0
- package/templates/nextblock-template/tsconfig.json +62 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// app/cms/users/components/UserForm.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useEffect, useState, useTransition } from "react";
|
|
5
|
+
import { useRouter, useSearchParams } 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 {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
} from "@nextblock-cms/ui";
|
|
16
|
+
import type { Database } from "@nextblock-cms/db";
|
|
17
|
+
import { useAuth } from "@/context/AuthContext";
|
|
18
|
+
|
|
19
|
+
type Profile = Database['public']['Tables']['profiles']['Row'];
|
|
20
|
+
type UserRole = Database['public']['Enums']['user_role'];
|
|
21
|
+
type AuthUser = {
|
|
22
|
+
id: string;
|
|
23
|
+
email: string | undefined;
|
|
24
|
+
created_at: string | undefined;
|
|
25
|
+
last_sign_in_at: string | undefined;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface UserFormProps {
|
|
29
|
+
userToEditAuth: AuthUser; // Auth details (email, id) - email usually not editable here
|
|
30
|
+
userToEditProfile: Profile | null; // Profile details (role, username, etc.)
|
|
31
|
+
formAction: (formData: FormData) => Promise<{ error?: string } | void>;
|
|
32
|
+
actionButtonText?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function UserForm({
|
|
36
|
+
userToEditAuth,
|
|
37
|
+
userToEditProfile,
|
|
38
|
+
formAction,
|
|
39
|
+
actionButtonText = "Save Changes",
|
|
40
|
+
}: UserFormProps) {
|
|
41
|
+
const router = useRouter();
|
|
42
|
+
const searchParams = useSearchParams();
|
|
43
|
+
const [isPending, startTransition] = useTransition();
|
|
44
|
+
const { isAdmin, isLoading: authLoading } = useAuth(); // For client-side guard
|
|
45
|
+
|
|
46
|
+
const [role, setRole] = useState<UserRole>(userToEditProfile?.role || "USER");
|
|
47
|
+
const [username, setUsername] = useState(userToEditProfile?.username || "");
|
|
48
|
+
const [fullName, setFullName] = useState(userToEditProfile?.full_name || "");
|
|
49
|
+
// Email is typically not changed here by an admin, it's part of auth.users managed by user or super-admin
|
|
50
|
+
const email = userToEditAuth.email || "N/A";
|
|
51
|
+
|
|
52
|
+
const [formMessage, setFormMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const successMessage = searchParams.get('success');
|
|
56
|
+
const errorMessage = searchParams.get('error');
|
|
57
|
+
if (successMessage) {
|
|
58
|
+
setFormMessage({ type: 'success', text: successMessage });
|
|
59
|
+
// Optionally clear the query param from URL
|
|
60
|
+
// router.replace(pathname, undefined, { shallow: true }); // if using next/router
|
|
61
|
+
} else if (errorMessage) {
|
|
62
|
+
setFormMessage({ type: 'error', text: errorMessage });
|
|
63
|
+
}
|
|
64
|
+
}, [searchParams, router]);
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
setFormMessage(null);
|
|
70
|
+
const formData = new FormData(event.currentTarget);
|
|
71
|
+
// Add user ID to form data if needed by action, or pass it directly
|
|
72
|
+
// formData.append("userId", userToEditAuth.id);
|
|
73
|
+
|
|
74
|
+
startTransition(async () => {
|
|
75
|
+
const result = await formAction(formData); // The action is already bound with userId
|
|
76
|
+
if (result?.error) {
|
|
77
|
+
setFormMessage({ type: 'error', text: result.error });
|
|
78
|
+
}
|
|
79
|
+
// Success is handled by redirect with query param in server action
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (authLoading) return <div>Loading...</div>;
|
|
84
|
+
if (!isAdmin) return <div>Access Denied. Admin role required.</div>;
|
|
85
|
+
|
|
86
|
+
const userRoles: UserRole[] = ['USER', 'WRITER', 'ADMIN'];
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
90
|
+
{formMessage && (
|
|
91
|
+
<div
|
|
92
|
+
className={`p-3 rounded-md text-sm ${
|
|
93
|
+
formMessage.type === 'success'
|
|
94
|
+
? 'bg-green-100 text-green-700 border border-green-200'
|
|
95
|
+
: 'bg-red-100 text-red-700 border border-red-200'
|
|
96
|
+
}`}
|
|
97
|
+
>
|
|
98
|
+
{formMessage.text}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
<div>
|
|
102
|
+
<Label htmlFor="email">Email (Read-only)</Label>
|
|
103
|
+
<Input id="email" name="email" value={email} readOnly disabled className="mt-1 bg-muted/50" />
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div>
|
|
107
|
+
<Label htmlFor="username">Username</Label>
|
|
108
|
+
<Input id="username" name="username" value={username} onChange={(e) => setUsername(e.target.value)} className="mt-1" />
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div>
|
|
112
|
+
<Label htmlFor="full_name">Full Name</Label>
|
|
113
|
+
<Input id="full_name" name="full_name" value={fullName} onChange={(e) => setFullName(e.target.value)} className="mt-1" />
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div>
|
|
117
|
+
<Label htmlFor="role">Role</Label>
|
|
118
|
+
<Select name="role" value={role} onValueChange={(value) => setRole(value as UserRole)} required>
|
|
119
|
+
<SelectTrigger className="mt-1"><SelectValue placeholder="Select role" /></SelectTrigger>
|
|
120
|
+
<SelectContent>
|
|
121
|
+
{userRoles.map((r) => (
|
|
122
|
+
<SelectItem key={r} value={r}>{r.charAt(0) + r.slice(1).toLowerCase()}</SelectItem>
|
|
123
|
+
))}
|
|
124
|
+
</SelectContent>
|
|
125
|
+
</Select>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="flex justify-end space-x-3 pt-4">
|
|
129
|
+
<Button type="button" variant="outline" onClick={() => router.push("/cms/users")} disabled={isPending}>
|
|
130
|
+
Cancel
|
|
131
|
+
</Button>
|
|
132
|
+
<Button type="submit" disabled={isPending || authLoading}>
|
|
133
|
+
{isPending ? "Saving..." : actionButtonText}
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</form>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { Button } from "@nextblock-cms/ui";
|
|
5
|
+
import {
|
|
6
|
+
Table,
|
|
7
|
+
TableBody,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableRow,
|
|
12
|
+
} from "@nextblock-cms/ui";
|
|
13
|
+
import { Badge } from "@nextblock-cms/ui";
|
|
14
|
+
import { MoreHorizontal, Edit3, Users } from "lucide-react";
|
|
15
|
+
import {
|
|
16
|
+
DropdownMenu,
|
|
17
|
+
DropdownMenuContent,
|
|
18
|
+
DropdownMenuItem,
|
|
19
|
+
DropdownMenuSeparator,
|
|
20
|
+
DropdownMenuTrigger,
|
|
21
|
+
} from "@nextblock-cms/ui";
|
|
22
|
+
import type { Database } from "@nextblock-cms/db";
|
|
23
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui";
|
|
24
|
+
|
|
25
|
+
type AuthUser = {
|
|
26
|
+
id: string;
|
|
27
|
+
email: string | undefined;
|
|
28
|
+
created_at: string | undefined;
|
|
29
|
+
last_sign_in_at: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
type UserWithProfile = {
|
|
32
|
+
authUser: AuthUser;
|
|
33
|
+
profile: Database['public']['Tables']['profiles']['Row'] | null;
|
|
34
|
+
};
|
|
35
|
+
import { DeleteUserButtonClient } from "./components/DeleteUserButton";
|
|
36
|
+
|
|
37
|
+
async function getUsersData(currentAdminId: string): Promise<UserWithProfile[]> {
|
|
38
|
+
// This needs to use a service role client to list all users from auth.users
|
|
39
|
+
const { createClient: createServiceRoleClient } = await import('@supabase/supabase-js');
|
|
40
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
41
|
+
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
42
|
+
|
|
43
|
+
if (!supabaseUrl || !serviceRoleKey) {
|
|
44
|
+
throw new Error('Missing required environment variables');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const supabaseAdmin = createServiceRoleClient(supabaseUrl, serviceRoleKey);
|
|
48
|
+
|
|
49
|
+
const { data: { users: authUsers }, error: usersError } = await supabaseAdmin.auth.admin.listUsers({
|
|
50
|
+
page: 1,
|
|
51
|
+
perPage: 1000, // Adjust as needed, handle pagination for very large user bases
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (usersError) {
|
|
55
|
+
console.error("Error fetching auth users:", usersError);
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
if (!authUsers) return [];
|
|
59
|
+
|
|
60
|
+
// Fetch all profiles
|
|
61
|
+
const supabase = createClient(); // Standard client for profile fetching (RLS applies)
|
|
62
|
+
const { data: profiles, error: profilesError } = await supabase
|
|
63
|
+
.from("profiles")
|
|
64
|
+
.select("*");
|
|
65
|
+
|
|
66
|
+
if (profilesError) {
|
|
67
|
+
console.error("Error fetching profiles:", profilesError);
|
|
68
|
+
// Continue without profiles if there's an error, or handle differently
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const profilesMap = new Map(profiles?.map(p => [p.id, p]));
|
|
72
|
+
|
|
73
|
+
return authUsers.map(authUser => {
|
|
74
|
+
// Simplify authUser to only include necessary fields to avoid sending too much data
|
|
75
|
+
const simplifiedAuthUser: AuthUser = {
|
|
76
|
+
id: authUser.id,
|
|
77
|
+
email: authUser.email,
|
|
78
|
+
created_at: authUser.created_at,
|
|
79
|
+
last_sign_in_at: authUser.last_sign_in_at,
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
authUser: simplifiedAuthUser,
|
|
83
|
+
profile: profilesMap.get(authUser.id) || null,
|
|
84
|
+
};
|
|
85
|
+
}).filter(user => user.authUser.id !== currentAdminId); // Filter out the current admin from the list
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default async function CmsUsersListPage() {
|
|
89
|
+
const supabase = createClient();
|
|
90
|
+
const { data: { user: currentAdmin } } = await supabase.auth.getUser();
|
|
91
|
+
|
|
92
|
+
if (!currentAdmin) {
|
|
93
|
+
// This should ideally be caught by middleware or layout auth checks
|
|
94
|
+
return <p>Access Denied. Not authenticated.</p>;
|
|
95
|
+
}
|
|
96
|
+
// Further check if current user is admin (already done by layout, but good for direct access attempts)
|
|
97
|
+
const { data: adminProfile } = await supabase.from('profiles').select('role').eq('id', currentAdmin.id).single();
|
|
98
|
+
if (adminProfile?.role !== 'ADMIN') {
|
|
99
|
+
return <p>Access Denied. Admin privileges required.</p>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const users = await getUsersData(currentAdmin.id);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="w-full">
|
|
106
|
+
<div className="flex justify-between items-center mb-6">
|
|
107
|
+
<h1 className="text-2xl font-semibold">Manage Users</h1>
|
|
108
|
+
{/* No "Create New User" button as users are created via sign-up flow. Admins manage roles. */}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{users.length === 0 ? (
|
|
112
|
+
<div className="text-center py-10 border rounded-lg">
|
|
113
|
+
<Users className="mx-auto h-12 w-12 text-muted-foreground" />
|
|
114
|
+
<h3 className="mt-2 text-sm font-medium text-foreground">No other users found</h3>
|
|
115
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
116
|
+
New users will appear here after they sign up.
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
) : (
|
|
120
|
+
<div className="rounded-lg border overflow-hidden">
|
|
121
|
+
<Table>
|
|
122
|
+
<TableHeader>
|
|
123
|
+
<TableRow>
|
|
124
|
+
<TableHead className="w-[80px]">Avatar</TableHead>
|
|
125
|
+
<TableHead>Email</TableHead>
|
|
126
|
+
<TableHead>Username</TableHead>
|
|
127
|
+
<TableHead>Full Name</TableHead>
|
|
128
|
+
<TableHead>Role</TableHead>
|
|
129
|
+
<TableHead>Joined</TableHead>
|
|
130
|
+
<TableHead className="text-right w-[80px]">Actions</TableHead>
|
|
131
|
+
</TableRow>
|
|
132
|
+
</TableHeader>
|
|
133
|
+
<TableBody>
|
|
134
|
+
{users.map(({ authUser, profile }) => (
|
|
135
|
+
<TableRow key={authUser.id}>
|
|
136
|
+
<TableCell>
|
|
137
|
+
<Avatar className="h-9 w-9">
|
|
138
|
+
<AvatarImage src={profile?.avatar_url || undefined} alt={profile?.username || authUser.email} />
|
|
139
|
+
<AvatarFallback>{authUser.email?.charAt(0).toUpperCase() || 'U'}</AvatarFallback>
|
|
140
|
+
</Avatar>
|
|
141
|
+
</TableCell>
|
|
142
|
+
<TableCell className="font-medium">{authUser.email}</TableCell>
|
|
143
|
+
<TableCell className="text-muted-foreground">{profile?.username || "N/A"}</TableCell>
|
|
144
|
+
<TableCell className="text-muted-foreground">{profile?.full_name || "N/A"}</TableCell>
|
|
145
|
+
<TableCell>
|
|
146
|
+
<Badge variant={
|
|
147
|
+
profile?.role === "ADMIN" ? "destructive" :
|
|
148
|
+
profile?.role === "WRITER" ? "secondary" : "outline"
|
|
149
|
+
}>
|
|
150
|
+
{profile?.role || "N/A"}
|
|
151
|
+
</Badge>
|
|
152
|
+
</TableCell>
|
|
153
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
154
|
+
{authUser.created_at ? new Date(authUser.created_at).toLocaleDateString() : "N/A"}
|
|
155
|
+
</TableCell>
|
|
156
|
+
<TableCell className="text-right">
|
|
157
|
+
<DropdownMenu>
|
|
158
|
+
<DropdownMenuTrigger asChild>
|
|
159
|
+
<Button variant="ghost" size="icon" disabled={authUser.id === currentAdmin.id}>
|
|
160
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
161
|
+
<span className="sr-only">User actions</span>
|
|
162
|
+
</Button>
|
|
163
|
+
</DropdownMenuTrigger>
|
|
164
|
+
<DropdownMenuContent align="end">
|
|
165
|
+
<DropdownMenuItem asChild>
|
|
166
|
+
<Link href={`/cms/users/${authUser.id}/edit`} className="flex items-center">
|
|
167
|
+
<Edit3 className="mr-2 h-4 w-4" /> Edit Role/Profile
|
|
168
|
+
</Link>
|
|
169
|
+
</DropdownMenuItem>
|
|
170
|
+
<DropdownMenuSeparator />
|
|
171
|
+
<DeleteUserButtonClient userId={authUser.id} userEmail={authUser.email} currentAdminId={currentAdmin.id} />
|
|
172
|
+
</DropdownMenuContent>
|
|
173
|
+
</DropdownMenu>
|
|
174
|
+
</TableCell>
|
|
175
|
+
</TableRow>
|
|
176
|
+
))}
|
|
177
|
+
</TableBody>
|
|
178
|
+
</Table>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
Binary file
|