diff --git a/public/duelfiassets/believe.svg b/public/duelfiassets/believe.svg new file mode 100644 index 0000000..bcdd761 --- /dev/null +++ b/public/duelfiassets/believe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/duelfiassets/coingecko.svg b/public/duelfiassets/coingecko.svg new file mode 100644 index 0000000..9da652d --- /dev/null +++ b/public/duelfiassets/coingecko.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/duelfiassets/dexscreener.svg b/public/duelfiassets/dexscreener.svg new file mode 100644 index 0000000..259b650 --- /dev/null +++ b/public/duelfiassets/dexscreener.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/duelfiassets/meteora.svg b/public/duelfiassets/meteora.svg new file mode 100644 index 0000000..58241f4 --- /dev/null +++ b/public/duelfiassets/meteora.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Activities.tsx b/src/components/Activities.tsx index 390ed3f..546f651 100644 --- a/src/components/Activities.tsx +++ b/src/components/Activities.tsx @@ -1,8 +1,9 @@ import Image from "next/image"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { API_BASE_URL } from "@/data/shared"; import { PlusCircleIcon, XCircleIcon, UserPlusIcon, TrophyIcon } from '@heroicons/react/24/outline'; import { GetGameByID } from "@/data/games"; +import { toast } from "sonner"; interface Activity { id: string; @@ -28,12 +29,76 @@ export default function Activities() { const [loading, setLoading] = useState(true); const [failedImages, setFailedImages] = useState>(new Set()); const defaultPFP = '/duelfiassets/PFP (1).png'; + const isFirstLoad = useRef(true); + const previousActivitiesRef = useRef([]); + const pendingToasts = useRef([]); + + const formatActivityMessage = (activity: Activity) => { + const ownerUsername = userData[activity.owner_id]?.username || "Anonymous"; + const joinerUsername = activity.joiner_id ? (userData[activity.joiner_id]?.username || "Anonymous") : null; + let amount = parseFloat(activity.amount); + if(activity.type == "won"){ + amount = amount * 1.9; + } + const formattedAmount = amount > 0 ? `${amount} SOL` : ""; + const gameData = GetGameByID(activity.game); + const gameName = gameData?.name || activity.game; + switch (activity.type) { + case "create": + return `${ownerUsername} created a ${gameName} game with a ${formattedAmount} entry fee.`; + case "close": + return `${ownerUsername} cancelled their ${gameName} game.`; + case "join": + return `${joinerUsername} joined ${gameName} with a ${formattedAmount} entry fee.`; + case "won": + return `${ownerUsername} won ${formattedAmount} in ${gameName}!`; + default: + return "Unknown activity"; + } + }; + + useEffect(() => { + // Show pending toasts when userData is updated + if (Object.keys(userData).length > 0 && pendingToasts.current.length > 0) { + pendingToasts.current.forEach((activity) => { + const message = formatActivityMessage(activity); + const toastConfig = { + duration: 5000, + position: 'top-right' as const, + icon: activity.type === "create" ? "🎮" : + activity.type === "close" ? "❌" : + activity.type === "join" ? "👋" : + activity.type === "won" ? "🏆" : "📢" + }; + toast(message, toastConfig); + }); + pendingToasts.current = []; + } + }, [userData]); useEffect(() => { const fetchActivities = async () => { try { const response = await fetch("/api/v1/get_activities.php"); const data = await response.json(); + + // Don't show toasts on first load + if (!isFirstLoad.current) { + // Check for new activities using the ref + const newActivities = data.filter((activity: Activity) => + !previousActivitiesRef.current.some(prevActivity => prevActivity.id === activity.id) + ); + + if (newActivities.length > 0) { + // Store new activities in pendingToasts + pendingToasts.current = newActivities; + } + } else { + isFirstLoad.current = false; + } + + // Update the ref with current activities + previousActivitiesRef.current = data; setActivities(data); // Fetch user data for all unique user IDs @@ -45,11 +110,8 @@ export default function Activities() { 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); @@ -58,9 +120,7 @@ export default function Activities() { }); 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); @@ -77,31 +137,7 @@ export default function Activities() { // 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; - let amount = parseFloat(activity.amount); - if(activity.type == "won"){ - amount = amount * 1.9; - } - const formattedAmount = amount > 0 ? `${amount} SOL` : ""; - const gameData = GetGameByID(activity.game); - const gameName = gameData?.name || activity.game; - switch (activity.type) { - case "create": - return <>{ownerUsername} created a {gameName} game with a {formattedAmount} entry fee.; - case "close": - return <>{ownerUsername} cancelled their {gameName} game.; - case "join": - return <>{joinerUsername} joined {gameName} with a {formattedAmount} entry fee.; - case "won": - return <>{ownerUsername} won {formattedAmount} in {gameName}!; - default: - return "Unknown activity"; - } - }; + }, []); // Remove dependencies since we're using refs const formatTimeAgo = (timestamp: string) => { const date = new Date(timestamp + 'Z'); diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index fb9d188..99c4a8a 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -4,6 +4,13 @@ import { URL_TELEGRAM, URL_TWITTER } from "@/shared/constants"; import Image from "next/image"; import Link from "next/link"; +const TRACK_TRADE_LINKS = { + BELIEVE: "https://believe.app/coin/FiQBQdASK3AJVZfE7Nc5KwHvQiTd8YepsHAexopPF1eS", + COINGECKO: "https://www.coingecko.com/en/search_redirect?id=duel-fi&type=coin", + DEXSCREENER: "https://dexscreener.com/solana/2HuT5v8fG2c5VHxoq1thaN4c2ARwUunEnhbibyQJC1qX", + METEORA: "https://www.meteora.ag/dlmm/8CXwRcwLzwHgj8F6nXbeYa2CgEGTAtGsFHmtjJ9dhGQ9" +}; + export default function Footer() { return (