chore: fix build warnings, add deployment config, and cleanup lint errors

- Fix React Hook dependencies and Image optimization warnings
- Add DATA_DIR and INITIAL_PASSWORD env var support
- Fix Tailwind v4 legacy syntax and suppress CSS directives warnings
- Add PropTypes and remove unused variables
This commit is contained in:
apeltekci
2026-01-20 12:58:51 +07:00
committed by decolua
parent 0943387d24
commit 0848dd5d13
21 changed files with 156 additions and 114 deletions
@@ -46,14 +46,14 @@ export default function ClaudeToolCard({
if (apiKeys?.length > 0 && !selectedApiKey) {
setSelectedApiKey(apiKeys[0].key);
}
}, [apiKeys]);
}, [apiKeys, selectedApiKey]);
useEffect(() => {
if (isExpanded && !claudeStatus) {
checkClaudeStatus();
fetchModelAliases();
}
}, [isExpanded]);
}, [isExpanded, claudeStatus]);
const fetchModelAliases = async () => {
try {
@@ -80,7 +80,7 @@ export default function ClaudeToolCard({
setSelectedApiKey(tokenFromFile);
}
}
}, [claudeStatus, apiKeys]);
}, [claudeStatus, apiKeys, tool.defaultModels, onModelMappingChange]);
const checkClaudeStatus = async () => {
setCheckingClaude(true);
@@ -21,14 +21,14 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api
if (apiKeys?.length > 0 && !selectedApiKey) {
setSelectedApiKey(apiKeys[0].key);
}
}, [apiKeys]);
}, [apiKeys, selectedApiKey]);
useEffect(() => {
if (isExpanded && !codexStatus) {
checkCodexStatus();
fetchModelAliases();
}
}, [isExpanded]);
}, [isExpanded, codexStatus]);
const fetchModelAliases = async () => {
try {
@@ -18,7 +18,7 @@ export default function APIPageClient({ machineId }) {
const [showCloudModal, setShowCloudModal] = useState(false);
const [showDisableModal, setShowDisableModal] = useState(false);
const [cloudSyncing, setCloudSyncing] = useState(false);
const [cloudStatus, setCloudStatus] = useState(null);
const [setCloudStatus] = useState(null);
const [syncStep, setSyncStep] = useState(""); // "syncing" | "verifying" | "disabling" | ""
const { copied, copy } = useCopyToClipboard();
@@ -110,7 +110,7 @@ export default function APIPageClient({ machineId }) {
try {
// Step 1: Sync latest data from cloud
const syncRes = await fetch("/api/sync/cloud", {
await fetch("/api/sync/cloud", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "sync" })
@@ -198,13 +198,9 @@ export default function APIPageClient({ machineId }) {
}
};
const isLocalhost = typeof window !== "undefined" &&
(window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
const baseUrl = typeof window !== "undefined" ? `${window.location.origin}/v1` : "/v1";
const localApiKey = "HELLO";
// New format: /v1 (machineId in key), Old format: /{machineId}/v1
const cloudEndpointNew = `${CLOUD_URL}/v1`;
const cloudEndpointOld = `${CLOUD_URL}/${machineId}/v1`;
if (loading) {
return (
@@ -244,7 +240,7 @@ export default function APIPageClient({ machineId }) {
icon="cloud_off"
onClick={() => handleCloudToggle(false)}
disabled={cloudSyncing}
className="!bg-red-500/10 !text-red-500 hover:!bg-red-500/20 !border-red-500/30"
className="bg-red-500/10! text-red-500! hover:bg-red-500/20! border-red-500/30!"
>
Disable Cloud
</Button>
@@ -254,7 +250,7 @@ export default function APIPageClient({ machineId }) {
icon="cloud_upload"
onClick={() => handleCloudToggle(true)}
disabled={cloudSyncing}
className="bg-gradient-to-r from-primary to-blue-500 hover:from-primary-hover hover:to-blue-600"
className="bg-linear-to-r from-primary to-blue-500 hover:from-primary-hover hover:to-blue-600"
>
Enable Cloud
</Button>
@@ -371,7 +367,7 @@ export default function APIPageClient({ machineId }) {
icon="cloud_off"
onClick={() => handleCloudToggle(false)}
disabled={cloudSyncing}
className="!bg-red-500/10 !text-red-500 hover:!bg-red-500/20 !border-red-500/30"
className="bg-red-500/10! text-red-500! hover:bg-red-500/20! border-red-500/30!"
>
Disable
</Button>
@@ -381,7 +377,7 @@ export default function APIPageClient({ machineId }) {
icon="cloud_upload"
onClick={() => handleCloudToggle(true)}
disabled={cloudSyncing}
className="bg-gradient-to-r from-primary to-blue-500 hover:from-primary-hover hover:to-blue-600 px-6"
className="bg-linear-to-r from-primary to-blue-500 hover:from-primary-hover hover:to-blue-600 px-6"
>
Enable Cloud
</Button>
@@ -580,7 +576,7 @@ export default function APIPageClient({ machineId }) {
onClick={handleConfirmDisable}
fullWidth
disabled={cloudSyncing}
className="!bg-red-500 hover:!bg-red-600 !text-white"
className="bg-red-500! hover:bg-red-600! text-white!"
>
{cloudSyncing ? (
<span className="flex items-center gap-2">
@@ -602,4 +598,8 @@ export default function APIPageClient({ machineId }) {
</Modal>
</div>
);
}
}
APIPageClient.propTypes = {
machineId: import("prop-types").string.isRequired,
};
@@ -30,9 +30,9 @@ export default function ProviderDetailPage() {
useEffect(() => {
fetchConnections();
fetchAliases();
}, [providerId]);
}, [fetchConnections, fetchAliases]);
const fetchAliases = async () => {
const fetchAliases = useCallback(async () => {
try {
const res = await fetch("/api/models/alias");
const data = await res.json();
@@ -42,7 +42,7 @@ export default function ProviderDetailPage() {
} catch (error) {
console.log("Error fetching aliases:", error);
}
};
}, []);
const handleSetAlias = async (modelId, alias) => {
const fullModel = `${providerAlias}/${modelId}`;
@@ -76,7 +76,7 @@ export default function ProviderDetailPage() {
}
};
const fetchConnections = async () => {
const fetchConnections = useCallback(async () => {
try {
const res = await fetch("/api/providers");
const data = await res.json();
@@ -89,7 +89,7 @@ export default function ProviderDetailPage() {
} finally {
setLoading(false);
}
};
}, [providerId]);
const handleDelete = async (id) => {
if (!confirm("Delete this connection?")) return;
@@ -228,11 +228,13 @@ export default function ProviderDetailPage() {
className="rounded-lg flex items-center justify-center"
style={{ backgroundColor: `${providerInfo.color}15` }}
>
<img
<Image
src={`/providers/${providerInfo.id}.png`}
alt={providerInfo.name}
className="size-12 object-contain rounded-lg"
onError={(e) => { e.target.style.display = "none"; }}
width={48}
height={48}
className="object-contain rounded-lg"
onError={(e) => { e.currentTarget.style.display = "none"; }}
/>
</div>
<div>
+16 -14
View File
@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { Card, CardSkeleton, Badge } from "@/shared/components";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/config";
import Link from "next/link";
@@ -11,21 +12,20 @@ export default function ProvidersPage() {
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 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);
}
};
const getProviderStats = (providerId, authType) => {
const providerConnections = connections.filter(
c => c.provider === providerId && c.authType === authType
@@ -141,10 +141,12 @@ function ProviderCard({ providerId, provider, stats }) {
style={{ backgroundColor: `${provider.color}15` }}
>
{!imgError ? (
<img
<Image
src={`/providers/${provider.id}.png`}
alt={provider.name}
className="size-10 object-contain rounded-lg"
width={40}
height={40}
className="object-contain rounded-lg"
onError={() => setImgError(true)}
/>
) : (
+8 -4
View File
@@ -1,7 +1,7 @@
"use client";
import { useState, Suspense } from "react";
import { UsageStats, RequestLogger } from "@/shared/components";
import { UsageStats, RequestLogger, CardSkeleton } from "@/shared/components";
export default function UsagePage() {
const [activeTab, setActiveTab] = useState("overview");
@@ -33,9 +33,13 @@ export default function UsagePage() {
</div>
{/* Content */}
<Suspense fallback={<div className="text-text-muted">Loading...</div>}>
{activeTab === "overview" ? <UsageStats /> : <RequestLogger />}
</Suspense>
{activeTab === "overview" ? (
<Suspense fallback={<CardSkeleton />}>
<UsageStats />
</Suspense>
) : (
<RequestLogger />
)}
</div>
);
}
+3 -1
View File
@@ -18,7 +18,9 @@ export async function POST(request) {
let isValid = false;
if (!storedHash) {
isValid = password === "123456";
// Use env var or default
const initialPassword = process.env.INITIAL_PASSWORD || "123456";
isValid = password === initialPassword;
} else {
isValid = await bcrypt.compare(password, storedHash);
}
+1
View File
@@ -1,3 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap');
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
+1 -1
View File
@@ -13,7 +13,7 @@ export default function HowItWorks() {
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 relative">
{/* Connection line */}
<div className="hidden md:block absolute top-12 left-[16%] right-[16%] h-[2px] bg-gradient-to-r from-gray-700 via-[#f97815] to-gray-700 -z-10"></div>
<div className="hidden md:block absolute top-12 left-[16%] right-[16%] h-[2px] bg-linear-to-r from-gray-700 via-[#f97815] to-gray-700 -z-10"></div>
{/* Step 1: CLI & SDKs */}
<div className="flex flex-col gap-6 relative group">
+1 -1
View File
@@ -11,7 +11,7 @@ export default function Navigation() {
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
{/* Logo */}
<div className="flex items-center gap-3 cursor-pointer" onClick={() => router.push("/")}>
<div className="size-8 rounded bg-gradient-to-br from-[#f97815] to-orange-700 flex items-center justify-center text-white">
<div className="size-8 rounded bg-linear-to-br from-[#f97815] to-orange-700 flex items-center justify-center text-white">
<span className="material-symbols-outlined text-[20px]">hub</span>
</div>
<h2 className="text-white text-xl font-bold tracking-tight">9Router</h2>
+1 -1
View File
@@ -47,7 +47,7 @@ export default function LandingPage() {
{/* CTA Section */}
<section className="py-32 px-6 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-t from-[#f97815]/5 to-transparent pointer-events-none"></div>
<div className="absolute inset-0 bg-linear-to-t from-[#f97815]/5 to-transparent pointer-events-none"></div>
<div className="max-w-4xl mx-auto text-center relative z-10">
<h2 className="text-4xl md:text-5xl font-black mb-6">Ready to Simplify Your AI Infrastructure?</h2>
<p className="text-xl text-gray-400 mb-10 max-w-2xl mx-auto">
+1 -4
View File
@@ -17,10 +17,7 @@ export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
</head>
<body className={`${inter.variable} font-sans antialiased`}>