mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
116 lines
4.7 KiB
TypeScript
116 lines
4.7 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { ArrowRightLeft, WalletCards } from "lucide-react";
|
|
import { getActiveSession } from "@/lib/require-auth";
|
|
import { formatBytes, formatDate } from "@/lib/utils";
|
|
import { getSubscriptionTransferFeePayerLabel, getSubscriptionTransferStatusLabel } from "@/lib/domain-labels";
|
|
import { PageHeader, PageShell, SectionHeader } from "@/components/shared/page-shell";
|
|
import type { StatusTone } from "@/components/shared/status-badge";
|
|
import { getSubscriptionPushPageData, type UserSubscriptionTransferRow } from "./push-data";
|
|
import { SubscriptionPushForm } from "./_components/subscription-push-form";
|
|
import { SubscriptionPushList, type SubscriptionTransferItem } from "./_components/subscription-push-list";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "套餐 Push",
|
|
description: "发起和接收用户间套餐转让。",
|
|
};
|
|
|
|
const statusTones: Record<string, StatusTone> = {
|
|
PENDING: "warning",
|
|
ACCEPTED: "success",
|
|
REJECTED: "neutral",
|
|
CANCELLED: "neutral",
|
|
EXPIRED: "danger",
|
|
};
|
|
|
|
function money(value: unknown) {
|
|
return `¥${Number(value).toFixed(2)}`;
|
|
}
|
|
|
|
function userLabel(user: { name: string | null; email: string }) {
|
|
return user.name ? `${user.name} · ${user.email}` : user.email;
|
|
}
|
|
|
|
function trafficLabel(transfer: UserSubscriptionTransferRow) {
|
|
const sub = transfer.subscription;
|
|
if (sub.plan.type !== "PROXY") return "不涉及流量限制";
|
|
if (!sub.trafficLimit) return "不限流量";
|
|
const remaining = sub.trafficLimit > sub.trafficUsed ? sub.trafficLimit - sub.trafficUsed : BigInt(0);
|
|
return `${formatBytes(remaining)} / ${formatBytes(sub.trafficLimit)}`;
|
|
}
|
|
|
|
function toTransferItem(transfer: UserSubscriptionTransferRow, userId: string): SubscriptionTransferItem {
|
|
return {
|
|
id: transfer.id,
|
|
role: transfer.recipientId === userId ? "incoming" : "outgoing",
|
|
status: transfer.status,
|
|
statusLabel: getSubscriptionTransferStatusLabel(transfer.status),
|
|
statusTone: statusTones[transfer.status] ?? "neutral",
|
|
planName: transfer.plan.name,
|
|
planTypeLabel: transfer.plan.type === "PROXY" ? "代理" : "流媒体",
|
|
senderLabel: userLabel(transfer.sender),
|
|
recipientLabel: userLabel(transfer.recipient),
|
|
feeLabel: money(transfer.feeAmount),
|
|
feePayerLabel: getSubscriptionTransferFeePayerLabel(transfer.feePayer),
|
|
createdAtLabel: formatDate(transfer.createdAt),
|
|
expiresAtLabel: formatDate(transfer.expiresAt),
|
|
acceptedAtLabel: transfer.acceptedAt ? formatDate(transfer.acceptedAt) : null,
|
|
endDateLabel: formatDate(transfer.subscription.endDate),
|
|
trafficLabel: trafficLabel(transfer),
|
|
};
|
|
}
|
|
|
|
export default async function SubscriptionPushPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
}) {
|
|
const session = await getActiveSession();
|
|
const [params, data] = await Promise.all([
|
|
searchParams,
|
|
getSubscriptionPushPageData(session!.user.id),
|
|
]);
|
|
const initialSubscriptionId = typeof params.subscriptionId === "string" ? params.subscriptionId : undefined;
|
|
const transferItems = data.transfers.map((transfer) => toTransferItem(transfer, session!.user.id));
|
|
|
|
return (
|
|
<PageShell>
|
|
<PageHeader
|
|
eyebrow="订阅管理"
|
|
title="套餐 Push"
|
|
description="将未到期套餐转给其他用户,对方需在 24 小时内确认。"
|
|
/>
|
|
|
|
<section className="grid gap-4 sm:grid-cols-3">
|
|
<div className="surface-card rounded-xl p-4">
|
|
<p className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
<WalletCards className="size-4 text-primary" /> 当前余额
|
|
</p>
|
|
<p className="mt-2 text-2xl font-semibold tabular-nums">{money(data.walletBalance)}</p>
|
|
</div>
|
|
<div className="surface-card rounded-xl p-4">
|
|
<p className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
<ArrowRightLeft className="size-4 text-primary" /> 转让费
|
|
</p>
|
|
<p className="mt-2 text-2xl font-semibold tabular-nums">{money(data.config.feeAmount)}</p>
|
|
</div>
|
|
<div className="surface-card rounded-xl p-4">
|
|
<p className="text-sm text-muted-foreground">单周期次数</p>
|
|
<p className="mt-2 text-2xl font-semibold tabular-nums">{data.config.limitPerCycle}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="grid gap-5 xl:grid-cols-[minmax(22rem,0.75fr)_1fr]">
|
|
<SubscriptionPushForm
|
|
subscriptions={data.subscriptions}
|
|
config={data.config}
|
|
initialSubscriptionId={initialSubscriptionId}
|
|
/>
|
|
<div className="space-y-4">
|
|
<SectionHeader title="Push 记录" />
|
|
<SubscriptionPushList transfers={transferItems} />
|
|
</div>
|
|
</section>
|
|
</PageShell>
|
|
);
|
|
}
|