148 lines
5.6 KiB
TypeScript
148 lines
5.6 KiB
TypeScript
/**
|
|
* CreditAvailabilityWidget - Shows available operations based on credit balance
|
|
* Calculates how many operations can be performed with remaining credits
|
|
*/
|
|
|
|
import { Link } from 'react-router-dom';
|
|
import {
|
|
GroupIcon,
|
|
BoltIcon,
|
|
FileTextIcon,
|
|
FileIcon,
|
|
DollarLineIcon,
|
|
} from '../../icons';
|
|
|
|
interface CreditAvailabilityWidgetProps {
|
|
availableCredits: number;
|
|
totalCredits: number;
|
|
loading?: boolean;
|
|
}
|
|
|
|
// Average credit costs per operation
|
|
const OPERATION_COSTS = {
|
|
clustering: { label: 'Clustering Runs', cost: 10, icon: GroupIcon, color: 'text-purple-600 dark:text-purple-400' },
|
|
ideas: { label: 'Content Ideas', cost: 2, icon: BoltIcon, color: 'text-orange-600 dark:text-orange-400' },
|
|
content: { label: 'Articles', cost: 50, icon: FileTextIcon, color: 'text-green-600 dark:text-green-400' },
|
|
images: { label: 'Images', cost: 5, icon: FileIcon, color: 'text-pink-600 dark:text-pink-400' },
|
|
};
|
|
|
|
export default function CreditAvailabilityWidget({
|
|
availableCredits,
|
|
totalCredits,
|
|
loading = false
|
|
}: CreditAvailabilityWidgetProps) {
|
|
const usedCredits = totalCredits - availableCredits;
|
|
const usagePercent = totalCredits > 0 ? Math.round((usedCredits / totalCredits) * 100) : 0;
|
|
|
|
// Calculate available operations
|
|
const availableOps = Object.entries(OPERATION_COSTS).map(([key, config]) => ({
|
|
type: key,
|
|
label: config.label,
|
|
icon: config.icon,
|
|
color: config.color,
|
|
cost: config.cost,
|
|
available: Math.floor(availableCredits / config.cost),
|
|
}));
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 p-5">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-base font-semibold text-gray-800 dark:text-gray-200 uppercase tracking-wide">
|
|
Credit Availability
|
|
</h3>
|
|
<Link
|
|
to="/billing/credits"
|
|
className="text-sm font-medium text-brand-600 hover:text-brand-700 dark:text-brand-400"
|
|
>
|
|
Add Credits →
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Credits Balance */}
|
|
<div className="bg-gradient-to-r from-brand-50 to-purple-50 dark:from-brand-900/20 dark:to-purple-900/20 rounded-lg p-4 mb-4">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Available Credits</span>
|
|
<span className="text-2xl font-bold text-brand-600 dark:text-brand-400">
|
|
{loading ? '—' : availableCredits.toLocaleString()}
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-white dark:bg-gray-800 rounded-full h-2 mb-1">
|
|
<div
|
|
className={`h-2 rounded-full transition-all ${
|
|
usagePercent > 90 ? 'bg-red-500' : usagePercent > 75 ? 'bg-amber-500' : 'bg-green-500'
|
|
}`}
|
|
style={{ width: `${Math.max(100 - usagePercent, 0)}%` }}
|
|
></div>
|
|
</div>
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
{totalCredits > 0 ? `${usedCredits.toLocaleString()} of ${totalCredits.toLocaleString()} used (${usagePercent}%)` : 'No credits allocated'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Available Operations */}
|
|
<div className="space-y-2.5">
|
|
<p className="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
You can run:
|
|
</p>
|
|
{loading ? (
|
|
<div className="py-4 text-center">
|
|
<p className="text-sm text-gray-500">Loading...</p>
|
|
</div>
|
|
) : availableCredits === 0 ? (
|
|
<div className="py-4 text-center">
|
|
<p className="text-sm text-gray-600 dark:text-gray-400">No credits available</p>
|
|
<Link
|
|
to="/billing/credits"
|
|
className="text-sm text-brand-600 hover:text-brand-700 dark:text-brand-400"
|
|
>
|
|
Purchase credits to continue
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
availableOps.map((op) => {
|
|
const Icon = op.icon;
|
|
return (
|
|
<div
|
|
key={op.type}
|
|
className="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
|
|
>
|
|
<div className={`flex-shrink-0 ${op.color}`}>
|
|
<Icon className="w-5 h-5" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
|
{op.label}
|
|
</p>
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
{op.cost} credits each
|
|
</p>
|
|
</div>
|
|
<span className={`text-lg font-bold ${
|
|
op.available > 10 ? 'text-green-600 dark:text-green-400' :
|
|
op.available > 0 ? 'text-amber-600 dark:text-amber-400' :
|
|
'text-gray-400 dark:text-gray-600'
|
|
}`}>
|
|
{op.available === 0 ? '—' : op.available > 999 ? '999+' : op.available}
|
|
</span>
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
|
|
{/* Warning if low */}
|
|
{!loading && availableCredits > 0 && availableCredits < 100 && (
|
|
<div className="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-start gap-2 text-amber-600 dark:text-amber-400">
|
|
<DollarLineIcon className="w-4 h-4 mt-0.5" />
|
|
<p className="text-xs">
|
|
You're running low on credits. Consider purchasing more to avoid interruptions.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|