feat: enhance credits settings with tabbed interface and improved translations

- Implemented a tabbed interface in the CreditsPage component to separate balance and transactions views.
- Updated CreditPackages and CreditTransactionsPageClient components to utilize the new tab structure.
- Enhanced translation support for credits-related messages in both English and Chinese.
- Improved error handling and user feedback in credit-related components.
- Refactored CreditTransactionsTable to utilize translations for table headers and pagination controls.
This commit is contained in:
javayhu 2025-07-06 17:17:24 +08:00
parent d8a12343c8
commit d9cda3e122
7 changed files with 308 additions and 205 deletions

View File

@ -28,7 +28,19 @@
"save": "Save",
"loading": "Loading...",
"cancel": "Cancel",
"logoutFailed": "Failed to log out"
"logoutFailed": "Failed to log out",
"table": {
"totalRecords": "Total {count} records",
"noResults": "No results",
"loading": "Loading...",
"columns": "Columns",
"rowsPerPage": "Rows per page",
"page": "Page",
"firstPage": "First Page",
"lastPage": "Last Page",
"nextPage": "Next Page",
"previousPage": "Previous Page"
}
},
"PricingPage": {
"title": "Pricing",
@ -494,44 +506,8 @@
"error": "Failed to unban user"
},
"close": "Close"
}
},
"creditTransactions": {
"title": "Credit Transactions",
"error": "Failed to get credit transactions",
"search": "Search credit transactions...",
"columns": {
"columns": "Columns",
"id": "ID",
"type": "Type",
"description": "Description",
"amount": "Amount",
"remainingAmount": "Remaining Amount",
"paymentId": "Payment ID",
"expirationDate": "Expiration Date",
"expirationDateProcessedAt": "Expiration Date Processed At",
"createdAt": "Created At",
"updatedAt": "Updated At"
},
"noResults": "No results",
"firstPage": "First Page",
"lastPage": "Last Page",
"nextPage": "Next Page",
"previousPage": "Previous Page",
"rowsPerPage": "Rows per page",
"page": "Page",
"loading": "Loading...",
"paymentIdCopied": "Payment ID copied to clipboard",
"types": {
"MONTHLY_REFRESH": "Monthly Refresh",
"REGISTER_GIFT": "Register Gift",
"PURCHASE": "Purchase",
"USAGE": "Usage",
"EXPIRE": "Expire"
},
"expired": "Expired",
"never": "Never"
}
},
"settings": {
"title": "Settings",
"profile": {
@ -596,9 +572,60 @@
"credits": {
"title": "Credits",
"description": "Manage your credits",
"credits": "Credits",
"creditsDescription": "You have {credits} credits",
"creditsExpired": "Credits expired"
"balance": {
"title": "Credit Balance",
"credits": "Credits",
"creditsDescription": "You have {credits} credits",
"creditsExpired": "Credits expired"
},
"packages": {
"balance": "Credit Balance",
"title": "Credit Packages",
"description": "Purchase additional credits to use our services",
"purchase": "Purchase",
"processing": "Processing...",
"popular": "Popular",
"completePurchase": "Complete Your Purchase",
"failedToFetchCredits": "Failed to fetch credits",
"failedToCreatePaymentIntent": "Failed to create payment intent",
"failedToInitiatePayment": "Failed to initiate payment",
"creditsAdded": "Credits have been added to your account",
"cancel": "Cancel",
"purchaseFailed": "Purchase credits failed",
"pay": "Pay"
},
"tabs": {
"balance": "Balance",
"transactions": "Transactions"
},
"transactions": {
"title": "Credit Transactions",
"error": "Failed to get credit transactions",
"search": "Search credit transactions...",
"columns": {
"columns": "Columns",
"id": "ID",
"type": "Type",
"description": "Description",
"amount": "Amount",
"remainingAmount": "Remaining Amount",
"paymentId": "Payment ID",
"expirationDate": "Expiration Date",
"expirationDateProcessedAt": "Expiration Date Processed At",
"createdAt": "Created At",
"updatedAt": "Updated At"
},
"paymentIdCopied": "Payment ID copied to clipboard",
"types": {
"MONTHLY_REFRESH": "Monthly Refresh",
"REGISTER_GIFT": "Register Gift",
"PURCHASE": "Purchase",
"USAGE": "Usage",
"EXPIRE": "Expire"
},
"expired": "Expired",
"never": "Never"
}
},
"notification": {
"title": "Notification",

View File

@ -11,9 +11,9 @@
"language": "切换语言",
"mode": {
"label": "切换模式",
"light": "浅色模式",
"dark": "深色模式",
"system": "跟随系统"
"light": "浅色",
"dark": "深色",
"system": "系统"
},
"theme": {
"label": "切换主题",
@ -28,7 +28,19 @@
"saving": "保存中...",
"loading": "加载中...",
"cancel": "取消",
"logoutFailed": "退出失败"
"logoutFailed": "退出失败",
"table": {
"totalRecords": "总共 {count} 条记录",
"noResults": "无结果",
"loading": "加载中...",
"columns": "列",
"rowsPerPage": "每页行数",
"page": "页",
"firstPage": "第一页",
"lastPage": "最后一页",
"nextPage": "下一页",
"previousPage": "上一页"
}
},
"PricingPage": {
"title": "价格",
@ -495,42 +507,6 @@
"error": "解除封禁失败"
},
"close": "关闭"
},
"creditTransactions": {
"title": "积分交易记录",
"error": "获取积分交易记录失败",
"search": "搜索积分交易记录...",
"columns": {
"columns": "列",
"id": "ID",
"type": "类型",
"description": "描述",
"amount": "金额",
"remainingAmount": "剩余金额",
"paymentId": "支付ID",
"expirationDate": "过期时间",
"expirationDateProcessedAt": "过期时间处理时间",
"createdAt": "创建时间",
"updatedAt": "更新时间"
},
"noResults": "无结果",
"firstPage": "首页",
"lastPage": "末页",
"nextPage": "下一页",
"previousPage": "上一页",
"rowsPerPage": "每页行数",
"page": "页",
"loading": "加载中...",
"paymentIdCopied": "支付ID已复制到剪贴板",
"types": {
"MONTHLY_REFRESH": "月度刷新",
"REGISTER_GIFT": "注册礼品",
"PURCHASE": "购买",
"USAGE": "使用",
"EXPIRE": "过期"
},
"expired": "已过期",
"never": "永不"
}
},
"settings": {
@ -594,6 +570,64 @@
"retry": "重试",
"errorMessage": "获取数据失败"
},
"credits": {
"title": "积分",
"description": "管理您的积分",
"balance": {
"title": "积分余额",
"credits": "积分",
"creditsDescription": "您有 {credits} 积分",
"creditsExpired": "积分已过期"
},
"packages": {
"balance": "积分余额",
"title": "积分套餐",
"description": "购买积分以使用我们的更多服务",
"purchase": "购买",
"processing": "处理中...",
"popular": "热门",
"completePurchase": "请支付订单",
"failedToFetchCredits": "获取积分失败",
"failedToCreatePaymentIntent": "创建付款意向失败",
"failedToInitiatePayment": "发起付款失败",
"creditsAdded": "积分已添加到您的账户",
"cancel": "取消",
"purchaseFailed": "购买积分失败",
"pay": "支付"
},
"tabs": {
"balance": "积分余额",
"transactions": "积分记录"
},
"transactions": {
"title": "积分记录",
"error": "获取积分交易记录失败",
"search": "搜索积分交易记录...",
"columns": {
"columns": "列",
"id": "ID",
"type": "类型",
"description": "描述",
"amount": "金额",
"remainingAmount": "剩余金额",
"paymentId": "支付编号",
"expirationDate": "过期日期",
"expirationDateProcessedAt": "过期日期处理时间",
"createdAt": "创建时间",
"updatedAt": "更新时间"
},
"paymentIdCopied": "支付ID已复制到剪贴板",
"types": {
"MONTHLY_REFRESH": "每月刷新",
"REGISTER_GIFT": "注册礼品",
"PURCHASE": "购买",
"USAGE": "使用",
"EXPIRE": "过期"
},
"expired": "已过期",
"never": "永不"
}
},
"notification": {
"title": "通知",
"description": "管理您的通知设置",

View File

@ -1,12 +1,29 @@
import { CreditPackages } from '@/components/settings/credits/credit-packages';
import { CreditTransactionsPageClient } from '@/components/settings/credits/credit-transactions-page';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useTranslations } from 'next-intl';
export default function CreditsPage() {
return (
<div className="flex flex-col gap-8">
<CreditPackages />
const t = useTranslations('Dashboard.settings.credits');
<CreditTransactionsPageClient />
</div>
return (
<Tabs defaultValue="balance" className="">
<TabsList className="">
<TabsTrigger value="balance" className="flex items-center gap-2 cursor-pointer">
{t('tabs.balance')}
</TabsTrigger>
<TabsTrigger value="transactions" className="flex items-center gap-2 cursor-pointer">
{t('tabs.transactions')}
</TabsTrigger>
</TabsList>
<TabsContent value="balance" className="space-y-6 py-4">
<CreditPackages />
</TabsContent>
<TabsContent value="transactions" className="space-y-6 py-4">
<CreditTransactionsPageClient />
</TabsContent>
</Tabs>
);
}

View File

@ -3,19 +3,20 @@
import { createCreditPaymentIntent, getCreditsAction } from '@/actions/credits.action';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { CREDIT_PACKAGES } from '@/lib/constants';
import { formatPrice } from '@/lib/formatter';
import { cn } from '@/lib/utils';
import { useTransactionStore } from '@/stores/transaction-store';
import { CircleCheckBigIcon, CoinsIcon, Loader2Icon } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
import { Separator } from '../../ui/separator';
import { StripePaymentForm } from './stripe-payment-form';
export function CreditPackages() {
const t = useTranslations('Dashboard.settings.credits.packages');
const [loadingCredits, setLoadingCredits] = useState(true);
const [loadingPackage, setLoadingPackage] = useState<string | null>(null);
const [credits, setCredits] = useState<number | null>(null);
@ -38,13 +39,13 @@ export function CreditPackages() {
console.log('CreditPackages, fetched credits:', result.data.credits);
setCredits(result.data.credits || 0);
} else {
const errorMessage = result?.data?.error || 'Failed to fetch credits';
const errorMessage = result?.data?.error || t('failedToFetchCredits');
console.error('CreditPackages, failed to fetch credits:', errorMessage);
toast.error(errorMessage);
}
} catch (error) {
console.error('CreditPackages, failed to fetch credits:', error);
toast.error('Failed to fetch credits');
toast.error(t('failedToFetchCredits'));
} finally {
setLoadingCredits(false);
}
@ -66,13 +67,13 @@ export function CreditPackages() {
clientSecret: result.data.clientSecret,
});
} else {
const errorMessage = result?.data?.error || 'Failed to create payment intent';
const errorMessage = result?.data?.error || t('failedToCreatePaymentIntent');
console.error('CreditPackages, failed to create payment intent:', errorMessage);
toast.error(errorMessage);
}
} catch (error) {
console.error('CreditPackages, failed to initiate payment:', error);
toast.error('Failed to initiate payment');
toast.error(t('failedToInitiatePayment'));
} finally {
setLoadingPackage(null);
}
@ -85,7 +86,7 @@ export function CreditPackages() {
packageId: null,
clientSecret: null,
});
toast.success('Credits have been added to your account');
toast.success(t('creditsAdded'));
};
const handlePaymentCancel = () => {
@ -105,7 +106,7 @@ export function CreditPackages() {
<div className="space-y-6">
<Card className="w-full">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-lg font-semibold">Credit Balance</CardTitle>
<CardTitle className="text-lg font-semibold">{t('balance')}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-4">
@ -119,74 +120,74 @@ export function CreditPackages() {
)}
</div>
</div>
</div>
</CardContent>
</Card>
<Separator className="my-2" />
<Card className="w-full">
<CardHeader>
<CardTitle className="text-lg font-semibold">{t('title')}</CardTitle>
<CardDescription className="text-sm text-muted-foreground">
{t('description')}
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{CREDIT_PACKAGES.map((pkg) => (
<Card key={pkg.id} className={cn(`relative ${pkg.popular ? 'border-primary' : ''}`,
'shadow-none border-1 border-border')}>
{pkg.popular && (
<div className="absolute -top-3.5 left-1/2 transform -translate-x-1/2">
<Badge variant="default" className="bg-primary text-primary-foreground">
{t('popular')}
</Badge>
</div>
)}
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<h2 className="text-lg font-semibold">Credit Packages</h2>
<p className="text-sm text-muted-foreground">
Purchase additional credits to use our services
</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{CREDIT_PACKAGES.map((pkg) => (
<Card key={pkg.id} className={cn(`relative ${pkg.popular ? 'border-primary' : ''}`,
'shadow-none border-1 border-border')}>
{pkg.popular && (
<div className="absolute -top-3.5 left-1/2 transform -translate-x-1/2">
<Badge variant="default" className="bg-primary text-primary-foreground">
Most Popular
</Badge>
</div>
)}
{/* <CardHeader className="text-center">
{/* <CardHeader className="text-center">
<CardTitle className="text-lg capitalize">{pkg.id}</CardTitle>
</CardHeader> */}
<CardContent className="space-y-3">
{/* Price and Credits - Left/Right Layout */}
<div className="flex items-center justify-between py-2">
<div className="text-left">
<div className="text-2xl font-semibold flex items-center gap-2">
<CoinsIcon className="h-4 w-4 text-muted-foreground" />
{pkg.credits.toLocaleString()}
</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-primary">
{formatPrice(pkg.price, 'USD')}
</div>
</div>
<CardContent className="space-y-3">
{/* Price and Credits - Left/Right Layout */}
<div className="flex items-center justify-between py-2">
<div className="text-left">
<div className="text-2xl font-semibold flex items-center gap-2">
<CoinsIcon className="h-4 w-4 text-muted-foreground" />
{pkg.credits.toLocaleString()}
</div>
<div className="text-sm text-muted-foreground text-left py-2 flex items-center gap-2">
<CircleCheckBigIcon className="h-4 w-4 text-green-500" />
{pkg.description}
</div>
<div className="text-right">
<div className="text-3xl font-bold text-primary">
{formatPrice(pkg.price, 'USD')}
</div>
</div>
</div>
{/* purchase button */}
<Button
onClick={() => handlePurchase(pkg.id)}
disabled={loadingPackage === pkg.id}
className="w-full cursor-pointer"
variant={pkg.popular ? 'default' : 'outline'}
>
{loadingPackage === pkg.id ? (
<>
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
Processing...
</>
) : (
'Purchase'
)}
</Button>
</CardContent>
</Card>
))}
</div>
</div>
<div className="text-sm text-muted-foreground text-left py-2 flex items-center gap-2">
<CircleCheckBigIcon className="h-4 w-4 text-green-500" />
{pkg.description}
</div>
{/* purchase button */}
<Button
onClick={() => handlePurchase(pkg.id)}
disabled={loadingPackage === pkg.id}
className="w-full cursor-pointer"
variant={pkg.popular ? 'default' : 'outline'}
>
{loadingPackage === pkg.id ? (
<>
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
{t('processing')}
</>
) : (
t('purchase')
)}
</Button>
</CardContent>
</Card>
))}
</div>
</CardContent>
</Card>
@ -195,7 +196,7 @@ export function CreditPackages() {
<Dialog open={paymentDialog.isOpen} onOpenChange={handlePaymentCancel}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Complete Your Purchase</DialogTitle>
<DialogTitle>{t('completePurchase')}</DialogTitle>
</DialogHeader>
{paymentDialog.clientSecret && paymentDialog.packageId && (

View File

@ -10,9 +10,9 @@ import { useEffect, useState } from 'react';
import { toast } from 'sonner';
export function CreditTransactionsPageClient() {
const t = useTranslations('Dashboard.admin.creditTransactions');
const t = useTranslations('Dashboard.settings.credits.transactions');
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(3);
const [pageSize, setPageSize] = useState(10);
const [search, setSearch] = useState('');
const [data, setData] = useState<CreditTransaction[]>([]);
const [total, setTotal] = useState(0);
@ -54,21 +54,17 @@ export function CreditTransactionsPageClient() {
}, [pageIndex, pageSize, search, sorting, refreshTrigger]);
return (
<div className="w-full space-y-4">
<h1 className="text-lg font-semibold">{t('title')}</h1>
<CreditTransactionsTable
data={data}
total={total}
pageIndex={pageIndex}
pageSize={pageSize}
search={search}
loading={loading}
onSearch={setSearch}
onPageChange={setPageIndex}
onPageSizeChange={setPageSize}
onSortingChange={setSorting}
/>
</div>
<CreditTransactionsTable
data={data}
total={total}
pageIndex={pageIndex}
pageSize={pageSize}
search={search}
loading={loading}
onSearch={setSearch}
onPageChange={setPageIndex}
onPageSizeChange={setPageSize}
onSortingChange={setSorting}
/>
);
}

View File

@ -123,7 +123,8 @@ export function CreditTransactionsTable({
onPageSizeChange,
onSortingChange,
}: CreditTransactionsTableProps) {
const t = useTranslations('Dashboard.admin.creditTransactions');
const t = useTranslations('Dashboard.settings.credits.transactions');
const tTable = useTranslations('Common.table');
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
@ -131,19 +132,6 @@ export function CreditTransactionsTable({
// show fake data in demo website
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
// Map column IDs to translation keys
const columnIdToTranslationKey = {
type: 'columns.type' as const,
amount: 'columns.amount' as const,
remainingAmount: 'columns.remainingAmount' as const,
description: 'columns.description' as const,
paymentId: 'columns.paymentId' as const,
expirationDate: 'columns.expirationDate' as const,
expirationDateProcessedAt: 'columns.expirationDateProcessedAt' as const,
createdAt: 'columns.createdAt' as const,
updatedAt: 'columns.updatedAt' as const,
} as const;
// Get transaction type icon and color
const getTransactionTypeIcon = (type: string) => {
switch (type) {
@ -180,6 +168,24 @@ export function CreditTransactionsTable({
}
};
// Get transaction type display name
const getTransactionTypeDisplayName = (type: string) => {
switch (type) {
case CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH:
return t('types.MONTHLY_REFRESH');
case CREDIT_TRANSACTION_TYPE.REGISTER_GIFT:
return t('types.REGISTER_GIFT');
case CREDIT_TRANSACTION_TYPE.PURCHASE:
return t('types.PURCHASE');
case CREDIT_TRANSACTION_TYPE.USAGE:
return t('types.USAGE');
case CREDIT_TRANSACTION_TYPE.EXPIRE:
return t('types.EXPIRE');
default:
return type;
}
};
// Table columns definition
const columns: ColumnDef<CreditTransaction>[] = [
{
@ -196,7 +202,7 @@ export function CreditTransactionsTable({
className={`px-2 py-1 flex items-center gap-1 ${getTransactionTypeColor(transaction.type)}`}
>
{getTransactionTypeIcon(transaction.type)}
{transaction.type}
{getTransactionTypeDisplayName(transaction.type)}
</Badge>
</div>
);
@ -419,6 +425,30 @@ export function CreditTransactionsTable({
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
const getColumnDisplayName = (columnId: string) => {
switch (columnId) {
case 'type':
return t('columns.type');
case 'amount':
return t('columns.amount');
case 'remainingAmount':
return t('columns.remainingAmount');
case 'description':
return t('columns.description');
case 'paymentId':
return t('columns.paymentId');
case 'expirationDate':
return t('columns.expirationDate');
case 'expirationDateProcessedAt':
return t('columns.expirationDateProcessedAt');
case 'createdAt':
return t('columns.createdAt');
case 'updatedAt':
return t('columns.updatedAt');
default:
return columnId;
}
};
return (
<DropdownMenuCheckboxItem
key={column.id}
@ -428,11 +458,7 @@ export function CreditTransactionsTable({
column.toggleVisibility(!!value)
}
>
{t(
columnIdToTranslationKey[
column.id as keyof typeof columnIdToTranslationKey
] || 'columns.columns'
)}
{getColumnDisplayName(column.id)}
</DropdownMenuCheckboxItem>
);
})}
@ -483,7 +509,7 @@ export function CreditTransactionsTable({
colSpan={columns.length}
className="h-24 text-center"
>
{loading ? t('loading') : t('noResults')}
{loading ? tTable('loading') : tTable('noResults')}
</TableCell>
</TableRow>
)}
@ -495,14 +521,14 @@ export function CreditTransactionsTable({
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
{total > 0 && (
<span>
{t('totalRecords', { count: total })}
{tTable('totalRecords', { count: total })}
</span>
)}
</div>
<div className="flex w-full items-center gap-8 lg:w-fit">
<div className="hidden items-center gap-2 lg:flex">
<Label htmlFor="rows-per-page" className="text-sm font-medium">
{t('rowsPerPage')}
{tTable('rowsPerPage')}
</Label>
<Select
value={`${pageSize}`}
@ -528,7 +554,7 @@ export function CreditTransactionsTable({
</Select>
</div>
<div className="flex w-fit items-center justify-center text-sm font-medium">
{t('page')} {pageIndex + 1} {' / '}
{tTable('page')} {pageIndex + 1} {' / '}
{Math.max(1, Math.ceil(total / pageSize))}
</div>
<div className="flex items-center gap-2">
@ -538,7 +564,7 @@ export function CreditTransactionsTable({
onClick={() => onPageChange(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">{t('goToFirstPage')}</span>
<span className="sr-only">{tTable('firstPage')}</span>
<ChevronsLeftIcon className="h-4 w-4" />
</Button>
<Button
@ -547,7 +573,7 @@ export function CreditTransactionsTable({
onClick={() => onPageChange(pageIndex - 1)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">{t('goToPreviousPage')}</span>
<span className="sr-only">{tTable('previousPage')}</span>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
<Button
@ -556,7 +582,7 @@ export function CreditTransactionsTable({
onClick={() => onPageChange(pageIndex + 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">{t('goToNextPage')}</span>
<span className="sr-only">{tTable('nextPage')}</span>
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
@ -565,7 +591,7 @@ export function CreditTransactionsTable({
onClick={() => onPageChange(Math.ceil(total / pageSize) - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">{t('goToLastPage')}</span>
<span className="sr-only">{tTable('lastPage')}</span>
<ChevronsRightIcon className="h-4 w-4" />
</Button>
</div>

View File

@ -14,6 +14,7 @@ import {
import { loadStripe } from '@stripe/stripe-js';
import { CoinsIcon, Loader2Icon } from 'lucide-react';
import { useTheme } from 'next-themes';
import { useTranslations } from 'next-intl';
import { useMemo, useState } from 'react';
import { toast } from 'sonner';
@ -82,6 +83,7 @@ function PaymentForm({
}: PaymentFormProps) {
const stripe = useStripe();
const elements = useElements();
const t = useTranslations('Dashboard.settings.credits.packages');
const [processing, setProcessing] = useState(false);
const { triggerRefresh } = useTransactionStore();
@ -133,7 +135,7 @@ function PaymentForm({
}
} catch (error) {
console.error('PaymentForm, payment error:', error);
toast.error('Purchase credits failed');
toast.error(t('purchaseFailed'));
} finally {
setProcessing(false);
}
@ -173,7 +175,7 @@ function PaymentForm({
disabled={processing}
className="cursor-pointer"
>
Cancel
{t('cancel')}
</Button>
<Button
type="submit"
@ -183,11 +185,11 @@ function PaymentForm({
{processing ? (
<>
<Loader2Icon className="h-4 w-4 animate-spin" />
Processing...
{t('processing')}
</>
) : (
<>
Pay {/* {formatPrice(packageInfo.price, 'USD')} */}
{t('pay')} {/* {formatPrice(packageInfo.price, 'USD')} */}
</>
)}
</Button>