Files
digicraft-fe/pages/ConfigPage.tsx
Fahri Can Seçer 6e3bee17ef
Some checks failed
Deploy Frontend / deploy (push) Has been cancelled
main
2026-02-05 01:34:13 +03:00

185 lines
10 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Layout from '../components/Layout';
import BrandKit from '../components/BrandKit';
import { Settings, Save, Plus, Trash2, ToggleLeft, ToggleRight, Search } from 'lucide-react';
interface ConfigItem {
key: string;
value: string;
description?: string;
}
export default function ConfigPage() {
const [configs, setConfigs] = useState<ConfigItem[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
const [newItem, setNewItem] = useState({ key: '', value: '', description: '' });
const [editingItem, setEditingItem] = useState<string | null>(null);
useEffect(() => {
fetchConfigs();
}, []);
const fetchConfigs = async () => {
try {
const res = await axios.get('/api/admin/config');
setConfigs(res.data.configs);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
const handleSave = async (key: string, value: string, description?: string) => {
try {
await axios.put('/api/admin/config', { key, value, description });
setEditingItem(null);
fetchConfigs();
if (key === newItem.key) setNewItem({ key: '', value: '', description: '' });
} catch (err) {
alert('Failed to save config');
}
};
const filteredConfigs = (configs || []).filter(c =>
c.key.toLowerCase().includes(search.toLowerCase()) ||
(c.description || '').toLowerCase().includes(search.toLowerCase())
);
return (
<Layout>
<div className="max-w-5xl mx-auto p-8">
<div className="flex justify-between items-end mb-8">
<div>
<h1 className="text-3xl font-black text-stone-900 tracking-tight flex items-center gap-3">
<Settings className="w-8 h-8 text-stone-400" />
System Configuration
</h1>
<p className="text-stone-500 mt-2">Manage global variables, feature flags, and system limits.</p>
</div>
</div>
<BrandKit />
{/* New Config Card */}
<div className="bg-white border border-stone-200 shadow-sm rounded-2xl p-6 mb-8 group focus-within:ring-2 ring-purple-500/20 transition-all">
<h3 className="text-xs font-bold text-stone-400 uppercase tracking-widest mb-4 flex items-center gap-2">
<Plus className="w-4 h-4" /> Add New Variable
</h3>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<input
placeholder="KEY_NAME (e.g. MAX_TOKENS)"
className="bg-stone-50 border border-stone-200 rounded-xl px-4 py-2 text-sm font-mono font-bold focus:outline-none focus:border-purple-500 transition-all"
value={newItem.key}
onChange={e => setNewItem({ ...newItem, key: e.target.value.toUpperCase() })}
/>
<input
placeholder="Value"
className="bg-stone-50 border border-stone-200 rounded-xl px-4 py-2 text-sm focus:outline-none focus:border-purple-500 transition-all"
value={newItem.value}
onChange={e => setNewItem({ ...newItem, value: e.target.value })}
/>
<input
placeholder="Description (Optional)"
className="bg-stone-50 border border-stone-200 rounded-xl px-4 py-2 text-sm focus:outline-none focus:border-purple-500 transition-all"
value={newItem.description}
onChange={e => setNewItem({ ...newItem, description: e.target.value })}
/>
<button
disabled={!newItem.key || !newItem.value}
onClick={() => handleSave(newItem.key, newItem.value, newItem.description)}
className="bg-stone-900 text-white rounded-xl font-bold text-sm hover:bg-purple-600 disabled:opacity-50 disabled:hover:bg-stone-900 transition-colors flex items-center justify-center gap-2"
>
<Save className="w-4 h-4" /> Save Variable
</button>
</div>
</div>
{/* Search Bar */}
<div className="relative mb-6">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-stone-400" />
<input
type="text"
placeholder="Search configuration keys..."
className="w-full pl-10 pr-4 py-3 bg-white border border-stone-200 rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-stone-100 transition-all"
value={search}
onChange={e => setSearch(e.target.value)}
/>
</div>
{/* Config Table */}
<div className="bg-white border border-stone-200 shadow-sm rounded-3xl overflow-hidden">
<table className="w-full">
<thead className="bg-stone-50 border-b border-stone-100">
<tr>
<th className="text-left py-4 px-6 text-[10px] font-black uppercase tracking-widest text-stone-400">Key Name</th>
<th className="text-left py-4 px-6 text-[10px] font-black uppercase tracking-widest text-stone-400">Value</th>
<th className="text-left py-4 px-6 text-[10px] font-black uppercase tracking-widest text-stone-400">Description</th>
<th className="text-right py-4 px-6 text-[10px] font-black uppercase tracking-widest text-stone-400">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-stone-100">
{filteredConfigs.map(config => (
<tr key={config.key} className="hover:bg-stone-50/50 transition-colors group">
<td className="py-4 px-6 font-mono text-xs font-bold text-purple-600 select-all">
{config.key}
</td>
<td className="py-4 px-6">
{editingItem === config.key ? (
<input
defaultValue={config.value}
className="w-full bg-white border border-purple-200 rounded-lg px-2 py-1 text-sm focus:outline-none"
onBlur={(e) => handleSave(config.key, e.target.value, config.description)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSave(config.key, e.currentTarget.value, config.description);
}}
autoFocus
/>
) : (
<button
onClick={() => setEditingItem(config.key)}
className="text-sm font-medium text-stone-700 hover:text-purple-600 border-b border-stone-200 border-dashed hover:border-purple-300 transition-colors text-left"
>
{config.value === 'true' || config.value === 'false' ? (
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-[10px] uppercase font-black tracking-wider ${config.value === 'true' ? 'bg-green-100 text-green-700' : 'bg-red-50 text-red-500'}`}>
{config.value === 'true' ? <ToggleRight className="w-4 h-4" /> : <ToggleLeft className="w-4 h-4" />}
{config.value.toUpperCase()}
</span>
) : (
<span className="truncate max-w-[200px] block" title={config.value}>{config.value}</span>
)}
</button>
)}
</td>
<td className="py-4 px-6 text-xs text-stone-400 font-medium">
{config.description || '-'}
</td>
<td className="py-4 px-6 text-right">
<button
onClick={() => setEditingItem(config.key)}
className="p-2 text-stone-300 hover:text-stone-900 transition-colors"
>
<Settings className="w-4 h-4" />
</button>
</td>
</tr>
))}
{filteredConfigs.length === 0 && (
<tr>
<td colSpan={4} className="py-10 text-center text-stone-400 text-sm">
No configuration keys found.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</Layout>
);
}