diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index e8ff412..3b60937 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -61,6 +61,13 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/nfts-by-account")), files: ["registry/default/ui/nfts-by-account.tsx"], }, + "trade-ranking": { + name: "trade-ranking", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/trade-ranking")), + files: ["registry/default/ui/trade-ranking.tsx"], + }, "nft-card-demo": { name: "nft-card-demo", type: "components:example", @@ -96,5 +103,12 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/example/nfts-by-account-collection-demo")), files: ["registry/default/example/nfts-by-account-collection-demo.tsx"], }, + "trade-ranking-demo": { + name: "trade-ranking-demo", + type: "components:example", + registryDependencies: [""], + component: React.lazy(() => import("@/registry/default/example/trade-ranking-demo")), + files: ["registry/default/example/trade-ranking-demo.tsx"], + }, }, } diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts index 28329d7..047a034 100644 --- a/apps/www/config/docs.ts +++ b/apps/www/config/docs.ts @@ -116,6 +116,11 @@ export const docsConfig: DocsConfig = { href: "/docs/components/nfts-by-account", items: [], }, + { + title: "Trade Ranking", + href: "/docs/components/trade-ranking", + items: [], + }, ], }, ], diff --git a/apps/www/content/docs/components/nfts-by-account.mdx b/apps/www/content/docs/components/nfts-by-account.mdx index 7f1cb98..518124e 100644 --- a/apps/www/content/docs/components/nfts-by-account.mdx +++ b/apps/www/content/docs/components/nfts-by-account.mdx @@ -2,9 +2,6 @@ title: NFTs by Account Showcase description: This component dynamically displays NFTs associated with a specific account address, allowing users to view NFT metadata including images, names, and collection details. Designed to fetch and render NFT data directly from the blockchain, it supports pagination for a user-friendly browsing experience. component: true ---- -To align the documentation with your updated component that focuses on displaying NFTs by account with pagination support, here's the revised version that incorporates the additional functionality and nuances of your new implementation: - --- diff --git a/apps/www/content/docs/components/trade-ranking.mdx b/apps/www/content/docs/components/trade-ranking.mdx new file mode 100644 index 0000000..bc8643d --- /dev/null +++ b/apps/www/content/docs/components/trade-ranking.mdx @@ -0,0 +1,75 @@ +--- +title: Trade Ranking Showcase +description: This component dynamically displays trading rankings for NFT collections, allowing users to view detailed metrics such as collection statistics, trading volumes, and prices. It supports customizable time frames, sorting options, and a responsive design for an optimal browsing experience. Utilizing the NFTScan API, it ensures up-to-date information is presented through an engaging and interactive UI. +component: true +--- + + + + + + + CLI + Manual + + + + +```bash +npx code100x-ui@latest add trade-ranking +``` + + + + + + + +Copy the provided code snippet and paste it into your project. + + + +Ensure the import paths are correctly adjusted to match your project's structure. + + + + + + + +#### Environment Setup + +The `TradeRanking` component requires the `NEXT_PUBLIC_NFTSCAN_KEY` environment variable. This key is used to fetch trading data via the NFTScan API. + +Add this line to your `.env` file: + +```env +NEXT_PUBLIC_NFTSCAN_KEY=your_nftscan_api_key_here +``` + +Replace `your_nftscan_api_key_here` with your actual API key, which you can obtain by registering at the [NFTScan developers portal](https://developer.nftscan.com/). + +#### Usage + +To use the `TradeRanking` component, first import it into your project: + +```tsx +import { TradeRanking } from "@/components/ui/trade-ranking" +``` + +Then, include it in your UI, specifying any desired props for customization: + +```tsx + +``` + +#### Props + +- `time`: Time frame for trading data (`"1d"`, `"15m"`, `"30m"`, `"1h"`, `"6h"`, `"12h"`). +- `sortBy`: Metric to sort by (`"volume"` or `"sales"`). +- `sort_order`: Sorting order (`"asc"` or `"desc"`). + diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index ef65a2a..74caf26 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -77,5 +77,15 @@ "ui/nfts-by-account.tsx" ], "type": "components:ui" + }, + { + "name": "trade-ranking", + "dependencies": [ + "loading" + ], + "files": [ + "ui/trade-ranking.tsx" + ], + "type": "components:ui" } ] \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/trade-ranking.json b/apps/www/public/registry/styles/default/trade-ranking.json new file mode 100644 index 0000000..23534b3 --- /dev/null +++ b/apps/www/public/registry/styles/default/trade-ranking.json @@ -0,0 +1,13 @@ +{ + "name": "trade-ranking", + "dependencies": [ + "loading" + ], + "files": [ + { + "name": "trade-ranking.tsx", + "content": "import { useEffect, useState } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\n\n\nimport { cn } from \"@/lib/utils\";\n\n\n\nimport { Card, CardContent, CardHeader } from \"./card\";\nimport Loading from \"./loading\";\n\n\nconst tradeRankingVariants = cva(\n \"inline-flex flex-col rounded-lg border p-4 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2\",\n {\n variants: {\n variant: {\n default: \"border-gray-200 bg-white text-black hover:bg-gray-50\",\n dark: \"border-gray-700 bg-gray-800 text-white hover:bg-gray-700\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\ninterface TradeRankingProps\n extends React.HTMLAttributes,\n VariantProps {\n time?: \"1d\" | \"15m\" | \"30m\" | \"1h\" | \"6h\" | \"12h\"\n sortBy?: \"volume\" | \"sales\"\n sort_order?: \"asc\" | \"desc\"\n}\n\nconst TradeRanking = ({\n className,\n variant,\n time = \"1d\",\n sortBy = \"volume\",\n sort_order = \"desc\",\n ...props\n}: TradeRankingProps) => {\n const [tradeRanking, setTradeRanking] = useState(null)\n\n const [loading, setLoading] = useState(false)\n useEffect(() => {\n setLoading(true)\n const cacheKeyPrefix = `tradingRanking`\n const cachedKey = `${cacheKeyPrefix}-${time}-${sortBy}-${sort_order}`\n const fetchCollectionData = async () => {\n const cachedData = sessionStorage.getItem(cachedKey)\n if (cachedData) {\n setTradeRanking(JSON.parse(cachedData))\n } else {\n let url = `https://solanaapi.nftscan.com/api/sol/statistics/ranking/trade?time=${time}&sort_field=${sortBy}&sort_direction=${sort_order}`\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"*/*\",\n \"X-API-KEY\": process.env.NEXT_PUBLIC_NFTSCAN_KEY!,\n },\n })\n const jsonData = await response.json()\n if (jsonData.code === 200) {\n try {\n sessionStorage.setItem(cachedKey, JSON.stringify(jsonData.data))\n } catch (e) {\n clearCache(cacheKeyPrefix)\n sessionStorage.setItem(cachedKey, JSON.stringify(jsonData.data))\n }\n setTradeRanking(jsonData.data)\n } else {\n console.error(\"Failed to fetch collection data:\", jsonData.message)\n }\n } catch (error) {\n console.error(\"Error fetching collection data:\", error)\n }\n }\n setLoading(false)\n }\n\n fetchCollectionData()\n }, [sortBy, sort_order, time])\n\n const clearCache = (prefix: string) => {\n for (let i = 0; i < sessionStorage.length; i++) {\n const key = sessionStorage.key(i)\n if (key && key.startsWith(prefix)) {\n sessionStorage.removeItem(key)\n }\n }\n }\n\n return (\n
\n {loading || !tradeRanking ? (\n \n ) : (\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {tradeRanking.map((collection: any) => (\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n ))}\n \n
CollectionLowest PriceAverage PriceHighest PriceVolumeSalesMint Price TotalMint Gas FeeItems TotalOwners TotalVolume ChangeAverage Price ChangeMarket CapMarket TrendMint Average Price
\n {collection.logo_url && (\n \n )}\n {collection.collection}\n {collection.lowest_price}{collection.average_price}{collection.highest_price}{collection.volume}{collection.sales}{collection.mint_price_total}{collection.mint_gas_fee}{collection.items_total}{collection.owners_total}{collection.volume_change}{collection.average_price_change}{collection.market_cap}{collection.market_trend}{collection.mint_average_price}
\n
\n )}\n
\n )\n}\nTradeRanking.displayName = \"TradeRanking\"\nexport { TradeRanking, tradeRankingVariants }" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/registry/default/example/trade-ranking-demo.tsx b/apps/www/registry/default/example/trade-ranking-demo.tsx new file mode 100644 index 0000000..a62fc14 --- /dev/null +++ b/apps/www/registry/default/example/trade-ranking-demo.tsx @@ -0,0 +1,7 @@ +import { TradeRanking } from "../ui/trade-ranking" + +const TradeRankingDemo = () => { + return +} + +export default TradeRankingDemo diff --git a/apps/www/registry/default/ui/trade-ranking.tsx b/apps/www/registry/default/ui/trade-ranking.tsx new file mode 100644 index 0000000..0270bca --- /dev/null +++ b/apps/www/registry/default/ui/trade-ranking.tsx @@ -0,0 +1,160 @@ +import { useEffect, useState } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +import { Card, CardContent, CardHeader } from "./card" +import Loading from "./loading" + +const tradeRankingVariants = cva( + "inline-flex flex-col rounded-lg border p-4 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", + { + variants: { + variant: { + default: "border-gray-200 bg-white text-black hover:bg-gray-50", + dark: "border-gray-700 bg-gray-800 text-white hover:bg-gray-700", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +interface TradeRankingProps + extends React.HTMLAttributes, + VariantProps { + time?: "1d" | "15m" | "30m" | "1h" | "6h" | "12h" + sortBy?: "volume" | "sales" + sort_order?: "asc" | "desc" +} + +const TradeRanking = ({ + className, + variant, + time = "1d", + sortBy = "volume", + sort_order = "desc", + ...props +}: TradeRankingProps) => { + const [tradeRanking, setTradeRanking] = useState(null) + + const [loading, setLoading] = useState(false) + useEffect(() => { + setLoading(true) + const cacheKeyPrefix = `tradingRanking` + const cachedKey = `${cacheKeyPrefix}-${time}-${sortBy}-${sort_order}` + const fetchCollectionData = async () => { + const cachedData = sessionStorage.getItem(cachedKey) + if (cachedData) { + setTradeRanking(JSON.parse(cachedData)) + } else { + let url = `https://solanaapi.nftscan.com/api/sol/statistics/ranking/trade?time=${time}&sort_field=${sortBy}&sort_direction=${sort_order}` + + try { + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "*/*", + "X-API-KEY": process.env.NEXT_PUBLIC_NFTSCAN_KEY!, + }, + }) + const jsonData = await response.json() + if (jsonData.code === 200) { + try { + sessionStorage.setItem(cachedKey, JSON.stringify(jsonData.data)) + } catch (e) { + clearCache(cacheKeyPrefix) + sessionStorage.setItem(cachedKey, JSON.stringify(jsonData.data)) + } + setTradeRanking(jsonData.data) + } else { + console.error("Failed to fetch collection data:", jsonData.message) + } + } catch (error) { + console.error("Error fetching collection data:", error) + } + } + setLoading(false) + } + + fetchCollectionData() + }, [sortBy, sort_order, time]) + + const clearCache = (prefix: string) => { + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i) + if (key && key.startsWith(prefix)) { + sessionStorage.removeItem(key) + } + } + } + + return loading || !tradeRanking ? ( + + ) : ( +
+ + + + + + + + + + + + + + + + + + + + + + {tradeRanking.map((collection: any) => ( + + + + + + + + + + + + + + + + + + + ))} + +
CollectionLowest PriceAverage PriceHighest PriceVolumeSalesMint Price TotalMint Gas FeeItems TotalOwners TotalVolume ChangeAverage Price ChangeMarket CapMarket TrendMint Average Price
+ {collection.logo_url && ( + {collection.collection} + )} + {collection.collection} + {collection.lowest_price}{collection.average_price}{collection.highest_price}{collection.volume}{collection.sales}{collection.mint_price_total}{collection.mint_gas_fee}{collection.items_total}{collection.owners_total}{collection.volume_change}{collection.average_price_change}{collection.market_cap}{collection.market_trend}{collection.mint_average_price}
+
+ ) +} +TradeRanking.displayName = "TradeRanking" +export { TradeRanking, tradeRankingVariants } diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index 619caa8..937709c 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -53,6 +53,12 @@ const ui: Registry = [ dependencies: ["pagination", "loading"], files: ["ui/nfts-by-account.tsx"], }, + { + name: "trade-ranking", + type: "components:ui", + dependencies: ["loading"], + files: ["ui/trade-ranking.tsx"], + }, ] const example: Registry = [ @@ -86,6 +92,12 @@ const example: Registry = [ registryDependencies: [""], files: ["example/nfts-by-account-collection-demo.tsx"], }, + { + name: "trade-ranking-demo", + type: "components:example", + registryDependencies: [""], + files: ["example/trade-ranking-demo.tsx"], + }, ] export const registry: Registry = [...ui, ...example] \ No newline at end of file