"use client"; import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import { RefreshCcw, X, Search } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; import type { MembershipEntry } from "@/types/ops"; const MembershipGrowthChart = dynamic( () => import("./MembershipGrowthChart").then((mod) => mod.MembershipGrowthChart), { ssr: false, loading: () =>
, }, ); type GrowthPoint = { date: string; trial: number; paid: number; total: number; cumulative: number }; type SubRow = { id?: string; status?: string; plan_code?: string; source?: string; starts_at?: string; expires_at?: string; created_at?: string; updated_at?: string; }; export function MembershipsPageClient() { const [loading, setLoading] = useState(true); const [memberships, setMemberships] = useState([]); const [growth, setGrowth] = useState([]); const [filter, setFilter] = useState<"all" | "paid" | "trial">("all"); // Detail modal state const [detailEmail, setDetailEmail] = useState(null); const [detailUserId, setDetailUserId] = useState(""); const [detailRows, setDetailRows] = useState([]); const [detailLoading, setDetailLoading] = useState(false); const [detailError, setDetailError] = useState(""); const load = async () => { setLoading(true); try { const data = await opsApi.membershipsOverview(200, 90); setMemberships((data as unknown as { memberships?: MembershipEntry[] }).memberships ?? []); setGrowth((data as { daily?: GrowthPoint[] })?.daily ?? []); } catch { /* */ } setLoading(false); }; useEffect(() => { void load(); }, []); const paid = memberships.filter((m) => !m.is_trial); const trials = memberships.filter((m) => m.is_trial); const filtered = filter === "paid" ? paid : filter === "trial" ? trials : memberships; const planLabel = (code?: string) => { if (!code) return "—"; if (code.startsWith("signup_trial")) return "3天体验"; if (code === "pro_monthly") return "月付"; if (code === "pro_quarterly") return "季付"; if (code === "pro_yearly") return "年付"; return code; }; const sourceLabel = (source?: string) => { if (!source) return "—"; if (source === "payment_contract") return "链上支付"; if (source === "ops_manual_grant") return "后台赠送"; if (source === "signup_trial") return "注册体验"; if (source === "weekly_reward") return "周奖励"; return source; }; const statusBadge = (status?: string) => { if (status === "active") return active; if (status === "expired") return expired; if (status === "cancelled") return cancelled; return {status ?? "—"}; }; const openDetail = async (email: string) => { if (!email) return; setDetailEmail(email); setDetailLoading(true); setDetailError(""); setDetailRows([]); setDetailUserId(""); try { const data = await opsApi.userSubscriptions(email); setDetailUserId(data.user_id ?? ""); setDetailRows(data.subscriptions ?? []); } catch (e: unknown) { setDetailError(e instanceof Error ? e.message : "查询失败"); } setDetailLoading(false); }; const closeDetail = () => { setDetailEmail(null); setDetailRows([]); setDetailUserId(""); setDetailError(""); }; if (loading) return
加载中...
; return (

会员订阅 ({memberships.length}) 付费 {paid.length} · 体验 {trials.length}

{(["all", "paid", "trial"] as const).map((f) => ( ))}
{/* Growth chart */} {growth.length > 0 && ( 会员增长趋势 — 近 {growth.length} 天
{growth.reduce((s, d) => s + d.total, 0)}
总新增
{growth[growth.length - 1]?.cumulative ?? 0}
当前累计
{(growth.reduce((s, d) => s + d.total, 0) / Math.max(1, growth.filter(d => d.total > 0).length)).toFixed(1)}
日均新增
{Math.max(...growth.map(d => d.total), 0)}
单日最高
)} {/* Table */}
{filtered.map((m, i) => ( ))} {filtered.length === 0 && ( )}
类型 邮箱 方案 起始 到期 排队天数
{m.is_trial ? ( 体验 ) : ( 付费 )} {planLabel(m.plan_code)} {m.starts_at?.slice(0, 10) ?? "—"} {m.expires_at?.slice(0, 10) ?? "—"} {m.queued_days ?? 0}
暂无会员
{/* Subscription detail modal */} {detailEmail && (
e.stopPropagation()} > {/* Header */}

订阅记录详情

{detailEmail} {detailUserId && ID: {detailUserId.slice(0, 8)}…}

{/* Content */}
{detailLoading && (
查询中...
)} {detailError && (
{detailError}
)} {!detailLoading && !detailError && detailRows.length === 0 && (
未找到订阅记录
)} {!detailLoading && detailRows.length > 0 && (
{detailRows.map((row, i) => { const isExpired = row.expires_at && new Date(row.expires_at) < new Date(); return ( ); })}
状态 方案 来源 起始 到期 创建
{statusBadge(row.status)} {planLabel(row.plan_code)} {sourceLabel(row.source)} {row.starts_at?.slice(0, 19)?.replace("T", " ") ?? "—"} {row.expires_at?.slice(0, 19)?.replace("T", " ") ?? "—"} {row.created_at?.slice(0, 19)?.replace("T", " ") ?? "—"}
共 {detailRows.length} 条记录 · 时间为 UTC
)}
)}
); }