All files / src/components FeedItem.tsx

78.94% Statements 15/19
88.88% Branches 16/18
85.71% Functions 6/7
78.94% Lines 15/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                  21x 21x     21x 1x     21x 1x   1x 1x   1x                       1x     1x         1x                   21x         1x 1x                                                  
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 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">
                <button
                    onClick={(e) => {
                        e.stopPropagation();
                        toggleStar();
                    }}
                    className={`star-btn ${item.starred ? 'is-starred' : 'is-unstarred'}`}
                    title={item.starred ? "Unstar" : "Star"}
                >
                    {item.starred ? '★' : '☆'}
                </button>
                <a href={item.url} target="_blank" rel="noopener noreferrer" className="item-title">
                    {item.title || '(No Title)'}
                </a>
            </div>
            <div className="dateline">
                <a href={item.url} target="_blank" rel="noopener noreferrer">
                    {new Date(item.publish_date).toLocaleDateString()}
                    {item.feed_title && ` - ${item.feed_title}`}
                </a>
                <div className="item-actions" style={{ display: 'inline-block', float: 'right' }}>
                </div>
            </div>
            {item.description && (
                <div className="item-description" dangerouslySetInnerHTML={{ __html: item.description }} />
            )}
        </li>
    );
}