···
8
8
import MatterLoadingAnimation from '../MatterLoadingAnimation';
9
9
import { Helmet } from 'react-helmet';
10
10
import { useAuth } from '../../contexts/AuthContext';
11
11
+
import Loading from '../Loading/Loading';
11
12
12
13
const CollectionsFeed = () => {
13
14
const { username } = useParams();
14
15
const navigate = useNavigate();
15
15
-
const { isAuthenticated, checkAuthStatus } = useAuth();
16
16
+
const { isAuthenticated, loading: authLoading, session } = useAuth();
16
17
17
18
// Initialize state variables
18
19
const [handle, setHandle] = useState(username || '');
···
154
155
}
155
156
156
157
try {
157
157
-
// Verify authentication before making the request
158
158
-
const authResult = await checkAuthStatus();
159
159
-
160
160
-
if (!authResult) {
161
161
-
setError('You are not authenticated. Please log in again.');
162
162
-
const returnUrl = encodeURIComponent(window.location.pathname);
163
163
-
navigate(`/login?returnUrl=${returnUrl}`);
164
164
-
return;
165
165
-
}
166
166
-
167
158
const response = await fetch(url, {
168
159
credentials: 'include'
169
160
});
···
171
162
if (!response.ok) {
172
163
if (response.status === 401) {
173
164
// Try to refresh auth once
174
174
-
const refreshResult = await checkAuthStatus();
165
165
+
const refreshResult = await isAuthenticated();
175
166
if (!refreshResult) {
176
167
setError('Your session has expired. Please log in again.');
177
168
const returnUrl = encodeURIComponent(window.location.pathname);
···
355
346
setFetchingMore(false);
356
347
setChartLoading(false);
357
348
}
358
358
-
}, [navigate, checkAuthStatus]);
349
349
+
}, [useRkeyTimestamp, isAuthenticated, navigate, collectionCursors]);
359
350
360
351
// Now define loadUserData after fetchCollectionRecords is defined
361
361
-
const loadUserData = useCallback(async (usernameOrDid) => {
362
362
-
if (!usernameOrDid || !isAuthenticated) {
363
363
-
setError('Please enter a username or DID, and ensure you are logged in.');
364
364
-
return;
365
365
-
}
366
366
-
367
367
-
// Reset state for new search
352
352
+
const loadUserData = useCallback(async (userToLoad) => {
353
353
+
console.log(`loadUserData called for: ${userToLoad}`);
368
354
setLoading(true);
369
369
-
setShowContent(false); // Hide content while loading
370
355
setError('');
371
371
-
setDid('');
372
372
-
setServiceEndpoint('');
356
356
+
setSearchPerformed(true);
373
357
setCollections([]);
374
374
-
setSelectedCollections([]);
375
358
setRecords([]);
376
359
setAllRecordsForChart([]);
360
360
+
setSelectedCollections([]);
377
361
setCollectionCursors({});
378
378
-
362
362
+
setDid('');
363
363
+
setHandle('');
364
364
+
setDisplayName('');
365
365
+
setServiceEndpoint('');
366
366
+
setDisplayCount(25); // Reset display count on new search
367
367
+
379
368
try {
380
380
-
// First, verify authentication is still valid
381
381
-
const authResult = await checkAuthStatus();
382
382
-
383
383
-
if (!authResult) {
384
384
-
setError('Your session has expired. Please log in again.');
385
385
-
setLoading(false);
386
386
-
setTimeout(() => {
387
387
-
const returnUrl = encodeURIComponent(window.location.pathname);
388
388
-
navigate(`/login?returnUrl=${returnUrl}`);
389
389
-
}, 3000);
390
390
-
return;
369
369
+
const identifier = userToLoad || session?.handle;
370
370
+
if (!identifier) {
371
371
+
throw new Error("No user identifier available to load data.");
391
372
}
392
392
-
393
393
-
// Continue with resolving the handle to DID
394
394
-
let userDid = usernameOrDid;
395
395
-
396
396
-
// If input doesn't look like a DID, try to resolve it as a handle
397
397
-
if (!userDid.startsWith('did:')) {
398
398
-
try {
399
399
-
userDid = await resolveHandleToDid(usernameOrDid);
400
400
-
} catch (resolveErr) {
401
401
-
setError(`Could not resolve handle: ${resolveErr.message}`);
402
402
-
setLoading(false);
403
403
-
return;
404
404
-
}
373
373
+
374
374
+
// Resolve identifier first
375
375
+
const resolveResponse = await fetch(`/api/resolve-identifier?identifier=${encodeURIComponent(identifier)}`);
376
376
+
if (!resolveResponse.ok) {
377
377
+
throw new Error(`Failed to resolve identifier: ${identifier}`);
405
378
}
406
406
-
407
407
-
// Get service endpoint
408
408
-
let endpoint;
409
409
-
try {
410
410
-
endpoint = await getServiceEndpointForDid(userDid);
411
411
-
setServiceEndpoint(endpoint);
412
412
-
} catch (endpointError) {
413
413
-
console.error('Error getting service endpoint:', endpointError);
414
414
-
setError(`Could not determine PDS endpoint for "${userDid}". The user's server may be offline.`);
415
415
-
setInitialLoad(false);
416
416
-
setLoading(false);
417
417
-
return;
418
418
-
}
419
419
-
420
420
-
// Fetch profile information
421
421
-
try {
422
422
-
const publicApiEndpoint = "https://public.api.bsky.app";
423
423
-
const profileResponse = await fetch(`${publicApiEndpoint}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(userDid)}`);
424
424
-
425
425
-
if (!profileResponse.ok) {
426
426
-
throw new Error(`Error fetching profile: ${profileResponse.statusText}`);
427
427
-
}
428
428
-
429
429
-
const profileData = await profileResponse.json();
430
430
-
setHandle(profileData.handle);
431
431
-
setDisplayName(profileData.displayName || profileData.handle);
432
432
-
} catch (profileError) {
433
433
-
console.error('Error fetching profile:', profileError);
434
434
-
// Continue without profile data, not critical
435
435
-
}
436
436
-
437
437
-
// Use our server-side API to fetch collections
438
438
-
let retryCount = 0;
439
439
-
const maxRetries = 2;
440
440
-
let collectionsData;
441
441
-
442
442
-
while (retryCount <= maxRetries) {
443
443
-
try {
444
444
-
// Verify authentication before making the request
445
445
-
const authResult = await checkAuthStatus();
446
446
-
447
447
-
if (!authResult) {
448
448
-
setError('Your session has expired. Please log in again.');
449
449
-
setLoading(false);
450
450
-
setTimeout(() => {
451
451
-
const returnUrl = encodeURIComponent(window.location.pathname);
452
452
-
navigate(`/login?returnUrl=${returnUrl}`);
453
453
-
}, 3000);
454
454
-
return;
455
455
-
}
456
456
-
457
457
-
const collectionsResponse = await fetch(`/api/collections/${encodeURIComponent(userDid)}?endpoint=${encodeURIComponent(endpoint)}`, {
458
458
-
credentials: 'include'
459
459
-
});
460
460
-
461
461
-
if (!collectionsResponse.ok) {
462
462
-
if (collectionsResponse.status === 401) {
463
463
-
// Try to refresh auth once more
464
464
-
const refreshResult = await checkAuthStatus();
465
465
-
if (!refreshResult) {
466
466
-
setError('You are not authenticated. Please log in to continue.');
467
467
-
setLoading(false);
468
468
-
setTimeout(() => {
469
469
-
const returnUrl = encodeURIComponent(window.location.pathname);
470
470
-
navigate(`/login?returnUrl=${returnUrl}`);
471
471
-
}, 3000);
472
472
-
return;
473
473
-
}
474
474
-
// If we're still here, try again
475
475
-
retryCount++;
476
476
-
continue;
477
477
-
}
478
478
-
479
479
-
// Try to parse the error response
480
480
-
let errorMessage;
481
481
-
try {
482
482
-
const errorData = await collectionsResponse.json();
483
483
-
errorMessage = errorData?.error || errorData?.details || collectionsResponse.statusText;
484
484
-
} catch (jsonErr) {
485
485
-
errorMessage = collectionsResponse.statusText || `HTTP ${collectionsResponse.status}`;
486
486
-
}
487
487
-
488
488
-
throw new Error(`Error fetching collections: ${errorMessage}`);
489
489
-
}
490
490
-
491
491
-
collectionsData = await collectionsResponse.json();
492
492
-
break; // Success, exit the retry loop
493
493
-
} catch (err) {
494
494
-
console.error(`Attempt ${retryCount + 1} failed:`, err);
495
495
-
if (retryCount === maxRetries) {
496
496
-
// This was our last attempt, propagate the error
497
497
-
throw err;
498
498
-
}
499
499
-
// Wait before retrying
500
500
-
await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
501
501
-
retryCount++;
502
502
-
}
379
379
+
const resolveData = await resolveResponse.json();
380
380
+
const resolvedDid = resolveData.did;
381
381
+
const resolvedHandle = resolveData.handle;
382
382
+
setDid(resolvedDid);
383
383
+
setHandle(resolvedHandle);
384
384
+
console.log('Resolved:', resolvedDid, resolvedHandle);
385
385
+
386
386
+
// Fetch collections using resolved DID
387
387
+
// Assume default endpoint or fetch dynamically if needed
388
388
+
const defaultEndpoint = 'https://bsky.social'; // Or determine dynamically
389
389
+
setServiceEndpoint(defaultEndpoint);
390
390
+
391
391
+
// Use server-side endpoint for collections
392
392
+
const collectionsResponse = await fetch(`/api/collections/${encodeURIComponent(resolvedDid)}?endpoint=${encodeURIComponent(defaultEndpoint)}`, {
393
393
+
credentials: 'include'
394
394
+
});
395
395
+
396
396
+
if (!collectionsResponse.ok) {
397
397
+
if (collectionsResponse.status === 401) {
398
398
+
// No need to call checkAuthStatus, just redirect
399
399
+
throw new Error('Authentication required. Please log in again.');
400
400
+
}
401
401
+
throw new Error(`Failed to fetch collections for ${resolvedDid}`);
503
402
}
504
504
-
505
505
-
if (collectionsData.collections && collectionsData.collections.length > 0) {
506
506
-
const sortedCollections = [...collectionsData.collections].sort();
507
507
-
setCollections(sortedCollections);
508
508
-
// By default, select all collections
509
509
-
setSelectedCollections(sortedCollections);
510
510
-
511
511
-
// Fetch records for each collection
512
512
-
try {
513
513
-
await fetchCollectionRecords(userDid, endpoint, sortedCollections);
514
514
-
} catch (recordsError) {
515
515
-
console.error('Error fetching collection records:', recordsError);
516
516
-
setError(`Successfully loaded collections, but could not load records: ${recordsError.message}`);
517
517
-
// Continue with the collections we have
518
518
-
}
519
519
-
} else {
520
520
-
setError('No collections found for this user.');
403
403
+
404
404
+
const collectionsData = await collectionsResponse.json();
405
405
+
const fetchedCollections = collectionsData.collections || [];
406
406
+
setDisplayName(collectionsData.displayName || resolvedHandle);
407
407
+
setCollections(fetchedCollections);
408
408
+
setSelectedCollections(fetchedCollections); // Select all by default
409
409
+
console.log('Fetched collections:', fetchedCollections);
410
410
+
411
411
+
// Fetch records for all collections initially for the chart
412
412
+
if (fetchedCollections.length > 0) {
413
413
+
await fetchCollectionRecords(resolvedDid, defaultEndpoint, fetchedCollections);
521
414
}
522
522
-
523
523
-
setSearchPerformed(true);
524
524
-
setInitialLoad(false);
525
525
-
setLoading(false);
526
526
-
// Add a slight delay before showing content for smooth transition
527
527
-
setTimeout(() => setShowContent(true), 100);
415
415
+
528
416
} catch (err) {
529
417
console.error('Error loading user data:', err);
530
530
-
531
531
-
// Provide more user-friendly error messages
532
532
-
let userMessage = 'An error occurred while loading data.';
533
533
-
534
534
-
if (err.message) {
535
535
-
if (err.message.includes('authenticated')) {
536
536
-
userMessage = 'Authentication error: Please log in again.';
537
537
-
setTimeout(() => {
538
538
-
const returnUrl = encodeURIComponent(window.location.pathname);
539
539
-
navigate(`/login?returnUrl=${returnUrl}`);
540
540
-
}, 3000);
541
541
-
} else if (err.message.includes('fetch')) {
542
542
-
userMessage = 'Network error: Could not connect to the server.';
543
543
-
} else if (err.message.includes('collections')) {
544
544
-
userMessage = `Collections error: ${err.message}`;
545
545
-
} else {
546
546
-
userMessage = err.message;
547
547
-
}
418
418
+
let userMessage = 'Failed to load user data. Please check the handle and try again.';
419
419
+
if (err.message?.includes('Authentication required')) {
420
420
+
userMessage = 'Authentication error. Please log in again.';
421
421
+
// Redirect to login
422
422
+
const returnUrl = encodeURIComponent(window.location.pathname);
423
423
+
navigate(`/login?returnUrl=${returnUrl}`);
424
424
+
return; // Stop further execution
548
425
}
549
549
-
550
426
setError(userMessage);
551
551
-
setInitialLoad(false);
427
427
+
} finally {
552
428
setLoading(false);
553
553
-
setShowContent(true); // Show content even on error so user can see error message
429
429
+
setInitialLoad(false);
430
430
+
setShowContent(true);
554
431
}
555
555
-
}, [navigate, checkAuthStatus]);
432
432
+
}, [session?.handle, navigate]);
556
433
557
434
// Now place useEffects after all the callbacks are defined
558
435
559
559
-
// Verify authentication first
436
436
+
// Effect to handle initial authentication and data loading
560
437
useEffect(() => {
561
561
-
const verifyAuth = async () => {
562
562
-
try {
563
563
-
setLoading(true);
564
564
-
setInitialLoad(true);
565
565
-
setShowContent(false);
566
566
-
567
567
-
// Reset states if username changes
568
568
-
if (username) {
569
569
-
setError('');
570
570
-
setSearchTerm(username);
571
571
-
}
572
572
-
573
573
-
const authResult = await checkAuthStatus();
574
574
-
575
575
-
if (!authResult) {
576
576
-
console.log('Not authenticated, redirecting to login');
577
577
-
// Save the current path for redirect after login
578
578
-
const returnUrl = encodeURIComponent(window.location.pathname);
579
579
-
navigate(`/login?returnUrl=${returnUrl}`);
580
580
-
return;
581
581
-
}
582
582
-
583
583
-
// If a username is provided in the URL, load that user's data
584
584
-
if (username && authResult) {
585
585
-
console.log('Username provided in URL, loading data for:', username);
586
586
-
loadUserData(username);
587
587
-
} else {
588
588
-
// Only set loading to false if we're not loading a specific user
589
589
-
setLoading(false);
590
590
-
setInitialLoad(false);
591
591
-
setShowContent(true);
592
592
-
}
593
593
-
} catch (err) {
594
594
-
console.error('Auth verification failed:', err);
595
595
-
setError('Authentication failed. Please try logging in again.');
438
438
+
// Wait for AuthProvider to finish loading
439
439
+
if (authLoading) {
440
440
+
console.log("CollectionsFeed: Waiting for AuthProvider...");
441
441
+
return;
442
442
+
}
443
443
+
444
444
+
console.log("CollectionsFeed: AuthProvider loaded. isAuthenticated:", isAuthenticated);
445
445
+
setInitialLoad(true); // Start initial load process
446
446
+
setShowContent(false);
447
447
+
448
448
+
if (!isAuthenticated) {
449
449
+
console.log('CollectionsFeed: Not authenticated, redirecting to login');
450
450
+
const returnUrl = encodeURIComponent(window.location.pathname + window.location.search);
451
451
+
navigate(`/login?returnUrl=${returnUrl}`);
452
452
+
return; // Stop execution
453
453
+
}
454
454
+
455
455
+
// If authenticated, proceed to load data
456
456
+
setError(''); // Clear previous errors
457
457
+
if (username) {
458
458
+
console.log('CollectionsFeed: Authenticated, username provided, loading data for:', username);
459
459
+
setSearchTerm(username); // Update search term from URL
460
460
+
loadUserData(username);
461
461
+
} else if (session?.handle) {
462
462
+
// If no username in URL, load data for the logged-in user
463
463
+
console.log('CollectionsFeed: Authenticated, no username in URL, loading data for logged-in user:', session.handle);
464
464
+
setSearchTerm(session.handle);
465
465
+
loadUserData(session.handle);
466
466
+
} else {
467
467
+
// Authenticated but no username in URL and no handle in session (should not happen ideally)
468
468
+
console.error('CollectionsFeed: Authenticated but no identifier found to load data.');
469
469
+
setError('Could not determine user to load data for.');
596
470
setLoading(false);
597
471
setInitialLoad(false);
598
472
setShowContent(true);
599
599
-
600
600
-
// Add a delay before redirecting to show the error message
601
601
-
setTimeout(() => {
602
602
-
navigate('/login');
603
603
-
}, 2000);
604
604
-
}
605
605
-
};
606
606
-
607
607
-
verifyAuth();
608
608
-
609
609
-
// Set up periodic auth checks
610
610
-
const interval = setInterval(async () => {
611
611
-
const authResult = await checkAuthStatus();
612
612
-
if (!authResult && isAuthenticated) {
613
613
-
// Session was lost during browsing
614
614
-
setError('Your session has expired. Please log in again.');
615
615
-
setInitialLoad(false);
616
616
-
// Show the error for 3 seconds before redirecting
617
617
-
setTimeout(() => {
618
618
-
const returnUrl = encodeURIComponent(window.location.pathname);
619
619
-
navigate(`/login?returnUrl=${returnUrl}`);
620
620
-
}, 3000);
621
621
-
}
622
622
-
}, 30000); // Check every 30 seconds
623
623
-
624
624
-
// Safety timeout to ensure initialLoad is cleared after 10 seconds maximum
473
473
+
}
474
474
+
475
475
+
// Safety timeout for initial load UI state
625
476
const loadingTimeout = setTimeout(() => {
626
626
-
setInitialLoad(false);
627
627
-
}, 10000);
628
628
-
477
477
+
if (initialLoad) {
478
478
+
console.warn("CollectionsFeed: Initial load timeout reached.");
479
479
+
setInitialLoad(false);
480
480
+
// Decide whether to show content or error based on current state
481
481
+
if (!error) setShowContent(true);
482
482
+
}
483
483
+
}, 15000); // Increase timeout slightly
484
484
+
485
485
+
// Cleanup function (no interval to clear anymore)
629
486
return () => {
630
630
-
clearInterval(interval);
631
487
clearTimeout(loadingTimeout);
632
488
};
633
633
-
}, [isAuthenticated, checkAuthStatus, navigate, username, loadUserData]);
489
489
+
// Depend on auth state and username param
490
490
+
}, [isAuthenticated, authLoading, navigate, username, session?.handle, loadUserData]);
634
491
635
492
// Effect to watch for selected collections changes
636
493
useEffect(() => {
···
709
566
710
567
// Handle refresh button click - only updates the feed, not the chart
711
568
const handleRefresh = async () => {
569
569
+
// Check auth status directly
570
570
+
if (!isAuthenticated) {
571
571
+
console.warn("Refresh cancelled: User not authenticated.");
572
572
+
navigate('/login'); // Redirect to login if session lost
573
573
+
return;
574
574
+
}
712
575
if (did && serviceEndpoint) {
713
713
-
// Set a loading state but not for the chart
714
576
setLoading(true);
715
715
-
716
716
-
// Reset display count to show only the first 25 records after refresh
717
577
setDisplayCount(25);
718
718
-
719
578
try {
720
720
-
// Fetch just the most recent records for the feed display
721
721
-
const refreshOnlyFeed = async () => {
722
579
let recentRecords = [];
723
723
-
724
724
-
// Process each selected collection sequentially
725
580
for (const collection of selectedCollections) {
726
726
-
try {
727
727
-
// Use server-side API endpoint for fetching records
728
728
-
const url = `/api/collections/${encodeURIComponent(did)}/records?endpoint=${encodeURIComponent(serviceEndpoint)}&collection=${encodeURIComponent(collection)}&limit=25`;
729
729
-
730
730
-
const response = await fetch(url, {
731
731
-
credentials: 'include'
732
732
-
});
733
733
-
734
734
-
if (!response.ok) {
735
735
-
if (response.status === 401) {
736
736
-
// Handle unauthorized
737
737
-
checkAuthStatus();
738
738
-
throw new Error('Authentication required. Please log in again.');
739
739
-
}
740
740
-
console.error(`Error refreshing ${collection}: ${response.statusText}`);
741
741
-
continue; // Skip this collection but continue with others
581
581
+
try {
582
582
+
const url = `/api/collections/${encodeURIComponent(did)}/records?endpoint=${encodeURIComponent(serviceEndpoint)}&collection=${encodeURIComponent(collection)}&limit=25`;
583
583
+
const response = await fetch(url, { credentials: 'include' });
584
584
+
if (!response.ok) {
585
585
+
if (response.status === 401) {
586
586
+
// Directly navigate to login on 401
587
587
+
throw new Error('Authentication required. Please log in again.');
588
588
+
}
589
589
+
console.error(`Error refreshing ${collection}: ${response.statusText}`);
590
590
+
continue;
591
591
+
}
592
592
+
const data = await response.json();
593
593
+
if (data.records && data.records.length > 0) {
594
594
+
const processedRecords = data.records.map(record => {
595
595
+
const contentTimestamp = extractTimestamp(record);
596
596
+
const rkey = record.uri.split('/').pop();
597
597
+
const rkeyTimestamp = tidToTimestamp(rkey);
598
598
+
return {
599
599
+
...record,
600
600
+
collection,
601
601
+
collectionType: record.value?.$type || collection,
602
602
+
contentTimestamp,
603
603
+
rkeyTimestamp,
604
604
+
rkey,
605
605
+
};
606
606
+
});
607
607
+
recentRecords = [...recentRecords, ...processedRecords];
608
608
+
}
609
609
+
} catch (err) {
610
610
+
console.error(`Error refreshing collection ${collection}:`, err);
611
611
+
if (err.message?.includes('Authentication required')) throw err; // Re-throw auth error
612
612
+
// Continue with other collections
742
613
}
743
743
-
744
744
-
const data = await response.json();
745
745
-
746
746
-
if (data.records && data.records.length > 0) {
747
747
-
// Process the records with timestamps
748
748
-
const processedRecords = data.records.map(record => {
749
749
-
const contentTimestamp = extractTimestamp(record);
750
750
-
const rkey = record.uri.split('/').pop();
751
751
-
const rkeyTimestamp = tidToTimestamp(rkey);
752
752
-
753
753
-
return {
754
754
-
...record,
755
755
-
collection,
756
756
-
collectionType: record.value?.$type || collection,
757
757
-
contentTimestamp,
758
758
-
rkeyTimestamp,
759
759
-
rkey,
760
760
-
};
761
761
-
});
762
762
-
763
763
-
// Add to our records array
764
764
-
recentRecords = [...recentRecords, ...processedRecords];
765
765
-
}
766
766
-
} catch (err) {
767
767
-
console.error(`Error refreshing collection ${collection}:`, err);
768
768
-
// Continue with other collections
769
769
-
}
770
614
}
771
771
-
772
772
-
// Sort the refreshed records by timestamp (newest first)
773
615
const sortedRecords = recentRecords.filter(record => {
774
774
-
if (useRkeyTimestamp) {
775
775
-
return record.rkeyTimestamp !== null;
776
776
-
} else {
777
777
-
return record.contentTimestamp !== null;
778
778
-
}
616
616
+
if (useRkeyTimestamp) {
617
617
+
return record.rkeyTimestamp !== null;
618
618
+
} else {
619
619
+
return record.contentTimestamp !== null;
620
620
+
}
779
621
}).sort((a, b) => {
780
780
-
const aTime = useRkeyTimestamp ? a.rkeyTimestamp : a.contentTimestamp;
781
781
-
const bTime = useRkeyTimestamp ? b.rkeyTimestamp : b.contentTimestamp;
782
782
-
return new Date(bTime) - new Date(aTime);
622
622
+
const aTime = useRkeyTimestamp ? a.rkeyTimestamp : a.contentTimestamp;
623
623
+
const bTime = useRkeyTimestamp ? b.rkeyTimestamp : b.contentTimestamp;
624
624
+
return new Date(bTime) - new Date(aTime);
783
625
});
784
784
-
785
785
-
// Only update the feed display records, not the chart data
786
626
setRecords(sortedRecords.slice(0, 25));
787
787
-
};
788
788
-
789
789
-
await refreshOnlyFeed();
790
627
} catch (err) {
791
791
-
console.error("Error during feed refresh:", err);
792
792
-
setError('Failed to refresh records. Please try again.');
628
628
+
console.error("Error during feed refresh:", err);
629
629
+
if (err.message?.includes('Authentication required')) {
630
630
+
const returnUrl = encodeURIComponent(window.location.pathname);
631
631
+
navigate(`/login?returnUrl=${returnUrl}`);
632
632
+
} else {
633
633
+
setError('Failed to refresh records. Please try again.');
634
634
+
}
793
635
} finally {
794
794
-
// Ensure loading state is reset
795
795
-
setLoading(false);
636
636
+
setLoading(false);
796
637
}
797
638
}
798
639
};
···
801
642
const handleLoadMore = async () => {
802
643
// Verify authentication before proceeding
803
644
if (!isAuthenticated) {
804
804
-
checkAuthStatus();
645
645
+
console.warn("Load more cancelled: User not authenticated.");
646
646
+
navigate('/login'); // Redirect to login if session lost
805
647
return;
806
648
}
807
649
···
1
1
-
import React, { useEffect, useRef, useState } from 'react';
1
1
+
import React, { useEffect, useState } from 'react';
2
2
import { Navigate, useLocation } from 'react-router-dom';
3
3
import { useAuth } from '../contexts/AuthContext';
4
4
import { isAccountAllowed } from '../config/allowlist';
···
6
6
7
7
// Component to protect routes that require authentication
8
8
const ProtectedRoute = ({ children }) => {
9
9
-
const { isAuthenticated, loading, session, checkAuthStatus } = useAuth();
9
9
+
const { isAuthenticated, loading, session } = useAuth();
10
10
const location = useLocation();
11
11
-
const [redirecting, setRedirecting] = useState(false);
12
12
-
const [checkingStatus, setCheckingStatus] = useState(false);
13
13
-
const checkCount = useRef(0);
14
14
-
const maxChecks = 3; // Maximum number of checks to prevent infinite loops
15
15
-
16
16
-
// Perform an immediate auth check when the component mounts
17
17
-
useEffect(() => {
18
18
-
const checkAuth = async () => {
19
19
-
if (checkCount.current >= maxChecks) {
20
20
-
console.error("Maximum auth check attempts reached. Stopping to prevent infinite loop.");
21
21
-
return;
22
22
-
}
23
23
-
24
24
-
// Only proceed if not already checking, not already redirecting, and not loading
25
25
-
if (!isAuthenticated && !checkingStatus && !redirecting && !loading) {
26
26
-
try {
27
27
-
console.log("ProtectedRoute: Checking authentication status");
28
28
-
setCheckingStatus(true);
29
29
-
checkCount.current += 1;
30
30
-
await checkAuthStatus();
31
31
-
} catch (error) {
32
32
-
console.error("ProtectedRoute: Auth check failed:", error);
33
33
-
} finally {
34
34
-
setCheckingStatus(false);
35
35
-
}
36
36
-
}
37
37
-
};
38
38
-
39
39
-
// Call immediately on mount or when dependency values change
40
40
-
checkAuth();
41
41
-
42
42
-
// Set up interval for periodic checks only if authenticated
43
43
-
let interval;
44
44
-
if (isAuthenticated && session) {
45
45
-
console.log("ProtectedRoute: Setting up periodic auth checks");
46
46
-
interval = setInterval(() => {
47
47
-
checkAuthStatus().catch(err => {
48
48
-
console.error("Error in periodic auth check:", err);
49
49
-
});
50
50
-
}, 30000); // Check every 30 seconds
51
51
-
}
52
52
-
53
53
-
return () => {
54
54
-
if (interval) {
55
55
-
console.log("ProtectedRoute: Clearing periodic auth checks");
56
56
-
clearInterval(interval);
57
57
-
}
58
58
-
};
59
59
-
}, [isAuthenticated, checkAuthStatus, redirecting, loading, checkingStatus, session]);
60
11
61
61
-
// Show loading state while authentication is being checked
62
62
-
if (loading || checkingStatus) {
63
63
-
return <Loading message="Checking authentication..." />;
12
12
+
// Show loading state while authentication is being determined by AuthProvider
13
13
+
if (loading) {
14
14
+
console.log("ProtectedRoute: Waiting for AuthProvider loading...");
15
15
+
return <Loading message="Loading authentication status..." />;
64
16
}
65
17
66
66
-
// If not authenticated, redirect to login with return URL
67
67
-
if (!isAuthenticated && !redirecting) {
68
68
-
console.log("ProtectedRoute: Not authenticated, redirecting to login");
69
69
-
setRedirecting(true); // Prevent multiple redirects
70
70
-
const returnUrl = encodeURIComponent(location.pathname);
18
18
+
// If not authenticated after loading, redirect to login with return URL
19
19
+
if (!isAuthenticated) {
20
20
+
console.log("ProtectedRoute: Not authenticated after loading, redirecting to login");
21
21
+
const returnUrl = encodeURIComponent(location.pathname + location.search);
71
22
return <Navigate to={`/login?returnUrl=${returnUrl}`} replace />;
72
23
}
73
24
74
74
-
// Check if user is allowed
25
25
+
// Check if user is allowed (allowlist check remains)
75
26
if (session && session.handle && !isAccountAllowed(session)) {
76
27
console.log("ProtectedRoute: User not in allowlist, redirecting to supporter page");
77
28
return <Navigate to="/supporter" replace />;
78
29
}
79
30
80
80
-
// Reset counter when rendering the protected content
81
81
-
checkCount.current = 0;
82
82
-
83
31
// Render children if authenticated and allowed
84
84
-
console.log("ProtectedRoute: Authentication successful, rendering protected content");
32
32
+
console.log(`ProtectedRoute: Authentication successful (${session?.handle}), rendering protected content for ${location.pathname}`);
85
33
return children;
86
34
};
87
35