feat: show remaining subscription push count

This commit is contained in:
JetSprow
2026-05-01 04:44:22 +10:00
parent e718d5edab
commit f4d71ca526
2 changed files with 90 additions and 5 deletions

View File

@@ -39,6 +39,12 @@ function remainingTrafficLabel(subscription: PushSubscriptionOption) {
return `剩余 ${formatBytes(remaining)}`;
}
function pushUsageLabel(subscription: PushSubscriptionOption) {
const usage = subscription.pushUsage;
if (usage.limit <= 0) return "本周期不可 Push";
return `本周期剩余 ${usage.remaining}/${usage.limit}`;
}
export function SubscriptionPushForm({
subscriptions,
config,
@@ -60,6 +66,8 @@ export function SubscriptionPushForm({
() => subscriptions.find((item) => item.id === subscriptionId) ?? null,
[subscriptionId, subscriptions],
);
const selectedRemaining = selectedSubscription?.pushUsage.remaining ?? 0;
const transferUnavailable = Boolean(selectedSubscription) && selectedRemaining <= 0;
return (
<form
@@ -122,7 +130,16 @@ export function SubscriptionPushForm({
</SelectContent>
</Select>
{selectedSubscription && (
<p className="text-xs text-muted-foreground">{remainingTrafficLabel(selectedSubscription)}</p>
<div className="grid gap-2 sm:grid-cols-2">
<div className="rounded-lg border border-border/60 bg-muted/25 px-3 py-2">
<p className="text-[11px] font-medium text-muted-foreground">Push </p>
<p className="mt-1 text-sm font-semibold">{pushUsageLabel(selectedSubscription)}</p>
</div>
<div className="rounded-lg border border-border/60 bg-muted/25 px-3 py-2">
<p className="text-[11px] font-medium text-muted-foreground"></p>
<p className="mt-1 text-sm font-semibold">{remainingTrafficLabel(selectedSubscription)}</p>
</div>
</div>
)}
</div>
@@ -155,8 +172,18 @@ export function SubscriptionPushForm({
{config.limitPerCycle} {config.minRemainingDays} {config.minRemainingTrafficGb} GB Push
</div>
<Button type="submit" className="w-full" disabled={pending || !config.enabled || subscriptions.length === 0}>
{pending ? "发起中..." : subscriptions.length === 0 ? "暂无可 Push 套餐" : "发起 Push"}
<Button
type="submit"
className="w-full"
disabled={pending || !config.enabled || subscriptions.length === 0 || transferUnavailable}
>
{pending
? "发起中..."
: subscriptions.length === 0
? "暂无可 Push 套餐"
: transferUnavailable
? "本周期次数已用尽"
: "发起 Push"}
</Button>
</form>
);

View File

@@ -30,14 +30,67 @@ const transferInclude = {
recipient: { select: { id: true, email: true, name: true } },
} satisfies Prisma.SubscriptionTransferInclude;
export type PushSubscriptionOption = Prisma.UserSubscriptionGetPayload<{
type PushSubscriptionBaseOption = Prisma.UserSubscriptionGetPayload<{
include: typeof pushSubscriptionInclude;
}>;
export type PushSubscriptionOption = PushSubscriptionBaseOption & {
pushUsage: {
limit: number;
used: number;
remaining: number;
cycleStartedAt: Date;
};
};
export type UserSubscriptionTransferRow = Prisma.SubscriptionTransferGetPayload<{
include: typeof transferInclude;
}>;
async function getSubscriptionCycleStartedAt(subscription: PushSubscriptionBaseOption) {
const latestRenewal = await prisma.order.findFirst({
where: {
targetSubscriptionId: subscription.id,
kind: "RENEWAL",
status: "PAID",
},
select: { createdAt: true },
orderBy: { createdAt: "desc" },
});
return latestRenewal?.createdAt ?? subscription.startDate;
}
async function attachPushUsage(
subscriptions: PushSubscriptionBaseOption[],
limitPerCycle: number,
): Promise<PushSubscriptionOption[]> {
const limit = Math.max(0, limitPerCycle);
return Promise.all(
subscriptions.map(async (subscription) => {
const cycleStartedAt = await getSubscriptionCycleStartedAt(subscription);
const used = await prisma.subscriptionTransfer.count({
where: {
subscriptionId: subscription.id,
status: "ACCEPTED",
cycleStartedAt,
},
});
return {
...subscription,
pushUsage: {
limit,
used,
remaining: Math.max(0, limit - used),
cycleStartedAt,
},
};
}),
);
}
export async function getSubscriptionPushPageData(userId: string) {
await processExpiredSubscriptionTransfers();
@@ -70,6 +123,11 @@ export async function getSubscriptionPushPageData(userId: string) {
}),
]);
const subscriptionsWithUsage = await attachPushUsage(
subscriptions,
config.subscriptionTransferLimitPerCycle,
);
return {
config: {
enabled: config.subscriptionTransferEnabled,
@@ -78,7 +136,7 @@ export async function getSubscriptionPushPageData(userId: string) {
minRemainingDays: config.subscriptionTransferMinRemainingDays,
minRemainingTrafficGb: config.subscriptionTransferMinRemainingTrafficGb,
},
subscriptions,
subscriptions: subscriptionsWithUsage,
transfers,
walletBalance: Number(wallet?.balance ?? 0),
};