diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/src/components/ArticleItem.tsx | 35 |
1 files changed, 26 insertions, 9 deletions
diff --git a/frontend/src/components/ArticleItem.tsx b/frontend/src/components/ArticleItem.tsx index e5a506f..8fb00b0 100644 --- a/frontend/src/components/ArticleItem.tsx +++ b/frontend/src/components/ArticleItem.tsx @@ -1,5 +1,6 @@ import { faCheck, faCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useOptimistic } from "react"; import { useMutation } from "urql"; import type { GetReadArticlesQuery, @@ -24,10 +25,22 @@ export function ArticleItem({ article, showReadStatus = true }: Props) { const [, markArticleRead] = useMutation(MarkArticleReadDocument); const [, markArticleUnread] = useMutation(MarkArticleUnreadDocument); + const [optimisticArticle, setOptimisticArticle] = useOptimistic( + article, + (currentArticle, newReadState: boolean) => ({ + ...currentArticle, + isRead: newReadState, + }), + ); + const handleToggleRead = async ( articleId: string, isCurrentlyRead: boolean, ) => { + const newReadState = !isCurrentlyRead; + + setOptimisticArticle(newReadState); + if (isCurrentlyRead) { await markArticleUnread({ id: articleId }); } else { @@ -38,7 +51,9 @@ export function ArticleItem({ article, showReadStatus = true }: Props) { const handleArticleClick = async (article: Article) => { // Open article in new tab and mark as read if it's unread window.open(article.url, "_blank", "noreferrer"); - if (!article.isRead) { + if (!optimisticArticle.isRead) { + setOptimisticArticle(true); + await markArticleRead({ id: article.id }); } }; @@ -46,7 +61,7 @@ export function ArticleItem({ article, showReadStatus = true }: Props) { return ( <div className={`group flex items-center gap-3 rounded-lg border p-3 hover:bg-gray-50 ${ - article.isRead + optimisticArticle.isRead ? "border-gray-200 bg-white" : "border-blue-200 bg-blue-50" }`} @@ -54,16 +69,16 @@ export function ArticleItem({ article, showReadStatus = true }: Props) { {showReadStatus && ( <button type="button" - onClick={() => handleToggleRead(article.id, article.isRead)} + onClick={() => handleToggleRead(article.id, optimisticArticle.isRead)} className={`flex-shrink-0 rounded p-1 transition-colors ${ - article.isRead + optimisticArticle.isRead ? "text-gray-400 hover:text-gray-600" : "text-blue-600 hover:text-blue-700" }`} - title={article.isRead ? "Mark as unread" : "Mark as read"} + title={optimisticArticle.isRead ? "Mark as unread" : "Mark as read"} > <FontAwesomeIcon - icon={article.isRead ? faCheck : faCircle} + icon={optimisticArticle.isRead ? faCheck : faCircle} className="w-4 h-4" /> </button> @@ -71,13 +86,15 @@ export function ArticleItem({ article, showReadStatus = true }: Props) { <div className="flex-1 min-w-0"> <button type="button" - onClick={() => handleArticleClick(article)} + onClick={() => handleArticleClick(optimisticArticle)} className={`text-left w-full group-hover:text-blue-600 transition-colors ${ - article.isRead ? "text-gray-700" : "text-gray-900 font-medium" + optimisticArticle.isRead + ? "text-gray-700" + : "text-gray-900 font-medium" }`} > <div className="flex items-center gap-2 break-words"> - {article.title} + {optimisticArticle.title} </div> </button> </div> |
