mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
Refactor
This commit is contained in:
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||
import QuotaTable from "./QuotaTable";
|
||||
import Toggle from "@/shared/components/Toggle";
|
||||
import Tooltip from "@/shared/components/Tooltip";
|
||||
import {
|
||||
parseQuotaData,
|
||||
calculatePercentage,
|
||||
@@ -34,9 +35,15 @@ import {
|
||||
QUOTA_SORT_OPTIONS,
|
||||
} from "./utils";
|
||||
import Card from "@/shared/components/Card";
|
||||
import { EditConnectionModal } from "@/shared/components";
|
||||
import { ConfirmModal, EditConnectionModal } from "@/shared/components";
|
||||
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
|
||||
|
||||
function getCodexResetCreditCount(quota) {
|
||||
const value = quota?.raw?.resetCredits?.availableCount;
|
||||
const count = typeof value === "number" ? value : Number(value);
|
||||
return Number.isFinite(count) ? Math.max(0, count) : 0;
|
||||
}
|
||||
|
||||
export default function ProviderLimits() {
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [quotaData, setQuotaData] = useState({});
|
||||
@@ -50,6 +57,8 @@ export default function ProviderLimits() {
|
||||
const [connectionsLoading, setConnectionsLoading] = useState(true);
|
||||
const [deletingId, setDeletingId] = useState(null);
|
||||
const [togglingId, setTogglingId] = useState(null);
|
||||
const [resettingLimitId, setResettingLimitId] = useState(null);
|
||||
const [resetConfirmState, setResetConfirmState] = useState(null);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [selectedConnection, setSelectedConnection] = useState(null);
|
||||
const [proxyPools, setProxyPools] = useState([]);
|
||||
@@ -207,6 +216,32 @@ export default function ProviderLimits() {
|
||||
[fetchQuota],
|
||||
);
|
||||
|
||||
const handleResetCodexLimit = useCallback(
|
||||
async (connectionId, provider) => {
|
||||
if (provider !== "codex" || resettingLimitId) return;
|
||||
|
||||
setResettingLimitId(connectionId);
|
||||
setErrors((prev) => ({ ...prev, [connectionId]: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/usage/${connectionId}/codex-reset-credits`, { method: "POST" });
|
||||
const result = await response.json().catch(() => ({}));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.message || result.error || result.code || "Failed to reset Codex limit");
|
||||
}
|
||||
|
||||
await fetchQuota(connectionId, provider);
|
||||
setLastUpdated(new Date());
|
||||
} catch (error) {
|
||||
setErrors((prev) => ({ ...prev, [connectionId]: error.message || "Failed to reset Codex limit" }));
|
||||
} finally {
|
||||
setResettingLimitId(null);
|
||||
}
|
||||
},
|
||||
[fetchQuota, resettingLimitId],
|
||||
);
|
||||
|
||||
const handleDeleteConnection = useCallback(
|
||||
async (id) => {
|
||||
if (!confirm("Delete this connection?")) return;
|
||||
@@ -791,7 +826,10 @@ export default function ProviderLimits() {
|
||||
|
||||
// Use table layout for all providers
|
||||
const isInactive = conn.isActive === false;
|
||||
const rowBusy = deletingId === conn.id || togglingId === conn.id;
|
||||
const isCodex = conn.provider === "codex";
|
||||
const resetCreditCount = getCodexResetCreditCount(quota);
|
||||
const isResettingLimit = resettingLimitId === conn.id;
|
||||
const rowBusy = deletingId === conn.id || togglingId === conn.id || isResettingLimit;
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -822,53 +860,90 @@ export default function ProviderLimits() {
|
||||
{getConnectionLabel(conn)}
|
||||
</p>
|
||||
) : null}
|
||||
{isCodex && (
|
||||
<p className="text-[11px] text-text-muted truncate">
|
||||
Reset eligible: {resetCreditCount}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refreshProvider(conn.id, conn.provider)}
|
||||
disabled={isLoading || rowBusy}
|
||||
aria-label="Refresh quota"
|
||||
className="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 transition-colors disabled:opacity-50"
|
||||
title="Refresh quota"
|
||||
>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[18px] text-text-muted ${isLoading ? "animate-spin" : ""}`}
|
||||
{isCodex && (
|
||||
<Tooltip text={`Codex reset credits remaining: ${resetCreditCount}`}>
|
||||
<div
|
||||
className={`hidden h-8 items-center gap-1 rounded-lg border px-2 text-[11px] sm:flex ${
|
||||
resetCreditCount > 0
|
||||
? "border-primary/30 bg-primary/5 text-primary"
|
||||
: "border-black/10 bg-black/[0.02] text-text-muted dark:border-white/10 dark:bg-white/[0.03]"
|
||||
}`}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">restart_alt</span>
|
||||
<span className="tabular-nums">{resetCreditCount}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isCodex && resetCreditCount > 0 && (
|
||||
<Tooltip text={`Use one Codex reset credit. Available: ${resetCreditCount}`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setResetConfirmState({ connection: conn, resetCreditCount })}
|
||||
disabled={isLoading || rowBusy}
|
||||
className="flex h-8 items-center gap-1 rounded-lg border border-primary/30 px-2 text-[11px] text-primary transition-colors hover:bg-primary/10 disabled:opacity-50"
|
||||
>
|
||||
<span className={`material-symbols-outlined text-[15px] ${isResettingLimit ? "animate-spin" : ""}`}>
|
||||
{isResettingLimit ? "progress_activity" : "bolt"}
|
||||
</span>
|
||||
<span className="hidden lg:inline">Reset limit</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip text="Refresh quota">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refreshProvider(conn.id, conn.provider)}
|
||||
disabled={isLoading || rowBusy}
|
||||
aria-label="Refresh quota"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-black/5 dark:hover:bg-white/5 transition-colors disabled:opacity-50"
|
||||
>
|
||||
refresh
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedConnection(conn);
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
disabled={rowBusy}
|
||||
aria-label="Edit connection"
|
||||
className="p-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary transition-colors disabled:opacity-50"
|
||||
title="Edit connection"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">
|
||||
edit
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDeleteConnection(conn.id)}
|
||||
disabled={rowBusy}
|
||||
aria-label="Delete connection"
|
||||
className="p-1.5 rounded-lg hover:bg-red-500/10 text-red-500 transition-colors disabled:opacity-50"
|
||||
title="Delete connection"
|
||||
>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[18px] ${deletingId === conn.id ? "animate-pulse" : ""}`}
|
||||
<span
|
||||
className={`material-symbols-outlined text-[18px] text-text-muted ${isLoading ? "animate-spin" : ""}`}
|
||||
>
|
||||
refresh
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Edit connection">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedConnection(conn);
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
disabled={rowBusy}
|
||||
aria-label="Edit connection"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary transition-colors disabled:opacity-50"
|
||||
>
|
||||
delete
|
||||
</span>
|
||||
</button>
|
||||
<span className="material-symbols-outlined text-[18px]">
|
||||
edit
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip text="Delete connection">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDeleteConnection(conn.id)}
|
||||
disabled={rowBusy}
|
||||
aria-label="Delete connection"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-red-500/10 text-red-500 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[18px] ${deletingId === conn.id ? "animate-pulse" : ""}`}
|
||||
>
|
||||
delete
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="inline-flex items-center pl-0.5"
|
||||
title={
|
||||
@@ -1047,6 +1122,25 @@ export default function ProviderLimits() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={Boolean(resetConfirmState)}
|
||||
onClose={() => {
|
||||
if (!resettingLimitId) setResetConfirmState(null);
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
const connection = resetConfirmState?.connection;
|
||||
if (!connection) return;
|
||||
await handleResetCodexLimit(connection.id, connection.provider);
|
||||
setResetConfirmState(null);
|
||||
}}
|
||||
title="Reset Codex limit?"
|
||||
message={`Use 1 Codex reset credit for ${getConnectionLabel(resetConfirmState?.connection || {}) || "this account"}. This cannot be undone. Remaining credits: ${resetConfirmState?.resetCreditCount ?? 0}.`}
|
||||
confirmText="Reset limit"
|
||||
cancelText="Cancel"
|
||||
variant="danger"
|
||||
loading={Boolean(resettingLimitId)}
|
||||
/>
|
||||
|
||||
<EditConnectionModal
|
||||
isOpen={showEditModal}
|
||||
connection={selectedConnection}
|
||||
|
||||
Reference in New Issue
Block a user