| // src/components/dashboard/widgets/RecentActivityWidget.tsx | |
| import { Activity, FileText, Loader2 } from "lucide-react"; | |
| import { useEffect, useState, useCallback } from "react"; | |
| import api from "@/lib/api"; | |
| import { Link } from "react-router-dom"; | |
| interface ActivityItem { | |
| id: string; | |
| type: "article"; | |
| content: string; | |
| link: string; | |
| timestamp: string; | |
| } | |
| export function RecentActivityWidget() { | |
| const [activities, setActivities] = useState<ActivityItem[]>([]); | |
| const [loading, setLoading] = useState(false); | |
| const fetchActivities = useCallback(async () => { | |
| if (loading) return; | |
| setLoading(true); | |
| try { | |
| const res = await api.get("/dashboard/recent-activity?limit=10"); | |
| const items = res.data.activity || []; | |
| const formatted: ActivityItem[] = items.map((item: any) => ({ | |
| id: String(item.id), | |
| type: "article", | |
| content: `Published "${item.title}" (${item.views || 0} views, ${item.likes || 0} likes)`, | |
| link: `/articles/${item.id}`, | |
| timestamp: item.createdAt || new Date().toISOString(), | |
| })); | |
| setActivities(formatted); | |
| } catch (err) { | |
| console.error("Failed to load activities:", err); | |
| setActivities([]); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [loading]); | |
| useEffect(() => { | |
| void fetchActivities(); | |
| }, []); | |
| return ( | |
| <div className="space-y-3 bg-white/5 dark:bg-black/20 backdrop-blur-md border border-white/10 dark:border-white/5 rounded-2xl p-4"> | |
| <div className="flex items-center gap-2"> | |
| <Activity className="w-4 h-4 text-primary" /> | |
| <h3 className="text-sm font-semibold text-gray-200">Recent Activity</h3> | |
| </div> | |
| <div className="space-y-2 max-h-64 overflow-y-auto"> | |
| {activities.length > 0 ? ( | |
| activities.map((a) => ( | |
| <Link | |
| key={a.id} | |
| to={a.link} | |
| className="flex items-center gap-3 text-xs p-2 rounded-lg bg-white/5 border border-white/5 hover:border-white/15 hover:bg-white/10 transition-all duration-300" | |
| > | |
| <FileText className="w-3.5 h-3.5 text-gray-400" /> | |
| <span className="flex-1 text-gray-300 font-medium truncate">{a.content}</span> | |
| <span className="text-[10px] text-gray-500">{new Date(a.timestamp).toLocaleDateString()}</span> | |
| </Link> | |
| )) | |
| ) : ( | |
| !loading && <p className="text-xs text-gray-500 py-4 text-center">No recent activity</p> | |
| )} | |
| {loading && ( | |
| <div className="flex justify-center py-4"> | |
| <Loader2 className="w-4 h-4 animate-spin text-gray-400" /> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 2.7 kB
- Xet hash:
- a1d6f23ad28e1521ff33403a6ab2aefc40b95a0b8e415d56be14d65e1a0e114c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.