diff --git a/landing/src/components/modules/bridged-resources.tsx b/landing/src/components/modules/bridged-resources.tsx new file mode 100644 index 000000000..03c7f1a61 --- /dev/null +++ b/landing/src/components/modules/bridged-resources.tsx @@ -0,0 +1,231 @@ +import { useDojo } from "@/hooks/context/DojoContext"; +import { ResourcesIds } from "@bibliothecadao/eternum"; +import { useAccount } from "@starknet-react/core"; +import { ArrowDownUp, Pickaxe } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import { TypeH2 } from "../typography/type-h2"; +import { Card, CardHeader } from "../ui/card"; +import { ResourceIcon } from "../ui/elements/ResourceIcon"; +import { getSeasonAddresses } from "../ui/utils/utils"; + +type SortKey = "totalSupply" | "balance"; +type SortDirection = "asc" | "desc"; + +export const BridgedResources = () => { + const [sortKey, setSortKey] = useState("totalSupply"); + const [sortDirection, setSortDirection] = useState("desc"); + const [sortedResources, setSortedResources] = useState<[string, [number, string]][]>([]); + const [resourceData, setResourceData] = useState>({}); + + useEffect(() => { + const getResources = async () => { + const addresses = await getSeasonAddresses(); + setSortedResources(Object.entries(addresses)); + }; + void getResources(); + }, []); + + const updateResourceData = useCallback((address: string, totalSupply: bigint, balance: bigint) => { + setResourceData((prev) => { + if (prev[address]?.totalSupply === totalSupply && prev[address]?.balance === balance) { + return prev; + } + return { + ...prev, + [address]: { totalSupply, balance }, + }; + }); + }, []); + + useEffect(() => { + if (Object.keys(resourceData).length > 0) { + setSortedResources((prev) => + [...prev].sort((a, b) => { + const aData = resourceData[a[1][1]] || { totalSupply: 0n, balance: 0n }; + const bData = resourceData[b[1][1]] || { totalSupply: 0n, balance: 0n }; + const comparison = Number(bData[sortKey] - aData[sortKey]); + return sortDirection === "desc" ? comparison : -comparison; + }), + ); + } + }, [sortKey, sortDirection, resourceData]); + + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortDirection(sortDirection === "desc" ? "asc" : "desc"); + } else { + setSortKey(key); + setSortDirection("desc"); + } + }; + + return ( + + +
+ + + Bridged Resources + +
+ + +
+
+
+
+ {sortedResources.map(([key, [id, address]]) => ( + + ))} +
+
+ ); +}; + +export const BridgeResource = ({ + name, + resourceId, + contractAddress, + onDataUpdate, +}: { + name: string; + resourceId: number; + contractAddress: string; + onDataUpdate: (address: string, totalSupply: bigint, balance: bigint) => void; +}) => { + const { + network: { provider }, + } = useDojo(); + const { account } = useAccount(); + + const [amountBridged, setAmountBridged] = useState(0n); + const [playerBalance, setPlayerBalance] = useState(0n); + const [copied, setCopied] = useState(false); + + useEffect(() => { + const fetchData = async () => { + try { + // Get total supply + const totalSupplyResult = await provider.provider.callContract({ + contractAddress: contractAddress, + entrypoint: "total_supply", + }); + const totalSupply = BigInt(totalSupplyResult[0]); + setAmountBridged(totalSupply); + + // Get player balance + let balance = 0n; + if (account?.address) { + const balanceResult = await provider.provider.callContract({ + contractAddress: contractAddress, + entrypoint: "balance_of", + calldata: [account.address], + }); + balance = BigInt(balanceResult[0]); + setPlayerBalance(balance); + } + + onDataUpdate(contractAddress, totalSupply, balance); + } catch (error) { + console.error("Error fetching data:", error); + setAmountBridged(0n); + setPlayerBalance(0n); + onDataUpdate(contractAddress, 0n, 0n); + } + }; + + fetchData(); + }, [provider, contractAddress, account?.address, onDataUpdate]); + + const percentage = amountBridged > 0n ? Number((playerBalance * 100n) / amountBridged) : 0; + const formattedBalance = (Number(playerBalance) / 10 ** 18).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + const formattedSupply = (Number(amountBridged) / 10 ** 18).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + + const copyToClipboard = () => { + navigator.clipboard.writeText(contractAddress); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ +
+ +
+ Balance: {formattedBalance}{" "} + ({percentage.toFixed(1)}%) +
+ +
+
+
+
Total Supply
+
{formattedSupply}
+
+
+ ); +}; diff --git a/landing/src/components/ui/utils/utils.ts b/landing/src/components/ui/utils/utils.ts index 99a24859e..0eabd5601 100644 --- a/landing/src/components/ui/utils/utils.ts +++ b/landing/src/components/ui/utils/utils.ts @@ -436,7 +436,7 @@ export const getJSONFile = async (filePath: string) => { return data; }; -interface ResourceAddresses { +export interface ResourceAddresses { [key: string]: [number, string]; } diff --git a/landing/src/routes/index.lazy.tsx b/landing/src/routes/index.lazy.tsx index 4eede8c04..edd0d9f80 100644 --- a/landing/src/routes/index.lazy.tsx +++ b/landing/src/routes/index.lazy.tsx @@ -1,4 +1,5 @@ import { AnimatedGrid } from "@/components/modules/animated-grid"; +import { BridgedResources } from "@/components/modules/bridged-resources"; import { DataCard, DataCardProps } from "@/components/modules/data-card"; import { PRIZE_POOL_ACHIEVEMENTS, @@ -127,7 +128,14 @@ function Index() { return (
, + }, + ]} renderItem={(item) => React.isValidElement(item.data) ? item.data : }