feat(api, agents): add agent node profile retrieval and update functionality

Implemented new API functions to fetch and update agent node profiles, enhancing the management capabilities for agent data. This addition improves the overall functionality of the admin agents console, allowing for better user interaction with agent profiles. Updated related types for improved type safety and clarity in the codebase.
This commit is contained in:
2026-06-04 09:17:55 +08:00
parent 59b0684ea1
commit cbc499e5b2
79 changed files with 3468 additions and 1406 deletions

View File

@@ -0,0 +1,239 @@
"use client";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { postAdminAgentLine } from "@/api/admin-agent-lines";
import { AdminPageCard } from "@/components/admin/admin-page-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { LotteryApiBizError } from "@/types/api/errors";
export function AgentLineProvisionWizard(): React.ReactElement {
const { t } = useTranslation(["agents", "common"]);
const [submitting, setSubmitting] = useState(false);
const [secrets, setSecrets] = useState<{ sso: string; wallet: string } | null>(null);
const [form, setForm] = useState({
code: "",
name: "",
username: "",
password: "",
currency_code: "NPR",
wallet_api_url: "",
notes: "",
total_share_rate: "0",
credit_limit: "0",
rebate_limit: "0",
default_player_rebate: "0",
settlement_cycle: "weekly" as "daily" | "weekly" | "monthly",
can_grant_extra_rebate: false,
});
async function onSubmit(e: React.FormEvent): Promise<void> {
e.preventDefault();
setSubmitting(true);
setSecrets(null);
try {
const result = await postAdminAgentLine({
code: form.code.trim().toLowerCase(),
name: form.name.trim(),
username: form.username.trim(),
password: form.password,
currency_code: form.currency_code,
wallet_api_url: form.wallet_api_url.trim() || null,
notes: form.notes.trim() || null,
total_share_rate: Number.parseFloat(form.total_share_rate) || 0,
credit_limit: Number.parseInt(form.credit_limit, 10) || 0,
rebate_limit: Number.parseFloat(form.rebate_limit) || 0,
default_player_rebate: Number.parseFloat(form.default_player_rebate) || 0,
settlement_cycle: form.settlement_cycle,
can_grant_extra_rebate: form.can_grant_extra_rebate,
});
if (result.secrets) {
setSecrets({
sso: result.secrets.sso_jwt_secret,
wallet: result.secrets.wallet_api_key,
});
}
toast.success(t("agents:lineProvision.success", { defaultValue: "线路已开通" }));
} catch (err) {
const msg =
err instanceof LotteryApiBizError ? err.message : t("common:error.generic");
toast.error(msg);
} finally {
setSubmitting(false);
}
}
return (
<AdminPageCard title={t("agents:lineProvision.title", { defaultValue: "开通代理线路" })}>
<form className="grid max-w-xl gap-4" onSubmit={onSubmit}>
<div className="grid gap-2">
<Label>{t("agents:lineProvision.code", { defaultValue: "站点 code" })}</Label>
<Input
value={form.code}
onChange={(e) => setForm((f) => ({ ...f, code: e.target.value }))}
required
pattern="[a-z0-9][a-z0-9_-]*"
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:lineProvision.name", { defaultValue: "线路名称" })}</Label>
<Input
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
required
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:lineProvision.username", { defaultValue: "代理账号" })}</Label>
<Input
value={form.username}
onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
required
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:lineProvision.password", { defaultValue: "初始密码" })}</Label>
<Input
type="password"
value={form.password}
onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
required
minLength={8}
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:lineProvision.walletUrl", { defaultValue: "钱包 API URL" })}</Label>
<Input
value={form.wallet_api_url}
onChange={(e) => setForm((f) => ({ ...f, wallet_api_url: e.target.value }))}
/>
</div>
<p className="text-sm font-medium">
{t("agents:profile.section", { defaultValue: "占成与授信" })}
</p>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-2">
<Label>{t("agents:profile.totalShareRate", { defaultValue: "占成比例 (%)" })}</Label>
<Input
type="number"
min={0}
max={100}
step="0.01"
value={form.total_share_rate}
onChange={(e) => setForm((f) => ({ ...f, total_share_rate: e.target.value }))}
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:profile.creditLimit", { defaultValue: "授信额度" })}</Label>
<Input
type="number"
min={0}
value={form.credit_limit}
onChange={(e) => setForm((f) => ({ ...f, credit_limit: e.target.value }))}
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:profile.rebateLimit", { defaultValue: "回水上限" })}</Label>
<Input
type="number"
min={0}
max={1}
step="0.0001"
value={form.rebate_limit}
onChange={(e) => setForm((f) => ({ ...f, rebate_limit: e.target.value }))}
/>
</div>
<div className="grid gap-2">
<Label>{t("agents:profile.defaultPlayerRebate", { defaultValue: "默认玩家回水" })}</Label>
<Input
type="number"
min={0}
max={1}
step="0.0001"
value={form.default_player_rebate}
onChange={(e) => setForm((f) => ({ ...f, default_player_rebate: e.target.value }))}
/>
</div>
</div>
<div className="grid gap-2">
<Label>{t("agents:profile.settlementCycle", { defaultValue: "结算周期" })}</Label>
<Select
value={form.settlement_cycle}
onValueChange={(value) =>
setForm((f) => ({
...f,
settlement_cycle: (value as "daily" | "weekly" | "monthly") ?? "weekly",
}))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="daily">
{t("agents:profile.cycleDaily", { defaultValue: "日结" })}
</SelectItem>
<SelectItem value="weekly">
{t("agents:profile.cycleWeekly", { defaultValue: "周结" })}
</SelectItem>
<SelectItem value="monthly">
{t("agents:profile.cycleMonthly", { defaultValue: "月结" })}
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<Switch
checked={form.can_grant_extra_rebate}
onCheckedChange={(checked) =>
setForm((f) => ({ ...f, can_grant_extra_rebate: checked }))
}
/>
<Label>
{t("agents:profile.canGrantExtraRebate", { defaultValue: "允许额外回水" })}
</Label>
</div>
<div className="grid gap-2">
<Label>{t("common:notes", { defaultValue: "备注" })}</Label>
<Textarea
value={form.notes}
onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}
/>
</div>
<Button type="submit" disabled={submitting}>
{submitting
? t("common:submitting", { defaultValue: "提交中…" })
: t("agents:lineProvision.submit", { defaultValue: "开通线路" })}
</Button>
</form>
{secrets ? (
<div className="mt-6 rounded-md border border-amber-500/40 bg-amber-500/5 p-4 text-sm">
<p className="font-medium text-amber-700">
{t("agents:lineProvision.secretsOnce", { defaultValue: "密钥仅显示一次,请妥善保存" })}
</p>
<p className="mt-2 break-all">
SSO: <code>{secrets.sso}</code>
</p>
<p className="mt-1 break-all">
Wallet API Key: <code>{secrets.wallet}</code>
</p>
</div>
) : null}
</AdminPageCard>
);
}