diff --git a/fresh.gen.ts b/fresh.gen.ts index 9be0b2ef..073a94bf 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -187,7 +187,7 @@ import * as $modules_Partners from "./islands/modules/Partners.tsx"; import * as $modules_RecursiveLayering from "./islands/modules/RecursiveLayering.tsx"; import * as $modules_StampChain from "./islands/modules/StampChain.tsx"; import * as $modules_Styles from "./islands/modules/Styles.ts"; -import * as $shared_Table from "./islands/shared/Tables.tsx"; +import * as $shared_Tables from "./islands/shared/Tables.tsx"; import * as $src20_FilterModal from "./islands/src20/FilterModal.tsx"; import * as $src20_SRC20Header from "./islands/src20/SRC20Header.tsx"; import * as $src20_SRC20Search from "./islands/src20/SRC20Search.tsx"; @@ -195,8 +195,6 @@ import * as $src20_SRC20Section from "./islands/src20/SRC20Section.tsx"; import * as $src20_cards_SRC20BaseCard from "./islands/src20/cards/SRC20BaseCard.tsx"; import * as $src20_cards_SRC20TokenMintingCard from "./islands/src20/cards/SRC20TokenMintingCard.tsx"; import * as $src20_cards_SRC20TokenOutmintedCard from "./islands/src20/cards/SRC20TokenOutmintedCard.tsx"; -import * as $src20_details_SRC20DetailsTab from "./islands/src20/details/SRC20DetailsTab.tsx"; -import * as $src20_details_SRC20TX from "./islands/src20/details/SRC20TX.tsx"; import * as $src20_details_SRC20TickHeader from "./islands/src20/details/SRC20TickHeader.tsx"; import * as $stamp_StampCard from "./islands/stamp/StampCard.tsx"; import * as $stamp_StampContent from "./islands/stamp/StampContent.tsx"; @@ -209,7 +207,6 @@ import * as $stamp_details_StampCodeModal from "./islands/stamp/details/StampCod import * as $stamp_details_StampImage from "./islands/stamp/details/StampImage.tsx"; import * as $stamp_details_StampImageFullScreen from "./islands/stamp/details/StampImageFullScreen.tsx"; import * as $stamp_details_StampInfo from "./islands/stamp/details/StampInfo.tsx"; -import * as $stamp_details_StampRelatedInfo from "./islands/stamp/details/StampRelatedInfo.tsx"; import * as $stamp_details_StampTextContent from "./islands/stamp/details/StampTextContent.tsx"; import * as $stamping_InputField from "./islands/stamping/InputField.tsx"; import * as $stamping_SelectField from "./islands/stamping/SelectField.tsx"; @@ -458,7 +455,7 @@ const manifest = { "./islands/modules/RecursiveLayering.tsx": $modules_RecursiveLayering, "./islands/modules/StampChain.tsx": $modules_StampChain, "./islands/modules/Styles.ts": $modules_Styles, - "./islands/shared/Tables.tsx": $shared_Table, + "./islands/shared/Tables.tsx": $shared_Tables, "./islands/src20/FilterModal.tsx": $src20_FilterModal, "./islands/src20/SRC20Header.tsx": $src20_SRC20Header, "./islands/src20/SRC20Search.tsx": $src20_SRC20Search, @@ -468,9 +465,6 @@ const manifest = { $src20_cards_SRC20TokenMintingCard, "./islands/src20/cards/SRC20TokenOutmintedCard.tsx": $src20_cards_SRC20TokenOutmintedCard, - "./islands/src20/details/SRC20DetailsTab.tsx": - $src20_details_SRC20DetailsTab, - "./islands/src20/details/SRC20TX.tsx": $src20_details_SRC20TX, "./islands/src20/details/SRC20TickHeader.tsx": $src20_details_SRC20TickHeader, "./islands/stamp/StampCard.tsx": $stamp_StampCard, @@ -485,8 +479,6 @@ const manifest = { "./islands/stamp/details/StampImageFullScreen.tsx": $stamp_details_StampImageFullScreen, "./islands/stamp/details/StampInfo.tsx": $stamp_details_StampInfo, - "./islands/stamp/details/StampRelatedInfo.tsx": - $stamp_details_StampRelatedInfo, "./islands/stamp/details/StampTextContent.tsx": $stamp_details_StampTextContent, "./islands/stamping/InputField.tsx": $stamping_InputField, diff --git a/islands/src20/details/SRC20DetailsTab.tsx b/islands/src20/details/SRC20DetailsTab.tsx deleted file mode 100644 index 1ee0b6cc..00000000 --- a/islands/src20/details/SRC20DetailsTab.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useEffect, useState } from "preact/hooks"; -import { SRC20TX } from "$islands/src20/details/SRC20TX.tsx"; -import { SRC20Row } from "$globals"; - -interface SRC20DetailsTabProps { - tick: string; -} - -type TabType = "MINT" | "TRANSFER"; - -const TABS: TabType[] = ["MINT", "TRANSFER"]; -const LIMIT = 50; - -export function SRC20DetailsTab({ tick }: SRC20DetailsTabProps) { - const [selectedTab, setSelectedTab] = useState("MINT"); - const [transactions, setTransactions] = useState< - Record - >({ - TRANSFER: [], - MINT: [], - }); - - const [totalCounts, setTotalCounts] = useState({ - TRANSFER: 0, - MINT: 0, - }); - - const fetchMoreTransactions = async ( - page: number, - type: TabType, - ): Promise => { - try { - const response = await fetch( - `/api/v2/src20/tick/${tick}?op=${type}&page=${page}&limit=${LIMIT}&sort=DESC`, - ); - const data = await response.json(); - return data.data; - } catch (error) { - console.error(`Error fetching more ${type.toLowerCase()}s:`, error); - return []; - } - }; - - const fetchTotalCounts = async () => { - try { - const [transferCount, mintCount] = await Promise.all([ - fetch(`/api/v2/src20/tick/${tick}?op=TRANSFER&limit=1`), - fetch(`/api/v2/src20/tick/${tick}?op=MINT&limit=1`), - ]); - - const [transferData, mintData] = await Promise.all([ - transferCount.json(), - mintCount.json(), - ]); - - setTotalCounts({ - TRANSFER: transferData.total || 0, - MINT: mintData.total || 0, - }); - } catch (error) { - console.error("Error fetching total counts:", error); - } - }; - - useEffect(() => { - fetchTotalCounts(); - const fetchInitialData = async () => { - if (transactions[selectedTab].length === 0) { - const initialTransactions = await fetchMoreTransactions(1, selectedTab); - setTransactions((prev) => ({ - ...prev, - [selectedTab]: initialTransactions, - })); - } - }; - fetchInitialData(); - }, [selectedTab, tick]); - - return ( -
-
- {TABS.map((tab, index) => ( -
-

setSelectedTab(tab)} - > - {tab.charAt(0) + tab.slice(1).toLowerCase() + "s"} -

-
setSelectedTab(tab)} - > - {totalCounts[tab]} -
-
- ))} -
- - fetchMoreTransactions(page, selectedTab)} - /> -
- ); -} diff --git a/islands/src20/details/SRC20TX.tsx b/islands/src20/details/SRC20TX.tsx deleted file mode 100644 index a22cc950..00000000 --- a/islands/src20/details/SRC20TX.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { - abbreviateAddress, - formatDate, - formatNumber, -} from "$lib/utils/formatUtils.ts"; -import { useEffect, useRef, useState } from "preact/hooks"; -import { SRC20Row } from "$globals"; - -type SRC20TXProps = { - txs: SRC20Row[]; - type: "MINT" | "TRANSFER"; - fetchMoreData: (page: number) => Promise; -}; - -export function SRC20TX(props: SRC20TXProps) { - const { txs, type, fetchMoreData } = props; - const [data, setData] = useState(txs || []); - const [page, setPage] = useState(1); - const [isFetching, setIsFetching] = useState(false); - const [hasMoreData, setHasMoreData] = useState(true); - const containerRef = useRef(null); - - const handleScroll = async () => { - if (!containerRef.current || isFetching || !hasMoreData) return; - - const { scrollTop, scrollHeight, clientHeight } = containerRef.current; - - if (scrollTop + clientHeight >= scrollHeight - 10) { - // User scrolled to the bottom - setIsFetching(true); - const nextPage = page + 1; - const newData = await fetchMoreData(nextPage); - - if (newData && newData.length > 0) { - setData((prevData) => [...prevData, ...newData]); - setPage(nextPage); - } else { - setHasMoreData(false); - } - setIsFetching(false); - } - }; - - const copyText = async (text: string) => { - await navigator.clipboard.writeText(text); - }; - - useEffect(() => { - // Reset data when txs prop changes - setData(txs || []); - setPage(1); - setHasMoreData(true); - }, [txs]); - - const tableHeaders = () => { - if (type === "TRANSFER") { - return ( - - From - To - - Amount - - - Tx Hash - - Date - - ); - } else if (type === "MINT") { - return ( - - - Block - - - Address - - - Amount - - - Tx Hash - - - Date - - - ); - } - return null; - }; - - const renderRows = () => { - return data.map((tx) => { - // Ensure amt is a valid number - let amtValue = Number(tx.amt); - if (isNaN(amtValue)) { - console.warn(`Invalid amount value: ${tx.amt}`); - amtValue = 0; - } - - // Format the amt value - const formattedAmt = formatNumber(amtValue, 0); - - if (type === "TRANSFER") { - return ( - - {abbreviateAddress(tx.creator)} - {abbreviateAddress(tx.destination)} - {formattedAmt} - - {formatDate(new Date(tx.block_time), { - month: "numeric", - day: "numeric", - year: "numeric", - })} - - *** - - ); - } else if (type === "MINT") { - return ( - - {tx.block_index} - {abbreviateAddress(tx.destination)} - {formattedAmt} - -

copyText(tx.tx_hash)} - > - {tx.tx_hash} -

- - *** - - ); - } - return null; - }); - }; - - return ( -
- - - {tableHeaders()} - - {renderRows()} -
- {isFetching && ( -
- Loading more data... -
- )} - {!hasMoreData && ( -
- No more data to load. -
- )} -
- ); -} diff --git a/islands/stamp/details/StampRelatedInfo.tsx b/islands/stamp/details/StampRelatedInfo.tsx deleted file mode 100644 index 3d6c991c..00000000 --- a/islands/stamp/details/StampRelatedInfo.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import { useCallback, useEffect, useState } from "preact/hooks"; - -import { StampListingsAll } from "$components/stampDetails/StampListingsAll.tsx"; -import { StampSales } from "$components/stampDetails/StampSales.tsx"; -import { StampTransfers } from "$components/stampDetails/StampTransfers.tsx"; - -interface StampRelatedInfoProps { - _stampId: string; - cpid: string; -} - -type TabType = "dispensers" | "sales" | "transfers"; - -// Move rate calculation to frontend -function mapDispensesWithRates(dispenses: any[], dispensers: any[]) { - if (!dispenses || !dispensers) return []; - - const dispenserRates = new Map( - dispensers.map((d) => [d.tx_hash, d.satoshirate]), - ); - - return dispenses.map((dispense) => ({ - ...dispense, - satoshirate: dispenserRates.get(dispense.dispenser_tx_hash) || 0, - })); -} - -const PAGE_SIZE = 20; - -export function StampRelatedInfo({ _stampId, cpid }: StampRelatedInfoProps) { - const [selectedTab, setSelectedTab] = useState("dispensers"); - const [dispensers, setDispensers] = useState([]); - const [dispenses, setDispenses] = useState([]); - const [sends, setSends] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - // Pagination state - const [page, setPage] = useState(1); - const [hasMore, setHasMore] = useState(true); - - // Add new state for total counts - const [totalCounts, setTotalCounts] = useState({ - dispensers: 0, - sales: 0, - transfers: 0, - }); - - const fetchData = useCallback( - async (pageNum: number, isTabChange = false) => { - if (isLoading) return; - if (!isTabChange && !hasMore) return; - - setIsLoading(true); - try { - const encodedCpid = encodeURIComponent(cpid); - - // Fetch total counts if it's the first page - if (pageNum === 1) { - const countParams = new URLSearchParams({ - limit: PAGE_SIZE.toString(), - sort: "DESC", - }); - - // Fetch counts for all tabs - const [dispensersCount, salesCount, transfersCount] = await Promise - .all( - [ - fetch( - `/api/v2/stamps/${encodedCpid}/dispensers?${countParams}`, - ), - fetch(`/api/v2/stamps/${encodedCpid}/dispenses?${countParams}`), - fetch(`/api/v2/stamps/${encodedCpid}/sends?${countParams}`), - ], - ); - - const [dispensersData, salesData, transfersData] = await Promise.all([ - dispensersCount.json(), - salesCount.json(), - transfersCount.json(), - ]); - - setTotalCounts({ - dispensers: dispensersData.total || 0, - sales: salesData.total || 0, - transfers: transfersData.total || 0, - }); - } - - console.log(`Fetching ${selectedTab} data for page ${pageNum}`); - const params = new URLSearchParams({ - page: pageNum.toString(), - limit: PAGE_SIZE.toString(), - sort: "DESC", - }); - - let response; - - switch (selectedTab) { - case "dispensers": { - response = await fetch( - `/api/v2/stamps/${encodedCpid}/dispensers?${params}`, - ); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - console.log("Dispensers data:", data); - if (data.data) { - setDispensers((prev) => - pageNum === 1 ? data.data : [...prev, ...data.data] - ); - setHasMore(data.data.length === PAGE_SIZE); - } - break; - } - case "sales": { - const [dispenseResponse, dispensersResponse] = await Promise.all([ - fetch(`/api/v2/stamps/${encodedCpid}/dispenses?${params}`), - fetch( - `/api/v2/stamps/${encodedCpid}/dispensers?${new URLSearchParams( - { - limit: "1000", - sort: "DESC", - }, - )}`, - ), - ]); - - if (!dispenseResponse.ok) { - throw new Error(`HTTP error! status: ${dispenseResponse.status}`); - } - if (!dispensersResponse.ok) { - throw new Error( - `HTTP error! status: ${dispensersResponse.status}`, - ); - } - - const [dispenseData, dispensersData] = await Promise.all([ - dispenseResponse.json(), - dispensersResponse.json(), - ]); - - console.log("Sales data:", { - dispenses: dispenseData, - dispensers: dispensersData, - }); - - if (dispenseData.data) { - setDispenses((prev) => - pageNum === 1 - ? dispenseData.data - : [...prev, ...dispenseData.data] - ); - setHasMore(dispenseData.data.length === PAGE_SIZE); - } - if (dispensersData.data) { - setDispensers(dispensersData.data); - } - break; - } - case "transfers": { - response = await fetch( - `/api/v2/stamps/${encodedCpid}/sends?${params}`, - ); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - console.log("Transfers data:", data); - if (data.data) { - setSends((prev) => - pageNum === 1 ? data.data : [...prev, ...data.data] - ); - setHasMore(data.data.length === PAGE_SIZE); - } - break; - } - } - } catch (error) { - console.error("Error fetching data:", error); - setHasMore(false); - } finally { - setIsLoading(false); - } - }, - [cpid, hasMore, isLoading, selectedTab], - ); - - const handleTabChange = useCallback((newTab: TabType) => { - if (selectedTab === newTab) return; - setSelectedTab(newTab); - }, [selectedTab]); - - // Remove the separate effects and combine into one - useEffect(() => { - if (!selectedTab) return; - - setPage(1); - setHasMore(true); - const timer = setTimeout(() => { - fetchData(1, true); - }, 0); - - return () => clearTimeout(timer); - }, [selectedTab]); // Only depend on selectedTab change - - const handleScroll = (e: Event) => { - const target = e.target as HTMLDivElement; - if ( - target.scrollHeight - target.scrollTop === target.clientHeight && - !isLoading && - hasMore - ) { - const nextPage = page + 1; - setPage(nextPage); - fetchData(nextPage); - } - }; - - const dispensesWithRates = mapDispensesWithRates(dispenses, dispensers); - - const renderTabContent = () => { - switch (selectedTab) { - case "dispensers": { - return ; - } - case "sales": { - return ; - } - case "transfers": { - return ; - } - } - }; - const dataLabel = - "text-base mobileLg:text-lg font-light text-stamp-grey-darker uppercase"; - const dataValueXLlink = - "text-3xl mobileLg:text-4xl font-black text-stamp-grey -mt-1"; - - // Update getTabsWithCounts to use totalCounts - function getTabsWithCounts() { - return [ - { - id: "dispensers", - label: ( -
- LISTINGS -
- {totalCounts.dispensers} -
-
- ), - }, - { - id: "sales", - label: ( -
- SALES -
- {totalCounts.sales} -
-
- ), - }, - { - id: "transfers", - label: ( -
- TRANSFERS -
- {totalCounts.transfers} -
-
- ), - }, - ] as const; - } - - return ( -
-
- {getTabsWithCounts().map(({ id, label }) => ( -

handleTabChange(id as TabType)} - > - {label} -

- ))} -
-
- {renderTabContent()} - {isLoading &&
Loading...
} -
-
- ); -}