-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
296 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
--- | ||
|
||
<ComponentPreview name="trade-ranking-demo" /> | ||
|
||
<Tabs defaultValue="cli"> | ||
|
||
<TabsList> | ||
<TabsTrigger value="cli">CLI</TabsTrigger> | ||
<TabsTrigger value="manual">Manual</TabsTrigger> | ||
</TabsList> | ||
|
||
<TabsContent value="cli"> | ||
|
||
```bash | ||
npx code100x-ui@latest add trade-ranking | ||
``` | ||
|
||
</TabsContent> | ||
|
||
<TabsContent value="manual"> | ||
|
||
<Steps> | ||
|
||
<Step>Copy the provided code snippet and paste it into your project.</Step> | ||
|
||
<ComponentSource name="trade-ranking" /> | ||
|
||
<Step>Ensure the import paths are correctly adjusted to match your project's structure.</Step> | ||
|
||
</Steps> | ||
|
||
</TabsContent> | ||
|
||
</Tabs> | ||
|
||
#### 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 | ||
<TradeRanking | ||
time="1d" | ||
sortBy="volume" | ||
sort_order="desc" | ||
/> | ||
``` | ||
|
||
#### 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"`). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
apps/www/public/registry/styles/default/trade-ranking.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HTMLDivElement>,\n VariantProps<typeof tradeRankingVariants> {\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<any>(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 <div>\n {loading || !tradeRanking ? (\n <Loading />\n ) : (\n <div className=\"overflow-auto border sm:rounded-lg\">\n <table className=\"min-w-full\">\n <thead>\n <tr className=\"border-b text-sm font-medium text-gray-500 dark:text-gray-400\">\n <th className=\"px-4 py-3\">Collection</th>\n <th className=\"px-4 py-3\">Lowest Price</th>\n <th className=\"px-4 py-3\">Average Price</th>\n <th className=\"px-4 py-3\">Highest Price</th>\n <th className=\"px-4 py-3\">Volume</th>\n <th className=\"px-4 py-3\">Sales</th>\n <th className=\"px-4 py-3\">Mint Price Total</th>\n <th className=\"px-4 py-3\">Mint Gas Fee</th>\n <th className=\"px-4 py-3\">Items Total</th>\n <th className=\"px-4 py-3\">Owners Total</th>\n <th className=\"px-4 py-3\">Volume Change</th>\n <th className=\"px-4 py-3\">Average Price Change</th>\n <th className=\"px-4 py-3\">Market Cap</th>\n <th className=\"px-4 py-3\">Market Trend</th>\n <th className=\"px-4 py-3\">Mint Average Price</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-gray-200 dark:divide-gray-800\">\n {tradeRanking.map((collection: any) => (\n <tr className=\"text-gray-500 dark:text-gray-400\">\n <td className=\"flex items-center gap-4 px-4 py-3\">\n {collection.logo_url && (\n <img\n alt={collection.collection}\n className=\"rounded-full\"\n height=\"40\"\n src={`https://images.hsingh.site/?url=${collection.logo_url}&output=webp&width=40&q=80w=40&h=40`}\n style={{\n aspectRatio: \"40/40\",\n objectFit: \"cover\",\n }}\n width=\"40\"\n />\n )}\n {collection.collection}\n </td>\n <td className=\"px-4 py-3\">{collection.lowest_price}</td>\n <td className=\"px-4 py-3\">{collection.average_price}</td>\n <td className=\"px-4 py-3\">{collection.highest_price}</td>\n <td className=\"px-4 py-3\">{collection.volume}</td>\n <td className=\"px-4 py-3\">{collection.sales}</td>\n\n <td className=\"px-4 py-3\">{collection.mint_price_total}</td>\n <td className=\"px-4 py-3\">{collection.mint_gas_fee}</td>\n <td className=\"px-4 py-3\">{collection.items_total}</td>\n <td className=\"px-4 py-3\">{collection.owners_total}</td>\n <td className=\"px-4 py-3\">{collection.volume_change}</td>\n <td className=\"px-4 py-3\">{collection.average_price_change}</td>\n <td className=\"px-4 py-3\">{collection.market_cap}</td>\n <td className=\"px-4 py-3\">{collection.market_trend}</td>\n <td className=\"px-4 py-3\">{collection.mint_average_price}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n )\n}\nTradeRanking.displayName = \"TradeRanking\"\nexport { TradeRanking, tradeRankingVariants }" | ||
} | ||
], | ||
"type": "components:ui" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { TradeRanking } from "../ui/trade-ranking" | ||
|
||
const TradeRankingDemo = () => { | ||
return <TradeRanking time="1d" sort_order="desc" sortBy="volume" /> | ||
} | ||
|
||
export default TradeRankingDemo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HTMLDivElement>, | ||
VariantProps<typeof tradeRankingVariants> { | ||
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<any>(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 ? ( | ||
<Loading /> | ||
) : ( | ||
<div className="overflow-auto border sm:rounded-lg"> | ||
<table className="min-w-full"> | ||
<thead> | ||
<tr className="border-b text-sm font-medium text-gray-500 dark:text-gray-400"> | ||
<th className="px-4 py-3">Collection</th> | ||
<th className="px-4 py-3">Lowest Price</th> | ||
<th className="px-4 py-3">Average Price</th> | ||
<th className="px-4 py-3">Highest Price</th> | ||
<th className="px-4 py-3">Volume</th> | ||
<th className="px-4 py-3">Sales</th> | ||
<th className="px-4 py-3">Mint Price Total</th> | ||
<th className="px-4 py-3">Mint Gas Fee</th> | ||
<th className="px-4 py-3">Items Total</th> | ||
<th className="px-4 py-3">Owners Total</th> | ||
<th className="px-4 py-3">Volume Change</th> | ||
<th className="px-4 py-3">Average Price Change</th> | ||
<th className="px-4 py-3">Market Cap</th> | ||
<th className="px-4 py-3">Market Trend</th> | ||
<th className="px-4 py-3">Mint Average Price</th> | ||
</tr> | ||
</thead> | ||
<tbody className="divide-y divide-gray-200 dark:divide-gray-800"> | ||
{tradeRanking.map((collection: any) => ( | ||
<tr className="text-gray-500 dark:text-gray-400"> | ||
<td className="flex items-center gap-4 px-4 py-3"> | ||
{collection.logo_url && ( | ||
<img | ||
alt={collection.collection} | ||
className="rounded-full" | ||
height="40" | ||
src={`https://images.hsingh.site/?url=${collection.logo_url}&output=webp&width=40&q=80w=40&h=40`} | ||
style={{ | ||
aspectRatio: "40/40", | ||
objectFit: "cover", | ||
}} | ||
width="40" | ||
/> | ||
)} | ||
{collection.collection} | ||
</td> | ||
<td className="px-4 py-3">{collection.lowest_price}</td> | ||
<td className="px-4 py-3">{collection.average_price}</td> | ||
<td className="px-4 py-3">{collection.highest_price}</td> | ||
<td className="px-4 py-3">{collection.volume}</td> | ||
<td className="px-4 py-3">{collection.sales}</td> | ||
|
||
<td className="px-4 py-3">{collection.mint_price_total}</td> | ||
<td className="px-4 py-3">{collection.mint_gas_fee}</td> | ||
<td className="px-4 py-3">{collection.items_total}</td> | ||
<td className="px-4 py-3">{collection.owners_total}</td> | ||
<td className="px-4 py-3">{collection.volume_change}</td> | ||
<td className="px-4 py-3">{collection.average_price_change}</td> | ||
<td className="px-4 py-3">{collection.market_cap}</td> | ||
<td className="px-4 py-3">{collection.market_trend}</td> | ||
<td className="px-4 py-3">{collection.mint_average_price}</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
) | ||
} | ||
TradeRanking.displayName = "TradeRanking" | ||
export { TradeRanking, tradeRankingVariants } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters