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
+6 -3
View File
@@ -2,6 +2,7 @@
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { ThemeToggle } from "@/shared/components";
import { APP_CONFIG, OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/config";
@@ -88,11 +89,13 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
) : (
<div className="flex items-center gap-2">
{crumb.image && (
<img
<Image
src={crumb.image}
alt={crumb.label}
className="size-7 object-contain rounded"
onError={(e) => { e.target.style.display = "none"; }}
width={28}
height={28}
className="object-contain rounded"
onError={(e) => { e.currentTarget.style.display = "none"; }}
/>
)}
<h1 className="text-2xl font-semibold text-text-main tracking-tight">
+17 -2
View File
@@ -1,6 +1,7 @@
"use client";
import { useState, useMemo, useEffect } from "react";
import PropTypes from "prop-types";
import Modal from "./Modal";
import { getModelsByProviderId, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/providers";
@@ -33,7 +34,7 @@ export default function ModelSelectModal({
}
}, [isOpen]);
const allProviders = { ...OAUTH_PROVIDERS, ...APIKEY_PROVIDERS };
const allProviders = useMemo(() => ({ ...OAUTH_PROVIDERS, ...APIKEY_PROVIDERS }), []);
// Group models by provider with priority order
const groupedModels = useMemo(() => {
@@ -141,7 +142,7 @@ export default function ModelSelectModal({
}}
title={title}
size="md"
className="!p-4"
className="p-4!"
>
{/* Search - compact */}
<div className="mb-3">
@@ -246,3 +247,17 @@ export default function ModelSelectModal({
);
}
ModelSelectModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
selectedModel: PropTypes.string,
activeProviders: PropTypes.arrayOf(
PropTypes.shape({
provider: PropTypes.string.isRequired,
})
),
title: PropTypes.string,
modelAliases: PropTypes.object,
};
+51 -50
View File
@@ -36,7 +36,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
// Auto start OAuth
startOAuthFlow();
}
}, [isOpen, provider]);
}, [isOpen, provider, startOAuthFlow]);
// Listen for OAuth callback via multiple methods
const callbackProcessedRef = useRef(false);
@@ -114,10 +114,11 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
window.removeEventListener("storage", handleStorage);
if (channel) channel.close();
};
}, [authData]);
}, [authData, exchangeTokens]);
// Exchange tokens
const exchangeTokens = async (code, state) => {
const exchangeTokens = useCallback(async (code, state) => {
if (!authData) return;
try {
const res = await fetch(`/api/oauth/${provider}/exchange`, {
method: "POST",
@@ -139,10 +140,54 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
setError(err.message);
setStep("error");
}
};
}, [authData, provider, onSuccess]);
// Poll for device code token
const startPolling = useCallback(async (deviceCode, codeVerifier, interval, extraData) => {
setPolling(true);
const maxAttempts = 60;
for (let i = 0; i < maxAttempts; i++) {
await new Promise((r) => setTimeout(r, interval * 1000));
try {
const res = await fetch(`/api/oauth/${provider}/poll`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceCode, codeVerifier, extraData }),
});
const data = await res.json();
if (data.success) {
setStep("success");
setPolling(false);
onSuccess?.();
return;
}
if (data.error === "expired_token" || data.error === "access_denied") {
throw new Error(data.errorDescription || data.error);
}
if (data.error === "slow_down") {
interval = Math.min(interval + 5, 30);
}
} catch (err) {
setError(err.message);
setStep("error");
setPolling(false);
return;
}
}
setError("Authorization timeout");
setStep("error");
setPolling(false);
}, [provider, onSuccess]);
// Start OAuth flow
const startOAuthFlow = async () => {
const startOAuthFlow = useCallback(async () => {
if (!provider) return;
try {
setError(null);
@@ -207,51 +252,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
setError(err.message);
setStep("error");
}
};
// Poll for device code token
const startPolling = async (deviceCode, codeVerifier, interval, extraData) => {
setPolling(true);
const maxAttempts = 60;
for (let i = 0; i < maxAttempts; i++) {
await new Promise((r) => setTimeout(r, interval * 1000));
try {
const res = await fetch(`/api/oauth/${provider}/poll`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceCode, codeVerifier, extraData }),
});
const data = await res.json();
if (data.success) {
setStep("success");
setPolling(false);
onSuccess?.();
return;
}
if (data.error === "expired_token" || data.error === "access_denied") {
throw new Error(data.errorDescription || data.error);
}
if (data.error === "slow_down") {
interval = Math.min(interval + 5, 30);
}
} catch (err) {
setError(err.message);
setStep("error");
setPolling(false);
return;
}
}
setError("Authorization timeout");
setStep("error");
setPolling(false);
};
}, [provider, isLocalhost, startPolling]);
// Handle manual URL input
const handleManualSubmit = async () => {
+8 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import PropTypes from "prop-types";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/shared/utils/cn";
@@ -38,7 +39,7 @@ export default function Sidebar({ onClose }) {
try {
await fetch("/api/shutdown", { method: "POST" });
} catch (e) {
// Expected to fail as server shuts down
// Expected to fail as server shuts down; ignore error
}
setIsShuttingDown(false);
setShowShutdownModal(false);
@@ -51,7 +52,7 @@ export default function Sidebar({ onClose }) {
{/* Logo */}
<div className="p-8">
<Link href="/dashboard" className="flex items-center gap-3">
<div className="flex items-center justify-center size-9 rounded bg-gradient-to-br from-[#f97815] to-[#c2590a]">
<div className="flex items-center justify-center size-9 rounded bg-linear-to-br from-[#f97815] to-[#c2590a]">
<span className="material-symbols-outlined text-white text-[20px]">hub</span>
</div>
<h1 className="text-lg font-semibold tracking-tight text-text-main">
@@ -161,7 +162,7 @@ export default function Sidebar({ onClose }) {
</div>
<h2 className="text-xl font-semibold text-white mb-2">Server Disconnected</h2>
<p className="text-text-muted mb-6">The proxy server has been stopped.</p>
<Button variant="secondary" onClick={() => window.location.reload()}>
<Button variant="secondary" onClick={() => globalThis.location.reload()}>
Reload Page
</Button>
</div>
@@ -170,3 +171,7 @@ export default function Sidebar({ onClose }) {
</>
);
}
Sidebar.propTypes = {
onClose: PropTypes.func,
};
+4 -4
View File
@@ -52,7 +52,7 @@ export default function UsageStats() {
router.replace(`?${params.toString()}`, { scroll: false });
};
const sortData = (dataMap, pendingMap = {}) => {
const sortData = useCallback((dataMap, pendingMap = {}) => {
return Object.entries(dataMap || {})
.map(([key, data]) => {
const totalTokens =
@@ -91,11 +91,11 @@ export default function UsageStats() {
if (valA > valB) return sortOrder === "asc" ? 1 : -1;
return 0;
});
};
}, [sortBy, sortOrder]);
const sortedModels = useMemo(
() => sortData(stats?.byModel, stats?.pending?.byModel),
[stats?.byModel, stats?.pending?.byModel, sortBy, sortOrder]
[stats?.byModel, stats?.pending?.byModel, sortData]
);
const sortedAccounts = useMemo(() => {
// For accounts, pendingMap is by connectionId, but dataMap is by accountKey
@@ -114,7 +114,7 @@ export default function UsageStats() {
});
}
return sortData(stats?.byAccount, accountPendingMap);
}, [stats?.byAccount, stats?.pending?.byAccount, sortBy, sortOrder]);
}, [stats?.byAccount, stats?.pending?.byAccount, sortData]);
const fetchStats = useCallback(async (showLoading = true) => {
if (showLoading) setLoading(true);