All files / src/components FeedItem.tsx

94.73% Statements 18/19
91.66% Branches 22/24
100% Functions 7/7
94.73% Lines 18/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                  12x 12x   12x 2x     12x 1x     12x 3x   3x 3x   3x                       2x     2x         2x     1x   1x 1x       12x                                                                  
import { useState } from 'react';
import type { Item } from '../types';
import './FeedItem.css';
 
interface FeedItemProps {
    item: Item;
}
 
export default function FeedItem({ item: initialItem }: FeedItemProps) {
    const [item, setItem] = useState(initialItem);
    const [loading, setLoading] = useState(false);
 
    const toggleRead = () => {
        updateItem({ ...item, read: !item.read });
    };
 
    const toggleStar = () => {
        updateItem({ ...item, starred: !item.starred });
    };
 
    const updateItem = (newItem: Item) => {
        setLoading(true);
        // Optimistic update
        const previousItem = item;
        setItem(newItem);
 
        fetch(`/api/item/${newItem._id}`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                _id: newItem._id,
                read: newItem.read,
                starred: newItem.starred,
            }),
        })
            .then((res) => {
                Iif (!res.ok) {
                    throw new Error('Failed to update item');
                }
                return res.json();
            })
            .then(() => {
                // Confirm with server response if needed, but for now we trust the optimistic update
                // or we could setItem(updated) if the server returns the full object
                setLoading(false);
            })
            .catch((err) => {
                console.error('Error updating item:', err);
                // Revert on error
                setItem(previousItem);
                setLoading(false);
            });
    };
 
    return (
        <li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}>
            <div className="item-header">
                <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title">
                    {item.title || '(No Title)'}
                </a>
                <div className="item-actions">
                    <button
                        onClick={toggleRead}
                        className={`action-btn ${item.read ? 'is-read' : 'is-unread'}`}
                        title={item.read ? "Mark as unread" : "Mark as read"}
                    >
                        {item.read ? '📖' : 'uo'}
                    </button>
                    <button
                        onClick={toggleStar}
                        className={`action-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`}
                        title={item.starred ? "Unstar" : "Star"}
                    >
                        {item.starred ? '★' : '☆'}
                    </button>
                </div>
            </div>
            <div className="item-meta">
                <span className="item-date">{new Date(item.publish_date).toLocaleDateString()}</span>
                {item.feed_title && <span className="item-feed"> - {item.feed_title}</span>}
            </div>
            {item.description && (
                <div className="item-description" dangerouslySetInnerHTML={{ __html: item.description }} />
            )}
        </li>
    );
}