icons and in-web notification

This commit is contained in:
Sewmina 2025-05-31 09:23:33 +00:00
parent ac6aae672d
commit a05b3305a7
6 changed files with 201 additions and 31 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="28" fill="none"><path d="M 9.03 0.103 C 9.286 -0.03 9.59 -0.035 9.85 0.09 C 10.11 0.214 10.297 0.453 10.355 0.736 L 11.942 8.711 C 12.065 9.329 11.352 9.77 10.847 9.387 L 10.412 9.057 C 9.756 8.559 9.071 8.098 8.362 7.678 L 4.411 16.262 C 4.077 16.986 3.904 17.774 3.904 18.571 C 3.904 21.637 6.408 24.123 9.496 24.123 L 15.846 24.123 C 18.193 24.123 20.096 22.234 20.096 19.904 C 20.096 17.574 18.193 15.685 15.846 15.685 L 12.402 15.685 C 11.886 15.687 11.391 15.484 11.025 15.12 C 10.659 14.757 10.452 14.263 10.45 13.747 C 10.45 12.677 11.324 11.808 12.402 11.808 L 16.19 11.808 C 16.888 11.811 17.559 11.536 18.054 11.044 C 18.55 10.552 18.829 9.884 18.832 9.186 C 18.83 8.488 18.55 7.819 18.054 7.327 C 17.559 6.835 16.888 6.56 16.19 6.563 L 15.846 6.563 C 15.33 6.565 14.835 6.362 14.469 5.998 C 14.103 5.635 13.896 5.141 13.894 4.625 C 13.894 3.554 14.768 2.686 15.846 2.686 L 16.19 2.686 C 19.805 2.686 22.736 5.596 22.736 9.186 C 22.737 10.832 22.11 12.417 20.981 13.615 C 22.89 15.145 24 17.459 24 19.905 C 24 24.374 20.35 28 15.847 28 L 9.496 28 C 4.251 28 0 23.778 0 18.57 C 0 17.218 0.293 15.88 0.86 14.65 L 4.842 6 C 4.124 5.735 3.393 5.508 2.652 5.32 L 1.896 5.127 C 1.62 5.058 1.416 4.825 1.383 4.543 C 1.35 4.26 1.496 3.987 1.749 3.857 Z" fill="rgb(255, 255, 255)"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,16 @@
<svg width="512" height="512" viewBox="0 0 506.623 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_207_2675)">
<path d="M506.621 253.985C507.254 394.894 394.35 509.631 254.463 510.268C114.557 510.906 0.635972 397.194 0.00265142 256.286C-0.630669 115.377 112.273 0.640368 252.179 0.00251182C392.066 -0.616016 505.987 113.077 506.621 253.985Z" fill="#FFE866"/>
<path d="M381.781 163.839C363.395 158.485 344.357 150.869 325.05 143.195C323.937 138.325 319.658 132.255 310.983 124.814C298.374 113.796 274.692 114.086 254.234 118.957C231.645 113.603 209.326 111.689 187.908 116.869C12.7658 165.482 112.063 284.026 47.752 403.228C56.9064 422.77 157.872 516.658 298.24 506.233C298.24 506.233 249.436 388.113 359.576 331.402C448.913 285.418 513.454 200.023 381.761 163.819L381.781 163.839Z" fill="#4BCC00"/>
<path d="M266.363 192.26C266.363 219.514 244.427 241.588 217.386 241.588C190.345 241.588 168.409 219.514 168.409 192.26C168.409 165.006 190.345 142.952 217.386 142.952C244.427 142.952 266.363 165.025 266.363 192.26Z" fill="white"/>
<path d="M443.077 263.251C403.408 291.413 358.25 312.771 294.247 312.771C264.289 312.771 258.205 280.705 238.399 296.419C228.17 304.537 192.129 322.687 163.514 321.315C134.65 319.923 88.5711 303.03 75.6168 241.544C70.4927 303.03 67.8826 348.337 44.9488 400.254C101.246 482.993 199.405 524.157 298.239 506.255C287.626 431.587 352.416 358.465 388.919 321.044C402.736 306.876 429.221 283.739 443.077 263.251Z" fill="#4BCC00"/>
<ellipse cx="238.03" cy="191.923" rx="28.7401" ry="40.2361" fill="#0D1217"/>
<path d="M249.526 118.863C265.206 125.131 322.467 144.203 347.242 151.689C321.947 109.717 283.613 112.139 249.526 118.863Z" fill="#35AF00"/>
<path d="M238.03 191.918L203.541 168.926V214.91L238.03 191.918Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_207_2675">
<rect width="506.623" height="512" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" fill-rule="evenodd" viewBox="0 0 252 300" focusable="false" class="chakra-icon custom-1jrmobr" fill="white"><path d="M151.818 106.866c9.177-4.576 20.854-11.312 32.545-20.541 2.465 5.119 2.735 9.586 1.465 13.193-.9 2.542-2.596 4.753-4.826 6.512-2.415 1.901-5.431 3.285-8.765 4.033-6.326 1.425-13.712.593-20.419-3.197m1.591 46.886l12.148 7.017c-24.804 13.902-31.547 39.716-39.557 64.859-8.009-25.143-14.753-50.957-39.556-64.859l12.148-7.017a5.95 5.95 0 003.84-5.845c-1.113-23.547 5.245-33.96 13.821-40.498 3.076-2.342 6.434-3.518 9.747-3.518s6.671 1.176 9.748 3.518c8.576 6.538 14.934 16.951 13.821 40.498a5.95 5.95 0 003.84 5.845zM126 0c14.042.377 28.119 3.103 40.336 8.406 8.46 3.677 16.354 8.534 23.502 14.342 3.228 2.622 5.886 5.155 8.814 8.071 7.897.273 19.438-8.5 24.796-16.709-9.221 30.23-51.299 65.929-80.43 79.589-.012-.005-.02-.012-.029-.018-5.228-3.992-11.108-5.988-16.989-5.988s-11.76 1.996-16.988 5.988c-.009.005-.017.014-.029.018-29.132-13.66-71.209-49.359-80.43-79.589 5.357 8.209 16.898 16.982 24.795 16.709 2.929-2.915 5.587-5.449 8.814-8.071C69.31 16.94 77.204 12.083 85.664 8.406 97.882 3.103 111.959.377 126 0m-25.818 106.866c-9.176-4.576-20.854-11.312-32.544-20.541-2.465 5.119-2.735 9.586-1.466 13.193.901 2.542 2.597 4.753 4.826 6.512 2.416 1.901 5.432 3.285 8.766 4.033 6.326 1.425 13.711.593 20.418-3.197"></path><path d="M197.167 75.016c6.436-6.495 12.107-13.684 16.667-20.099l2.316 4.359c7.456 14.917 11.33 29.774 11.33 46.494l-.016 26.532.14 13.754c.54 33.766 7.846 67.929 24.396 99.193l-34.627-27.922-24.501 39.759-25.74-24.231L126 299.604l-41.132-66.748-25.739 24.231-24.501-39.759L0 245.25c16.55-31.264 23.856-65.427 24.397-99.193l.14-13.754-.016-26.532c0-16.721 3.873-31.578 11.331-46.494l2.315-4.359c4.56 6.415 10.23 13.603 16.667 20.099l-2.01 4.175c-3.905 8.109-5.198 17.176-2.156 25.799 1.961 5.554 5.54 10.317 10.154 13.953 4.48 3.531 9.782 5.911 15.333 7.161 3.616.814 7.3 1.149 10.96 1.035-.854 4.841-1.227 9.862-1.251 14.978L53.2 160.984l25.206 14.129a41.926 41.926 0 015.734 3.869c20.781 18.658 33.275 73.855 41.861 100.816 8.587-26.961 21.08-82.158 41.862-100.816a41.865 41.865 0 015.734-3.869l25.206-14.129-32.665-18.866c-.024-5.116-.397-10.137-1.251-14.978 3.66.114 7.344-.221 10.96-1.035 5.551-1.25 10.854-3.63 15.333-7.161 4.613-3.636 8.193-8.399 10.153-13.953 3.043-8.623 1.749-17.689-2.155-25.799l-2.01-4.175z"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,51 @@
<svg width="128" height="128" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11565_169621)">
<g filter="url(#filter0_d_11565_169621)">
<path d="M3.09074 25.1666C4.44267 27.0471 6.17683 28.6205 8.1795 29.7838C10.1822 30.947 12.4081 31.6738 14.7114 31.9165C13.5264 30.1333 11.8039 28.4928 9.65354 27.2438C7.50318 25.9948 5.22592 25.3125 3.09074 25.1666Z" fill="url(#paint0_linear_11565_169621)"/>
<path d="M12.543 22.2705C8.40015 19.8636 3.91612 19.2502 0.707663 20.3338C1.0174 21.3575 1.42589 22.3487 1.92738 23.2934C4.71498 23.2288 7.75856 23.9859 10.5906 25.6308C13.4227 27.2757 15.5888 29.5459 16.9143 32C17.9839 31.9672 19.0479 31.8309 20.0913 31.5932C19.4426 28.2698 16.6849 24.6779 12.543 22.2705Z" fill="url(#paint1_linear_11565_169621)"/>
<path d="M32.2852 12.5009C31.7585 10.3584 30.8054 8.34403 29.4829 6.57804C28.1604 4.81205 26.4956 3.33067 24.5879 2.22235C22.6802 1.11403 20.5687 0.401504 18.3796 0.127309C16.1904 -0.146885 13.9684 0.0228794 11.8463 0.626465C15.3915 1.06033 19.3267 2.39122 23.1859 4.63324C27.0452 6.87525 30.1533 9.63411 32.2852 12.5009Z" fill="url(#paint2_linear_11565_169621)"/>
<path d="M27.1271 20.3583C25.3124 17.3446 22.2038 14.4588 18.3743 12.2342C14.5449 10.0095 10.4991 8.7388 6.98531 8.65474C3.894 8.58152 1.57389 9.48017 0.621548 11.1197C0.616125 11.1294 0.608532 11.1386 0.602566 11.1484C0.516877 11.4559 0.44312 11.7639 0.37587 12.0731C1.70568 11.5481 3.24645 11.2558 4.95969 11.2232C8.76959 11.1517 13.0334 12.3703 16.9681 14.6562C20.9027 16.9422 24.0759 20.0438 25.9003 23.3878C26.7182 24.8944 27.2285 26.3777 27.4308 27.7948C27.6662 27.5844 27.8972 27.3669 28.1212 27.1408C28.1272 27.1305 28.131 27.1196 28.1369 27.1088C29.0893 25.4677 28.721 23.0076 27.1271 20.3583Z" fill="url(#paint3_linear_11565_169621)"/>
<path d="M15.4609 17.2485C9.59662 13.8416 3.11626 13.3079 0 15.6855C0.00612096 16.4297 0.0630166 17.1726 0.170292 17.9091C1.08699 17.6312 2.03177 17.4562 2.98718 17.3874C6.46952 17.1254 10.3087 18.0957 13.7927 20.1207C17.2766 22.1458 20.023 25.0018 21.5209 28.1543C21.935 29.018 22.2508 29.9254 22.4624 30.8595C23.1555 30.5878 23.8294 30.2694 24.4794 29.9066C25.0011 26.0213 21.3268 20.656 15.4609 17.2485Z" fill="url(#paint4_linear_11565_169621)"/>
<path d="M30.1434 15.3141C28.3082 12.3036 25.1724 9.40969 21.3158 7.17039C17.4593 4.93109 13.3977 3.64033 9.87257 3.53674C7.1853 3.45919 5.10382 4.11053 4.02457 5.34109C8.50588 4.58182 14.4168 5.85794 20.146 9.18625C25.8753 12.5146 29.9135 17.0181 31.4722 21.2868C32.0064 19.7406 31.5416 17.6098 30.1434 15.3141Z" fill="url(#paint5_linear_11565_169621)"/>
</g>
</g>
<defs>
<filter id="filter0_d_11565_169621" x="-22.7449" y="-20.4704" width="77.7749" height="77.4898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.27449"/>
<feGaussianBlur stdDeviation="11.3724"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11565_169621"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_11565_169621" result="shape"/>
</filter>
<linearGradient id="paint0_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<linearGradient id="paint1_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<linearGradient id="paint2_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<linearGradient id="paint3_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<linearGradient id="paint4_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<linearGradient id="paint5_linear_11565_169621" x1="21.5" y1="6.5" x2="6.66667" y2="32" gradientUnits="userSpaceOnUse">
<stop offset="0.0001" stop-color="#C7F284"/>
<stop offset="1" stop-color="#00BEF0"/>
</linearGradient>
<clipPath id="clip0_11565_169621">
<rect width="32.2852" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,8 +1,9 @@
import Image from "next/image"; import Image from "next/image";
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import { API_BASE_URL } from "@/data/shared"; import { API_BASE_URL } from "@/data/shared";
import { PlusCircleIcon, XCircleIcon, UserPlusIcon, TrophyIcon } from '@heroicons/react/24/outline'; import { PlusCircleIcon, XCircleIcon, UserPlusIcon, TrophyIcon } from '@heroicons/react/24/outline';
import { GetGameByID } from "@/data/games"; import { GetGameByID } from "@/data/games";
import { toast } from "sonner";
interface Activity { interface Activity {
id: string; id: string;
@ -28,12 +29,76 @@ export default function Activities() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [failedImages, setFailedImages] = useState<Set<string>>(new Set()); const [failedImages, setFailedImages] = useState<Set<string>>(new Set());
const defaultPFP = '/duelfiassets/PFP (1).png'; const defaultPFP = '/duelfiassets/PFP (1).png';
const isFirstLoad = useRef(true);
const previousActivitiesRef = useRef<Activity[]>([]);
const pendingToasts = useRef<Activity[]>([]);
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(() => { useEffect(() => {
const fetchActivities = async () => { const fetchActivities = async () => {
try { try {
const response = await fetch("/api/v1/get_activities.php"); const response = await fetch("/api/v1/get_activities.php");
const data = await response.json(); 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); setActivities(data);
// Fetch user data for all unique user IDs // Fetch user data for all unique user IDs
@ -45,11 +110,8 @@ export default function Activities() {
const userDataPromises = Array.from(userIds).map(async (userId) => { const userDataPromises = Array.from(userIds).map(async (userId) => {
try { try {
console.log('Fetching user data for ID:', userId);
const response = await fetch(`/api/v1/get_user_by_id.php?id=${userId}`); const response = await fetch(`/api/v1/get_user_by_id.php?id=${userId}`);
const data = await response.json(); const data = await response.json();
console.log('Received user data:', data);
return [userId, data]; return [userId, data];
} catch (error) { } catch (error) {
console.error(`Error fetching user data for ${userId}:`, error); console.error(`Error fetching user data for ${userId}:`, error);
@ -58,9 +120,7 @@ export default function Activities() {
}); });
const userDataResults = await Promise.all(userDataPromises); const userDataResults = await Promise.all(userDataPromises);
console.log('All user data results:', userDataResults);
const userDataMap = Object.fromEntries(userDataResults); const userDataMap = Object.fromEntries(userDataResults);
console.log('Final user data map:', userDataMap);
setUserData(userDataMap); setUserData(userDataMap);
} catch (error) { } catch (error) {
console.error("Error fetching activities:", error); console.error("Error fetching activities:", error);
@ -77,31 +137,7 @@ export default function Activities() {
// Cleanup function to clear the interval when component unmounts // Cleanup function to clear the interval when component unmounts
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, []); }, []); // Remove dependencies since we're using refs
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 <><span className="font-bold">{ownerUsername}</span> created a <span className="font-bold">{gameName}</span> game with a {formattedAmount} entry fee.</>;
case "close":
return <><span className="font-bold">{ownerUsername}</span> cancelled their <span className="font-bold">{gameName}</span> game.</>;
case "join":
return <><span className="font-bold">{joinerUsername}</span> joined <span className="font-bold">{gameName}</span> with a {formattedAmount} entry fee.</>;
case "won":
return <><span className="font-bold">{ownerUsername}</span> won {formattedAmount} in <span className="font-bold">{gameName}</span>!</>;
default:
return "Unknown activity";
}
};
const formatTimeAgo = (timestamp: string) => { const formatTimeAgo = (timestamp: string) => {
const date = new Date(timestamp + 'Z'); const date = new Date(timestamp + 'Z');

View File

@ -4,6 +4,13 @@ import { URL_TELEGRAM, URL_TWITTER } from "@/shared/constants";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; 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() { export default function Footer() {
return ( return (
<footer className="w-full bg-[rgb(17,17,17)] text-white py-8"> <footer className="w-full bg-[rgb(17,17,17)] text-white py-8">
@ -50,6 +57,64 @@ export default function Footer() {
</a> </a>
</div> </div>
{/* Track & Trade Section */}
<div className="mt-8">
<div className="flex gap-6 items-center">
<a
href={TRACK_TRADE_LINKS.BELIEVE}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"
>
<Image
src="/duelfiassets/believe.svg"
alt="Believe"
width={32}
height={32}
/>
</a>
<a
href={TRACK_TRADE_LINKS.COINGECKO}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"
>
<Image
src="/duelfiassets/coingecko.svg"
alt="CoinGecko"
width={32}
height={32}
/>
</a>
<a
href={TRACK_TRADE_LINKS.DEXSCREENER}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"
>
<Image
src="/duelfiassets/dexscreener.svg"
alt="DexScreener"
width={32}
height={32}
/>
</a>
<a
href={TRACK_TRADE_LINKS.METEORA}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"
>
<Image
src="/duelfiassets/meteora.svg"
alt="Meteora"
width={32}
height={32}
/>
</a>
</div>
</div>
{/* Spacer */} {/* Spacer */}
<div className="h-6"></div> <div className="h-6"></div>