"use client"; import { Pencil, Plus, Save, Search, Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { addBlacklistWord as addBlacklistWordAction, createPrefix, deletePrefix as deletePrefixAction, removeBlacklistWord as removeBlacklistWordAction, updatePrefix, updatePrefixSettings, } from "@/actions/prefixes"; import { useConfirmDialog } from "@/components/admin/confirm-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useServerAction } from "@/hooks/use-server-action"; import { formatDate } from "@/lib/format-date"; import { PrefixDialog } from "./prefix-dialog"; import { type BlacklistWord, PRESET_EFFECTS, type PrefixFormData, type PrefixSettings, type UserPrefix, } from "./prefix-model"; export function PrefixesClient({ canEdit }: { canEdit: boolean }) { const t = useTranslations("pages.admin.prefixes.prefixesClient"); const { run, isPending } = useServerAction(); const { confirm, dialog: confirmDialog } = useConfirmDialog(); const [prefixes, setPrefixes] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [blacklist, setBlacklist] = useState([]); const [newWord, setNewWord] = useState(""); const [settings, setSettings] = useState({ max_length: "15", min_rank_to_buy: "1", price_credits: "5", price_points: "0", points_type: "0", }); const [dialogOpen, setDialogOpen] = useState(false); const [editPrefix, setEditPrefix] = useState(null); const fetchPrefixes = useCallback(async () => { const params = new URLSearchParams({ page: String(page) }); if (searchQuery) params.set("q", searchQuery); const res = await fetch(`/api/admin/prefixes?${params}`); if (!res.ok) return; const data = await res.json(); if (data.ok) { setPrefixes(data.prefixes); setTotalPages(data.pages); } }, [page, searchQuery]); const fetchBlacklist = useCallback(async () => { const res = await fetch("/api/admin/prefixes/blacklist"); if (!res.ok) return; const data = await res.json(); if (data.ok) setBlacklist(data.words); }, []); const fetchSettings = useCallback(async () => { const res = await fetch("/api/admin/prefixes/settings"); if (!res.ok) return; const data = await res.json(); if (data.ok) setSettings(data.settings); }, []); useEffect(() => { fetchPrefixes(); }, [fetchPrefixes]); useEffect(() => { fetchBlacklist(); fetchSettings(); }, [fetchBlacklist, fetchSettings]); const handleDeletePrefix = async (id: number) => { const ok = await confirm({ title: t("confirmDeleteTitle"), description: t("confirmDeleteDesc"), confirmLabel: t("confirmDelete"), cancelLabel: t("cancel"), }); if (!ok) return; run(() => deletePrefixAction({ id }), { successMessage: t("successPrefixDeleted"), onSuccess: () => { fetchPrefixes(); }, }); }; const handleSavePrefix = ( data: PrefixFormData, editId?: number, ): Promise => { if (isPending) return Promise.resolve(false); return new Promise((resolve) => { const fields = { text: data.text, color: data.color, icon: data.icon, effect: data.effect, active: data.active ? 1 : 0, }; run( () => editId ? updatePrefix({ id: editId, ...fields }) : createPrefix({ username: data.username, ...fields }), { successMessage: editId ? t("successPrefixUpdated") : t("successPrefixCreated"), onSuccess: () => { void fetchPrefixes(); resolve(true); }, onError: () => resolve(false), }, ); }); }; const openAddDialog = () => { setEditPrefix(null); setDialogOpen(true); }; const openEditDialog = (prefix: UserPrefix) => { setEditPrefix(prefix); setDialogOpen(true); }; const handleAddBlacklistWord = async () => { if (!newWord.trim()) return; run(() => addBlacklistWordAction({ word: newWord.trim() }), { successMessage: t("successWordAdded"), onSuccess: () => { setNewWord(""); fetchBlacklist(); }, }); }; const handleRemoveBlacklistWord = async (id: number) => { run(() => removeBlacklistWordAction({ id }), { successMessage: t("successWordRemoved"), onSuccess: () => { fetchBlacklist(); }, }); }; const saveSettings = async () => { run( () => updatePrefixSettings({ settings: settings as unknown as Record, }), { successMessage: t("successSettingsSaved"), }, ); }; return ( <> {confirmDialog} setDialogOpen(false)} onSave={handleSavePrefix} /> {t("tabUserPrefixes")} {t("tabBlacklist")} {t("tabSettings")}
{ setSearchQuery(e.target.value); setPage(1); }} />
{canEdit && ( )}
ID User Prefix Color Icon Effect Active Date {canEdit && Actions} {prefixes.map((prefix) => { const colors = prefix.color.split(","); const hasMultiColor = colors.length > 1 && new Set(colors).size > 1; const prefixIcon = prefix.icon || ""; return ( {prefix.id} {prefix.username || `#${prefix.user_id}`} {hasMultiColor ? ( <> {"{"} {[...prefix.text].map((char, i) => ( {char} ))} {"}"} ) : ( {"{"} {prefix.text} {"}"} )}
{colors.slice(0, 5).map((c, i) => (
))} {colors.length > 5 && ( +{colors.length - 5} )}
{prefixIcon || "-"} {prefix.effect ? PRESET_EFFECTS.find((fx) => fx.id === prefix.effect) ?.label || prefix.effect : "-"} {prefix.active ? "Yes" : "No"} {prefix.created_at ? formatDate(new Date(prefix.created_at), "date") : "—"} {canEdit && (
)} ); })} {prefixes.length === 0 && ( No prefixes found )}
{totalPages > 1 && (
Page {page} of {totalPages}
)}
Forbidden Words {canEdit && (
setNewWord(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleAddBlacklistWord() } />
)}
{blacklist.map((word) => (
{word.word} {canEdit && ( )}
))} {blacklist.length === 0 && (

No blacklist words

)}
Prefix Settings
Max Length setSettings({ ...settings, max_length: e.target.value }) } />
Minimum Rank setSettings({ ...settings, min_rank_to_buy: e.target.value, }) } />
Price (Credits) setSettings({ ...settings, price_credits: e.target.value, }) } />
Price (Points) setSettings({ ...settings, price_points: e.target.value }) } />
Points Type setSettings({ ...settings, points_type: e.target.value }) } />

0 = Duckets, 5 = Diamonds, etc.

{canEdit && ( )}
); }