mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
feat: show remaining subscription push count
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user