feat(agents, config, dashboard, i18n): add agent line provision wizard, site deletion, and site dashboard with multi-language support
Added agent line provision wizard page with permission gating, replacing redirect placeholder. Introduced site deletion API and UI with confirmation dialog in integration sites management. Added new site-scoped dashboard panel showing bet metrics, P/L trends, active players, and quick links. Enhanced chart tooltip to support custom formatters and fix indicator color
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Copy, Download, Link2, Pencil, ShieldAlert } from "lucide-react";
|
||||
import { Copy, Download, Link2, Pencil, ShieldAlert, Trash2 } from "lucide-react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAsyncEffect } from "@/hooks/use-async-effect";
|
||||
@@ -8,6 +8,7 @@ import { useTranslationRef } from "@/hooks/use-translation-ref";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
deleteAdminIntegrationSite,
|
||||
getAdminIntegrationSite,
|
||||
getAdminIntegrationSiteExport,
|
||||
getAdminIntegrationSites,
|
||||
@@ -243,6 +244,8 @@ export function IntegrationSitesConsole({
|
||||
const [editingId, setEditingId] = useState<number | null>(null);
|
||||
const [form, setForm] = useState<FormState>(EMPTY_FORM);
|
||||
const [rotateTarget, setRotateTarget] = useState<AdminIntegrationSiteRow | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<AdminIntegrationSiteRow | null>(null);
|
||||
const [deleteBusy, setDeleteBusy] = useState(false);
|
||||
const [rotateBusy, setRotateBusy] = useState(false);
|
||||
const [secretsDialog, setSecretsDialog] = useState<{
|
||||
siteCode: string;
|
||||
@@ -388,6 +391,25 @@ export function IntegrationSitesConsole({
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(): Promise<void> {
|
||||
if (!deleteTarget || !canManage) return;
|
||||
|
||||
setDeleteBusy(true);
|
||||
try {
|
||||
await deleteAdminIntegrationSite(deleteTarget.id);
|
||||
toast.success(t("integrationSites.deleteSuccess", { code: deleteTarget.code }));
|
||||
secretsCacheRef.current.delete(deleteTarget.id);
|
||||
setDeleteTarget(null);
|
||||
await load();
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof LotteryApiBizError ? error.message : t("integrationSites.deleteFailed"),
|
||||
);
|
||||
} finally {
|
||||
setDeleteBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openConnectivity(row: AdminIntegrationSiteRow): void {
|
||||
setConnectivityTarget(row);
|
||||
setConnectivityPlayerId("10001");
|
||||
@@ -519,7 +541,13 @@ export function IntegrationSitesConsole({
|
||||
{loading ? (
|
||||
<AdminLoadingState minHeight="8rem" label={t("integrationSites.loading")} />
|
||||
) : items.length === 0 ? (
|
||||
<AdminNoResourceState />
|
||||
<AdminNoResourceState message={t("integrationSites.empty")}>
|
||||
{canCreate ? (
|
||||
<Button type="button" size="sm" onClick={openCreate}>
|
||||
{t("integrationSites.create")}
|
||||
</Button>
|
||||
) : null}
|
||||
</AdminNoResourceState>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
@@ -637,6 +665,14 @@ export function IntegrationSitesConsole({
|
||||
hidden: !canManage,
|
||||
onClick: () => setRotateTarget(row),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("integrationSites.delete"),
|
||||
icon: Trash2,
|
||||
destructive: true,
|
||||
hidden: !canManage,
|
||||
onClick: () => setDeleteTarget(row),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -850,6 +886,33 @@ export function IntegrationSitesConsole({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={deleteTarget !== null} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("integrationSites.deleteConfirmTitle")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("integrationSites.deleteConfirmDescription", {
|
||||
code: deleteTarget?.code ?? "",
|
||||
name: deleteTarget?.name ?? "",
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => setDeleteTarget(null)}>
|
||||
{t("integrationSites.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
disabled={deleteBusy}
|
||||
onClick={() => void confirmDelete()}
|
||||
>
|
||||
{deleteBusy ? t("integrationSites.deleting") : t("integrationSites.deleteConfirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={connectivityTarget !== null}
|
||||
onOpenChange={(open) => {
|
||||
|
||||
Reference in New Issue
Block a user