feat: add ConsumeCreditCard component for credit consumption

This commit is contained in:
javayhu 2025-07-12 11:08:28 +08:00
parent 4160305a67
commit c7a1ec69bb
5 changed files with 63 additions and 30 deletions

View File

@ -633,9 +633,9 @@
"types": {
"MONTHLY_REFRESH": "Monthly Refresh",
"REGISTER_GIFT": "Register Gift",
"PURCHASE": "Purchase",
"USAGE": "Usage",
"EXPIRE": "Expire",
"PURCHASE": "Purchased Credits",
"USAGE": "Consumed Credits",
"EXPIRE": "Expired Credits",
"SUBSCRIPTION_RENEWAL": "Subscription Renewal",
"LIFETIME_MONTHLY": "Lifetime Monthly"
},

View File

@ -0,0 +1,51 @@
'use client';
import { CreditsBalanceButton } from '@/components/layout/credits-balance-button';
import { Button } from '@/components/ui/button';
import { useCredits } from '@/hooks/use-credits';
import { CoinsIcon } from 'lucide-react';
import { useState } from 'react';
import { toast } from 'sonner';
const CONSUME_CREDITS = 50;
export function ConsumeCreditCard() {
const { consumeCredits, hasEnoughCredits, isLoading } = useCredits();
const [loading, setLoading] = useState(false);
const handleConsume = async () => {
if (!hasEnoughCredits(CONSUME_CREDITS)) {
toast.error('Insufficient credits, please buy more credits.');
return;
}
setLoading(true);
const success = await consumeCredits(
CONSUME_CREDITS,
`AI Text Credit Consumption (${CONSUME_CREDITS} credits)`
);
setLoading(false);
if (success) {
toast.success(`${CONSUME_CREDITS} credits have been consumed.`);
} else {
toast.error('Failed to consume credits, please try again later.');
}
};
return (
<div className="flex flex-col items-center gap-8 p-4 border rounded-lg">
<div className="w-full flex flex-row items-center justify-end">
<CreditsBalanceButton />
</div>
<Button
variant="outline"
size="sm"
onClick={handleConsume}
disabled={isLoading || loading}
className="w-full cursor-pointer"
>
<CoinsIcon className="size-4" />
<span>Consume {CONSUME_CREDITS} credits</span>
</Button>
</div>
);
}

View File

@ -1,3 +1,4 @@
import { ConsumeCreditCard } from '@/ai/text/components/consume-credit-card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { constructMetadata } from '@/lib/metadata';
import { getUrlWithLocale } from '@/lib/urls/urls';
@ -28,7 +29,7 @@ export default async function AITextPage() {
<div className="max-w-4xl mx-auto space-y-8">
{/* about section */}
<div className="relative max-w-(--breakpoint-md) mx-auto mb-24 mt-8 md:mt-16">
<div className="mx-auto flex flex-col justify-between">
<div className="mx-auto flex flex-col justify-between gap-8">
<div className="flex flex-row items-center gap-8">
{/* avatar and name */}
<div className="flex items-center gap-8">
@ -48,6 +49,9 @@ export default async function AITextPage() {
</div>
</div>
</div>
{/* simulate consume credits */}
<ConsumeCreditCard />
</div>
</div>
</div>

View File

@ -223,17 +223,6 @@ export function CreditTransactionsTable({
}
};
// Get transaction type badge variant
const getTransactionTypeBadgeVariant = (type: string) => {
switch (type) {
case CREDIT_TRANSACTION_TYPE.USAGE:
case CREDIT_TRANSACTION_TYPE.EXPIRE:
return 'destructive' as const;
default:
return 'outline' as const;
}
};
// Get transaction type display name
const getTransactionTypeDisplayName = (type: string) => {
switch (type) {
@ -268,8 +257,8 @@ export function CreditTransactionsTable({
return (
<div className="flex items-center gap-2 pl-3">
<Badge
variant={getTransactionTypeBadgeVariant(transaction.type)}
className="cursor-pointer hover:bg-accent transition-colors"
variant="outline"
className="hover:bg-accent transition-colors"
>
{getTransactionTypeIcon(transaction.type)}
{getTransactionTypeDisplayName(transaction.type)}

View File

@ -70,17 +70,6 @@ export function CreditDetailViewer({ transaction }: CreditDetailViewerProps) {
}
};
// Get transaction type badge variant
const getTransactionTypeBadgeVariant = (type: string) => {
switch (type) {
case CREDIT_TRANSACTION_TYPE.USAGE:
case CREDIT_TRANSACTION_TYPE.EXPIRE:
return 'destructive' as const;
default:
return 'outline' as const;
}
};
// Get transaction type display name
const getTransactionTypeDisplayName = (type: string) => {
switch (type) {
@ -131,8 +120,8 @@ export function CreditDetailViewer({ transaction }: CreditDetailViewerProps) {
<div className="flex items-center gap-2">
{/* Transaction Type Badge */}
<Badge
variant={getTransactionTypeBadgeVariant(transaction.type)}
className="px-2 py-1"
variant="outline"
className="hover:bg-accent transition-colors"
>
{getTransactionTypeIcon(transaction.type)}
{getTransactionTypeDisplayName(transaction.type)}