Update README.md to provide a comprehensive overview of the ScriptShare platform, including features, tech stack, setup instructions, admin capabilities, and contribution guidelines.
This commit is contained in:
238
src/components/admin/AdminDashboard.tsx
Normal file
238
src/components/admin/AdminDashboard.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Users,
|
||||
FileText,
|
||||
MessageSquare,
|
||||
TrendingUp,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
BarChart3
|
||||
} from 'lucide-react';
|
||||
|
||||
interface AdminDashboardProps {
|
||||
onCreateUser: () => void;
|
||||
onViewScripts: () => void;
|
||||
onViewAnalytics: () => void;
|
||||
}
|
||||
|
||||
export function AdminDashboard({ onCreateUser, onViewScripts, onViewAnalytics }: AdminDashboardProps) {
|
||||
// Mock data - in a real app, this would come from API calls
|
||||
const stats = {
|
||||
totalUsers: 1247,
|
||||
totalScripts: 89,
|
||||
pendingApprovals: 12,
|
||||
totalComments: 456,
|
||||
activeUsers: 234,
|
||||
scriptsThisWeek: 8,
|
||||
commentsThisWeek: 23,
|
||||
systemHealth: 'healthy'
|
||||
};
|
||||
|
||||
const recentActivity = [
|
||||
{ id: 1, type: 'script', action: 'submitted', title: 'Docker Setup Script', user: 'john_doe', time: '2 hours ago' },
|
||||
{ id: 2, type: 'comment', action: 'reported', title: 'Comment on Backup Script', user: 'jane_smith', time: '4 hours ago' },
|
||||
{ id: 3, type: 'user', action: 'registered', title: 'New user account', user: 'alex_wilson', time: '6 hours ago' },
|
||||
{ id: 4, type: 'script', action: 'approved', title: 'Network Monitor', user: 'admin', time: '1 day ago' },
|
||||
];
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'script': return <FileText className="h-4 w-4" />;
|
||||
case 'comment': return <MessageSquare className="h-4 w-4" />;
|
||||
case 'user': return <Users className="h-4 w-4" />;
|
||||
default: return <BarChart3 className="h-4 w-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getActivityColor = (type: string) => {
|
||||
switch (type) {
|
||||
case 'script': return 'text-blue-600';
|
||||
case 'comment': return 'text-green-600';
|
||||
case 'user': return 'text-purple-600';
|
||||
default: return 'text-gray-600';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Users</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{stats.totalUsers.toLocaleString()}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+{stats.activeUsers} active this month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Scripts</CardTitle>
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{stats.totalScripts}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+{stats.scriptsThisWeek} this week
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Pending Approvals</CardTitle>
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-amber-600">{stats.pendingApprovals}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Require attention
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">System Health</CardTitle>
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-green-600 capitalize">{stats.systemHealth}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
All systems operational
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Actions</CardTitle>
|
||||
<CardDescription>
|
||||
Common administrative tasks and shortcuts.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Button
|
||||
onClick={onCreateUser}
|
||||
className="h-20 flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<Users className="h-6 w-6" />
|
||||
<span>Create Admin User</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={onViewScripts}
|
||||
variant="outline"
|
||||
className="h-20 flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<FileText className="h-6 w-6" />
|
||||
<span>Review Scripts</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={onViewAnalytics}
|
||||
variant="outline"
|
||||
className="h-20 flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<BarChart3 className="h-6 w-6" />
|
||||
<span>View Analytics</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Activity</CardTitle>
|
||||
<CardDescription>
|
||||
Latest platform activities requiring attention.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{recentActivity.map((activity) => (
|
||||
<div key={activity.id} className="flex items-center gap-3 p-3 rounded-lg hover:bg-muted/50 transition-colors">
|
||||
<div className={`p-2 rounded-full bg-muted ${getActivityColor(activity.type)}`}>
|
||||
{getActivityIcon(activity.type)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">
|
||||
{activity.title}
|
||||
</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{activity.action}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
by @{activity.user} • {activity.time}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="ghost" size="sm">
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* System Status */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>System Status</CardTitle>
|
||||
<CardDescription>
|
||||
Current platform status and alerts.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">Database</span>
|
||||
</div>
|
||||
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||
Operational
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">File Storage</span>
|
||||
</div>
|
||||
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||
Operational
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="font-medium text-green-800 dark:text-green-200">Email Service</span>
|
||||
</div>
|
||||
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||
Operational
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
171
src/components/admin/AdminUsersList.tsx
Normal file
171
src/components/admin/AdminUsersList.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Crown, Shield, User, Trash2, Edit, MoreHorizontal } from 'lucide-react';
|
||||
import { AdminUser } from '@/lib/admin';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
|
||||
export function AdminUsersList() {
|
||||
const [adminUsers, setAdminUsers] = useState<AdminUser[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Load admin users from localStorage (in a real app, this would be an API call)
|
||||
const loadAdminUsers = () => {
|
||||
try {
|
||||
const users = JSON.parse(localStorage.getItem('scriptshare-admin-users') || '[]');
|
||||
setAdminUsers(users);
|
||||
} catch (error) {
|
||||
console.error('Failed to load admin users:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadAdminUsers();
|
||||
}, []);
|
||||
|
||||
const handleDeleteUser = (userId: string) => {
|
||||
if (window.confirm('Are you sure you want to delete this admin user? This action cannot be undone.')) {
|
||||
const updatedUsers = adminUsers.filter(user => user.id !== userId);
|
||||
setAdminUsers(updatedUsers);
|
||||
localStorage.setItem('scriptshare-admin-users', JSON.stringify(updatedUsers));
|
||||
}
|
||||
};
|
||||
|
||||
const getPermissionBadges = (user: AdminUser) => {
|
||||
const badges = [];
|
||||
|
||||
if (user.isSuperUser) {
|
||||
badges.push(
|
||||
<Badge key="super" variant="default" className="bg-amber-600 hover:bg-amber-700">
|
||||
<Crown className="h-3 w-3 mr-1" />
|
||||
Super User
|
||||
</Badge>
|
||||
);
|
||||
} else if (user.isAdmin) {
|
||||
badges.push(
|
||||
<Badge key="admin" variant="secondary">
|
||||
<Shield className="h-3 w-3 mr-1" />
|
||||
Admin
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (user.isModerator) {
|
||||
badges.push(
|
||||
<Badge key="mod" variant="outline">
|
||||
<User className="h-3 w-3 mr-1" />
|
||||
Moderator
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
return badges;
|
||||
};
|
||||
|
||||
const getInitials = (displayName: string) => {
|
||||
return displayName
|
||||
.split(' ')
|
||||
.map(name => name[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-8">
|
||||
<div className="text-center text-muted-foreground">
|
||||
Loading admin users...
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (adminUsers.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-8">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Shield className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<h3 className="text-lg font-medium mb-2">No Admin Users Found</h3>
|
||||
<p className="text-sm">
|
||||
Create your first admin user to get started with platform management.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Shield className="h-5 w-5" />
|
||||
Admin Users ({adminUsers.length})
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Manage platform administrators and moderators.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{adminUsers.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage src={user.avatarUrl} alt={user.displayName} />
|
||||
<AvatarFallback className="text-sm font-medium">
|
||||
{getInitials(user.displayName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium">{user.displayName}</h4>
|
||||
{getPermissionBadges(user)}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>@{user.username} • {user.email}</div>
|
||||
{user.bio && <div className="max-w-md truncate">{user.bio}</div>}
|
||||
<div className="text-xs">
|
||||
Created {formatDate(user.createdAt)} • Last updated {formatDate(user.updatedAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteUser(user.id)}
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
225
src/components/admin/CreateAdminForm.tsx
Normal file
225
src/components/admin/CreateAdminForm.tsx
Normal file
@ -0,0 +1,225 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Shield, Crown, UserPlus } from 'lucide-react';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { showSuccess, showError } from '@/utils/toast';
|
||||
|
||||
interface CreateAdminFormProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function CreateAdminForm({ onSuccess }: CreateAdminFormProps) {
|
||||
const { createSuperUser, createAdminUser } = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSuperUser, setIsSuperUser] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
username: '',
|
||||
displayName: '',
|
||||
password: '',
|
||||
bio: '',
|
||||
avatarUrl: '',
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const success = isSuperUser
|
||||
? await createSuperUser(formData)
|
||||
: await createAdminUser(formData);
|
||||
|
||||
if (success) {
|
||||
showSuccess(
|
||||
isSuperUser
|
||||
? 'Super user created successfully!'
|
||||
: 'Admin user created successfully!'
|
||||
);
|
||||
setFormData({
|
||||
email: '',
|
||||
username: '',
|
||||
displayName: '',
|
||||
password: '',
|
||||
bio: '',
|
||||
avatarUrl: '',
|
||||
});
|
||||
onSuccess?.();
|
||||
} else {
|
||||
showError('Failed to create user. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('An error occurred while creating the user.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<UserPlus className="h-5 w-5" />
|
||||
Create {isSuperUser ? 'Super User' : 'Admin User'}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Create a new {isSuperUser ? 'super user' : 'admin user'} with elevated permissions.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant={isSuperUser ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setIsSuperUser(true)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Crown className="h-4 w-4" />
|
||||
Super User
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={!isSuperUser ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setIsSuperUser(false)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Shield className="h-4 w-4" />
|
||||
Admin User
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isSuperUser && (
|
||||
<div className="p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-amber-800 dark:text-amber-200">
|
||||
<Crown className="h-4 w-4" />
|
||||
<span className="font-medium">Super User Permissions</span>
|
||||
</div>
|
||||
<p className="text-sm text-amber-700 dark:text-amber-300 mt-1">
|
||||
Super users have full system access including user management, system configuration, and all moderation capabilities.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="email" className="text-sm font-medium">
|
||||
Email *
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
placeholder="admin@example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="username" className="text-sm font-medium">
|
||||
Username *
|
||||
</label>
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
required
|
||||
value={formData.username}
|
||||
onChange={handleInputChange}
|
||||
placeholder="admin_user"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="displayName" className="text-sm font-medium">
|
||||
Display Name *
|
||||
</label>
|
||||
<Input
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
type="text"
|
||||
required
|
||||
value={formData.displayName}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Admin User"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="password" className="text-sm font-medium">
|
||||
Password *
|
||||
</label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="bio" className="text-sm font-medium">
|
||||
Bio
|
||||
</label>
|
||||
<textarea
|
||||
id="bio"
|
||||
name="bio"
|
||||
rows={3}
|
||||
value={formData.bio}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Brief description about this user..."
|
||||
className="w-full px-3 py-2 border border-input bg-background rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="avatarUrl" className="text-sm font-medium">
|
||||
Avatar URL
|
||||
</label>
|
||||
<Input
|
||||
id="avatarUrl"
|
||||
name="avatarUrl"
|
||||
type="url"
|
||||
value={formData.avatarUrl}
|
||||
onChange={handleInputChange}
|
||||
placeholder="https://example.com/avatar.jpg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={isSuperUser ? "default" : "secondary"}>
|
||||
{isSuperUser ? "Super User" : "Admin User"}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{isSuperUser ? "Full system access" : "Moderation access"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Creating...' : `Create ${isSuperUser ? 'Super User' : 'Admin User'}`}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user