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:
2025-08-12 22:31:26 +01:00
parent 978b9b26bc
commit aa10ea0b26
64 changed files with 12998 additions and 2 deletions

View 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>
);
}

View 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>
);
}

View 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>
);
}