Spaces:
Paused
Paused
import * as React from 'react'; | |
import { useState, useEffect, useCallback } from 'react'; | |
import Box from '@mui/material/Box'; | |
import Card from '@mui/material/Card'; | |
import CardContent from '@mui/material/CardContent'; | |
import Typography from '@mui/material/Typography'; | |
import './Sources.css'; | |
// Helper function to extract a friendly domain name from a URL. | |
const getDomainName = (url) => { | |
try { | |
const hostname = new URL(url).hostname; | |
// Remove "www." if present. | |
const domain = hostname.startsWith('www.') ? hostname.slice(4) : hostname; | |
// Return the first part in title case. | |
const parts = domain.split('.'); | |
return parts[0].charAt(0).toUpperCase() + parts[0].slice(1); | |
} catch (err) { | |
return url; | |
} | |
}; | |
export default function Sources({ sources, handleSourceClick }) { | |
// "sources" prop is the payload passed from the parent. | |
const [fetchedSources, setFetchedSources] = useState([]); | |
const [loading, setLoading] = useState(true); | |
const [error, setError] = useState(null); | |
const fetchSources = useCallback(async () => { | |
setLoading(true); | |
setError(null); | |
const startTime = Date.now(); // record start time | |
try { | |
// Use sources.payload if it exists. | |
const bodyData = sources && sources.payload ? sources.payload : sources; | |
const res = await fetch("/action/sources", { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify(bodyData) | |
}); | |
const data = await res.json(); | |
// Backend returns {"result": [...]} | |
setFetchedSources(data.result); | |
} catch (err) { | |
console.error("Error fetching sources:", err); | |
setError("Error fetching sources."); | |
} | |
const elapsed = Date.now() - startTime; | |
// Ensure that the loading state lasts at least 1 second. | |
if (elapsed < 500) { | |
setTimeout(() => { | |
setLoading(false); | |
}, 500 - elapsed); | |
} else { | |
setLoading(false); | |
} | |
}, [sources]); | |
useEffect(() => { | |
if (sources) { | |
fetchSources(); | |
} | |
}, [sources, fetchSources]); | |
if (loading) { | |
return ( | |
<Box className="sources-container"> | |
<Typography className="loading-sources" variant="body2">Loading Sources...</Typography> | |
</Box> | |
); | |
} | |
if (error) { | |
return ( | |
<Box className="sources-container"> | |
<Typography variant="body2" color="error">{error}</Typography> | |
</Box> | |
); | |
} | |
return ( | |
<Box className="sources-container"> | |
{fetchedSources.map((source, index) => { | |
const domain = getDomainName(source.link); | |
let hostname = ''; | |
try { | |
hostname = new URL(source.link).hostname; | |
} catch (err) { | |
hostname = source.link; | |
} | |
return ( | |
<Card | |
key={index} | |
variant="outlined" | |
className="source-card" | |
onClick={() => handleSourceClick(source)} | |
> | |
<CardContent> | |
{/* Header/Title */} | |
<Typography variant="h6" component="div" className="source-title"> | |
{source.title} | |
</Typography> | |
{/* Link info: icon, domain, bullet, serial number */} | |
<Typography variant="body2" className="source-link"> | |
<img | |
src={`https://www.google.com/s2/favicons?domain=${hostname}`} | |
alt={domain} | |
className="source-icon" | |
/> | |
<span className="source-domain">{domain}</span> | |
<span className="separator"> • </span> | |
<span className="source-serial">{index + 1}</span> | |
</Typography> | |
{/* Description */} | |
<Typography variant="body2" className="source-description"> | |
{source.description} | |
</Typography> | |
</CardContent> | |
</Card> | |
); | |
})} | |
</Box> | |
); | |
} |