import { useEffect, useState } from 'react';
import { FeedItem } from '@/lib/db';
import Link from 'next/link';
import ReactMarkdown from 'react-markdown';
interface FeedItemDetailProps {
itemId: string;
}
interface ArticleContent {
id: string;
url: string;
title: string;
content: string;
html: string;
author: string;
published_date: string;
image_url: string;
summary: string;
fetched_at: number;
}
export default function FeedItemDetail({ itemId }: FeedItemDetailProps) {
const [item, setItem] = useState<FeedItem | null>(null);
const [article, setArticle] = useState<ArticleContent | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [fetchingContent, setFetchingContent] = useState(false);
useEffect(() => {
const fetchItem = async () => {
try {
setLoading(true);
setArticle(null);
console.log('Fetching item with ID:', itemId);
const response = await fetch(`/api/items?id=${encodeURIComponent(itemId)}`);
console.log('fetchItem url', itemId, 'response:', response);
if (!response.ok) {
if (response.status === 404) {
throw new Error('Item not found');
}
throw new Error('Failed to fetch item');
}
const data = await response.json();
console.log('fetchItem data:', data);
console.log('Item content:', data.content);
console.log('Item summary:', data.summary);
console.log('Item link:', data.link);
setItem(data);
setLoading(false);
// After getting the item, fetch the full content only if a valid link is available
if (data && data.link) {
console.log('Calling fetchArticleContent with link:', data.link);
fetchArticleContent(data.link);
} else {
console.log('No valid link available for item, cannot fetch article content');
setFetchingContent(false);
}
} catch (err) {
console.error('Error in fetchItem:', err);
setError(err instanceof Error ? err.message : 'An error occurred');
setLoading(false);
}
};
fetchItem();
}, [itemId]);
// Function to fetch article content via firecrawl
const fetchArticleContent = async (url: string) => {
console.log('Fetching article content for:', url);
try {
setFetchingContent(true);
// Check if we already have the article in the database
console.log('Checking if article exists in database');
const articleResponse = await fetch(`/api/articles?url=${encodeURIComponent(url)}`);
console.log('Article response status:', articleResponse.status);
if (articleResponse.ok) {
const articleData = await articleResponse.json();
console.log('Article data from database:', articleData);
if (articleData && (articleData.html || articleData.content)) {
console.log('Fetched article from database:', articleData);
setArticle(articleData);
setFetchingContent(false);
return;
}
}
// If article is not in the database, fetch it using the API
console.log('Article not found in database, fetching from API');
const fetchResponse = await fetch('/api/articles/fetch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
});
if (fetchResponse.ok) {
const fetchedArticle = await fetchResponse.json();
console.log('Fetched article from API:', fetchedArticle);
setArticle(fetchedArticle);
} else {
console.error('Failed to fetch article from API');
}
setFetchingContent(false);
} catch (err) {
console.error('Error fetching article content:', err);
setFetchingContent(false);
}
};
if (loading) {
return (
<div className="p-6 max-w-4xl mx-auto">
<div className="animate-pulse">
<div className="h-8 bg-gray-300 rounded w-3/4 mb-4"></div>
<div className="h-4 bg-gray-300 rounded w-1/4 mb-6"></div>
<div className="h-4 bg-gray-300 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-300 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-300 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-300 rounded w-3/4 mb-2"></div>
</div>
</div>
);
}
if (error) {
return (
<div className="p-6 max-w-4xl mx-auto">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<p>Error: {error}</p>
</div>
</div>
);
}
if (!item) {
return (
<div className="p-6 max-w-4xl mx-auto">
<div className="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded">
<p>Item not found.</p>
</div>
</div>
);
}
// Format the published date
const formattedDate = new Date(item.published * 1000).toLocaleString();
return (
<div className="p-6 max-w-4xl mx-auto">
<div className="mb-6">
<h1 className="text-2xl font-bold mb-2">{item.title}</h1>
<div className="flex justify-between items-center mb-4">
<div className="text-gray-600">
{item.feed_title && (
<span>
From: <Link href={`/feed/${encodeURIComponent(item.feed_id)}`} className="text-blue-600 hover:underline">
{item.feed_title}
</Link>
</span>
)}
{item.author && <span> • By {item.author}</span>}
</div>
<div className="text-gray-500">{formattedDate}</div>
</div>
{item.categories && item.categories.length > 0 && (
<div className="flex gap-2 mb-4">
{item.categories.map(category => (
<span key={category} className="bg-gray-200 px-2 py-1 rounded text-sm">
{category}
</span>
))}
</div>
)}
{item.link && (
<div className="mb-6">
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 inline-block"
>
Read Original Article
</a>
</div>
)}
</div>
<div className="prose prose-lg max-w-none">
{fetchingContent && (
<div className="flex flex-col items-center justify-center p-6">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div>
<p className="text-lg text-gray-600">コンテンツをロード中</p>
</div>
)}
{!fetchingContent && article && article.html ? (
<div dangerouslySetInnerHTML={{ __html: article.html }} />
) : !fetchingContent && article && article.content ? (
<ReactMarkdown>{article.content}</ReactMarkdown>
) : !fetchingContent && (item.content || item.summary) ? (
<div dangerouslySetInnerHTML={{ __html: item.content || item.summary || '' }} />
) : !fetchingContent ? (
<div className="text-center p-6">
<p className="text-gray-600 mb-4">この記事のコンテンツは利用できません。</p>
{item.link ? (
<p className="text-gray-600">元の記事を読むには、上の「Read Original Article」ボタンをクリックしてください。</p>
) : (
<p className="text-gray-600">この記事の元のURLは利用できません。</p>
)}
</div>
) : null}
</div>
</div>
);
}