mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
feat: add wallet and recharge cards
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { createAdminRechargeCards } from "@/actions/admin/recharge-cards";
|
||||
import { BooleanToggle } from "@/components/ui/boolean-toggle";
|
||||
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 { getErrorMessage } from "@/lib/errors";
|
||||
|
||||
interface PlanOption {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "PROXY" | "STREAMING";
|
||||
remainingCount: number | null;
|
||||
}
|
||||
|
||||
export function RechargeCardForm({ plans }: { plans: PlanOption[] }) {
|
||||
const router = useRouter();
|
||||
const [type, setType] = useState<"BALANCE" | "PLAN">("BALANCE");
|
||||
const [planId, setPlanId] = useState(plans[0]?.id ?? "");
|
||||
const [pending, startTransition] = useTransition();
|
||||
const selectedPlan = plans.find((plan) => plan.id === planId) ?? null;
|
||||
const planSoldOut = type === "PLAN" && selectedPlan?.remainingCount === 0;
|
||||
|
||||
return (
|
||||
<form
|
||||
className="form-panel space-y-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
formData.set("type", type);
|
||||
if (type === "PLAN") formData.set("planId", planId);
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await createAdminRechargeCards(formData);
|
||||
toast.success("充值卡已生成");
|
||||
form.reset();
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
toast.error(getErrorMessage(error, "生成充值卡失败"));
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="font-semibold">生成充值卡</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">余额卡充值钱包,套餐卡兑换后直接激活套餐。</p>
|
||||
</div>
|
||||
<BooleanToggle
|
||||
value={type === "PLAN"}
|
||||
onChange={(value) => setType(value ? "PLAN" : "BALANCE")}
|
||||
trueLabel="套餐卡"
|
||||
falseLabel="余额卡"
|
||||
ariaLabel="充值卡类型"
|
||||
className="w-36"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="type" value={type} />
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="recharge-card-quantity">生成数量</Label>
|
||||
<Input
|
||||
id="recharge-card-quantity"
|
||||
name="quantity"
|
||||
type="number"
|
||||
min="1"
|
||||
max={type === "PLAN" && selectedPlan?.remainingCount != null ? Math.min(200, selectedPlan.remainingCount) : 200}
|
||||
defaultValue={1}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="recharge-card-batch">批次名称</Label>
|
||||
<Input id="recharge-card-batch" name="batchName" placeholder="五月活动" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{type === "BALANCE" ? (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="recharge-card-amount">余额金额</Label>
|
||||
<Input id="recharge-card-amount" name="balanceAmount" type="number" min="0.01" step="0.01" placeholder="100.00" required />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Label>绑定套餐</Label>
|
||||
<Select value={planId} onValueChange={(value) => setPlanId(value ?? "")}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="选择套餐" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{plans.map((plan) => (
|
||||
<SelectItem key={plan.id} value={plan.id}>
|
||||
{plan.name} · {plan.type === "PROXY" ? "代理" : "流媒体"} · 余 {plan.remainingCount ?? "不限"}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedPlan && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{selectedPlan.remainingCount == null ? "库存不限" : `当前可生成 ${selectedPlan.remainingCount} 张`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="recharge-card-expires">过期时间</Label>
|
||||
<Input id="recharge-card-expires" name="expiresAt" type="datetime-local" />
|
||||
</div>
|
||||
|
||||
<Button className="w-full" disabled={pending || (type === "PLAN" && (!planId || planSoldOut))}>
|
||||
{pending ? "生成中..." : planSoldOut ? "套餐已售罄" : "生成充值卡"}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user