'use client' import Link from 'next/link' import { useSearchParams } from 'next/navigation' import { ArrowLeft } from 'lucide-react' import { Suspense, useCallback, useEffect, useRef, useState } from 'react' import { SentimentalityBar } from '@/components/widgets/SentimentalityBar' import { getNewsFeed, refreshNewsFeed, formatPublishedDate, type NewsItem } from '@/lib/newsApi' // ── Pull-to-refresh constants ────────────────────────────────────────────── const PULL_THRESHOLD = 72 // px of pull before triggering refresh const PULL_MAX = 100 // px cap so it doesn't over-stretch function PullIndicator({ pullY, refreshing, toast }: { pullY: number refreshing: boolean toast: 'refreshed' | 'error' | null }) { const progress = Math.min(pullY / PULL_THRESHOLD, 1) const circumference = 2 * Math.PI * 10 return (
0 ? 'none' : 'transform 240ms ease, top 240ms ease', height: 56, }} >
{refreshing ? ( // Spinning ring ) : ( // Progress arc that fills as user pulls )}
{/* Toast message */} {toast && (
{toast === 'refreshed' ? 'Refreshed' : 'Unable to refresh headlines'}
)}
) } function InterestDetailsContent() { const searchParams = useSearchParams() const topic = searchParams.get('topic') || 'Topic' const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [limit, setLimit] = useState(10) // Pull-to-refresh state const [pullY, setPullY] = useState(0) const [refreshing, setRefreshing] = useState(false) const [toast, setToast] = useState<'refreshed' | 'error' | null>(null) const scrollRef = useRef(null) const touchStartY = useRef(0) const isPulling = useRef(false) const isRefreshing = useRef(false) // guards against duplicate calls const toastTimer = useRef | null>(null) // ── Fetch headlines ────────────────────────────────────────────────────── const fetchHeadlines = useCallback(() => { setLoading(true) setError(null) getNewsFeed(topic, 'frontend', limit) .then(setItems) .catch((err) => setError(err.message)) .finally(() => setLoading(false)) }, [topic, limit]) useEffect(() => { fetchHeadlines() }, [fetchHeadlines]) // ── Clear toast on unmount ─────────────────────────────────────────────── useEffect(() => () => { if (toastTimer.current) clearTimeout(toastTimer.current) }, []) // ── Show toast then hide after 2.5s ───────────────────────────────────── const showToast = (type: 'refreshed' | 'error') => { setToast(type) if (toastTimer.current) clearTimeout(toastTimer.current) toastTimer.current = setTimeout(() => setToast(null), 2500) } // ── Trigger refresh ────────────────────────────────────────────────────── const triggerRefresh = useCallback(async () => { if (isRefreshing.current) return isRefreshing.current = true setRefreshing(true) setPullY(0) try { const ok = await refreshNewsFeed('frontend') if (ok) { showToast('refreshed') fetchHeadlines() } else { showToast('error') } } catch { showToast('error') } finally { setRefreshing(false) isRefreshing.current = false } }, [fetchHeadlines]) // ── Touch handlers ─────────────────────────────────────────────────────── const handleTouchStart = useCallback((e: React.TouchEvent) => { if (isRefreshing.current) return const el = scrollRef.current if (el && el.scrollTop === 0) { touchStartY.current = e.touches[0].clientY isPulling.current = true } }, []) const handleTouchMove = useCallback((e: React.TouchEvent) => { if (!isPulling.current || isRefreshing.current) return const dy = e.touches[0].clientY - touchStartY.current if (dy <= 0) { setPullY(0); return } // Rubber-band decay const capped = Math.min(dy * 0.45, PULL_MAX) setPullY(capped) }, []) const handleTouchEnd = useCallback(() => { if (!isPulling.current) return isPulling.current = false if (pullY >= PULL_THRESHOLD) { triggerRefresh() } else { setPullY(0) } }, [pullY, triggerRefresh]) return (
{/* Header */}

Top headlines for {topic}

{/* Scroll area with pull-to-refresh */}
{/* Push content down while pulling or refreshing */}
{loading && (
Loading headlines...
)} {error && (
{error}
)} {!loading && !error && items.length === 0 && (
No headlines found for "{topic}"
)} {!loading && !error && items.length > 0 && ( )}
) } export default function InterestDetails() { return (
Loading...
}>
) }