Skip to content

Commit 0a652dc

Browse files
committed
feat: nfts by account component
1 parent 2324df9 commit 0a652dc

File tree

14 files changed

+417
-22
lines changed

14 files changed

+417
-22
lines changed

apps/www/__registry__/index.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,27 @@ export const Index: Record<string, any> = {
4040
component: React.lazy(() => import("@/registry/default/ui/pagination")),
4141
files: ["registry/default/ui/pagination.tsx"],
4242
},
43+
"card": {
44+
name: "card",
45+
type: "components:ui",
46+
registryDependencies: [""],
47+
component: React.lazy(() => import("@/registry/default/ui/card")),
48+
files: ["registry/default/ui/card.tsx"],
49+
},
4350
"collection-stats": {
4451
name: "collection-stats",
4552
type: "components:ui",
46-
registryDependencies: ["button"],
53+
registryDependencies: ["card","loading"],
4754
component: React.lazy(() => import("@/registry/default/ui/collection-stats")),
4855
files: ["registry/default/ui/collection-stats.tsx"],
4956
},
57+
"nfts-by-account": {
58+
name: "nfts-by-account",
59+
type: "components:ui",
60+
registryDependencies: undefined,
61+
component: React.lazy(() => import("@/registry/default/ui/nfts-by-account")),
62+
files: ["registry/default/ui/nfts-by-account.tsx"],
63+
},
5064
"nft-card-demo": {
5165
name: "nft-card-demo",
5266
type: "components:example",
@@ -68,5 +82,19 @@ export const Index: Record<string, any> = {
6882
component: React.lazy(() => import("@/registry/default/example/collection-stats-demo")),
6983
files: ["registry/default/example/collection-stats-demo.tsx"],
7084
},
85+
"nfts-by-account-demo": {
86+
name: "nfts-by-account-demo",
87+
type: "components:example",
88+
registryDependencies: [""],
89+
component: React.lazy(() => import("@/registry/default/example/nfts-by-account-demo")),
90+
files: ["registry/default/example/nfts-by-account-demo.tsx"],
91+
},
92+
"nfts-by-account-collection-demo": {
93+
name: "nfts-by-account-collection-demo",
94+
type: "components:example",
95+
registryDependencies: [""],
96+
component: React.lazy(() => import("@/registry/default/example/nfts-by-account-collection-demo")),
97+
files: ["registry/default/example/nfts-by-account-collection-demo.tsx"],
98+
},
7199
},
72100
}

apps/www/config/docs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ export const docsConfig: DocsConfig = {
111111
href: "/docs/components/collection-stats",
112112
items: [],
113113
},
114+
{
115+
title: "Nfts by Account",
116+
href: "/docs/components/nfts-by-account",
117+
items: [],
118+
},
114119
],
115120
},
116121
],
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
title: NFTs by Account Showcase
3+
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.
4+
component: true
5+
---
6+
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:
7+
8+
---
9+
<ComponentPreview name="nfts-by-account-demo" />
10+
11+
#### Installation
12+
13+
Choose either CLI or manual installation to add the NFTs by Account component to your project, following the steps below based on your preference.
14+
15+
<Tabs defaultValue="cli">
16+
17+
<TabsList>
18+
<TabsTrigger value="cli">CLI</TabsTrigger>
19+
<TabsTrigger value="manual">Manual</TabsTrigger>
20+
</TabsList>
21+
22+
<TabsContent value="cli">
23+
24+
```bash
25+
npx code100x-ui@latest add nfts-by-account
26+
```
27+
28+
</TabsContent>
29+
30+
<TabsContent value="manual">
31+
32+
<Steps>
33+
34+
<Step>Manually copy and paste the provided code snippet into your project.</Step>
35+
36+
<ComponentSource name="nfts-by-account" />
37+
38+
<Step>Adjust the import paths to ensure they align with your project's structure.</Step>
39+
40+
</Steps>
41+
42+
</TabsContent>
43+
44+
</Tabs>
45+
46+
#### Environment Setup
47+
48+
To utilize the `NftsByAccount` component effectively, you must have the `NEXT_PUBLIC_NFTSCAN_KEY` environment variable configured within your project. This key enables the fetching of NFT data via the NFTScan API.
49+
50+
Ensure your `.env` file at the root of your project contains the following line:
51+
52+
```env
53+
NEXT_PUBLIC_NFTSCAN_KEY=your_nftscan_api_key_here
54+
```
55+
56+
Replace `your_nftscan_api_key_here` with the actual API key obtained from the NFTScan developers portal. Visit [NFTScan developers portal](https://developer.nftscan.com/) to register and generate an API key.
57+
58+
#### Usage
59+
60+
Begin by importing the `NftsByAccount` component:
61+
62+
```tsx
63+
import { NftsByAccount } from "@/components/ui/nfts-by-account"
64+
```
65+
66+
Then, incorporate it into your project, specifying the necessary props:
67+
68+
```tsx
69+
<NftsByAccount
70+
accountAddress="SolanaAccountAddressHere"
71+
collectionName="OptionalCollectionName"
72+
limit={4}
73+
/>
74+
```
75+
76+
#### Props
77+
78+
- `accountAddress`: The blockchain account address to fetch NFTs for.
79+
- `collectionName`: (Optional) Specify a collection name to filter NFTs within the account.
80+
- `limit`: Number of NFTs to display per page. Defaults to 2.
81+
82+
#### Examples
83+
84+
##### NFTs Collection by Account
85+
86+
<ComponentPreview name="nfts-by-account-collection-demo" />
87+
88+

apps/www/public/registry/index.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,36 @@
4646
],
4747
"type": "components:ui"
4848
},
49+
{
50+
"name": "card",
51+
"registryDependencies": [
52+
""
53+
],
54+
"files": [
55+
"ui/card.tsx"
56+
],
57+
"type": "components:ui"
58+
},
4959
{
5060
"name": "collection-stats",
5161
"registryDependencies": [
52-
"button"
62+
"card",
63+
"loading"
5364
],
5465
"files": [
5566
"ui/collection-stats.tsx"
5667
],
5768
"type": "components:ui"
69+
},
70+
{
71+
"name": "nfts-by-account",
72+
"dependencies": [
73+
"pagination",
74+
"loading"
75+
],
76+
"files": [
77+
"ui/nfts-by-account.tsx"
78+
],
79+
"type": "components:ui"
5880
}
5981
]

