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:
239
src/modules/agents/agent-line-provision-wizard.tsx
Normal file
239
src/modules/agents/agent-line-provision-wizard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user