mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
feat: add subscription push transfers
This commit is contained in:
115
src/app/(user)/subscriptions/push/page.tsx
Normal file
115
src/app/(user)/subscriptions/push/page.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user