import { useSignal } from '@preact/signals'; import { NewsFeedArticle } from '/lib/types.ts'; import { RequestBody as RefreshRequestBody, ResponseBody as RefreshResponseBody, } from '/routes/api/news/refresh-articles.tsx'; import { RequestBody as ReadRequestBody, ResponseBody as ReadResponseBody } from '/routes/api/news/mark-read.tsx'; interface ArticlesProps { initialArticles: NewsFeedArticle[]; } interface Filter { status: 'all' | 'unread'; } export default function Articles({ initialArticles }: ArticlesProps) { const isRefreshing = useSignal(false); const articles = useSignal(initialArticles); const filter = useSignal({ status: 'unread' }); const sessionReadArticleIds = useSignal>(new Set()); const isFilterDropdownOpen = useSignal(false); const dateFormat = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium' }); async function refreshArticles() { if (isRefreshing.value) { return; } isRefreshing.value = true; try { const requestBody: RefreshRequestBody = {}; const response = await fetch(`/api/news/refresh-articles`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as RefreshResponseBody; if (!result.success) { throw new Error('Failed to refresh articles!'); } articles.value = [...result.newArticles]; } catch (error) { console.error(error); } isRefreshing.value = false; } const filteredArticles = articles.value.filter((article) => { if (filter.value.status === 'unread') { if (article.is_read && !sessionReadArticleIds.value.has(article.id)) { return false; } return true; } return true; }); async function onClickView(articleId: string) { const newArticles = [...articles.value]; const matchingArticle = newArticles.find((article) => article.id === articleId); if (matchingArticle) { if (matchingArticle.is_read) { return; } matchingArticle.is_read = true; } else { return; } sessionReadArticleIds.value.add(articleId); articles.value = [...newArticles]; try { const requestBody: ReadRequestBody = { articleId }; const response = await fetch(`/api/news/mark-read`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as ReadResponseBody; if (!result.success) { throw new Error('Failed to mark article as read!'); } } catch (error) { console.error(error); } } async function onClickMarkAllRead() { const newArticles = [...articles.value].map((article) => { article.is_read = true; sessionReadArticleIds.value.add(article.id); return article; }); articles.value = [...newArticles]; try { const requestBody: ReadRequestBody = { articleId: 'all' }; const response = await fetch(`/api/news/mark-read`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as ReadResponseBody; if (!result.success) { throw new Error('Failed to mark all articles as read!'); } } catch (error) { console.error(error); } } function toggleFilterDropdown() { isFilterDropdownOpen.value = !isFilterDropdownOpen.value; } function setNewFilter(newFilter: Partial) { filter.value = { ...filter.value, ...newFilter }; isFilterDropdownOpen.value = false; } return ( <>
Manage feeds
{filteredArticles.length === 0 ?

There are no new articles to show.

: (
{filteredArticles.map((article) => (
onClickView(article.id)} > {article.article_title} {dateFormat.format(new Date(article.article_date))}
{article.article_summary}
onClickView(article.id)} > {article.article_url} View article
))}
)}
); }