"use client"; import { useCreate, useGetIdentity, useInvalidate, useUpdate, } from "@refinedev/core"; import { App, Button, DatePicker, Form, Input, InputNumber, Modal, Space, Tag, Typography, type TableColumnsType, } from "antd"; import dayjs from "dayjs"; import { useState } from "react"; import { ConfirmActionModal } from "@/components/admin/confirm-action-modal"; import { formatAdminDate, ResourceTable, } from "@/components/admin/resource-table"; import { adminRequestJson, type AdminIdentity } from "@/lib/admin/providers"; type CodeRecord = { id: string; code?: string; mask: string; credits: number; expiresAt: string | null; note: string | null; createdAt: string; redeemedEmail: string | null; redeemedAt: string | null; revokedAt: string | null; status: "available" | "expired" | "redeemed" | "revoked"; }; type CreateValues = { credits: number; count: number; expiresAt?: ReturnType; note?: string; }; type EditValues = { note?: string; expiresAt?: ReturnType | null; }; const statusColors: Record = { available: "green", expired: "orange", redeemed: "blue", revoked: "red", }; export default function CodesPage() { const { message } = App.useApp(); const invalidate = useInvalidate(); const { data: identity } = useGetIdentity(); const { mutateAsync: createCodes, mutation: createMutation } = useCreate<{ id: string; generated: CodeRecord[]; }>(); const { mutateAsync: updateCode, mutation: updateMutation } = useUpdate(); const [createOpen, setCreateOpen] = useState(false); const [pendingCreate, setPendingCreate] = useState(null); const [editRecord, setEditRecord] = useState(null); const [pendingEdit, setPendingEdit] = useState(null); const [generated, setGenerated] = useState([]); const [revokeRecord, setRevokeRecord] = useState(null); const [revoking, setRevoking] = useState(false); const [createForm] = Form.useForm(); const [editForm] = Form.useForm(); const writable = Boolean( identity?.permissions.includes("billing.adjustments.write"), ); async function submitCreate() { if (!pendingCreate) return; const result = await createCodes({ resource: "codes", values: { credits: pendingCreate.credits, count: pendingCreate.count, expiresAt: pendingCreate.expiresAt?.toISOString() ?? null, note: pendingCreate.note?.trim() || null, }, successNotification: false, }); setGenerated(result.data.generated); setPendingCreate(null); setCreateOpen(false); createForm.resetFields(); } async function submitEdit() { if (!editRecord || !pendingEdit) return; await updateCode({ resource: "codes", id: editRecord.id, values: { note: pendingEdit.note?.trim() || null, expiresAt: pendingEdit.expiresAt?.toISOString() ?? null, }, }); setPendingEdit(null); setEditRecord(null); } async function revoke(record: CodeRecord) { setRevoking(true); try { await adminRequestJson(`/api/admin/codes/${record.id}`, { method: "DELETE", headers: { "x-request-id": crypto.randomUUID() }, }); await invalidate({ resource: "codes", invalidates: ["list"] }); message.success("兑换码已撤销"); setRevokeRecord(null); } catch (error) { message.error(error instanceof Error ? error.message : "撤销失败"); } finally { setRevoking(false); } } const columns: TableColumnsType = [ { title: "兑换码", dataIndex: "mask" }, { title: "点数", dataIndex: "credits", sorter: true }, { title: "状态", dataIndex: "status", sorter: true, render: (value) => ( {value} ), }, { title: "到期时间", dataIndex: "expiresAt", sorter: true, render: formatAdminDate, }, { title: "备注", dataIndex: "note", render: (value) => value || "—" }, { title: "兑换账户", dataIndex: "redeemedEmail", render: (value) => value || "—", }, { title: "兑换时间", dataIndex: "redeemedAt", render: formatAdminDate }, { title: "撤销时间", dataIndex: "revokedAt", render: formatAdminDate }, { title: "创建时间", dataIndex: "createdAt", sorter: true, render: formatAdminDate, }, { title: "操作", fixed: "right", render: (_, record) => writable && record.status !== "redeemed" && record.status !== "revoked" ? ( ) : ( "—" ), }, ]; return ( <> resource="codes" title={`兑换码${identity ? ` · ${identity.email} (${identity.roles.join("、")})` : ""}`} columns={columns} statusOptions={[ { label: "可用", value: "available" }, { label: "已过期", value: "expired" }, { label: "已兑换", value: "redeemed" }, { label: "已撤销", value: "revoked" }, ]} extra={ writable ? ( ) : null } /> { setPendingCreate(null); setCreateOpen(false); }} footer={null} destroyOnHidden >
0} onCancel={() => setGenerated([])} footer={} > 关闭后无法再次查看完整兑换码,请立即安全保存。 {generated.map((record) => ( {record.code} ))} { setPendingEdit(null); setEditRecord(null); }} footer={null} destroyOnHidden >
setPendingCreate(null)} onConfirm={submitCreate} /> setPendingEdit(null)} onConfirm={submitEdit} /> setRevokeRecord(null)} onConfirm={() => revoke(revokeRecord!)} /> ); }