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)}`;
|
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({
|
export function SubscriptionPushForm({
|
||||||
subscriptions,
|
subscriptions,
|
||||||
config,
|
config,
|
||||||
@@ -60,6 +66,8 @@ export function SubscriptionPushForm({
|
|||||||
() => subscriptions.find((item) => item.id === subscriptionId) ?? null,
|
() => subscriptions.find((item) => item.id === subscriptionId) ?? null,
|
||||||
[subscriptionId, subscriptions],
|
[subscriptionId, subscriptions],
|
||||||
);
|
);
|
||||||
|
const selectedRemaining = selectedSubscription?.pushUsage.remaining ?? 0;
|
||||||
|
const transferUnavailable = Boolean(selectedSubscription) && selectedRemaining <= 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
@@ -122,7 +130,16 @@ export function SubscriptionPushForm({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{selectedSubscription && (
|
{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>
|
</div>
|
||||||
|
|
||||||
@@ -155,8 +172,18 @@ export function SubscriptionPushForm({
|
|||||||
单周期最多 {config.limitPerCycle} 次;低于 {config.minRemainingDays} 天或代理剩余流量低于 {config.minRemainingTrafficGb} GB 时不可 Push。
|
单周期最多 {config.limitPerCycle} 次;低于 {config.minRemainingDays} 天或代理剩余流量低于 {config.minRemainingTrafficGb} GB 时不可 Push。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button type="submit" className="w-full" disabled={pending || !config.enabled || subscriptions.length === 0}>
|
<Button
|
||||||
{pending ? "发起中..." : subscriptions.length === 0 ? "暂无可 Push 套餐" : "发起 Push"}
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={pending || !config.enabled || subscriptions.length === 0 || transferUnavailable}
|
||||||
|
>
|
||||||
|
{pending
|
||||||
|
? "发起中..."
|
||||||
|
: subscriptions.length === 0
|
||||||
|
? "暂无可 Push 套餐"
|
||||||
|
: transferUnavailable
|
||||||
|
? "本周期次数已用尽"
|
||||||
|
: "发起 Push"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,14 +30,67 @@ const transferInclude = {
|
|||||||
recipient: { select: { id: true, email: true, name: true } },
|
recipient: { select: { id: true, email: true, name: true } },
|
||||||
} satisfies Prisma.SubscriptionTransferInclude;
|
} satisfies Prisma.SubscriptionTransferInclude;
|
||||||
|
|
||||||
export type PushSubscriptionOption = Prisma.UserSubscriptionGetPayload<{
|
type PushSubscriptionBaseOption = Prisma.UserSubscriptionGetPayload<{
|
||||||
include: typeof pushSubscriptionInclude;
|
include: typeof pushSubscriptionInclude;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type PushSubscriptionOption = PushSubscriptionBaseOption & {
|
||||||
|
pushUsage: {
|
||||||
|
limit: number;
|
||||||
|
used: number;
|
||||||
|
remaining: number;
|
||||||
|
cycleStartedAt: Date;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type UserSubscriptionTransferRow = Prisma.SubscriptionTransferGetPayload<{
|
export type UserSubscriptionTransferRow = Prisma.SubscriptionTransferGetPayload<{
|
||||||
include: typeof transferInclude;
|
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) {
|
export async function getSubscriptionPushPageData(userId: string) {
|
||||||
await processExpiredSubscriptionTransfers();
|
await processExpiredSubscriptionTransfers();
|
||||||
|
|
||||||
@@ -70,6 +123,11 @@ export async function getSubscriptionPushPageData(userId: string) {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const subscriptionsWithUsage = await attachPushUsage(
|
||||||
|
subscriptions,
|
||||||
|
config.subscriptionTransferLimitPerCycle,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {
|
||||||
enabled: config.subscriptionTransferEnabled,
|
enabled: config.subscriptionTransferEnabled,
|
||||||
@@ -78,7 +136,7 @@ export async function getSubscriptionPushPageData(userId: string) {
|
|||||||
minRemainingDays: config.subscriptionTransferMinRemainingDays,
|
minRemainingDays: config.subscriptionTransferMinRemainingDays,
|
||||||
minRemainingTrafficGb: config.subscriptionTransferMinRemainingTrafficGb,
|
minRemainingTrafficGb: config.subscriptionTransferMinRemainingTrafficGb,
|
||||||
},
|
},
|
||||||
subscriptions,
|
subscriptions: subscriptionsWithUsage,
|
||||||
transfers,
|
transfers,
|
||||||
walletBalance: Number(wallet?.balance ?? 0),
|
walletBalance: Number(wallet?.balance ?? 0),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user