Skip to content

Commit

Permalink
feat: nfts by account component
Browse files Browse the repository at this point in the history
  • Loading branch information
siinghd committed Apr 3, 2024
1 parent 2324df9 commit 0a652dc
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 22 deletions.
30 changes: 29 additions & 1 deletion apps/www/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,27 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/ui/pagination")),
files: ["registry/default/ui/pagination.tsx"],
},
"card": {
name: "card",
type: "components:ui",
registryDependencies: [""],
component: React.lazy(() => import("@/registry/default/ui/card")),
files: ["registry/default/ui/card.tsx"],
},
"collection-stats": {
name: "collection-stats",
type: "components:ui",
registryDependencies: ["button"],
registryDependencies: ["card","loading"],
component: React.lazy(() => import("@/registry/default/ui/collection-stats")),
files: ["registry/default/ui/collection-stats.tsx"],
},
"nfts-by-account": {
name: "nfts-by-account",
type: "components:ui",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/ui/nfts-by-account")),
files: ["registry/default/ui/nfts-by-account.tsx"],
},
"nft-card-demo": {
name: "nft-card-demo",
type: "components:example",
Expand All @@ -68,5 +82,19 @@ export const Index: Record<string, any> = {
component: React.lazy(() => import("@/registry/default/example/collection-stats-demo")),
files: ["registry/default/example/collection-stats-demo.tsx"],
},
"nfts-by-account-demo": {
name: "nfts-by-account-demo",
type: "components:example",
registryDependencies: [""],
component: React.lazy(() => import("@/registry/default/example/nfts-by-account-demo")),
files: ["registry/default/example/nfts-by-account-demo.tsx"],
},
"nfts-by-account-collection-demo": {
name: "nfts-by-account-collection-demo",
type: "components:example",
registryDependencies: [""],
component: React.lazy(() => import("@/registry/default/example/nfts-by-account-collection-demo")),
files: ["registry/default/example/nfts-by-account-collection-demo.tsx"],
},
},
}
5 changes: 5 additions & 0 deletions apps/www/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/collection-stats",
items: [],
},
{
title: "Nfts by Account",
href: "/docs/components/nfts-by-account",
items: [],
},
],
},
],
Expand Down
88 changes: 88 additions & 0 deletions apps/www/content/docs/components/nfts-by-account.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
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:

---
<ComponentPreview name="nfts-by-account-demo" />

#### Installation

Choose either CLI or manual installation to add the NFTs by Account component to your project, following the steps below based on your preference.

<Tabs defaultValue="cli">

<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">

```bash
npx code100x-ui@latest add nfts-by-account
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Manually copy and paste the provided code snippet into your project.</Step>

<ComponentSource name="nfts-by-account" />

<Step>Adjust the import paths to ensure they align with your project's structure.</Step>

</Steps>

</TabsContent>

</Tabs>

#### Environment Setup

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.

Ensure your `.env` file at the root of your project contains the following line:

```env
NEXT_PUBLIC_NFTSCAN_KEY=your_nftscan_api_key_here
```

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.

#### Usage

Begin by importing the `NftsByAccount` component:

```tsx
import { NftsByAccount } from "@/components/ui/nfts-by-account"
```

Then, incorporate it into your project, specifying the necessary props:

```tsx
<NftsByAccount
accountAddress="SolanaAccountAddressHere"
collectionName="OptionalCollectionName"
limit={4}
/>
```

#### Props

- `accountAddress`: The blockchain account address to fetch NFTs for.
- `collectionName`: (Optional) Specify a collection name to filter NFTs within the account.
- `limit`: Number of NFTs to display per page. Defaults to 2.

#### Examples

##### NFTs Collection by Account

<ComponentPreview name="nfts-by-account-collection-demo" />


24 changes: 23 additions & 1 deletion apps/www/public/registry/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,36 @@
],
"type": "components:ui"
},
{
"name": "card",
"registryDependencies": [
""
],
"files": [
"ui/card.tsx"
],
"type": "components:ui"
},
{
"name": "collection-stats",
"registryDependencies": [
"button"
"card",
"loading"
],
"files": [
"ui/collection-stats.tsx"
],
"type": "components:ui"
},
{
"name": "nfts-by-account",
"dependencies": [
"pagination",
"loading"
],
"files": [
"ui/nfts-by-account.tsx"
],
"type": "components:ui"
}
]
5 changes: 4 additions & 1 deletion apps/www/public/registry/styles/default/card.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "card",
"registryDependencies": [
""
],
"files": [
{
"name": "card.tsx",
"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"
"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"
}
],
"type": "components:ui"
Expand Down
5 changes: 3 additions & 2 deletions apps/www/public/registry/styles/default/collection-stats.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "collection-stats",
"registryDependencies": [
"button"
"card",
"loading"
],
"files": [
{
"name": "collection-stats.tsx",
"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"
"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 }"
}
],
"type": "components:ui"
Expand Down
Loading

0 comments on commit 0a652dc

Please sign in to comment.