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:
parent
d8a12343c8
commit
d9cda3e122
109
messages/en.json
109
messages/en.json
@ -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",
|
||||
|
114
messages/zh.json
114
messages/zh.json
@ -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": "管理您的通知设置",
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 && (
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user