Spaces:
Running
Running
Anish-530 commited on
Commit ·
78b07c7
1
Parent(s): 1116aba
Fix loader race condition and feedback modal error visibility
Browse files
frontend/app/dashboard/page.tsx
CHANGED
|
@@ -25,6 +25,8 @@ export default function DashboardPage() {
|
|
| 25 |
|
| 26 |
// Fetch Files & Guard
|
| 27 |
useEffect(() => {
|
|
|
|
|
|
|
| 28 |
if (!loading && !isAuthenticated) {
|
| 29 |
router.push('/login');
|
| 30 |
} else if (isAuthenticated) {
|
|
|
|
| 25 |
|
| 26 |
// Fetch Files & Guard
|
| 27 |
useEffect(() => {
|
| 28 |
+
// Reset immediately on mount so stale flag from previous page doesn't leak
|
| 29 |
+
(window as any).__PAGE_LOADED = false;
|
| 30 |
if (!loading && !isAuthenticated) {
|
| 31 |
router.push('/login');
|
| 32 |
} else if (isAuthenticated) {
|
frontend/app/login/page.tsx
CHANGED
|
@@ -17,15 +17,19 @@ export default function LoginPage() {
|
|
| 17 |
const [turnstileToken, setTurnstileToken] = useState("");
|
| 18 |
const router = useRouter();
|
| 19 |
|
| 20 |
-
// OAuth Token Intake
|
| 21 |
useEffect(() => {
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
const token = new URLSearchParams(window.location.search).get('access_token');
|
| 24 |
if (token) {
|
| 25 |
localStorage.setItem('access_token', token);
|
| 26 |
window.history.replaceState({}, document.title, '/login');
|
| 27 |
window.location.href = '/dashboard';
|
| 28 |
}
|
|
|
|
| 29 |
}, []);
|
| 30 |
|
| 31 |
// Visual Mesh / Parallax / Reveal Engine
|
|
|
|
| 17 |
const [turnstileToken, setTurnstileToken] = useState("");
|
| 18 |
const router = useRouter();
|
| 19 |
|
| 20 |
+
// OAuth Token Intake + Page loaded signal
|
| 21 |
useEffect(() => {
|
| 22 |
+
// Reset immediately so a stale 'true' from previous page doesn't leak
|
| 23 |
+
(window as any).__PAGE_LOADED = false;
|
| 24 |
+
// Signal ready after a short paint delay
|
| 25 |
+
const t = setTimeout(() => { (window as any).__PAGE_LOADED = true; }, 50);
|
| 26 |
const token = new URLSearchParams(window.location.search).get('access_token');
|
| 27 |
if (token) {
|
| 28 |
localStorage.setItem('access_token', token);
|
| 29 |
window.history.replaceState({}, document.title, '/login');
|
| 30 |
window.location.href = '/dashboard';
|
| 31 |
}
|
| 32 |
+
return () => clearTimeout(t);
|
| 33 |
}, []);
|
| 34 |
|
| 35 |
// Visual Mesh / Parallax / Reveal Engine
|
frontend/app/profile/page.tsx
CHANGED
|
@@ -24,6 +24,8 @@ export default function ProfilePage() {
|
|
| 24 |
// Handle cursor physics (Globally handled now, we just need the local parallax if any, but profile page doesn't have a dash-grid)
|
| 25 |
|
| 26 |
useEffect(() => {
|
|
|
|
|
|
|
| 27 |
if (!loading && !isAuthenticated) {
|
| 28 |
window.location.href = "/login";
|
| 29 |
}
|
|
|
|
| 24 |
// Handle cursor physics (Globally handled now, we just need the local parallax if any, but profile page doesn't have a dash-grid)
|
| 25 |
|
| 26 |
useEffect(() => {
|
| 27 |
+
// Reset immediately on mount so stale flag from previous page doesn't leak
|
| 28 |
+
(window as any).__PAGE_LOADED = false;
|
| 29 |
if (!loading && !isAuthenticated) {
|
| 30 |
window.location.href = "/login";
|
| 31 |
}
|
frontend/app/result/[id]/page.tsx
CHANGED
|
@@ -45,6 +45,7 @@ export default function ResultPage() {
|
|
| 45 |
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
|
| 46 |
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
|
| 47 |
const [feedbackLoading, setFeedbackLoading] = useState(false);
|
|
|
|
| 48 |
|
| 49 |
useEffect(() => {
|
| 50 |
const handleKeyDown = (e: KeyboardEvent) => {
|
|
@@ -101,22 +102,26 @@ export default function ResultPage() {
|
|
| 101 |
}, [isUploadModalOpen]);
|
| 102 |
|
| 103 |
useEffect(() => {
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
}
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
};
|
| 119 |
-
if (fileId) fetchFile();
|
| 120 |
}, [fileId, router]);
|
| 121 |
|
| 122 |
useEffect(() => {
|
|
@@ -222,19 +227,21 @@ export default function ResultPage() {
|
|
| 222 |
|
| 223 |
const submitFeedback = async (selectedLabel: string) => {
|
| 224 |
setFeedbackLoading(true);
|
|
|
|
| 225 |
try {
|
| 226 |
await apiLayer.submitFeedback({
|
| 227 |
file_id: parseInt(fileId),
|
| 228 |
label: selectedLabel.toLowerCase(),
|
| 229 |
-
confidence: confidenceVal
|
| 230 |
-
freq_score: freqScore
|
| 231 |
-
cnn_score: cnnScore
|
| 232 |
});
|
| 233 |
setFeedbackSubmitted(true);
|
| 234 |
setShowFeedbackPopup(false);
|
| 235 |
setShowFeedbackModal(false);
|
| 236 |
} catch (err: any) {
|
| 237 |
-
|
|
|
|
| 238 |
} finally {
|
| 239 |
setFeedbackLoading(false);
|
| 240 |
}
|
|
@@ -619,14 +626,23 @@ export default function ResultPage() {
|
|
| 619 |
</p>
|
| 620 |
|
| 621 |
<div className="flex flex-col gap-3 w-full">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
{['AI', 'Suspicious', 'Real'].map((lbl) => (
|
| 623 |
<button
|
| 624 |
key={lbl}
|
| 625 |
onClick={() => submitFeedback(lbl)}
|
| 626 |
disabled={feedbackLoading}
|
| 627 |
-
className={`w-full py-4 rounded-xl relative overflow-hidden group dash-border transition !cursor-none font-bold tracking-widest text-sm uppercase ${lbl === 'AI' ? 'bg-red-500/10 text-red-400 hover:bg-red-500/20 border border-red-500/20' : lbl === 'Suspicious' ? 'bg-amber-500/10 text-amber-400 hover:bg-amber-500/20 border border-amber-500/20' : 'bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 border border-emerald-500/20'}`}
|
| 628 |
>
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
</button>
|
| 631 |
))}
|
| 632 |
</div>
|
|
|
|
| 45 |
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
|
| 46 |
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
|
| 47 |
const [feedbackLoading, setFeedbackLoading] = useState(false);
|
| 48 |
+
const [feedbackError, setFeedbackError] = useState<string | null>(null);
|
| 49 |
|
| 50 |
useEffect(() => {
|
| 51 |
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
| 102 |
}, [isUploadModalOpen]);
|
| 103 |
|
| 104 |
useEffect(() => {
|
| 105 |
+
// Reset on mount so stale flag from previous page doesn't leak
|
| 106 |
+
(window as any).__PAGE_LOADED = false;
|
| 107 |
+
if (fileId) {
|
| 108 |
+
const fetchFile = async () => {
|
| 109 |
+
try {
|
| 110 |
+
const res = await apiLayer.getFileById(fileId);
|
| 111 |
+
setFileData(res.data);
|
| 112 |
+
} catch (err: any) {
|
| 113 |
+
console.error("Fetch error", err);
|
| 114 |
+
if (err.response?.status === 404 || err.response?.status === 403) {
|
| 115 |
+
router.push("/");
|
| 116 |
+
} else {
|
| 117 |
+
setError("Failed to load file data.");
|
| 118 |
+
}
|
| 119 |
+
} finally {
|
| 120 |
+
setLoading(false);
|
| 121 |
}
|
| 122 |
+
};
|
| 123 |
+
fetchFile();
|
| 124 |
+
}
|
|
|
|
|
|
|
| 125 |
}, [fileId, router]);
|
| 126 |
|
| 127 |
useEffect(() => {
|
|
|
|
| 227 |
|
| 228 |
const submitFeedback = async (selectedLabel: string) => {
|
| 229 |
setFeedbackLoading(true);
|
| 230 |
+
setFeedbackError(null);
|
| 231 |
try {
|
| 232 |
await apiLayer.submitFeedback({
|
| 233 |
file_id: parseInt(fileId),
|
| 234 |
label: selectedLabel.toLowerCase(),
|
| 235 |
+
confidence: confidenceVal ?? 0,
|
| 236 |
+
freq_score: freqScore ?? 0,
|
| 237 |
+
cnn_score: cnnScore ?? 0
|
| 238 |
});
|
| 239 |
setFeedbackSubmitted(true);
|
| 240 |
setShowFeedbackPopup(false);
|
| 241 |
setShowFeedbackModal(false);
|
| 242 |
} catch (err: any) {
|
| 243 |
+
const msg = err.response?.data?.detail || "Failed to submit. Please try again.";
|
| 244 |
+
setFeedbackError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
| 245 |
} finally {
|
| 246 |
setFeedbackLoading(false);
|
| 247 |
}
|
|
|
|
| 626 |
</p>
|
| 627 |
|
| 628 |
<div className="flex flex-col gap-3 w-full">
|
| 629 |
+
{feedbackError && (
|
| 630 |
+
<div className="bg-red-500/10 border border-red-500/20 text-red-400 text-xs p-3 rounded-lg text-center mb-1">
|
| 631 |
+
{feedbackError}
|
| 632 |
+
</div>
|
| 633 |
+
)}
|
| 634 |
{['AI', 'Suspicious', 'Real'].map((lbl) => (
|
| 635 |
<button
|
| 636 |
key={lbl}
|
| 637 |
onClick={() => submitFeedback(lbl)}
|
| 638 |
disabled={feedbackLoading}
|
| 639 |
+
className={`w-full py-4 rounded-xl relative overflow-hidden group dash-border transition !cursor-none font-bold tracking-widest text-sm uppercase flex items-center justify-center gap-2 disabled:opacity-60 ${lbl === 'AI' ? 'bg-red-500/10 text-red-400 hover:bg-red-500/20 border border-red-500/20' : lbl === 'Suspicious' ? 'bg-amber-500/10 text-amber-400 hover:bg-amber-500/20 border border-amber-500/20' : 'bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 border border-emerald-500/20'}`}
|
| 640 |
>
|
| 641 |
+
{feedbackLoading ? (
|
| 642 |
+
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
| 643 |
+
) : (
|
| 644 |
+
`It was actually ${lbl}`
|
| 645 |
+
)}
|
| 646 |
</button>
|
| 647 |
))}
|
| 648 |
</div>
|
frontend/components/shared/PageLoader.tsx
CHANGED
|
@@ -21,8 +21,6 @@ export default function PageLoader({ children }: { children: React.ReactNode })
|
|
| 21 |
setLoadProgress(0);
|
| 22 |
|
| 23 |
let progress = 0;
|
| 24 |
-
// Reset the flag to false on path change
|
| 25 |
-
(window as any).__PAGE_LOADED = false;
|
| 26 |
|
| 27 |
const interval = setInterval(() => {
|
| 28 |
if (progress < 90) {
|
|
|
|
| 21 |
setLoadProgress(0);
|
| 22 |
|
| 23 |
let progress = 0;
|
|
|
|
|
|
|
| 24 |
|
| 25 |
const interval = setInterval(() => {
|
| 26 |
if (progress < 90) {
|