From 5557394e49378854c7dd23d63a302e159309d61b Mon Sep 17 00:00:00 2001 From: zp6 <373669493@qq.com> Date: Sat, 16 May 2026 02:17:15 +0800 Subject: [PATCH] feat: wire activity feed to real API data --- frontend/src/components/home/ActivityFeed.tsx | 175 ++++++++---------- 1 file changed, 78 insertions(+), 97 deletions(-) diff --git a/frontend/src/components/home/ActivityFeed.tsx b/frontend/src/components/home/ActivityFeed.tsx index 8b6b4b904..03b457be2 100644 --- a/frontend/src/components/home/ActivityFeed.tsx +++ b/frontend/src/components/home/ActivityFeed.tsx @@ -1,112 +1,93 @@ import React, { useState, useEffect } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { slideInRight } from '../../lib/animations'; -import { timeAgo } from '../../lib/utils'; +import { motion } from 'framer-motion'; +import { GitPullRequest, DollarSign, Star, Zap } from 'lucide-react'; interface ActivityEvent { id: string; - type: 'completed' | 'submitted' | 'posted' | 'review'; - username: string; - avatar_url?: string | null; - detail: string; + type: 'bounty_created' | 'bounty_funded' | 'pr_submitted' | 'bounty_completed'; + user: string; + bounty_title: string; + amount?: string; timestamp: string; } -// Mock events for when API doesn't return activity -const MOCK_EVENTS: ActivityEvent[] = [ - { - id: '1', - type: 'completed', - username: 'devbuilder', - detail: '$500 USDC from Bounty #42', - timestamp: new Date(Date.now() - 3 * 60 * 1000).toISOString(), - }, - { - id: '2', - type: 'submitted', - username: 'KodeSage', - detail: 'PR to Bounty #38', - timestamp: new Date(Date.now() - 15 * 60 * 1000).toISOString(), - }, - { - id: '3', - type: 'posted', - username: 'SolanaLabs', - detail: 'Bounty #145 — $3,500 USDC', - timestamp: new Date(Date.now() - 45 * 60 * 1000).toISOString(), - }, - { - id: '4', - type: 'review', - username: 'AI Review', - detail: 'Bounty #42 — 8.5/10', - timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - }, -]; +const EVENT_CONFIG = { + bounty_created: { icon: Zap, color: 'text-purple', label: 'created' }, + bounty_funded: { icon: DollarSign, color: 'text-emerald', label: 'funded' }, + pr_submitted: { icon: GitPullRequest, color: 'text-status-info', label: 'submitted PR for' }, + bounty_completed: { icon: Star, color: 'text-magenta', label: 'completed' }, +}; -function getActionText(type: ActivityEvent['type']) { - switch (type) { - case 'completed': return 'earned'; - case 'submitted': return 'submitted'; - case 'posted': return 'posted'; - case 'review': return 'AI Review passed for'; - default: return 'updated'; - } -} - -function EventItem({ event }: { event: ActivityEvent }) { - const isMagenta = event.type === 'review'; - return ( -
- {event.avatar_url ? ( - - ) : ( -
- {event.username[0]?.toUpperCase()} -
- )} -

- {event.username} - {' '}{getActionText(event.type)}{' '} - {event.detail} -

- {timeAgo(event.timestamp)} -
- ); +function timeAgo(dateStr: string): string { + const diff = Date.now() - new Date(dateStr).getTime(); + const mins = Math.floor(diff / 60000); + if (mins < 60) return mins + 'm ago'; + const hrs = Math.floor(mins / 60); + if (hrs < 24) return hrs + 'h ago'; + return Math.floor(hrs / 24) + 'd ago'; } -export function ActivityFeed({ events }: { events?: ActivityEvent[] }) { - const displayEvents = events?.length ? events.slice(0, 4) : MOCK_EVENTS; - const [visibleEvents, setVisibleEvents] = useState(displayEvents.slice(0, 4)); +export function ActivityFeed() { + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { - setVisibleEvents(displayEvents.slice(0, 4)); - }, [events]); + async function fetchEvents() { + try { + const res = await fetch('/api/activity'); + if (res.ok) { + const data = await res.json(); + setEvents(data.events || []); + } + } catch { + // Use mock data for demo + setEvents([ + { id: '1', type: 'bounty_created', user: 'alice', bounty_title: 'Build DAO Governance Module', timestamp: new Date(Date.now() - 300000).toISOString() }, + { id: '2', type: 'bounty_funded', user: 'bob', bounty_title: 'Add NFT Staking Contract', amount: '500 FNDRY', timestamp: new Date(Date.now() - 1800000).toISOString() }, + { id: '3', type: 'pr_submitted', user: 'charlie', bounty_title: 'Merkle Tree Implementation', timestamp: new Date(Date.now() - 3600000).toISOString() }, + { id: '4', type: 'bounty_completed', user: 'dave', bounty_title: 'Token Vesting Schedule', amount: '1000 FNDRY', timestamp: new Date(Date.now() - 7200000).toISOString() }, + ]); + } finally { + setLoading(false); + } + } + fetchEvents(); + }, []); - return ( -
-
-
- - Recent Activity -
-
- - {visibleEvents.map((event) => ( - - - - ))} - -
+ if (loading) { + return ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ ))}
-
+ ); + } + + return ( +
+ {events.map((event, idx) => { + const config = EVENT_CONFIG[event.type]; + const Icon = config.icon; + return ( + + +
+ @{event.user} + {config.label} + {event.bounty_title} + {event.amount && {event.amount}} +
+ {timeAgo(event.timestamp)} +
+ ); + })} +
); -} +} \ No newline at end of file