chore: update credit expiration messaging and logic to reflect upcoming expiration in days
This commit is contained in:
parent
6837c5a8d4
commit
01f5734dd5
@ -601,8 +601,7 @@
|
||||
"creditsAdded": "Credits have been added to your account",
|
||||
"viewTransactions": "View Credit Transactions",
|
||||
"retry": "Retry",
|
||||
|
||||
"expiringCredits": "{credits} credits expiring on {date}"
|
||||
"expiringCredits": "{credits} credits expiring in the next {days} days"
|
||||
},
|
||||
"packages": {
|
||||
"title": "Credit Packages",
|
||||
|
@ -601,8 +601,7 @@
|
||||
"creditsAdded": "积分已添加到您的账户",
|
||||
"viewTransactions": "查看积分记录",
|
||||
"retry": "重试",
|
||||
|
||||
"expiringCredits": "{credits} 积分将在 {date} 过期"
|
||||
"expiringCredits": "{credits} 积分将在 {days} 天内过期"
|
||||
},
|
||||
"packages": {
|
||||
"title": "积分套餐",
|
||||
|
@ -3,11 +3,10 @@
|
||||
import { getDb } from '@/db';
|
||||
import { creditTransaction } from '@/db/schema';
|
||||
import type { User } from '@/lib/auth-types';
|
||||
import { CREDITS_EXPIRATION_DAYS } from '@/lib/constants';
|
||||
import { userActionClient } from '@/lib/safe-action';
|
||||
import { addDays } from 'date-fns';
|
||||
import { and, eq, gte, isNotNull, lte, sql, sum } from 'drizzle-orm';
|
||||
|
||||
const CREDITS_EXPIRATION_DAYS = 31;
|
||||
import { and, eq, gt, gte, isNotNull, lte, sum } from 'drizzle-orm';
|
||||
|
||||
/**
|
||||
* Get credit statistics for a user
|
||||
@ -18,12 +17,14 @@ export const getCreditStatsAction = userActionClient.action(async ({ ctx }) => {
|
||||
const userId = currentUser.id;
|
||||
|
||||
const db = await getDb();
|
||||
// Get credits expiring in the next CREDITS_EXPIRATION_DAYS days
|
||||
const expirationDaysFromNow = addDays(new Date(), CREDITS_EXPIRATION_DAYS);
|
||||
const expiringCredits = await db
|
||||
const now = new Date();
|
||||
// Get credits expiring in the next 30 days
|
||||
const expirationDaysFromNow = addDays(now, CREDITS_EXPIRATION_DAYS);
|
||||
|
||||
// Get total credits expiring in the next 30 days
|
||||
const expiringCreditsResult = await db
|
||||
.select({
|
||||
amount: sum(creditTransaction.remainingAmount),
|
||||
earliestExpiration: sql<Date>`MIN(${creditTransaction.expirationDate})`,
|
||||
totalAmount: sum(creditTransaction.remainingAmount),
|
||||
})
|
||||
.from(creditTransaction)
|
||||
.where(
|
||||
@ -31,18 +32,20 @@ export const getCreditStatsAction = userActionClient.action(async ({ ctx }) => {
|
||||
eq(creditTransaction.userId, userId),
|
||||
isNotNull(creditTransaction.expirationDate),
|
||||
isNotNull(creditTransaction.remainingAmount),
|
||||
gte(creditTransaction.remainingAmount, 1),
|
||||
gt(creditTransaction.remainingAmount, 0),
|
||||
lte(creditTransaction.expirationDate, expirationDaysFromNow),
|
||||
gte(creditTransaction.expirationDate, new Date())
|
||||
gte(creditTransaction.expirationDate, now)
|
||||
)
|
||||
);
|
||||
|
||||
const totalExpiringCredits =
|
||||
Number(expiringCreditsResult[0]?.totalAmount) || 0;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
expiringCredits: {
|
||||
amount: Number(expiringCredits[0]?.amount) || 0,
|
||||
earliestExpiration: expiringCredits[0]?.earliestExpiration || null,
|
||||
amount: totalExpiringCredits,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ import { websiteConfig } from '@/config/website';
|
||||
import { useCreditBalance, useCreditStats } from '@/hooks/use-credits';
|
||||
import { useMounted } from '@/hooks/use-mounted';
|
||||
import { useLocaleRouter } from '@/i18n/navigation';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { CREDITS_EXPIRATION_DAYS } from '@/lib/constants';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { RefreshCwIcon } from 'lucide-react';
|
||||
@ -102,13 +102,12 @@ export default function CreditsBalanceCard() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-1">
|
||||
<div className="flex items-center justify-start space-x-4">
|
||||
<Skeleton className="h-6 w-1/5" />
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-2">
|
||||
<Skeleton className="h-6 w-3/5" />
|
||||
<Skeleton className="h-8 w-1/5" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="">{/* show nothing */}</CardFooter>
|
||||
<CardFooter className="px-6 py-4 flex justify-between items-center bg-background rounded-none">
|
||||
<Skeleton className="h-6 w-3/5" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -147,7 +146,7 @@ export default function CreditsBalanceCard() {
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1">
|
||||
{/* Credits balance display */}
|
||||
{/* Credits balance */}
|
||||
<div className="flex items-center justify-start space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* <CoinsIcon className="h-6 w-6 text-muted-foreground" /> */}
|
||||
@ -159,25 +158,20 @@ export default function CreditsBalanceCard() {
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="px-6 py-4 flex justify-between items-center bg-background rounded-none">
|
||||
{/* Balance information */}
|
||||
<div className="text-sm text-muted-foreground space-y-2">
|
||||
{/* Expiring credits warning */}
|
||||
{!isLoadingStats &&
|
||||
creditStats &&
|
||||
creditStats.expiringCredits.amount > 0 &&
|
||||
creditStats.expiringCredits.earliestExpiration && (
|
||||
<div className="flex items-center gap-2 text-amber-600">
|
||||
<span>
|
||||
{t('expiringCredits', {
|
||||
credits: creditStats.expiringCredits.amount,
|
||||
date: formatDate(
|
||||
new Date(creditStats.expiringCredits.earliestExpiration)
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Expiring credits warning */}
|
||||
{!isLoadingStats && creditStats && (
|
||||
<div className="text-sm text-muted-foreground space-y-2">
|
||||
{' '}
|
||||
<div className="flex items-center gap-2 text-amber-600">
|
||||
<span>
|
||||
{t('expiringCredits', {
|
||||
credits: creditStats.expiringCredits.amount,
|
||||
days: CREDITS_EXPIRATION_DAYS,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
|
@ -1,2 +1,10 @@
|
||||
/**
|
||||
* in next 30 days for credits expiration
|
||||
*/
|
||||
export const CREDITS_EXPIRATION_DAYS = 30;
|
||||
|
||||
/**
|
||||
* placeholder image for blog post card
|
||||
*/
|
||||
export const PLACEHOLDER_IMAGE =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAoJJREFUWEfFl4lu4zAMRO3cx/9/au6reMaOdkxTTl0grQFCRoqaT+SQotq2bV9N8rRt28xms87m83l553eZ/9vr9Wpkz+ezkT0ej+6dv1X81AFw7M4FBACPVn2c1Z3zLgDeJwHgeLFYdAARYioAEAKJEG2WAjl3gCwNYymQQ9b7/V4spmIAwO6Wy2VnAMikBWlDURBELf8CuN1uHQSrPwMAHK5WqwFELQ01AIXdAa7XawfAb3p6AOwK5+v1ugAoEq4FRSFLgavfQ49jAGQpAE5wjgGCeRrGdBArwHOPcwFcLpcGU1X0IsBuN5tNgYhaiFFwHTiAwq8I+O5xfj6fOz38K+X/fYAdb7fbAgFAjIJ6Aav3AYlQ6nfnDoDz0+lUxNiLALvf7XaDNGQ6GANQBKR85V27B4D3QQRw7hGIYlQKWGM79hSweyCUe1blXhEAogfABwHAXAcqSYkxCtHLUK3XBajSc4Dj8dilAeiSAgD2+30BAEKV4GKcAuDqB4TdYwBgPQByCgApUBoE4EJUGvxUjF3Q69/zLw3g/HA45ABKgdIQu+JPIyDnisCfAxAFNFM0EFNQ64gfS0EUoQP8ighrZSjn3oziZEQpauyKbfjbZchHUL/3AS/Dd30gAkxuRACgfO+EWQW8qwI1o+wseNuKcQiESjALvwNoMI0TcRzD4lFcPYwIM+JTF5x6HOs8yI7jeB5oKhpMRFH9UwaSCDB2Jmg4rc6E2TT0biIaG0rQhNqyhpHBcayTTSXH6vcDL7/sdqRK8LkwTsU499E8vRcAojHcZ4AxABdilgrp4lsXk8oVqgwh7+6H3phqd8J0Kk4vbx/+sZqCD/vNLya/5dT9fAH8g1WdNGgwbQAAAABJRU5ErkJggg==';
|
||||
|
Loading…
Reference in New Issue
Block a user