apps/www/public/registry/styles/default/card.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
22
"name": "card",
3+
"registryDependencies": [
4+
""
5+
],
36
"files": [
47
{
58
"name": "card.tsx",
6-
"content": "import * as React from \"react\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst Card = React.forwardRef<\r\n HTMLDivElement,\r\n React.HTMLAttributes<HTMLDivElement>\r\n>(({ className, ...props }, ref) => (\r\n <div\r\n ref={ref}\r\n className={cn(\r\n \"rounded-lg border bg-card text-card-foreground shadow-sm\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n))\r\nCard.displayName = \"Card\"\r\n\r\nconst CardHeader = React.forwardRef<\r\n HTMLDivElement,\r\n React.HTMLAttributes<HTMLDivElement>\r\n>(({ className, ...props }, ref) => (\r\n <div\r\n ref={ref}\r\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\r\n {...props}\r\n />\r\n))\r\nCardHeader.displayName = \"CardHeader\"\r\n\r\nconst CardTitle = React.forwardRef<\r\n HTMLParagraphElement,\r\n React.HTMLAttributes<HTMLHeadingElement>\r\n>(({ className, ...props }, ref) => (\r\n <h3\r\n ref={ref}\r\n className={cn(\r\n \"text-2xl font-semibold leading-none tracking-tight\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n))\r\nCardTitle.displayName = \"CardTitle\"\r\n\r\nconst CardDescription = React.forwardRef<\r\n HTMLParagraphElement,\r\n React.HTMLAttributes<HTMLParagraphElement>\r\n>(({ className, ...props }, ref) => (\r\n <p\r\n ref={ref}\r\n className={cn(\"text-sm text-muted-foreground\", className)}\r\n {...props}\r\n />\r\n))\r\nCardDescription.displayName = \"CardDescription\"\r\n\r\nconst CardContent = React.forwardRef<\r\n HTMLDivElement,\r\n React.HTMLAttributes<HTMLDivElement>\r\n>(({ className, ...props }, ref) => (\r\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\r\n))\r\nCardContent.displayName = \"CardContent\"\r\n\r\nconst CardFooter = React.forwardRef<\r\n HTMLDivElement,\r\n React.HTMLAttributes<HTMLDivElement>\r\n>(({ className, ...props }, ref) => (\r\n <div\r\n ref={ref}\r\n className={cn(\"flex items-center p-6 pt-0\", className)}\r\n {...props}\r\n />\r\n))\r\nCardFooter.displayName = \"CardFooter\"\r\n\r\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\r\n"
9+
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n className\n )}\n {...props}\n />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n {...props}\n />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn(\n \"text-2xl font-semibold leading-none tracking-tight\",\n className\n )}\n {...props}\n />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n <p\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center p-6 pt-0\", className)}\n {...props}\n />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n"
710
}
811
],
912
"type": "components:ui"

