billing accoutn with all the mess here

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-05 03:59:54 +00:00
parent 6b291671bd
commit 6cf786b03f
41 changed files with 7257 additions and 685 deletions

View File

@@ -3,11 +3,12 @@
* User-facing credits usage, transactions, and billing information
*/
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import PageMeta from '../../components/common/PageMeta';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { getCreditBalance, getCreditTransactions, CreditBalance, CreditTransaction } from '../../services/billing.api';
import Button from '../../components/ui/button/Button';
import Badge from '../../components/ui/badge/Badge';
import {
@@ -17,38 +18,12 @@ import {
CheckCircleIcon
} from '../../icons';
interface CreditTransaction {
id: number;
transaction_type: string;
amount: number;
balance_after: number;
description: string;
created_at: string;
}
interface CreditUsageLog {
id: number;
operation_type: string;
credits_used: number;
model_used: string;
created_at: string;
metadata: any;
}
interface AccountBalance {
credits: number;
subscription_plan: string;
monthly_credits_included: number;
bonus_credits: number;
}
const CreditsAndBilling: React.FC = () => {
const toast = useToast();
const [balance, setBalance] = useState<AccountBalance | null>(null);
const [balance, setBalance] = useState<CreditBalance | null>(null);
const [transactions, setTransactions] = useState<CreditTransaction[]>([]);
const [usageLogs, setUsageLogs] = useState<CreditUsageLog[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'overview' | 'transactions' | 'usage'>('overview');
const [activeTab, setActiveTab] = useState<'overview' | 'transactions'>('overview');
useEffect(() => {
loadData();
@@ -57,15 +32,13 @@ const CreditsAndBilling: React.FC = () => {
const loadData = async () => {
try {
setLoading(true);
const [balanceData, transactionsData, usageData] = await Promise.all([
fetchAPI('/v1/billing/account_balance/'),
fetchAPI('/v1/billing/transactions/?limit=50'),
fetchAPI('/v1/billing/usage/?limit=50'),
const [balanceData, transactionsData] = await Promise.all([
getCreditBalance(),
getCreditTransactions(),
]);
setBalance(balanceData);
setTransactions(transactionsData.results || []);
setUsageLogs(usageData.results || []);
} catch (error: any) {
toast?.error(error?.message || 'Failed to load billing data');
} finally {
@@ -78,6 +51,7 @@ const CreditsAndBilling: React.FC = () => {
case 'purchase': return 'success';
case 'grant': return 'info';
case 'deduction': return 'warning';
case 'usage': return 'error';
case 'refund': return 'primary';
case 'adjustment': return 'secondary';
default: return 'default';
@@ -113,42 +87,38 @@ const CreditsAndBilling: React.FC = () => {
Manage your credits, view transactions, and monitor usage
</p>
</div>
<Button
variant="primary"
startIcon={<DollarLineIcon className="w-4 h-4" />}
onClick={() => {
// TODO: Link to purchase credits page
toast?.info('Purchase credits feature coming soon');
}}
>
Purchase Credits
</Button>
<Link to="/account/purchase-credits">
<Button variant="primary">
Purchase Credits
</Button>
</Link>
</div>
{/* Credit Balance Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<EnhancedMetricCard
title="Current Balance"
value={balance?.credits || 0}
value={balance?.balance || 0}
icon={<BoltIcon />}
accentColor="orange"
/>
<EnhancedMetricCard
title="Monthly Included"
value={balance?.monthly_credits_included || 0}
subtitle={balance?.subscription_plan || 'Free'}
title="Monthly Credits"
value={balance?.monthly_credits || 0}
subtitle={balance?.subscription_plan || 'No plan'}
icon={<CheckCircleIcon />}
accentColor="green"
/>
<EnhancedMetricCard
title="Bonus Credits"
value={balance?.bonus_credits || 0}
title="Subscription"
value={balance?.subscription_plan || 'None'}
subtitle={balance?.subscription_status || 'Inactive'}
icon={<DollarLineIcon />}
accentColor="blue"
/>
<EnhancedMetricCard
title="Total This Month"
value={usageLogs.reduce((sum, log) => sum + log.credits_used, 0)}
title="Total Transactions"
value={transactions.length}
icon={<TimeIcon />}
accentColor="purple"
/>
@@ -177,31 +147,21 @@ const CreditsAndBilling: React.FC = () => {
>
Transactions ({transactions.length})
</button>
<button
onClick={() => setActiveTab('usage')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'usage'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Usage History ({usageLogs.length})
</button>
</nav>
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-1 gap-6">
{/* Recent Transactions */}
<ComponentCard title="Recent Transactions">
<div className="space-y-3">
{transactions.slice(0, 5).map((transaction) => (
{transactions.slice(0, 10).map((transaction) => (
<div key={transaction.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-1">
<div className="flex items-center gap-2">
<Badge tone={getTransactionTypeColor(transaction.transaction_type) as any}>
{transaction.transaction_type}
{formatOperationType(transaction.transaction_type)}
</Badge>
<span className="text-sm text-gray-900 dark:text-white">
{transaction.description}
@@ -209,15 +169,13 @@ const CreditsAndBilling: React.FC = () => {
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{new Date(transaction.created_at).toLocaleString()}
{transaction.reference_id && ` • Ref: ${transaction.reference_id}`}
</div>
</div>
<div className="text-right">
<div className={`font-bold ${transaction.amount > 0 ? 'text-green-600' : 'text-red-600'}`}>
{transaction.amount > 0 ? '+' : ''}{transaction.amount}
</div>
<div className="text-xs text-gray-500">
Balance: {transaction.balance_after}
</div>
</div>
</div>
))}
@@ -228,34 +186,6 @@ const CreditsAndBilling: React.FC = () => {
)}
</div>
</ComponentCard>
{/* Recent Usage */}
<ComponentCard title="Recent Usage">
<div className="space-y-3">
{usageLogs.slice(0, 5).map((log) => (
<div key={log.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-1">
<div className="text-sm font-medium text-gray-900 dark:text-white">
{formatOperationType(log.operation_type)}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{log.model_used} {new Date(log.created_at).toLocaleString()}
</div>
</div>
<div className="text-right">
<div className="font-bold text-amber-600 dark:text-amber-400">
{log.credits_used} credits
</div>
</div>
</div>
))}
{usageLogs.length === 0 && (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
No usage history yet
</div>
)}
</div>
</ComponentCard>
</div>
)}
@@ -274,11 +204,11 @@ const CreditsAndBilling: React.FC = () => {
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Description
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Amount
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Reference
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Balance
Amount
</th>
</tr>
</thead>
@@ -290,63 +220,20 @@ const CreditsAndBilling: React.FC = () => {
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Badge tone={getTransactionTypeColor(transaction.transaction_type) as any}>
{transaction.transaction_type}
{formatOperationType(transaction.transaction_type)}
</Badge>
</td>
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
{transaction.description}
</td>
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
{transaction.reference_id || '-'}
</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm text-right font-bold ${
transaction.amount > 0 ? 'text-green-600' : 'text-red-600'
}`}>
{transaction.amount > 0 ? '+' : ''}{transaction.amount}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white text-right">
{transaction.balance_after}
</td>
</tr>
))}
</tbody>
</table>
</div>
</ComponentCard>
)}
{activeTab === 'usage' && (
<ComponentCard title="Usage History">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Operation
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Model
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Credits
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{usageLogs.map((log) => (
<tr key={log.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{new Date(log.created_at).toLocaleString()}
</td>
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
{formatOperationType(log.operation_type)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{log.model_used || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-right font-bold text-amber-600 dark:text-amber-400">
{log.credits_used}
</td>
</tr>
))}
</tbody>