diff --git a/admin/src/app/usernames/page.tsx b/admin/src/app/usernames/page.tsx new file mode 100644 index 0000000..0f51f30 --- /dev/null +++ b/admin/src/app/usernames/page.tsx @@ -0,0 +1,458 @@ +'use client'; + +import AdminShell from '@/components/AdminShell'; +import { api } from '@/lib/api'; +import { useEffect, useState } from 'react'; +import { AtSign, Plus, Trash2, Search, Check, X, Clock, ChevronDown, Upload } from 'lucide-react'; + +const CATEGORIES = [ + { value: '', label: 'All Categories' }, + { value: 'platform', label: 'Platform' }, + { value: 'brand', label: 'Brand' }, + { value: 'public_figure', label: 'Public Figure' }, + { value: 'custom', label: 'Custom' }, +]; + +export default function UsernamesPage() { + const [tab, setTab] = useState<'reserved' | 'claims'>('reserved'); + + return ( + +
+

Username Management

+

Manage reserved usernames and review claim requests

+
+ + {/* Tab Switcher */} +
+ + +
+ + {tab === 'reserved' ? : } +
+ ); +} + +// ─── Reserved Usernames Tab ─────────────────────────────── + +function ReservedTab() { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(true); + const [search, setSearch] = useState(''); + const [category, setCategory] = useState(''); + const [showAdd, setShowAdd] = useState(false); + const [showBulk, setShowBulk] = useState(false); + const [addForm, setAddForm] = useState({ username: '', category: 'custom', reason: '' }); + const [bulkForm, setBulkForm] = useState({ text: '', category: 'custom', reason: '' }); + const [saving, setSaving] = useState(false); + const [offset, setOffset] = useState(0); + const limit = 50; + + const load = () => { + setLoading(true); + api.listReservedUsernames({ search: search || undefined, category: category || undefined, limit, offset }) + .then((data) => { + setItems(data.reserved_usernames || []); + setTotal(data.total || 0); + }) + .catch(() => {}) + .finally(() => setLoading(false)); + }; + + useEffect(() => { load(); }, [search, category, offset]); + + const handleAdd = async () => { + if (!addForm.username.trim()) return; + setSaving(true); + try { + await api.addReservedUsername(addForm); + setAddForm({ username: '', category: 'custom', reason: '' }); + setShowAdd(false); + load(); + } catch (e: any) { + alert(e.message); + } finally { + setSaving(false); + } + }; + + const handleBulkAdd = async () => { + const usernames = bulkForm.text.split('\n').map(u => u.trim()).filter(Boolean); + if (usernames.length === 0) return; + setSaving(true); + try { + const res = await api.bulkAddReservedUsernames({ usernames, category: bulkForm.category, reason: bulkForm.reason }); + alert(res.message); + setBulkForm({ text: '', category: 'custom', reason: '' }); + setShowBulk(false); + load(); + } catch (e: any) { + alert(e.message); + } finally { + setSaving(false); + } + }; + + const handleRemove = async (id: string, username: string) => { + if (!confirm(`Remove "${username}" from reserved list?`)) return; + try { + await api.removeReservedUsername(id); + load(); + } catch (e: any) { + alert(e.message); + } + }; + + return ( +
+ {/* Actions Bar */} +
+
+ + { setSearch(e.target.value); setOffset(0); }} + className="w-full pl-9 pr-3 py-2 text-sm border border-warm-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500" + /> +
+ + + +
+ + {/* Add Form */} + {showAdd && ( +
+

Add Reserved Username

+
+ setAddForm({ ...addForm, username: e.target.value.toLowerCase() })} + className="text-sm border border-warm-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brand-500" + /> + + setAddForm({ ...addForm, reason: e.target.value })} + className="text-sm border border-warm-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brand-500" + /> + +
+
+ )} + + {/* Bulk Add Form */} + {showBulk && ( +
+

Bulk Add Reserved Usernames

+

One username per line

+