"use client";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Music,
Code,
Monitor,
MessageSquare,
Send,
X,
Loader2,
Github,
Tv,
Gamepad2,
Coffee,
Headphones,
Terminal,
Sparkles,
ExternalLink,
Activity,
Waves,
Zap,
} from "lucide-react";
interface ActivityData {
activity: {
type:
| "coding"
| "listening"
| "watching"
| "gaming"
| "reading"
| "running";
details: string;
timestamp: string;
project?: string;
language?: string;
repo?: string;
link?: string;
} | null;
music: {
isPlaying: boolean;
track: string;
artist: string;
album?: string;
platform: "spotify" | "apple";
progress?: number;
albumArt?: string;
spotifyUrl?: string;
} | null;
watching: {
title: string;
platform: "youtube" | "netflix" | "twitch";
type: "video" | "stream" | "movie" | "series";
} | null;
gaming: {
game: string;
platform: "steam" | "playstation" | "xbox";
status: "playing" | "idle";
} | null;
status: {
mood: string;
customMessage?: string;
} | null;
}
// Matrix rain effect for coding
const MatrixRain = () => {
const chars = "01";
return (
{[...Array(15)].map((_, i) => (
{[...Array(20)].map((_, j) => (
{chars[Math.floor(Math.random() * chars.length)]}
))}
))}
);
};
// Sound waves for music
const SoundWaves = () => {
return (
{[...Array(5)].map((_, i) => (
))}
);
};
// Running animation
const RunningAnimation = () => {
return (
);
};
// Gaming particles
const GamingParticles = () => {
return (
{[...Array(10)].map((_, i) => (
))}
);
};
// TV scan lines
const TVScanLines = () => {
return (
);
};
const activityIcons = {
coding: Terminal,
listening: Headphones,
watching: Tv,
gaming: Gamepad2,
reading: Coffee,
running: Activity,
};
const activityColors = {
coding: {
bg: "from-liquid-mint/20 to-liquid-sky/20",
border: "border-liquid-mint/40",
text: "text-liquid-mint",
pulse: "bg-green-500",
},
listening: {
bg: "from-liquid-rose/20 to-liquid-coral/20",
border: "border-liquid-rose/40",
text: "text-liquid-rose",
pulse: "bg-red-500",
},
watching: {
bg: "from-liquid-lavender/20 to-liquid-pink/20",
border: "border-liquid-lavender/40",
text: "text-liquid-lavender",
pulse: "bg-purple-500",
},
gaming: {
bg: "from-liquid-peach/20 to-liquid-yellow/20",
border: "border-liquid-peach/40",
text: "text-liquid-peach",
pulse: "bg-orange-500",
},
reading: {
bg: "from-liquid-teal/20 to-liquid-lime/20",
border: "border-liquid-teal/40",
text: "text-liquid-teal",
pulse: "bg-teal-500",
},
running: {
bg: "from-liquid-lime/20 to-liquid-mint/20",
border: "border-liquid-lime/40",
text: "text-liquid-lime",
pulse: "bg-lime-500",
},
};
export const ActivityFeed = () => {
const [data, setData] = useState(null);
const [showChat, setShowChat] = useState(false);
const [chatMessage, setChatMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [chatHistory, setChatHistory] = useState<
{
role: "user" | "ai";
text: string;
timestamp: number;
}[]
>([
{
role: "ai",
text: "Hi! I'm Dennis's AI assistant. Ask me anything about his work, skills, or projects! 🚀",
timestamp: Date.now(),
},
]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("/api/n8n/status");
if (res.ok) {
const json = await res.json();
setData(json);
}
} catch (e) {
console.error("Failed to fetch activity", e);
}
};
fetchData();
const interval = setInterval(fetchData, 30000); // Poll every 30s
return () => clearInterval(interval);
}, []);
const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!chatMessage.trim() || isLoading) return;
const userMsg = chatMessage;
setChatHistory((prev) => [
...prev,
{ role: "user", text: userMsg, timestamp: Date.now() },
]);
setChatMessage("");
setIsLoading(true);
try {
const response = await fetch("/api/n8n/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: userMsg }),
});
if (response.ok) {
const data = await response.json();
setChatHistory((prev) => [
...prev,
{ role: "ai", text: data.reply, timestamp: Date.now() },
]);
} else {
throw new Error("Chat API failed");
}
} catch (error) {
console.error("Chat error:", error);
setChatHistory((prev) => [
...prev,
{
role: "ai",
text: "Sorry, I encountered an error. Please try again later.",
timestamp: Date.now(),
},
]);
} finally {
setIsLoading(false);
}
};
const renderActivityBubble = () => {
if (!data?.activity) return null;
const { type, details, project, language, link } = data.activity;
const Icon = activityIcons[type];
const colors = activityColors[type];
return (
{/* Background Animation based on activity type */}
{type === "coding" && }
{type === "running" && }
{type === "gaming" && }
{type === "watching" && }
{type}
{details}
{project && (
{project}
)}
{language && (
{language}
)}
{link && (
View
)}
);
};
const renderMusicBubble = () => {
if (!data?.music?.isPlaying) return null;
const { track, artist, album, progress, albumArt, spotifyUrl } = data.music;
return (
{/* Animated sound waves background */}
{albumArt && (
)}
Now Playing
{track}
{artist}
{progress !== undefined && (
)}
{spotifyUrl && (
Listen with me
)}
);
};
const renderStatusBubble = () => {
if (!data?.status) return null;
const { mood, customMessage } = data.status;
return (
{mood}
{customMessage && (
{customMessage}
)}
);
};
return (
{/* Chat Window */}
{showChat && (
AI Assistant
{chatHistory.map((msg, i) => (
{msg.text}
))}
{isLoading && (
Thinking...
)}
)}
{/* Activity Bubbles */}
{renderActivityBubble()}
{renderMusicBubble()}
{renderStatusBubble()}
{/* Chat Toggle Button with Notification Indicator */}
setShowChat(!showChat)}
className="relative bg-stone-900 text-white rounded-full p-4 shadow-xl hover:bg-stone-950 transition-all duration-500 ease-out"
title="Ask me anything about Dennis"
>
{!showChat && (
)}
);
};