"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import PropTypes from "prop-types";
import { Card, CardSkeleton, Badge } from "@/shared/components";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/config";
import Link from "next/link";
import { getErrorCode, getRelativeTime } from "@/shared/utils";
// Shared helper function to avoid code duplication between ProviderCard and ApiKeyProviderCard
function getStatusDisplay(connected, error, errorCode) {
const parts = [];
if (connected > 0) {
parts.push(
{connected} Connected
);
}
if (error > 0) {
const errText = errorCode ? `${error} Error (${errorCode})` : `${error} Error`;
parts.push(
{errText}
);
}
if (parts.length === 0) {
return No connections;
}
return parts;
}
export default function ProvidersPage() {
const [connections, setConnections] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("/api/providers");
const data = await res.json();
if (res.ok) setConnections(data.connections || []);
} catch (error) {
console.log("Error fetching data:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const getProviderStats = (providerId, authType) => {
const providerConnections = connections.filter(
c => c.provider === providerId && c.authType === authType
);
// Helper: check if connection is effectively active (cooldown expired)
const getEffectiveStatus = (conn) => {
const isCooldown = conn.rateLimitedUntil && new Date(conn.rateLimitedUntil).getTime() > Date.now();
return (conn.testStatus === "unavailable" && !isCooldown) ? "active" : conn.testStatus;
};
const connected = providerConnections.filter(c => {
const status = getEffectiveStatus(c);
return status === "active" || status === "success";
}).length;
const errorConns = providerConnections.filter(c => {
const status = getEffectiveStatus(c);
return status === "error" || status === "expired" || status === "unavailable";
});
const error = errorConns.length;
const total = providerConnections.length;
// Get latest error info
const latestError = errorConns.sort((a, b) =>
new Date(b.lastErrorAt || 0) - new Date(a.lastErrorAt || 0)
)[0];
const errorCode = latestError ? getErrorCode(latestError.lastError) : null;
const errorTime = latestError?.lastErrorAt ? getRelativeTime(latestError.lastErrorAt) : null;
return { connected, error, total, errorCode, errorTime };
};
if (loading) {
return (
);
}
return (
{/* OAuth Providers */}
OAuth Providers
{Object.entries(OAUTH_PROVIDERS).map(([key, info]) => (
))}
{/* API Key Providers */}
API Key Providers
{Object.entries(APIKEY_PROVIDERS).map(([key, info]) => (
))}
);
}
function ProviderCard({ providerId, provider, stats }) {
const { connected, error, errorCode, errorTime } = stats;
const [imgError, setImgError] = useState(false);
return (
{imgError ? (
{provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
) : (
setImgError(true)}
/>
)}
{provider.name}
{getStatusDisplay(connected, error, errorCode)}
{errorTime && • {errorTime}}
chevron_right
);
}
ProviderCard.propTypes = {
providerId: PropTypes.string.isRequired,
provider: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
color: PropTypes.string,
textIcon: PropTypes.string,
}).isRequired,
stats: PropTypes.shape({
connected: PropTypes.number,
error: PropTypes.number,
errorCode: PropTypes.string,
errorTime: PropTypes.string,
}).isRequired,
};
// API Key providers - only use textIcon, no image
function ApiKeyProviderCard({ providerId, provider, stats }) {
const { connected, error, errorCode, errorTime } = stats;
return (
{provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
{provider.name}
{getStatusDisplay(connected, error, errorCode)}
{errorTime && • {errorTime}}
chevron_right
);
}
ApiKeyProviderCard.propTypes = {
providerId: PropTypes.string.isRequired,
provider: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
color: PropTypes.string,
textIcon: PropTypes.string,
}).isRequired,
stats: PropTypes.shape({
connected: PropTypes.number,
error: PropTypes.number,
errorCode: PropTypes.string,
errorTime: PropTypes.string,
}).isRequired,
};