Create Discord Management admin component

cgen-df0d81d8fe084790bbc3ad2f339764aa
This commit is contained in:
Builder.io 2025-11-08 13:44:29 +00:00
parent 2ef9b59fe9
commit 1cfe2ee740

View file

@ -0,0 +1,318 @@
import React, { useState, useEffect } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../ui/card";
import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogHeader,
AlertDialogTitle,
} from "../ui/alert-dialog";
interface RoleMapping {
id: string;
arm: string;
user_type?: string;
discord_role: string;
server_id?: string;
created_at: string;
}
const ARMS = [
{ value: "labs", label: "Labs", color: "bg-yellow-500/20" },
{ value: "gameforge", label: "GameForge", color: "bg-green-500/20" },
{ value: "corp", label: "Corp", color: "bg-blue-500/20" },
{ value: "foundation", label: "Foundation", color: "bg-red-500/20" },
{ value: "devlink", label: "Dev-Link", color: "bg-cyan-500/20" },
];
export function AdminDiscordManagement() {
const [mappings, setMappings] = useState<RoleMapping[]>([]);
const [loading, setLoading] = useState(true);
const [newMapping, setNewMapping] = useState({
arm: "labs",
discord_role: "",
server_id: "",
});
const [editingId, setEditingId] = useState<string | null>(null);
const [deleteId, setDeleteId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
useEffect(() => {
fetchMappings();
}, []);
const fetchMappings = async () => {
try {
setLoading(true);
const response = await fetch("/api/discord/role-mappings");
if (!response.ok) throw new Error("Failed to fetch mappings");
const data = await response.json();
setMappings(data);
setError(null);
} catch (err) {
console.error("Error fetching mappings:", err);
setError("Failed to load role mappings");
} finally {
setLoading(false);
}
};
const handleCreateMapping = async (e: React.FormEvent) => {
e.preventDefault();
if (!newMapping.discord_role) {
setError("Discord role is required");
return;
}
try {
const response = await fetch("/api/discord/role-mappings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newMapping),
});
if (!response.ok) throw new Error("Failed to create mapping");
const data = await response.json();
setMappings([...mappings, data]);
setNewMapping({ arm: "labs", discord_role: "", server_id: "" });
setSuccess("Role mapping created successfully!");
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
console.error("Error creating mapping:", err);
setError("Failed to create role mapping");
}
};
const handleDeleteMapping = async (id: string) => {
try {
const response = await fetch(`/api/discord/role-mappings?id=${id}`, {
method: "DELETE",
});
if (!response.ok) throw new Error("Failed to delete mapping");
setMappings(mappings.filter((m) => m.id !== id));
setSuccess("Role mapping deleted successfully!");
setTimeout(() => setSuccess(null), 3000);
} catch (err) {
console.error("Error deleting mapping:", err);
setError("Failed to delete role mapping");
} finally {
setDeleteId(null);
}
};
const getArmLabel = (armValue: string) => {
return ARMS.find((a) => a.value === armValue)?.label || armValue;
};
return (
<div className="space-y-6">
{/* Bot Status */}
<Card className="border-purple-500/30 bg-purple-950/20">
<CardHeader>
<CardTitle>Discord Bot Status</CardTitle>
<CardDescription>Real-time bot configuration</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-gradient-to-br from-green-500/10 to-green-600/5 p-4 rounded-lg border border-green-500/20">
<p className="text-sm text-green-300">Bot Status</p>
<p className="text-lg font-bold text-green-400 mt-1">Online</p>
</div>
<div className="bg-gradient-to-br from-blue-500/10 to-blue-600/5 p-4 rounded-lg border border-blue-500/20">
<p className="text-sm text-blue-300">Linked Accounts</p>
<p className="text-lg font-bold text-blue-400 mt-1">
{mappings.length > 0 ? "Active" : "0"}
</p>
</div>
<div className="bg-gradient-to-br from-purple-500/10 to-purple-600/5 p-4 rounded-lg border border-purple-500/20">
<p className="text-sm text-purple-300">Role Mappings</p>
<p className="text-lg font-bold text-purple-400 mt-1">
{mappings.length}
</p>
</div>
<div className="bg-gradient-to-br from-pink-500/10 to-pink-600/5 p-4 rounded-lg border border-pink-500/20">
<p className="text-sm text-pink-300">Servers</p>
<p className="text-lg font-bold text-pink-400 mt-1">6</p>
</div>
</div>
</CardContent>
</Card>
{/* Role Mappings */}
<Card className="border-purple-500/30 bg-purple-950/20">
<CardHeader>
<CardTitle>Discord Role Mappings</CardTitle>
<CardDescription>
Configure which Discord roles are assigned for each arm
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{error && (
<div className="p-3 bg-red-500/10 border border-red-500/30 rounded text-red-300 text-sm">
{error}
</div>
)}
{success && (
<div className="p-3 bg-green-500/10 border border-green-500/30 rounded text-green-300 text-sm">
{success}
</div>
)}
{/* Add New Mapping Form */}
<div className="bg-gradient-to-br from-purple-900/20 to-blue-900/20 p-4 rounded-lg border border-purple-500/20">
<h4 className="font-semibold mb-4">Add New Role Mapping</h4>
<form onSubmit={handleCreateMapping} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="text-sm text-gray-300">Arm</label>
<select
value={newMapping.arm}
onChange={(e) =>
setNewMapping({ ...newMapping, arm: e.target.value })
}
className="w-full mt-1 bg-gray-800 border border-purple-500/30 rounded px-3 py-2 text-white focus:outline-none focus:border-purple-500"
>
{ARMS.map((arm) => (
<option key={arm.value} value={arm.value}>
{arm.label}
</option>
))}
</select>
</div>
<div>
<label className="text-sm text-gray-300">
Discord Role Name
</label>
<input
type="text"
placeholder="e.g., Labs Member"
value={newMapping.discord_role}
onChange={(e) =>
setNewMapping({
...newMapping,
discord_role: e.target.value,
})
}
className="w-full mt-1 bg-gray-800 border border-purple-500/30 rounded px-3 py-2 text-white focus:outline-none focus:border-purple-500"
/>
</div>
<div>
<label className="text-sm text-gray-300">
Server ID (optional)
</label>
<input
type="text"
placeholder="Discord Server ID"
value={newMapping.server_id}
onChange={(e) =>
setNewMapping({ ...newMapping, server_id: e.target.value })
}
className="w-full mt-1 bg-gray-800 border border-purple-500/30 rounded px-3 py-2 text-white focus:outline-none focus:border-purple-500"
/>
</div>
</div>
<Button type="submit" className="w-full bg-purple-600 hover:bg-purple-700">
Add Mapping
</Button>
</form>
</div>
{/* Mappings Table */}
{loading ? (
<div className="text-center py-8 text-gray-400">
Loading mappings...
</div>
) : mappings.length === 0 ? (
<div className="text-center py-8 text-gray-400">
No role mappings configured yet. Add one above!
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="border-b border-purple-500/30">
<tr>
<th className="text-left py-2 px-3 text-purple-300">Arm</th>
<th className="text-left py-2 px-3 text-purple-300">
Discord Role
</th>
<th className="text-left py-2 px-3 text-purple-300">
Server ID
</th>
<th className="text-left py-2 px-3 text-purple-300">
Actions
</th>
</tr>
</thead>
<tbody>
{mappings.map((mapping) => (
<tr
key={mapping.id}
className="border-b border-purple-500/10 hover:bg-purple-500/5"
>
<td className="py-3 px-3">
<Badge variant="outline">
{getArmLabel(mapping.arm)}
</Badge>
</td>
<td className="py-3 px-3 text-gray-300">
{mapping.discord_role}
</td>
<td className="py-3 px-3 text-gray-400 text-xs">
{mapping.server_id || "Global"}
</td>
<td className="py-3 px-3">
<Button
variant="ghost"
size="sm"
className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
onClick={() => setDeleteId(mapping.id)}
>
Delete
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
{/* Delete Confirmation */}
<AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Role Mapping?</AlertDialogTitle>
<AlertDialogDescription>
This role mapping will be permanently deleted. Users will no longer
receive this role when setting this arm.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteId && handleDeleteMapping(deleteId)}
className="bg-red-600 hover:bg-red-700"
>
Delete
</AlertDialogAction>
</AlertDialogContent>
</AlertDialog>
</div>
);
}