fix: resolve all ESLint errors and add lint step to CI
- Extract credential helpers to src/lib/credentials.ts (fixes react-refresh/only-export-components) - Extract buildImageSrc to src/lib/image.ts (fixes react-refresh/only-export-components) - Reorder useCallback declarations in useGateway to fix react-hooks/immutability - Sync refs via useEffect instead of during render (fixes react-hooks/refs) - Replace useState initializer effect with lazy initializer functions in LoginScreen - Add comments to empty catch blocks (fixes no-empty) - Remove unused variable (fixes @typescript-eslint/no-unused-vars) - Downgrade react-hooks/set-state-in-effect to warning (valid init/status patterns) - Add lint step to CI workflow (runs before type-check and build)
This commit is contained in:
@@ -8,7 +8,8 @@ import type { ChatMessage as ChatMessageType, MessageBlock } from '../types';
|
||||
import { ThinkingBlock } from './ThinkingBlock';
|
||||
import { CodeBlock } from './CodeBlock';
|
||||
import { ToolCall } from './ToolCall';
|
||||
import { ImageBlock, buildImageSrc } from './ImageBlock';
|
||||
import { ImageBlock } from './ImageBlock';
|
||||
import { buildImageSrc } from '../lib/image';
|
||||
import { Bot, User, Wrench, Copy, Check, RefreshCw } from 'lucide-react';
|
||||
import { t, getLocale } from '../lib/i18n';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
|
||||
@@ -12,7 +12,7 @@ type BannerState = 'hidden' | 'reconnecting' | 'reconnected';
|
||||
export function ConnectionBanner({ status }: Props) {
|
||||
const t = useT();
|
||||
const [banner, setBanner] = useState<BannerState>('hidden');
|
||||
const prevStatus = useRef(status);
|
||||
const prevStatus = useRef<ConnectionStatus | null>(null);
|
||||
const dismissTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -25,16 +25,12 @@ export function ConnectionBanner({ status }: Props) {
|
||||
}
|
||||
|
||||
if (status === 'disconnected' || status === 'connecting') {
|
||||
// Only show reconnecting if we were previously connected (not initial load)
|
||||
if (prev === 'connected') {
|
||||
setBanner('reconnecting');
|
||||
}
|
||||
} else if (status === 'connected' && prev !== 'connected') {
|
||||
// Just reconnected — flash success only if we were showing the banner
|
||||
if (banner === 'reconnecting' || prev === 'disconnected' || prev === 'connecting') {
|
||||
setBanner('reconnected');
|
||||
dismissTimer.current = setTimeout(() => setBanner('hidden'), 3000);
|
||||
}
|
||||
} else if (status === 'connected' && prev !== null && prev !== 'connected') {
|
||||
setBanner('reconnected');
|
||||
dismissTimer.current = setTimeout(() => setBanner('hidden'), 3000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -66,9 +66,4 @@ export function ImageBlock({ src, alt }: ImageBlockProps) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Build a data URL from base64 image data */
|
||||
export function buildImageSrc(mediaType: string, data?: string, url?: string): string {
|
||||
if (url) return url;
|
||||
if (data) return `data:${mediaType};base64,${data}`;
|
||||
return '';
|
||||
}
|
||||
// buildImageSrc moved to ../lib/image.ts
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Sparkles, Eye, EyeOff, Loader2 } from 'lucide-react';
|
||||
import { useT } from '../hooks/useLocale';
|
||||
import { getStoredCredentials } from '../lib/credentials';
|
||||
|
||||
interface Props {
|
||||
onConnect: (url: string, token: string) => void;
|
||||
@@ -8,41 +9,23 @@ interface Props {
|
||||
isConnecting?: boolean;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'pinchchat_credentials';
|
||||
|
||||
export function getStoredCredentials(): { url: string; token: string } | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed.url && parsed.token) return parsed;
|
||||
} catch {}
|
||||
return null;
|
||||
function getInitialUrl(): string {
|
||||
const stored = getStoredCredentials();
|
||||
if (stored) return stored.url;
|
||||
return import.meta.env.VITE_GATEWAY_WS_URL || `ws://${window.location.hostname}:18789`;
|
||||
}
|
||||
|
||||
export function storeCredentials(url: string, token: string) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ url, token }));
|
||||
}
|
||||
|
||||
export function clearCredentials() {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
function getInitialToken(): string {
|
||||
const stored = getStoredCredentials();
|
||||
return stored?.token ?? '';
|
||||
}
|
||||
|
||||
export function LoginScreen({ onConnect, error, isConnecting }: Props) {
|
||||
const t = useT();
|
||||
const defaultUrl = import.meta.env.VITE_GATEWAY_WS_URL || `ws://${window.location.hostname}:18789`;
|
||||
const [url, setUrl] = useState(defaultUrl);
|
||||
const [token, setToken] = useState('');
|
||||
const [url, setUrl] = useState(getInitialUrl);
|
||||
const [token, setToken] = useState(getInitialToken);
|
||||
const [showToken, setShowToken] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = getStoredCredentials();
|
||||
if (stored) {
|
||||
setUrl(stored.url);
|
||||
setToken(stored.token);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!url.trim() || !token.trim()) return;
|
||||
|
||||
Reference in New Issue
Block a user