- Add goals feature (models, migrations, API, web pages) - Add reserved/centralized wallet balance service - Add wallet detail page and overview components - Add new UI components (progress, multi-select, FAB) - Remove stray empty -H/-d files from working tree
210 lines
7.2 KiB
TypeScript
Executable File
210 lines
7.2 KiB
TypeScript
Executable File
import { useState, useEffect } from 'react'
|
|
import axios from 'axios'
|
|
import { Globe, Database, Save } from 'lucide-react'
|
|
import { toast } from 'sonner'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'
|
|
|
|
interface GeneralSettings {
|
|
appName: string
|
|
appUrl: string
|
|
supportEmail: string
|
|
maintenanceMode: boolean
|
|
maintenanceMessage: string
|
|
}
|
|
|
|
export function AdminSettingsGeneral() {
|
|
const [settings, setSettings] = useState<GeneralSettings>({
|
|
appName: 'Tabungin',
|
|
appUrl: 'https://tabungin.app',
|
|
supportEmail: 'support@tabungin.app',
|
|
maintenanceMode: false,
|
|
maintenanceMessage: 'System is under maintenance. Please try again later.',
|
|
})
|
|
const [loading, setLoading] = useState(true)
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
useEffect(() => {
|
|
fetchSettings()
|
|
}, [])
|
|
|
|
const fetchSettings = async () => {
|
|
try {
|
|
setLoading(true)
|
|
const token = localStorage.getItem('token')
|
|
const response = await axios.get(`${API_URL}/api/admin/config/by-category`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
|
|
const configData = response.data
|
|
const settingsObj: GeneralSettings = {
|
|
appName: configData.general?.find((c: any) => c.key === 'app_name')?.value || 'Tabungin',
|
|
appUrl: configData.general?.find((c: any) => c.key === 'app_url')?.value || 'https://tabungin.app',
|
|
supportEmail: configData.general?.find((c: any) => c.key === 'support_email')?.value || 'support@tabungin.app',
|
|
maintenanceMode: configData.system?.find((c: any) => c.key === 'maintenance_mode')?.value === 'true',
|
|
maintenanceMessage: configData.system?.find((c: any) => c.key === 'maintenance_message')?.value || 'System is under maintenance. Please try again later.',
|
|
}
|
|
setSettings(settingsObj)
|
|
} catch (error) {
|
|
console.error('Failed to fetch settings:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
setSaving(true)
|
|
const token = localStorage.getItem('token')
|
|
|
|
const configUpdates = [
|
|
{ key: 'app_name', value: settings.appName, category: 'general', label: 'Application Name', type: 'text' },
|
|
{ key: 'app_url', value: settings.appUrl, category: 'general', label: 'Application URL', type: 'text' },
|
|
{ key: 'support_email', value: settings.supportEmail, category: 'general', label: 'Support Email', type: 'email' },
|
|
{ key: 'maintenance_mode', value: String(settings.maintenanceMode), category: 'system', label: 'Maintenance Mode', type: 'boolean' },
|
|
{ key: 'maintenance_message', value: settings.maintenanceMessage, category: 'system', label: 'Maintenance Message', type: 'text' },
|
|
]
|
|
|
|
await Promise.all(
|
|
configUpdates.map((config) =>
|
|
axios.post(`${API_URL}/api/admin/config/${config.key}`, config, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
)
|
|
)
|
|
|
|
toast.success('Settings saved successfully')
|
|
fetchSettings()
|
|
} catch (error) {
|
|
console.error('Failed to save settings:', error)
|
|
toast.error('Failed to save settings')
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
const handleChange = (field: keyof GeneralSettings, value: string | boolean) => {
|
|
setSettings((prev) => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="text-muted-foreground">Loading...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* General Settings Card */}
|
|
<div className="bg-card rounded-xl border border-border p-6">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="p-2 rounded-lg bg-primary/10">
|
|
<Globe className="h-5 w-5 text-primary" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-foreground">General Settings</h2>
|
|
<p className="text-sm text-muted-foreground">Basic application information</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-foreground mb-2">
|
|
Application Name
|
|
</label>
|
|
<Input
|
|
type="text"
|
|
value={settings.appName}
|
|
onChange={(e) => handleChange('appName', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-foreground mb-2">
|
|
Application URL
|
|
</label>
|
|
<Input
|
|
type="url"
|
|
value={settings.appUrl}
|
|
onChange={(e) => handleChange('appUrl', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-foreground mb-2">
|
|
Support Email
|
|
</label>
|
|
<Input
|
|
type="email"
|
|
value={settings.supportEmail}
|
|
onChange={(e) => handleChange('supportEmail', e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Maintenance Mode Card */}
|
|
<div className="bg-card rounded-xl border border-border p-6">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="p-2 rounded-lg bg-primary/10">
|
|
<Database className="h-5 w-5 text-primary" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-foreground">Maintenance Mode</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Temporarily disable access for maintenance
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between p-4 rounded-lg bg-destructive/10 border border-destructive/20">
|
|
<div>
|
|
<p className="font-medium text-foreground">Maintenance Mode</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Enable to temporarily close access
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={settings.maintenanceMode}
|
|
onCheckedChange={(checked) => handleChange('maintenanceMode', checked)}
|
|
className="data-[state=checked]:bg-destructive"
|
|
/>
|
|
</div>
|
|
|
|
{settings.maintenanceMode && (
|
|
<div>
|
|
<label className="block text-sm font-medium text-foreground mb-2">
|
|
Maintenance Message
|
|
</label>
|
|
<Textarea
|
|
value={settings.maintenanceMessage}
|
|
onChange={(e) => handleChange('maintenanceMessage', e.target.value)}
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Save Button */}
|
|
<div className="flex justify-end">
|
|
<Button
|
|
onClick={handleSave}
|
|
disabled={saving}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<Save className="h-5 w-5" />
|
|
{saving ? 'Saving...' : 'Save Settings'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|