diff --git a/src/app/page.tsx b/src/app/page.tsx
index d57e86c..37d1e5a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -4,7 +4,7 @@ import Footer from "@/components/Footer";
import Header from "@/components/Header";
import HeroSection from "@/components/HeroSection";
import Leaderboard from "@/components/Leaderboard";
-// import Leaderboard from "@/components/Leaderboard";
+import Activities from "@/components/Activities";
import { PrivyProvider } from "@privy-io/react-auth";
import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
import { Toaster } from "sonner";
@@ -39,6 +39,11 @@ export default function Home() {
+
+
+
+
+
>
diff --git a/src/components/Activities.tsx b/src/components/Activities.tsx
new file mode 100644
index 0000000..b9d57ed
--- /dev/null
+++ b/src/components/Activities.tsx
@@ -0,0 +1,148 @@
+import Image from "next/image";
+import { useState, useEffect } from "react";
+import { API_BASE_URL } from "@/data/shared";
+
+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([]);
+ const [userData, setUserData] = useState>({});
+ const [loading, setLoading] = useState(true);
+ const [failedImages, setFailedImages] = useState>(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();
+ 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);
+ }
+ };
+
+ fetchActivities();
+ }, []);
+
+ 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 `${ownerUsername} Created a new ${activity.game} game${formattedAmount ? ` for ${formattedAmount}` : ""}`;
+ case "close":
+ return `${ownerUsername} Closed ${activity.game} game${formattedAmount ? ` for ${formattedAmount}` : ""}`;
+ case "join":
+ return `${joinerUsername} Joined a ${activity.game} game`;
+ case "won":
+ return `${ownerUsername} won the ${activity.game} game${formattedAmount ? ` and won ${formattedAmount}` : ""}`;
+ default:
+ return "Unknown activity";
+ }
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
Recent Activities
+
+ {activities.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 (
+
+
+
{formatActivityMessage(activity)}
+
{new Date(activity.time).toLocaleString()}
+
+
{
+ // @ts-expect-error - Type mismatch expected
+ e.target.src = defaultPFP;
+ setFailedImages(prev => new Set(prev).add(profileUrl));
+ }}
+ />
+
+ );
+ })}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/shared/data_fetcher.ts b/src/shared/data_fetcher.ts
index eecb44e..41f23f8 100644
--- a/src/shared/data_fetcher.ts
+++ b/src/shared/data_fetcher.ts
@@ -15,4 +15,17 @@ export async function showNewGameNotification(username:string, game:string, wage
}catch(error){
console.error("Error showing new game notification:", error);
}
+}
+
+export async function add_new_activity(type:string, owner_id:string, joiner_id:string, game:string, amount:number ){
+ try{
+ const isDevnet = CLUSTER_URL === clusterApiUrl("devnet");
+ if(isDevnet){
+ await fetch(`${API_URL}add_activity.php?type=${type}&owner_id=${owner_id}&joiner_id=${joiner_id}&game=${game}&amount=${amount}&devnet=${isDevnet ? "1" : "0"}`)
+ }else{
+ await fetch(`${API_URL}add_activity.php?type=${type}&owner_id=${owner_id}&joiner_id=${joiner_id}&game=${game}&amount=${amount}`)
+ }
+ }catch(error){
+ console.error("Error adding new activity:", error);
+ }
}
\ No newline at end of file
diff --git a/src/shared/solana_helpers.ts b/src/shared/solana_helpers.ts
index f038245..b468932 100644
--- a/src/shared/solana_helpers.ts
+++ b/src/shared/solana_helpers.ts
@@ -7,7 +7,7 @@ import idl from "../idl/bets_idl.json";
import { Bet } from "@/types/Bet";
import { toast } from "sonner";
import { CONFIRMATION_THRESHOLD } from "./constants";
-
+import { add_new_activity } from "./data_fetcher";
export async function fetchOpenBets(): Promise {
const response = await fetch(`${VALIDATOR_URL}fetchBets`);
@@ -78,11 +78,12 @@ export async function closeBet(wallets: ConnectedSolanaWallet, uid:string, betI
const betList = await program.account.betsList.fetch(bet_list_pda);
let chosenBet: PublicKey | null = null;
-
+ let game_id:string = "";
for (const bet of betList.bets) {
const betAcc = await program.account.betVault.fetch(bet);
if (betAcc.owner.toBase58() === wallets.address && betAcc.gameId.toString() === betId) {
chosenBet = bet;
+ game_id = betAcc.gameId.toString();
break;
}
}
@@ -112,8 +113,8 @@ export async function closeBet(wallets: ConnectedSolanaWallet, uid:string, betI
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
-
- console.log(`Transaction: ${tx}`);
+ add_new_activity("close", uid, "", game_id, 0);
+ console.log(`Transaction: ${txId}`);
return txId;
} catch (error) {
console.error("Error closing bet:", error);
@@ -161,7 +162,7 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction sent: ${txId}`);
console.log(`Transaction confirmed: ${txId}`);
-
+ add_new_activity("create", uid, "", selectedGame, selectedPrice);
if (get_account_address) {
const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id)
const nonceBytes = new BN(nonce).toArrayLike(Buffer, "le", 8); // _nonce.to_le_bytes()
@@ -234,7 +235,7 @@ export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameI
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
-
+ add_new_activity("join", "",uid, gameId, 0);
console.log(`Transaction ID: ${txId}`);
return txId;
} catch (error: unknown) {