feat: add activity feed and background effects
- Implemented ActivityFeed component to display real-time user activity including coding, music, and chat interactions. - Added GooFilter and BackgroundBlobs components for enhanced visual effects. - Updated layout to include new components and ensure proper stacking context. - Enhanced Tailwind CSS configuration with new color and font settings. - Created API route to mock activity data from n8n. - Refactored main page structure to streamline component rendering.
This commit is contained in:
160
app/components/ActivityFeed.tsx
Normal file
160
app/components/ActivityFeed.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Music, Code, Monitor, MessageSquare, Send, X } from 'lucide-react';
|
||||
|
||||
interface ActivityData {
|
||||
activity: {
|
||||
type: 'coding' | 'listening' | 'watching';
|
||||
details: string;
|
||||
timestamp: string;
|
||||
} | null;
|
||||
music: {
|
||||
isPlaying: boolean;
|
||||
track: string;
|
||||
artist: string;
|
||||
platform: 'spotify' | 'apple';
|
||||
} | null;
|
||||
watching: {
|
||||
title: string;
|
||||
platform: 'youtube' | 'netflix';
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const ActivityFeed = () => {
|
||||
const [data, setData] = useState<ActivityData | null>(null);
|
||||
const [showChat, setShowChat] = useState(false);
|
||||
const [chatMessage, setChatMessage] = useState('');
|
||||
const [chatHistory, setChatHistory] = useState<{
|
||||
role: 'user' | 'ai';
|
||||
text: string;
|
||||
}[]>([
|
||||
{ role: 'ai', text: 'Hi! I am Dennis\'s AI assistant. Ask me anything about him!' }
|
||||
]);
|
||||
|
||||
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 = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!chatMessage.trim()) return;
|
||||
|
||||
const userMsg = chatMessage;
|
||||
setChatHistory(prev => [...prev, { role: 'user', text: userMsg }]);
|
||||
setChatMessage('');
|
||||
|
||||
// Mock AI response - would connect to n8n webhook
|
||||
setTimeout(() => {
|
||||
setChatHistory(prev => [...prev, { role: 'ai', text: `That's a great question about "${userMsg}"! I'll ask Dennis to add more info about that.` }]);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-6 right-6 z-40 flex flex-col items-end gap-4 pointer-events-none">
|
||||
|
||||
{/* Chat Window */}
|
||||
<AnimatePresence>
|
||||
{showChat && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
className="pointer-events-auto bg-white/80 backdrop-blur-xl border border-white/60 shadow-2xl rounded-2xl w-80 overflow-hidden"
|
||||
>
|
||||
<div className="p-4 border-b border-white/50 flex justify-between items-center bg-white/40">
|
||||
<span className="font-semibold text-stone-800 flex items-center gap-2">
|
||||
<MessageSquare size={16} />
|
||||
Ask me anything
|
||||
</span>
|
||||
<button onClick={() => setShowChat(false)} className="text-stone-500 hover:text-stone-800">
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-64 overflow-y-auto p-4 space-y-3 bg-white/20">
|
||||
{chatHistory.map((msg, i) => (
|
||||
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
<div className={`max-w-[85%] p-3 rounded-xl text-sm ${
|
||||
msg.role === 'user'
|
||||
? 'bg-stone-800 text-white rounded-tr-none'
|
||||
: 'bg-white text-stone-800 shadow-sm rounded-tl-none'
|
||||
}`}>
|
||||
{msg.text}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<form onSubmit={handleSendMessage} className="p-3 border-t border-white/50 bg-white/40 flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={chatMessage}
|
||||
onChange={(e) => setChatMessage(e.target.value)}
|
||||
placeholder="Type a message..."
|
||||
className="flex-1 bg-white/60 border border-white/60 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-stone-400"
|
||||
/>
|
||||
<button type="submit" className="p-2 bg-stone-800 text-white rounded-lg hover:bg-black transition-colors">
|
||||
<Send size={16} />
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Activity Bubbles */}
|
||||
<div className="flex flex-col items-end gap-2 pointer-events-auto">
|
||||
{data.activity?.type === 'coding' && (
|
||||
<motion.div
|
||||
initial={{ x: 50, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
className="bg-white/80 backdrop-blur-md border border-white/60 shadow-lg rounded-full px-4 py-2 flex items-center gap-2 text-sm text-stone-700"
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
||||
</span>
|
||||
<Code size={14} />
|
||||
<span>Working on <strong>{data.activity.details}</strong></span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{data.music?.isPlaying && (
|
||||
<motion.div
|
||||
initial={{ x: 50, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-white/80 backdrop-blur-md border border-white/60 shadow-lg rounded-full px-4 py-2 flex items-center gap-2 text-sm text-stone-700"
|
||||
>
|
||||
<Music size={14} className="animate-pulse text-liquid-rose" />
|
||||
<span>Listening to <strong>{data.music.track}</strong></span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Chat Toggle Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => setShowChat(!showChat)}
|
||||
className="bg-stone-900 text-white rounded-full p-4 shadow-xl hover:bg-black transition-all"
|
||||
>
|
||||
<MessageSquare size={20} />
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user