initial commit
This commit is contained in:
592
components/admin/SystemSettings.tsx
Normal file
592
components/admin/SystemSettings.tsx
Normal file
@@ -0,0 +1,592 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Settings, Save, RefreshCw, Database, Bell, Shield, Users, Activity } from 'lucide-react'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
|
||||
interface SystemSettingsData {
|
||||
maintenanceMode: boolean
|
||||
registrationEnabled: boolean
|
||||
emailVerificationRequired: boolean
|
||||
maxUserBalance: number
|
||||
defaultUserRole: 'user' | 'admin'
|
||||
systemMessage: string
|
||||
paymentGatewayEnabled: boolean
|
||||
developerHireEnabled: boolean
|
||||
vpsDeploymentEnabled: boolean
|
||||
kubernetesDeploymentEnabled: boolean
|
||||
vpnServiceEnabled: boolean
|
||||
lastUpdated: string
|
||||
updatedBy: string
|
||||
}
|
||||
|
||||
interface SystemStatistics {
|
||||
totalUsers: number
|
||||
adminUsers: number
|
||||
verifiedUsers: number
|
||||
unverifiedUsers: number
|
||||
}
|
||||
|
||||
interface RecentActivity {
|
||||
id: string
|
||||
action: string
|
||||
details: string
|
||||
timestamp: string
|
||||
adminName: string
|
||||
}
|
||||
|
||||
export default function SystemSettings() {
|
||||
const [settings, setSettings] = useState<SystemSettingsData | null>(null)
|
||||
const [statistics, setStatistics] = useState<SystemStatistics | null>(null)
|
||||
const [recentActivities, setRecentActivities] = useState<RecentActivity[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [notificationDialog, setNotificationDialog] = useState(false)
|
||||
const [notificationForm, setNotificationForm] = useState({
|
||||
message: '',
|
||||
targetUsers: 'all',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchSettings()
|
||||
}, [])
|
||||
|
||||
const fetchSettings = async () => {
|
||||
try {
|
||||
const token =
|
||||
localStorage.getItem('accessToken') ||
|
||||
document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('accessToken='))
|
||||
?.split('=')[1]
|
||||
|
||||
const response = await fetch('/api/admin/settings', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch settings')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setSettings(data.settings)
|
||||
setStatistics(data.statistics)
|
||||
setRecentActivities(data.recentActivities)
|
||||
} catch (error) {
|
||||
console.error('Settings fetch error:', error)
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to load system settings',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveSettings = async () => {
|
||||
if (!settings) return
|
||||
|
||||
setSaving(true)
|
||||
try {
|
||||
const token =
|
||||
localStorage.getItem('accessToken') ||
|
||||
document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('accessToken='))
|
||||
?.split('=')[1]
|
||||
|
||||
const response = await fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'updateSettings',
|
||||
settings,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update settings')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setSettings(data.settings)
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: data.message,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Settings update error:', error)
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to update settings',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSystemAction = async (action: string, additionalData?: any) => {
|
||||
try {
|
||||
const token =
|
||||
localStorage.getItem('accessToken') ||
|
||||
document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('accessToken='))
|
||||
?.split('=')[1]
|
||||
|
||||
const response = await fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
...additionalData,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to ${action}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: data.message,
|
||||
})
|
||||
|
||||
if (action === 'sendSystemNotification') {
|
||||
setNotificationDialog(false)
|
||||
setNotificationForm({ message: '', targetUsers: 'all' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${action} error:`, error)
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: `Failed to ${action}`,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSendNotification = () => {
|
||||
handleSystemAction('sendSystemNotification', notificationForm)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardContent className="p-4">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
|
||||
<div className="h-8 bg-gray-200 rounded w-1/3"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!settings || !statistics) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<p className="text-gray-500">Failed to load system settings</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* System Statistics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">Total Users</p>
|
||||
<p className="text-2xl font-bold">{statistics.totalUsers}</p>
|
||||
</div>
|
||||
<Users className="h-8 w-8 text-blue-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">Admin Users</p>
|
||||
<p className="text-2xl font-bold">{statistics.adminUsers}</p>
|
||||
</div>
|
||||
<Shield className="h-8 w-8 text-purple-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">Verified Users</p>
|
||||
<p className="text-2xl font-bold">{statistics.verifiedUsers}</p>
|
||||
</div>
|
||||
<Activity className="h-8 w-8 text-green-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">Unverified Users</p>
|
||||
<p className="text-2xl font-bold">{statistics.unverifiedUsers}</p>
|
||||
</div>
|
||||
<Activity className="h-8 w-8 text-orange-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* System Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Settings className="h-5 w-5 mr-2" />
|
||||
System Settings
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* General Settings */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">General</h3>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="maintenanceMode">Maintenance Mode</Label>
|
||||
<p className="text-sm text-gray-500">Disable access for non-admin users</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="maintenanceMode"
|
||||
checked={settings.maintenanceMode}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, maintenanceMode: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="registrationEnabled">Registration Enabled</Label>
|
||||
<p className="text-sm text-gray-500">Allow new user registrations</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="registrationEnabled"
|
||||
checked={settings.registrationEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, registrationEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="emailVerificationRequired">Email Verification Required</Label>
|
||||
<p className="text-sm text-gray-500">Require email verification for new users</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="emailVerificationRequired"
|
||||
checked={settings.emailVerificationRequired}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, emailVerificationRequired: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="maxUserBalance">Max User Balance (₹)</Label>
|
||||
<Input
|
||||
id="maxUserBalance"
|
||||
type="number"
|
||||
value={settings.maxUserBalance}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, maxUserBalance: parseInt(e.target.value) || 0 })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="defaultUserRole">Default User Role</Label>
|
||||
<Select
|
||||
value={settings.defaultUserRole}
|
||||
onValueChange={(value: 'user' | 'admin') =>
|
||||
setSettings({ ...settings, defaultUserRole: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="systemMessage">System Message</Label>
|
||||
<Textarea
|
||||
id="systemMessage"
|
||||
value={settings.systemMessage}
|
||||
onChange={(e) => setSettings({ ...settings, systemMessage: e.target.value })}
|
||||
placeholder="System-wide message for users..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Service Settings */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">Services</h3>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="paymentGatewayEnabled">Payment Gateway</Label>
|
||||
<p className="text-sm text-gray-500">Enable payment processing</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="paymentGatewayEnabled"
|
||||
checked={settings.paymentGatewayEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, paymentGatewayEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="developerHireEnabled">Developer Hire Service</Label>
|
||||
<p className="text-sm text-gray-500">Enable developer hiring</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="developerHireEnabled"
|
||||
checked={settings.developerHireEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, developerHireEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="vpsDeploymentEnabled">VPS Deployment</Label>
|
||||
<p className="text-sm text-gray-500">Enable VPS deployments</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="vpsDeploymentEnabled"
|
||||
checked={settings.vpsDeploymentEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, vpsDeploymentEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="kubernetesDeploymentEnabled">Kubernetes Deployment</Label>
|
||||
<p className="text-sm text-gray-500">Enable Kubernetes deployments</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="kubernetesDeploymentEnabled"
|
||||
checked={settings.kubernetesDeploymentEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, kubernetesDeploymentEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="vpnServiceEnabled">VPN Service</Label>
|
||||
<p className="text-sm text-gray-500">Enable VPN services</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="vpnServiceEnabled"
|
||||
checked={settings.vpnServiceEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings({ ...settings, vpnServiceEnabled: checked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSaveSettings} disabled={saving}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
{saving ? 'Saving...' : 'Save Settings'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{settings.lastUpdated && (
|
||||
<div className="text-sm text-gray-500 border-t pt-4">
|
||||
Last updated{' '}
|
||||
{settings.lastUpdated
|
||||
? formatDistanceToNow(new Date(settings.lastUpdated), { addSuffix: true })
|
||||
: 'N/A'}
|
||||
by {settings.updatedBy}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* System Actions & Recent Activity */}
|
||||
<div className="space-y-6">
|
||||
{/* System Actions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>System Actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start"
|
||||
onClick={() => handleSystemAction('clearCache')}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Clear System Cache
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start"
|
||||
onClick={() => handleSystemAction('backupDatabase')}
|
||||
>
|
||||
<Database className="h-4 w-4 mr-2" />
|
||||
Backup Database
|
||||
</Button>
|
||||
|
||||
<Dialog open={notificationDialog} onOpenChange={setNotificationDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="w-full justify-start">
|
||||
<Bell className="h-4 w-4 mr-2" />
|
||||
Send System Notification
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Send System Notification</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="message">Message</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
value={notificationForm.message}
|
||||
onChange={(e) =>
|
||||
setNotificationForm({ ...notificationForm, message: e.target.value })
|
||||
}
|
||||
placeholder="Enter notification message..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="targetUsers">Target Users</Label>
|
||||
<Select
|
||||
value={notificationForm.targetUsers}
|
||||
onValueChange={(value) =>
|
||||
setNotificationForm({ ...notificationForm, targetUsers: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Users</SelectItem>
|
||||
<SelectItem value="verified">Verified Users</SelectItem>
|
||||
<SelectItem value="unverified">Unverified Users</SelectItem>
|
||||
<SelectItem value="admins">Admin Users</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button variant="outline" onClick={() => setNotificationDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSendNotification}>Send Notification</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Activities */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Activities</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{recentActivities.length === 0 ? (
|
||||
<p className="text-gray-500 text-sm">No recent activities</p>
|
||||
) : (
|
||||
recentActivities.map((activity) => (
|
||||
<div key={activity.id} className="border-b last:border-b-0 pb-3 last:pb-0">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-gray-900">{activity.action}</p>
|
||||
<p className="text-sm text-gray-600">{activity.details}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
by {activity.adminName} •{' '}
|
||||
{activity.timestamp
|
||||
? formatDistanceToNow(new Date(activity.timestamp), {
|
||||
addSuffix: true,
|
||||
})
|
||||
: 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user