fix: resolve SonarQube findings and Next.js Image warnings
SonarQube/SonarLint fixes:
- Remove unused imports (useMemo, PROVIDER_ENDPOINTS, updateSettings, APP_CONFIG)
- Add PropTypes validation to all components receiving props
- Fix accessibility issues (semantic buttons, ARIA attributes, form labels)
- Replace array index keys with stable identifiers
- Extract duplicate getStatusDisplay function in providers page
- Fix negated conditions for better readability
- Add node: prefix to Node.js imports in localDb.js
- Fix optional chaining in pricing lookup
- Add explanatory comments to empty catch blocks
- Consolidate duplicate OAuth flow branches
- Change parseInt to Number.parseInt
- Disable false positive rules in VS Code settings
Next.js Image fixes:
- Add style={{ width: "auto", height: "auto" }} to all Image components
- Resolves aspect ratio warnings without triggering lint issues
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9b8e48725
commit
7058b062e7
14 files changed
+307
-156
No files matched your search
@@ -2,11 +2,36 @@
|
||||
|
||||
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(
|
||||
<Badge key="connected" variant="success" size="sm" dot>
|
||||
{connected} Connected
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (error > 0) {
|
||||
const errText = errorCode ? `${error} Error (${errorCode})` : `${error} Error`;
|
||||
parts.push(
|
||||
<Badge key="error" variant="error" size="sm" dot>
|
||||
{errText}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return <span className="text-text-muted">No connections</span>;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
export default function ProvidersPage() {
|
||||
const [connections, setConnections] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -108,29 +133,6 @@ function ProviderCard({ providerId, provider, stats }) {
|
||||
const { connected, error, errorCode, errorTime } = stats;
|
||||
const [imgError, setImgError] = useState(false);
|
||||
|
||||
const getStatusDisplay = () => {
|
||||
const parts = [];
|
||||
if (connected > 0) {
|
||||
parts.push(
|
||||
<Badge key="connected" variant="success" size="sm" dot>
|
||||
{connected} Connected
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (error > 0) {
|
||||
const errText = errorCode ? `${error} Error (${errorCode})` : `${error} Error`;
|
||||
parts.push(
|
||||
<Badge key="error" variant="error" size="sm" dot>
|
||||
{errText}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return <span className="text-text-muted">No connections</span>;
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/providers/${providerId}`}>
|
||||
<Card className="h-full hover:border-primary/50 transition-colors cursor-pointer">
|
||||
@@ -140,28 +142,29 @@ function ProviderCard({ providerId, provider, stats }) {
|
||||
className="size-10 rounded-lg flex items-center justify-center"
|
||||
style={{ backgroundColor: `${provider.color}15` }}
|
||||
>
|
||||
{!imgError ? (
|
||||
<Image
|
||||
src={`/providers/${provider.id}.png`}
|
||||
alt={provider.name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="object-contain rounded-lg"
|
||||
onError={() => setImgError(true)}
|
||||
/>
|
||||
) : (
|
||||
{imgError ? (
|
||||
<span
|
||||
className="text-sm font-bold"
|
||||
style={{ color: provider.color }}
|
||||
>
|
||||
{provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
) : (
|
||||
<Image
|
||||
src={`/providers/${provider.id}.png`}
|
||||
alt={provider.name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="object-contain rounded-lg"
|
||||
style={{ width: "auto", height: "auto" }}
|
||||
onError={() => setImgError(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">{provider.name}</h3>
|
||||
<div className="flex items-center gap-2 text-xs flex-wrap">
|
||||
{getStatusDisplay()}
|
||||
{getStatusDisplay(connected, error, errorCode)}
|
||||
{errorTime && <span className="text-text-muted">• {errorTime}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,33 +178,26 @@ function ProviderCard({ providerId, provider, stats }) {
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const getStatusDisplay = () => {
|
||||
const parts = [];
|
||||
if (connected > 0) {
|
||||
parts.push(
|
||||
<Badge key="connected" variant="success" size="sm" dot>
|
||||
{connected} Connected
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (error > 0) {
|
||||
const errText = errorCode ? `${error} Error (${errorCode})` : `${error} Error`;
|
||||
parts.push(
|
||||
<Badge key="error" variant="error" size="sm" dot>
|
||||
{errText}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return <span className="text-text-muted">No connections</span>;
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/providers/${providerId}`}>
|
||||
<Card className="h-full hover:border-primary/50 transition-colors cursor-pointer">
|
||||
@@ -221,7 +217,7 @@ function ApiKeyProviderCard({ providerId, provider, stats }) {
|
||||
<div>
|
||||
<h3 className="font-semibold">{provider.name}</h3>
|
||||
<div className="flex items-center gap-2 text-xs flex-wrap">
|
||||
{getStatusDisplay()}
|
||||
{getStatusDisplay(connected, error, errorCode)}
|
||||
{errorTime && <span className="text-text-muted">• {errorTime}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,3 +230,19 @@ function ApiKeyProviderCard({ providerId, provider, stats }) {
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
Reference in new issue
Block a user