import { useCallback, useEffect, useRef, useState } from 'react'; import { Bell } from 'lucide-react'; import { FullLogo } from './full-logo'; import { IconButton } from '@/components/ui/icon-button'; import { Button } from '@/components/ui/button'; import { NewsDrawer, getLastSeenFeedVersion, type NewsItem } from './news-drawer'; import { useFocusTrap } from '../hooks/use-focus-trap'; import { useScrollLock } from '../hooks/use-scroll-lock'; import { __ } from '../lib/i18n'; import { NEWS_FEED_CONSENT_EVENT, persistNewsFeedConsent, readNewsFeedConsent, updatePrivacySettings, } from '../lib/privacy-settings'; interface TopBarProps { pluginUrl?: string; } interface NewsApiResponse { success: boolean; configured?: boolean; consent_required?: boolean; feedVersion?: string; lastUpdated?: string; items?: NewsItem[]; error?: string; message?: string; } function getRestConfig(): { root: string; nonce: string } | null { const w = window as { wpApiSettings?: { root: string; nonce: string } }; if (w.wpApiSettings?.root && w.wpApiSettings?.nonce) { return { root: w.wpApiSettings.root.replace(/\/$/, ''), nonce: w.wpApiSettings.nonce }; } return null; } function readTopBarBadge(): { label: string; href: string } | null { const badge = window.llmagnetTopBarBadge; if (!badge || typeof badge.label !== 'string' || badge.label.trim() === '') { return null; } if (typeof badge.href !== 'string') { return null; } return { label: badge.label, href: badge.href }; } export function TopBar({ pluginUrl }: TopBarProps) { const topBarBadge = readTopBarBadge(); const finalPluginUrl = pluginUrl || (window as any).llmagnetDashboardData?.pluginUrl || (window as any).llmagnetBotAnalyticsData?.pluginUrl || (window as any).llmagnetPagesData?.pluginUrl || (window as any).llmagnetReportsData?.pluginUrl || (window as any).llmagnetAnalyticsData?.pluginUrl || ''; const [newsOpen, setNewsOpen] = useState(false); const [consentOpen, setConsentOpen] = useState(false); const [consentSaving, setConsentSaving] = useState(false); const [hasConsent, setHasConsent] = useState(readNewsFeedConsent); const [newsLoading, setNewsLoading] = useState(false); const [newsError, setNewsError] = useState(null); const [newsItems, setNewsItems] = useState([]); const [feedVersion, setFeedVersion] = useState(''); const [lastUpdated, setLastUpdated] = useState(''); const [configured, setConfigured] = useState(false); const [hasUnread, setHasUnread] = useState(false); const fetchNews = useCallback(async () => { const cfg = getRestConfig(); if (!cfg) { setNewsError(null); setConfigured(false); setNewsItems([]); setFeedVersion(''); setLastUpdated(''); setHasUnread(false); return; } setNewsLoading(true); setNewsError(null); try { const url = `${cfg.root}/llm-analytics/v1/news`; const res = await fetch(url, { headers: { 'X-WP-Nonce': cfg.nonce }, credentials: 'same-origin', }); const data = (await res.json()) as NewsApiResponse; if (data.consent_required) { setHasConsent(false); persistNewsFeedConsent(false); setNewsItems([]); setHasUnread(false); return; } if (!data.success) { setNewsError(data.error || __('Failed to load news', 'llmagnet-llm-txt-generator')); setNewsItems([]); setFeedVersion(''); setLastUpdated(''); setHasUnread(false); return; } setConfigured(!!data.configured); const fv = data.feedVersion || ''; const items = data.items || []; setFeedVersion(fv); setLastUpdated(data.lastUpdated || ''); setNewsItems(items); const last = getLastSeenFeedVersion(); setHasUnread(!!fv && fv !== last && items.length > 0); } catch (e: unknown) { const msg = e instanceof Error ? e.message : __('Network error', 'llmagnet-llm-txt-generator'); setNewsError(msg); setHasUnread(false); } finally { setNewsLoading(false); } }, []); useEffect(() => { if (hasConsent) { fetchNews(); } }, [hasConsent, fetchNews]); useEffect(() => { const onConsent = (event: Event) => { const enabled = Boolean((event as CustomEvent<{ enabled?: boolean }>).detail?.enabled); setHasConsent(enabled); if (!enabled) { setNewsItems([]); setHasUnread(false); } }; window.addEventListener(NEWS_FEED_CONSENT_EVENT, onConsent); return () => window.removeEventListener(NEWS_FEED_CONSENT_EVENT, onConsent); }, []); const handleMarkSeen = useCallback(() => { setHasUnread(false); }, []); const handleBellClick = useCallback(() => { if (!hasConsent) { setConsentOpen(true); return; } setNewsOpen(true); }, [hasConsent]); const handleApproveFeed = useCallback(async () => { setConsentSaving(true); try { const result = await updatePrivacySettings({ news_feed_enabled: true }); const enabled = Boolean(result.news_feed_enabled); setHasConsent(enabled); persistNewsFeedConsent(enabled); setConsentOpen(false); if (enabled) { setNewsOpen(true); } } catch { setNewsError(__('Could not save your choice. Please try again.', 'llmagnet-llm-txt-generator')); } finally { setConsentSaving(false); } }, []); return (
{topBarBadge && ( topBarBadge.href ? ( {topBarBadge.label} ) : ( {topBarBadge.label} ) )}
setConsentOpen(false)} /> setNewsOpen(false)} items={newsItems} loading={newsLoading} error={newsError} feedVersion={feedVersion} lastUpdated={lastUpdated} configured={configured} onMarkSeen={handleMarkSeen} />
); } function NewsFeedConsentDialog({ open, busy, onApprove, onCancel, }: { open: boolean; busy: boolean; onApprove: () => void; onCancel: () => void; }) { const dialogRef = useRef(null); useFocusTrap(dialogRef, { active: open, onEscape: onCancel }); useScrollLock(open); if (!open) { return null; } return (
{ if (e.target === e.currentTarget && !busy) { onCancel(); } }} >
e.stopPropagation()} >

{__( 'We load product news and notifications from an external LLMagnet feed. This is a server-side request from your WordPress site. No site URL or admin email is sent.', 'llmagnet-llm-txt-generator', )}

{__( 'Approve to load these updates. We will remember this choice and will not ask again.', 'llmagnet-llm-txt-generator', )}

); }