Fix : usage convert
This commit is contained in:
1 parent
7881db81ec
commit
e6e44ac364
10 files changed
+974
-16
No files matched your search
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import Card from "@/shared/components/Card";
|
||||
import Badge from "@/shared/components/Badge";
|
||||
import QuotaProgressBar from "./QuotaProgressBar";
|
||||
import { calculatePercentage } from "./utils";
|
||||
|
||||
const planVariants = {
|
||||
free: "default",
|
||||
pro: "primary",
|
||||
ultra: "success",
|
||||
enterprise: "info",
|
||||
};
|
||||
|
||||
export default function ProviderLimitCard({
|
||||
provider,
|
||||
name,
|
||||
plan,
|
||||
quotas = [],
|
||||
message = null,
|
||||
loading = false,
|
||||
error = null,
|
||||
onRefresh,
|
||||
}) {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [imgError, setImgError] = useState(false);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
if (!onRefresh || refreshing) return;
|
||||
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await onRefresh();
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Get provider info from config
|
||||
const getProviderColor = () => {
|
||||
const colors = {
|
||||
github: "#000000",
|
||||
antigravity: "#4285F4",
|
||||
codex: "#10A37F",
|
||||
kiro: "#FF9900",
|
||||
claude: "#D97757",
|
||||
};
|
||||
return colors[provider?.toLowerCase()] || "#6B7280";
|
||||
};
|
||||
|
||||
const providerColor = getProviderColor();
|
||||
const planVariant = planVariants[plan?.toLowerCase()] || "default";
|
||||
|
||||
return (
|
||||
<Card padding="md" className="flex flex-col gap-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Provider Logo */}
|
||||
<div
|
||||
className="size-10 rounded-lg flex items-center justify-center p-1.5"
|
||||
style={{ backgroundColor: `${providerColor}15` }}
|
||||
>
|
||||
{imgError ? (
|
||||
<span
|
||||
className="text-sm font-bold"
|
||||
style={{ color: providerColor }}
|
||||
>
|
||||
{provider?.slice(0, 2).toUpperCase() || "PR"}
|
||||
</span>
|
||||
) : (
|
||||
<Image
|
||||
src={`/providers/${provider}.png`}
|
||||
alt={provider || "Provider"}
|
||||
width={40}
|
||||
height={40}
|
||||
className="object-contain rounded-lg"
|
||||
sizes="40px"
|
||||
onError={() => setImgError(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold text-text-primary">{name || provider}</h3>
|
||||
{plan && (
|
||||
<Badge
|
||||
variant={planVariants[plan?.toLowerCase()] || "default"}
|
||||
size="xs"
|
||||
>
|
||||
{plan}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Refresh Button */}
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshing || loading}
|
||||
className="p-2 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Refresh quota"
|
||||
>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[20px] text-text-muted ${
|
||||
refreshing || loading ? "animate-spin" : ""
|
||||
}`}
|
||||
>
|
||||
refresh
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-black/5 dark:bg-white/5 rounded animate-pulse" />
|
||||
<div className="h-2 bg-black/5 dark:bg-white/5 rounded animate-pulse" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-black/5 dark:bg-white/5 rounded animate-pulse" />
|
||||
<div className="h-2 bg-black/5 dark:bg-white/5 rounded animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error State */}
|
||||
{!loading && error && (
|
||||
<div className="p-4 rounded-lg bg-red-500/10 border border-red-500/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="material-symbols-outlined text-red-500 text-[20px]">
|
||||
error
|
||||
</span>
|
||||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Message (for providers without API) */}
|
||||
{!loading && !error && message && (
|
||||
<div className="p-4 rounded-lg bg-blue-500/10 border border-blue-500/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="material-symbols-outlined text-blue-500 text-[20px]">
|
||||
info
|
||||
</span>
|
||||
<p className="text-sm text-blue-600 dark:text-blue-400">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quota Progress Bars */}
|
||||
{!loading && !error && !message && quotas?.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
{quotas.map((quota, index) => {
|
||||
const percentage = calculatePercentage(quota.used, quota.total);
|
||||
const unlimited = quota.total === 0 || quota.total === null;
|
||||
|
||||
return (
|
||||
<QuotaProgressBar
|
||||
key={`${quota.name}-${index}`}
|
||||
label={quota.name}
|
||||
used={quota.used}
|
||||
total={quota.total}
|
||||
percentage={percentage}
|
||||
unlimited={unlimited}
|
||||
resetTime={quota.resetAt}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{!loading && !error && !message && quotas?.length === 0 && (
|
||||
<div className="text-center py-8 text-text-muted">
|
||||
<span className="material-symbols-outlined text-[48px] opacity-20">
|
||||
data_usage
|
||||
</span>
|
||||
<p className="text-sm mt-2">No quota data available</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user