duelfi_web/src/components/Activities.tsx
2025-05-30 18:21:01 +05:30

194 lines
7.3 KiB
TypeScript

import Image from "next/image";
import { useState, useEffect } from "react";
import { API_BASE_URL } from "@/data/shared";
import { PlusCircleIcon, XCircleIcon, UserPlusIcon, TrophyIcon } from '@heroicons/react/24/outline';
interface Activity {
id: string;
type: string;
owner_id: string;
joiner_id: string | null;
game: string;
amount: string;
comments: string | null;
time: string;
}
interface UserData {
id: string;
username: string;
bio: string;
x_profile_url: string;
}
export default function Activities() {
const [activities, setActivities] = useState<Activity[]>([]);
const [userData, setUserData] = useState<Record<string, UserData>>({});
const [loading, setLoading] = useState(true);
const [failedImages, setFailedImages] = useState<Set<string>>(new Set());
const defaultPFP = '/duelfiassets/PFP (1).png';
useEffect(() => {
const fetchActivities = async () => {
try {
const response = await fetch("/api/v1/get_activities.php");
const data = await response.json();
setActivities(data);
// Fetch user data for all unique user IDs
const userIds = new Set<string>();
data.forEach((activity: Activity) => {
if (activity.owner_id) userIds.add(activity.owner_id);
if (activity.joiner_id) userIds.add(activity.joiner_id);
});
const userDataPromises = Array.from(userIds).map(async (userId) => {
try {
console.log('Fetching user data for ID:', userId);
const response = await fetch(`/api/v1/get_user_by_id.php?id=${userId}`);
const data = await response.json();
console.log('Received user data:', data);
return [userId, data];
} catch (error) {
console.error(`Error fetching user data for ${userId}:`, error);
return [userId, null];
}
});
const userDataResults = await Promise.all(userDataPromises);
console.log('All user data results:', userDataResults);
const userDataMap = Object.fromEntries(userDataResults);
console.log('Final user data map:', userDataMap);
setUserData(userDataMap);
} catch (error) {
console.error("Error fetching activities:", error);
} finally {
setLoading(false);
}
};
// Initial fetch
fetchActivities();
// Set up interval for periodic updates
const intervalId = setInterval(fetchActivities, 5000);
// Cleanup function to clear the interval when component unmounts
return () => clearInterval(intervalId);
}, []);
const formatActivityMessage = (activity: Activity) => {
const ownerUsername = userData[activity.owner_id]?.username || "Anonymous";
const joinerUsername = activity.joiner_id ? (userData[activity.joiner_id]?.username || "Anonymous") : null;
const amount = parseFloat(activity.amount);
const formattedAmount = amount > 0 ? `${amount} SOL` : "";
switch (activity.type) {
case "create":
return <><span className="font-bold">{ownerUsername}</span> created a <span className="font-bold">{activity.game}</span> game with a {formattedAmount} entry fee.</>;
case "close":
return <><span className="font-bold">{ownerUsername}</span> cancelled their <span className="font-bold">{activity.game}</span> game.</>;
case "join":
return <><span className="font-bold">{joinerUsername}</span> joined <span className="font-bold">{activity.game}</span> with a {formattedAmount} entry fee.</>;
case "won":
return <><span className="font-bold">{ownerUsername}</span> won {formattedAmount} in <span className="font-bold">{activity.game}</span>!</>;
default:
return "Unknown activity";
}
};
const formatTimeAgo = (timestamp: string) => {
const date = new Date(timestamp + 'Z');
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const diffInMinutes = Math.floor(diffInSeconds / 60);
const diffInHours = Math.floor(diffInMinutes / 60);
const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays > 0) {
return date.toLocaleString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
});
}
if (diffInHours > 0) {
return `${diffInHours} hour${diffInHours === 1 ? '' : 's'} ago`;
}
if (diffInMinutes > 0) {
return `${diffInMinutes} minute${diffInMinutes === 1 ? '' : 's'} ago`;
}
return "less than 1 minute ago";
};
if (loading) {
return (
<div className="container mx-auto max-w-screen-xl px-3">
<div className="bg-[rgb(30,30,30)] rounded-xl p-6 shadow-lg">
<h2 className="text-2xl font-bold text-white mb-4">Recent Activities</h2>
<div className="flex justify-center items-center h-40">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-[rgb(248,144,22)]"></div>
</div>
</div>
</div>
);
}
return (
<div className="container mx-auto max-w-screen-xl px-3 md:px-40">
<div className="bg-[rgb(30,30,30)] rounded-xl p-6 shadow-lg">
<h2 className="text-2xl font-bold text-white mb-4">Recent Activities</h2>
<div className="space-y-4">
{[...activities].sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime()).map((activity) => {
const userId = activity.type === "join" ? activity.joiner_id : activity.owner_id;
if (!userId) return null;
let profileUrl = userData[userId]?.x_profile_url || `${API_BASE_URL}profile_pics/${userId}.jpg`;
if (failedImages.has(profileUrl)) {
profileUrl = defaultPFP;
}
return (
<div
key={activity.id}
className="flex items-center gap-4 p-3 px-6 rounded-lg bg-[rgb(10,10,10)] border border-[rgb(30,30,30)] hover:border-[rgb(248,144,22)] transition-all duration-300"
>
<Image
src={profileUrl}
alt="Profile"
width={40}
height={40}
className="rounded-full border border-gray-700 object-cover"
onError={(e) => {
// @ts-expect-error - Type mismatch expected
e.target.src = defaultPFP;
setFailedImages(prev => new Set(prev).add(profileUrl));
}}
/>
<div className="flex-1">
<p className="text-white">{formatActivityMessage(activity)}</p>
<p className="text-sm text-gray-400">
{formatTimeAgo(activity.time)}
</p>
</div>
<div>
{activity.type === "create" && <PlusCircleIcon className="w-6 h-6 text-green-500" />}
{activity.type === "close" && <XCircleIcon className="w-6 h-6 text-red-500" />}
{activity.type === "join" && <UserPlusIcon className="w-6 h-6 text-[rgb(248,144,22)]" />}
{activity.type === "won" && <TrophyIcon className="w-6 h-6 text-[rgb(248,144,22)]" />}
</div>
</div>
);
})}
</div>
</div>
</div>
);
}