apps/www/public/registry/styles/default/collection-stats.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
22
"name": "collection-stats",
33
"registryDependencies": [
4-
"button"
4+
"card",
5+
"loading"
56
],
67
"files": [
78
{
89
"name": "collection-stats.tsx",
9-
"content": "import { useEffect, useState } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Card, CardContent, CardHeader } from \"./card\"\nimport Loading from \"./loading\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\n\nconst collectionStatsVariants = 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 CollectionStatsProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof collectionStatsVariants> {\n collectionName: string\n limit?: number\n}\n\nconst CollectionStats = ({\n className,\n variant,\n collectionName,\n ...props\n}: CollectionStatsProps) => {\n const [collections, setCollections] = useState<any>(null)\n\n const [loading, setLoading] = useState(false)\n useEffect(() => {\n setLoading(true)\n const cacheKeyPrefix = `collectionData-${collectionName}`\n const fetchCollectionData = async () => {\n const cachedData = sessionStorage.getItem(cacheKeyPrefix)\n if (cachedData) {\n setCollections(JSON.parse(cachedData))\n } else {\n let url = `https://solanaapi.nftscan.com/api/sol/assets/collection/${collectionName}?show_attribute=false`\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(\n cacheKeyPrefix,\n JSON.stringify(jsonData.data)\n )\n } catch (e) {\n clearCollectionCache(cacheKeyPrefix)\n sessionStorage.setItem(\n cacheKeyPrefix,\n JSON.stringify(jsonData.data)\n )\n }\n setCollections(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 }, [collectionName])\n\n const clearCollectionCache = (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 ? (\n <Loading />\n ) : (\n <div className=\"grid md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-6\">\n {collections?.content?.map((collection: any) => (\n <Card\n key=\"1\"\n className=\"max-w-sm bg-[#242c37] text-white rounded-lg shadow-md dark:bg-[#1a202c]\"\n >\n <CardHeader className=\"flex items-center justify-between p-4 border-b border-gray-600\">\n <Avatar>\n <AvatarImage\n src=\"https://github.com/code100x.png\"\n alt=\"@code100x\"\n />\n <AvatarFallback>CN</AvatarFallback>\n </Avatar>\n <Button className=\"bg-green-500 text-xs text-white px-2 py-1 rounded-md\">\n OKAY\n </Button>\n </CardHeader>\n <CardContent className=\"p-4\">\n <h2 className=\"text-lg font-bold\">collection.image</h2>\n <div className=\"grid grid-cols-2 gap-4 mt-4 text-sm\">\n <div>\n <p className=\"font-semibold\">3.84</p>\n <p className=\"text-gray-400\">Lowest</p>\n </div>\n <div>\n <p className=\"font-semibold\">3.38M</p>\n <p className=\"text-gray-400\">Volume</p>\n </div>\n <div>\n <p className=\"font-semibold\">4.2K</p>\n <p className=\"text-gray-400\">Owners</p>\n </div>\n <div>\n <p className=\"font-semibold\">10K</p>\n <p className=\"text-gray-400\">Items</p>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n </div>\n )\n}\nCollectionStats.displayName = \"CollectionStats\"\nexport { CollectionStats, collectionStatsVariants }\n"
10+
"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 collectionStatsVariants = 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 CollectionStatsProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof collectionStatsVariants> {\n collectionName: string\n limit?: number\n}\n\nconst Stat = ({ title, value }: { title: string; value: string }) => {\n return (\n <div>\n <p className=\"font-semibold text-black dark:text-white\">{value}</p>\n <p className=\"text-gray-600 dark:text-gray-300\">{title}</p>\n </div>\n )\n}\n\nconst CollectionStats = ({\n className,\n variant,\n collectionName,\n ...props\n}: CollectionStatsProps) => {\n const [collectionStats, setCollectionStats] = useState<any>(null)\n\n const [loading, setLoading] = useState(false)\n useEffect(() => {\n setLoading(true)\n const cacheKeyPrefix = `collectionData-stats`\n const cachedKey = `${cacheKeyPrefix}-${collectionName}`\n const fetchCollectionData = async () => {\n const cachedData = sessionStorage.getItem(cachedKey)\n if (cachedData) {\n setCollectionStats(JSON.parse(cachedData))\n } else {\n let url = `https://solanaapi.nftscan.com/api/sol/statistics/collection/${collectionName}`\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 setCollectionStats(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 }, [collectionName])\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 || !collectionStats ? (\n <Loading />\n ) : (\n <Card key=\"1\" className=\"max-w-sm text-white rounded-xl shadow-md \">\n <CardHeader\n className=\"flex items-center justify-between p-4 border-b border-gray-600 bg-contain h-[100px]\"\n style={{ backgroundImage: `url(${collectionStats.logo_url})` }}\n />\n <CardContent className=\"p-4\">\n <h2 className=\"text-lg font-bold\">{collectionStats.collection}</h2>\n <div className=\"grid grid-cols-2 gap-4 mt-4 text-sm\">\n <Stat\n title=\"Lowest Price (24h)\"\n value={collectionStats.lowest_price_24h}\n />\n <Stat\n title=\"Average Price (24h)\"\n value={collectionStats.average_price_24h.toFixed(2)}\n />\n <Stat title=\"Sales (24h)\" value={collectionStats.sales_24h} />\n <Stat\n title=\"Highest Price\"\n value={collectionStats.highest_price}\n />\n <Stat\n title=\"Volume (24h)\"\n value={collectionStats.volume_24h.toFixed(3)}\n />\n <Stat\n title=\"Total Volume\"\n value={collectionStats.total_volume.toLocaleString()}\n />\n <Stat title=\"Owners\" value={collectionStats.owners_total} />\n <Stat title=\"Items\" value={collectionStats.items_total} />\n </div>\n </CardContent>\n </Card>\n )}\n </div>\n )\n}\nCollectionStats.displayName = \"CollectionStats\"\nexport { CollectionStats, collectionStatsVariants }"
1011
}
1112
],
1213
"type": "components:ui"

0 commit comments

Comments
 (0)