"use client"; import React, { useEffect, useState } from "react"; import Image from "next/image"; import { motion, AnimatePresence } from "framer-motion"; import { Code2, Disc3, Gamepad2, Zap, Clock, ChevronDown, ChevronUp, Activity, X, } from "lucide-react"; // Types matching your n8n output interface StatusData { status: { text: string; color: string; }; music: { isPlaying: boolean; track: string; artist: string; album: string; albumArt: string; url: string; } | null; gaming: { isPlaying: boolean; name: string; image: string | null; state?: string; details?: string; } | null; coding: { isActive: boolean; project?: string; file?: string; language?: string; stats?: { time: string; topLang: string; topProject: string; }; } | null; } export default function ActivityFeed() { const [data, setData] = useState(null); const [isExpanded, setIsExpanded] = useState(true); const [isMinimized, setIsMinimized] = useState(false); const [hasActivity, setHasActivity] = useState(false); const [quote, setQuote] = useState<{ content: string; author: string; } | null>(null); // Fetch data every 30 seconds (optimized to match server cache) useEffect(() => { const fetchData = async () => { try { // Add timestamp to prevent aggressive caching but respect server cache const res = await fetch("/api/n8n/status", { cache: "default", }); if (!res.ok) return; let json = await res.json(); console.log("ActivityFeed data (raw):", json); // Handle array response if API returns it wrapped if (Array.isArray(json)) { json = json[0] || null; } console.log("ActivityFeed data (processed):", json); setData(json); // Check if there's any active activity const hasActiveActivity = json.coding?.isActive || json.gaming?.isPlaying || json.music?.isPlaying; console.log("Has activity:", hasActiveActivity, { coding: json.coding?.isActive, gaming: json.gaming?.isPlaying, music: json.music?.isPlaying, }); setHasActivity(hasActiveActivity); // Auto-expand if there's new activity and not minimized if (hasActiveActivity && !isMinimized) { setIsExpanded(true); } } catch (e) { console.error("Failed to fetch activity", e); } }; fetchData(); // Optimized: Poll every 30 seconds instead of 10 to reduce server load // The n8n API already has 30s cache, so faster polling doesn't help const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, [isMinimized]); // Fetch nerdy quote when idle useEffect(() => { if (!hasActivity && !quote) { const techQuotes = [ { content: "Simplicity is the soul of efficiency.", author: "Austin Freeman", }, { content: "Talk is cheap. Show me the code.", author: "Linus Torvalds", }, { content: "Code is like humor. When you have to explain it, it’s bad.", author: "Cory House", }, { content: "Fix the cause, not the symptom.", author: "Steve Maguire", }, { content: "Optimism is an occupational hazard of programming: feedback is the treatment.", author: "Kent Beck", }, { content: "Make it work, make it right, make it fast.", author: "Kent Beck", }, { content: "First, solve the problem. Then, write the code.", author: "John Johnson", }, { content: "Experience is the name everyone gives to their mistakes.", author: "Oscar Wilde", }, { content: "In order to be irreplaceable, one must always be different.", author: "Coco Chanel", }, { content: "Java is to JavaScript what car is to Carpet.", author: "Chris Heilmann", }, { content: "Knowledge is power.", author: "Francis Bacon", }, { content: "Before software can be reusable it first has to be usable.", author: "Ralph Johnson", }, { content: "It’s not a bug – it’s an undocumented feature.", author: "Anonymous", }, { content: "Deleted code is debugged code.", author: "Jeff Sickel", }, { content: "Walking on water and developing software from a specification are easy if both are frozen.", author: "Edward V. Berard", }, { content: "If debugging is the process of removing software bugs, then programming must be the process of putting them in.", author: "Edsger Dijkstra", }, { content: "A user interface is like a joke. If you have to explain it, it’s not that good.", author: "Martin Leblanc", }, { content: "The best error message is the one that never shows up.", author: "Thomas Fuchs", }, { content: "The most damaging phrase in the language is.. it's always been done this way", author: "Grace Hopper", }, { content: "Stay hungry, stay foolish.", author: "Steve Jobs", }, ]; setQuote(techQuotes[Math.floor(Math.random() * techQuotes.length)]); } }, [hasActivity, quote]); if (!data) return null; const activeCount = [ data.coding?.isActive, data.gaming?.isPlaying, data.music?.isPlaying, ].filter(Boolean).length; // If minimized, show only a small indicator if (isMinimized) { return ( setIsMinimized(false)} className="fixed bottom-4 right-4 md:bottom-6 md:right-6 z-40 pointer-events-auto bg-black/80 backdrop-blur-xl border border-white/10 p-3 rounded-full shadow-2xl hover:scale-110 transition-transform" > {activeCount > 0 && ( {activeCount} )} ); } return (
{/* Main Container */} {/* Header - Always Visible - Changed from button to div to fix nesting error */}
setIsExpanded(!isExpanded)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setIsExpanded(!isExpanded); } }} className="w-full px-4 py-3 flex items-center justify-between hover:bg-white/5 transition-colors cursor-pointer" >
{hasActivity && ( )}

Live Activity

{activeCount > 0 ? `${activeCount} active now` : "No activity"}

{ e.stopPropagation(); setIsMinimized(true); }} className="p-1 hover:bg-white/10 rounded-lg transition-colors cursor-pointer" role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.stopPropagation(); setIsMinimized(true); } }} >
{isExpanded ? ( ) : ( )}
{/* Expandable Content */} {isExpanded && (
{/* CODING CARD */} {data.coding && ( {/* "RIGHT NOW" Indicator */} {data.coding.isActive && (
Right Now
)}
{data.coding.isActive ? ( ) : ( )}
{data.coding.isActive ? ( <>
Coding Live

{data.coding.project || "Active Project"}

{data.coding.file || "Writing code..."}

{data.coding.language && (
{data.coding.language}
)} ) : ( <>
Today's Coding

{data.coding.stats?.time || "0m"}

{data.coding.stats?.topLang || "No activity yet"}

)}
)} {/* GAMING CARD */} {data.gaming?.isPlaying && ( {/* "RIGHT NOW" Indicator */}
Right Now
{/* Background Glow */}
{data.gaming.image ? ( Game ) : (
)}
Gaming Now

{data.gaming.name}

{data.gaming.details || data.gaming.state || "Playing..."}

)} {/* MUSIC CARD */} {data.music?.isPlaying && ( {/* "RIGHT NOW" Indicator */}
Right Now
Album
Spotify {/* Equalizer Animation */}
{[1, 2, 3].map((i) => ( ))}

{data.music.track}

{data.music.artist}

)} {/* Quote of the Day (when idle) */} {!hasActivity && quote && (

Quote of the moment

"{quote.content}"

— {quote.author}

)} {/* Status Footer */}
{data.status.text === "dnd" ? "Do Not Disturb" : data.status.text}
Updates every 30s
)}
); }