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
- (window as any).__PAGE_LOADED = true;
 
 
 
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
- const fetchFile = async () => {
105
- try {
106
- const res = await apiLayer.getFileById(fileId);
107
- setFileData(res.data);
108
- } catch (err: any) {
109
- console.error("Fetch error", err);
110
- if (err.response?.status === 404 || err.response?.status === 403) {
111
- router.push("/");
112
- } else {
113
- setError("Failed to load file data.");
 
 
 
 
 
 
114
  }
115
- } finally {
116
- setLoading(false);
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 || 0,
230
- freq_score: freqScore || 0,
231
- cnn_score: cnnScore || 0
232
  });
233
  setFeedbackSubmitted(true);
234
  setShowFeedbackPopup(false);
235
  setShowFeedbackModal(false);
236
  } catch (err: any) {
237
- console.error("Feedback error", err);
 
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
- It was actually {lbl}
 
 
 
 
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) {