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": { "types": {
"MONTHLY_REFRESH": "Monthly Refresh", "MONTHLY_REFRESH": "Monthly Refresh",
"REGISTER_GIFT": "Register Gift", "REGISTER_GIFT": "Register Gift",
"PURCHASE": "Purchase", "PURCHASE": "Purchased Credits",
"USAGE": "Usage", "USAGE": "Consumed Credits",
"EXPIRE": "Expire", "EXPIRE": "Expired Credits",
"SUBSCRIPTION_RENEWAL": "Subscription Renewal", "SUBSCRIPTION_RENEWAL": "Subscription Renewal",
"LIFETIME_MONTHLY": "Lifetime Monthly" "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 { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { constructMetadata } from '@/lib/metadata'; import { constructMetadata } from '@/lib/metadata';
import { getUrlWithLocale } from '@/lib/urls/urls'; 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"> <div className="max-w-4xl mx-auto space-y-8">
{/* about section */} {/* about section */}
<div className="relative max-w-(--breakpoint-md) mx-auto mb-24 mt-8 md:mt-16"> <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"> <div className="flex flex-row items-center gap-8">
{/* avatar and name */} {/* avatar and name */}
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
@ -48,6 +49,9 @@ export default async function AITextPage() {
</div> </div>
</div> </div>
</div> </div>
{/* simulate consume credits */}
<ConsumeCreditCard />
</div> </div>
</div> </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 // Get transaction type display name
const getTransactionTypeDisplayName = (type: string) => { const getTransactionTypeDisplayName = (type: string) => {
switch (type) { switch (type) {
@ -268,8 +257,8 @@ export function CreditTransactionsTable({
return ( return (
<div className="flex items-center gap-2 pl-3"> <div className="flex items-center gap-2 pl-3">
<Badge <Badge
variant={getTransactionTypeBadgeVariant(transaction.type)} variant="outline"
className="cursor-pointer hover:bg-accent transition-colors" className="hover:bg-accent transition-colors"
> >
{getTransactionTypeIcon(transaction.type)} {getTransactionTypeIcon(transaction.type)}
{getTransactionTypeDisplayName(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 // Get transaction type display name
const getTransactionTypeDisplayName = (type: string) => { const getTransactionTypeDisplayName = (type: string) => {
switch (type) { switch (type) {
@ -131,8 +120,8 @@ export function CreditDetailViewer({ transaction }: CreditDetailViewerProps) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Transaction Type Badge */} {/* Transaction Type Badge */}
<Badge <Badge
variant={getTransactionTypeBadgeVariant(transaction.type)} variant="outline"
className="px-2 py-1" className="hover:bg-accent transition-colors"
> >
{getTransactionTypeIcon(transaction.type)} {getTransactionTypeIcon(transaction.type)}
{getTransactionTypeDisplayName(transaction.type)} {getTransactionTypeDisplayName(transaction.type)}