Spaces:
Running
Running
fix(lint): resolve react-hooks/set-state-in-effect errors in CI
Browse files- auth.tsx: lazy-initialize token state from localStorage instead of setToken inside useEffect
- dashboard/page.tsx: wrap loadDocuments() in async IIFE so setState fires in callback not effect body
- PDFViewer.tsx: remove useEffect+setPageInput sync and unused token variable; initialize pageInput from prop directly
frontend/src/app/dashboard/page.tsx
CHANGED
|
@@ -46,7 +46,11 @@ export default function DashboardPage() {
|
|
| 46 |
}, []);
|
| 47 |
|
| 48 |
useEffect(() => {
|
| 49 |
-
if (user)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}, [user, loadDocuments]);
|
| 51 |
|
| 52 |
// Poll for processing status
|
|
|
|
| 46 |
}, []);
|
| 47 |
|
| 48 |
useEffect(() => {
|
| 49 |
+
if (!user) return;
|
| 50 |
+
// Use IIFE so setState calls happen in async callback, not effect body directly
|
| 51 |
+
void (async () => {
|
| 52 |
+
await loadDocuments();
|
| 53 |
+
})();
|
| 54 |
}, [user, loadDocuments]);
|
| 55 |
|
| 56 |
// Poll for processing status
|
frontend/src/components/document/PDFViewer.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { useState
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Input } from "@/components/ui/input";
|
| 6 |
import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Loader2 } from "lucide-react";
|
|
@@ -16,17 +16,13 @@ interface Props {
|
|
| 16 |
export default function PDFViewer({ documentId, currentPage, onPageChange, totalPages }: Props) {
|
| 17 |
const [scale, setScale] = useState(1.0);
|
| 18 |
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
| 19 |
const [pageInput, setPageInput] = useState(String(currentPage));
|
| 20 |
-
|
| 21 |
-
useEffect(() => {
|
| 22 |
-
setPageInput(String(currentPage));
|
| 23 |
-
}, [currentPage]);
|
| 24 |
-
|
| 25 |
-
const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
|
| 26 |
const pdfUrl = `${API_BASE}/api/v1/documents/${documentId}/pdf`;
|
| 27 |
|
| 28 |
-
//
|
| 29 |
-
// We append a page fragment for navigation
|
| 30 |
const iframeSrc = `${pdfUrl}#page=${currentPage}`;
|
| 31 |
|
| 32 |
const handlePageSubmit = (e: React.FormEvent) => {
|
|
@@ -35,10 +31,12 @@ export default function PDFViewer({ documentId, currentPage, onPageChange, total
|
|
| 35 |
if (!isNaN(num) && num >= 1 && num <= totalPages) {
|
| 36 |
onPageChange(num);
|
| 37 |
} else {
|
|
|
|
| 38 |
setPageInput(String(currentPage));
|
| 39 |
}
|
| 40 |
};
|
| 41 |
|
|
|
|
| 42 |
return (
|
| 43 |
<div className="h-full flex flex-col bg-background">
|
| 44 |
{/* ββ Toolbar βββββββββββββββββββββββββββββββββββ */}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useState } from "react";
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Input } from "@/components/ui/input";
|
| 6 |
import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Loader2 } from "lucide-react";
|
|
|
|
| 16 |
export default function PDFViewer({ documentId, currentPage, onPageChange, totalPages }: Props) {
|
| 17 |
const [scale, setScale] = useState(1.0);
|
| 18 |
const [loading, setLoading] = useState(true);
|
| 19 |
+
// Local editable value β initialized from currentPage prop.
|
| 20 |
+
// The iframe key={documentId-currentPage} already forces remount on
|
| 21 |
+
// external page changes, so no useEffect sync is needed.
|
| 22 |
const [pageInput, setPageInput] = useState(String(currentPage));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
const pdfUrl = `${API_BASE}/api/v1/documents/${documentId}/pdf`;
|
| 24 |
|
| 25 |
+
// Append page fragment for native viewer navigation
|
|
|
|
| 26 |
const iframeSrc = `${pdfUrl}#page=${currentPage}`;
|
| 27 |
|
| 28 |
const handlePageSubmit = (e: React.FormEvent) => {
|
|
|
|
| 31 |
if (!isNaN(num) && num >= 1 && num <= totalPages) {
|
| 32 |
onPageChange(num);
|
| 33 |
} else {
|
| 34 |
+
// Reset to the current valid page without needing a useEffect
|
| 35 |
setPageInput(String(currentPage));
|
| 36 |
}
|
| 37 |
};
|
| 38 |
|
| 39 |
+
|
| 40 |
return (
|
| 41 |
<div className="h-full flex flex-col bg-background">
|
| 42 |
{/* ββ Toolbar βββββββββββββββββββββββββββββββββββ */}
|
frontend/src/lib/auth.tsx
CHANGED
|
@@ -24,26 +24,28 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
| 24 |
|
| 25 |
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
| 26 |
const [user, setUser] = useState<User | null>(null);
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
const [loading, setLoading] = useState(true);
|
| 29 |
|
| 30 |
-
// ββ
|
| 31 |
useEffect(() => {
|
| 32 |
-
|
| 33 |
-
if (saved) {
|
| 34 |
-
setToken(saved);
|
| 35 |
-
api
|
| 36 |
-
.get<User>("/api/v1/auth/me", { token: saved })
|
| 37 |
-
.then(setUser)
|
| 38 |
-
.catch(() => {
|
| 39 |
-
localStorage.removeItem("token");
|
| 40 |
-
setToken(null);
|
| 41 |
-
})
|
| 42 |
-
.finally(() => setLoading(false));
|
| 43 |
-
} else {
|
| 44 |
setLoading(false);
|
|
|
|
| 45 |
}
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
const login = useCallback(async (email: string, password: string) => {
|
| 49 |
const data = await api.post<{ access_token: string; user: User }>(
|
|
|
|
| 24 |
|
| 25 |
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
| 26 |
const [user, setUser] = useState<User | null>(null);
|
| 27 |
+
// Lazy initializer reads localStorage once β avoids setState-in-effect lint error
|
| 28 |
+
const [token, setToken] = useState<string | null>(
|
| 29 |
+
() => (typeof window !== "undefined" ? localStorage.getItem("token") : null)
|
| 30 |
+
);
|
| 31 |
const [loading, setLoading] = useState(true);
|
| 32 |
|
| 33 |
+
// ββ Validate saved token on mount βββββββββββββββββ
|
| 34 |
useEffect(() => {
|
| 35 |
+
if (!token) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
setLoading(false);
|
| 37 |
+
return;
|
| 38 |
}
|
| 39 |
+
api
|
| 40 |
+
.get<User>("/api/v1/auth/me", { token })
|
| 41 |
+
.then(setUser)
|
| 42 |
+
.catch(() => {
|
| 43 |
+
localStorage.removeItem("token");
|
| 44 |
+
setToken(null);
|
| 45 |
+
})
|
| 46 |
+
.finally(() => setLoading(false));
|
| 47 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 48 |
+
}, []); // intentionally runs once on mount only
|
| 49 |
|
| 50 |
const login = useCallback(async (email: string, password: string) => {
|
| 51 |
const data = await api.post<{ access_token: string; user: User }>(
|