From f4d71ca52624cd607f0365b65e0777ccc283fb7f Mon Sep 17 00:00:00 2001 From: JetSprow Date: Fri, 1 May 2026 04:44:22 +1000 Subject: [PATCH] feat: show remaining subscription push count --- .../_components/subscription-push-form.tsx | 33 +++++++++- .../(user)/subscriptions/push/push-data.ts | 62 ++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/app/(user)/subscriptions/push/_components/subscription-push-form.tsx b/src/app/(user)/subscriptions/push/_components/subscription-push-form.tsx index 13e83f5..8535a21 100644 --- a/src/app/(user)/subscriptions/push/_components/subscription-push-form.tsx +++ b/src/app/(user)/subscriptions/push/_components/subscription-push-form.tsx @@ -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 (
{selectedSubscription && ( -

{remainingTrafficLabel(selectedSubscription)}

+
+
+

Push 次数

+

{pushUsageLabel(selectedSubscription)}

+
+
+

剩余资源

+

{remainingTrafficLabel(selectedSubscription)}

+
+
)} @@ -155,8 +172,18 @@ export function SubscriptionPushForm({ 单周期最多 {config.limitPerCycle} 次;低于 {config.minRemainingDays} 天或代理剩余流量低于 {config.minRemainingTrafficGb} GB 时不可 Push。 -
); diff --git a/src/app/(user)/subscriptions/push/push-data.ts b/src/app/(user)/subscriptions/push/push-data.ts index 5c1b443..34a8fdd 100644 --- a/src/app/(user)/subscriptions/push/push-data.ts +++ b/src/app/(user)/subscriptions/push/push-data.ts @@ -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 { + 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), };