135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import axios from 'axios';
|
|
|
|
interface Detail {
|
|
token: string;
|
|
gains: number;
|
|
price_at_creation: number;
|
|
price_now: number;
|
|
icon_url: string;
|
|
ca: string; // Add contract address to the Detail interface
|
|
}
|
|
|
|
const Modal: React.FC<{
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
item: { username: string; points: number; img_url: string }; // Include img_url in the item prop
|
|
period: string;
|
|
}> = ({ isOpen, onClose, item, period }) => {
|
|
const [details, setDetails] = useState<Detail[] | null>(null);
|
|
const [urls, setUrls] = useState<{ [key: string]: string }>({}); // State to store Dexscreener URLs
|
|
|
|
useEffect(() => {
|
|
if (isOpen && item) {
|
|
const fetchDetails = async () => {
|
|
try {
|
|
let urlAddition = '';
|
|
if (period === 'Weekly') {
|
|
urlAddition = '_weekly';
|
|
} else if (period === 'Monthly') {
|
|
urlAddition = '_monthly';
|
|
}
|
|
const response = await fetch(`https://api.callfi.io/get_user_callouts${urlAddition}.php?tag=${item.username}`);
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
const data: Detail[] = await response.json();
|
|
setDetails(data);
|
|
|
|
// Fetch URLs for each detail
|
|
const newUrls: { [key: string]: string } = {};
|
|
for (const detail of data) {
|
|
try {
|
|
const response = await axios.get(`https://api.dexscreener.com/latest/dex/tokens/${detail.ca}`);
|
|
if (response.data && response.data.pairs && response.data.pairs.length > 0) {
|
|
newUrls[detail.ca] = response.data.pairs[0].url;
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to fetch URL for ${detail.ca}:`, error);
|
|
}
|
|
}
|
|
setUrls(newUrls);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to fetch user details:', error);
|
|
}
|
|
};
|
|
|
|
fetchDetails();
|
|
}
|
|
}, [isOpen, item, period]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center overflow-x-hidden overflow-y-auto outline-none focus:outline-none modal-bg"
|
|
onClick={onClose}
|
|
>
|
|
<div className="relative w-auto max-w-3xl mx-auto my-6 modal">
|
|
<div className="relative flex flex-col border-0 rounded-lg shadow-lg outline-none focus:outline-none">
|
|
<div className="flex items-start justify-between p-5 border-b border-solid rounded-t border-blueGray-200">
|
|
<div className="flex items-center">
|
|
<img
|
|
src={item.img_url}
|
|
alt={`${item.username}'s profile`}
|
|
className="w-12 h-12 rounded-full mr-4"
|
|
/>
|
|
<h3 className="text-3xl font-semibold">{item.username}s Callouts</h3>
|
|
</div>
|
|
</div>
|
|
<div className="relative p-6 flex-auto">
|
|
<p
|
|
className={`my-4 text-blueGray-500 text-2xl font-bold leading-relaxed ${
|
|
item.points > 0 ? 'text-green-500' : 'text-red-500'
|
|
}`}
|
|
>
|
|
Total Gains : {parseFloat(item.points.toString()).toFixed(2)} x
|
|
</p>
|
|
</div>
|
|
<div className="relative p-6 flex-auto">
|
|
{details !== null ? (
|
|
<div>
|
|
{details.map((detail) => (
|
|
<a
|
|
key={detail.token}
|
|
href={urls[detail.ca] || '#'}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="block"
|
|
>
|
|
<p
|
|
className={`my-4 text-lg leading-relaxed ${
|
|
detail.gains > 0 ? 'text-green-500' : 'text-red-400'
|
|
}`}
|
|
>
|
|
<img
|
|
src={detail.icon_url}
|
|
alt={`${detail.token} icon`}
|
|
className="inline-block w-6 h-6 mr-2"
|
|
/>
|
|
{detail.token}: {parseFloat(detail.gains.toString()).toFixed(2)}x [
|
|
{parseFloat(detail.price_at_creation.toString()).toFixed(8) +
|
|
' => ' +
|
|
parseFloat(detail.price_now.toString()).toFixed(8)}
|
|
]
|
|
</p>
|
|
</a>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p>Loading...</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default Modal;
|