Merge branch 'develop' of git.lumeweb.com:LumeWeb/web3toybox.com into develop
This commit is contained in:
commit
fff1df134c
|
@ -1,30 +1,30 @@
|
|||
import { defineConfig } from "astro/config"
|
||||
import * as fs from "node:fs"
|
||||
import { defineConfig } from 'astro/config'
|
||||
import * as fs from 'node:fs'
|
||||
|
||||
import react from "@astrojs/react"
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import optimizer from "vite-plugin-optimizer"
|
||||
import react from '@astrojs/react'
|
||||
import tailwind from '@astrojs/tailwind'
|
||||
import optimizer from 'vite-plugin-optimizer'
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [react(), tailwind({ applyBaseStyles: false })],
|
||||
vite: {
|
||||
server: {
|
||||
https: {
|
||||
cert: fs.readFileSync("./.local-ssl/localhost.pem"),
|
||||
key: fs.readFileSync("./.local-ssl/localhost-key.pem")
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ["@lumeweb/libportal", "@lumeweb/libweb", "@lumeweb/libkernel"]
|
||||
},
|
||||
plugins: [
|
||||
optimizer({
|
||||
"node-fetch":
|
||||
"const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};"
|
||||
})
|
||||
]
|
||||
}
|
||||
integrations: [react(), tailwind({ applyBaseStyles: false })],
|
||||
vite: {
|
||||
server: process.env.MODE === 'development' ? {
|
||||
https: {
|
||||
cert: fs.readFileSync('./.local-ssl/localhost.pem'),
|
||||
key: fs.readFileSync('./.local-ssl/localhost-key.pem')
|
||||
},
|
||||
} : {},
|
||||
build: {
|
||||
minify: false
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ['@lumeweb/libportal', '@lumeweb/libweb', '@lumeweb/libkernel']
|
||||
},
|
||||
plugins: [
|
||||
optimizer({
|
||||
'node-fetch':
|
||||
'const e = undefined; export default e;export {e as Response, e as FormData, e as Blob};'
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "opposite-osiris",
|
||||
"name": "@lumeweb/web3toybox.com",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "opposite-osiris",
|
||||
"name": "@lumeweb/web3toybox.com",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^3.0.3",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "opposite-osiris",
|
||||
"name": "@lumeweb/web3toybox.com",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import "@lumeweb/sdk/lib/style.css"
|
||||
import "@/styles/global.css"
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import "@lumeweb/sdk/lib/style.css";
|
||||
import "@/styles/global.css";
|
||||
import React, {
|
||||
createContext,
|
||||
createRef,
|
||||
type ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from "react"
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
type AuthContextType,
|
||||
AuthProvider,
|
||||
|
@ -22,53 +22,49 @@ import {
|
|||
useAuth,
|
||||
useLumeStatus,
|
||||
useNetworks,
|
||||
LumeDashboardTrigger
|
||||
} from "@lumeweb/sdk"
|
||||
import * as kernel from "@lumeweb/libkernel/kernel"
|
||||
import { kernelLoaded } from "@lumeweb/libkernel/kernel"
|
||||
LumeDashboardTrigger,
|
||||
} from "@lumeweb/sdk";
|
||||
import * as kernel from "@lumeweb/libkernel/kernel";
|
||||
import { kernelLoaded } from "@lumeweb/libkernel/kernel";
|
||||
import {
|
||||
dnsClient,
|
||||
ethClient,
|
||||
ipfsClient,
|
||||
networkRegistryClient,
|
||||
peerDiscoveryClient,
|
||||
swarmClient
|
||||
} from "@/lib/clients"
|
||||
import { ethers } from "ethers"
|
||||
import * as ethersBytes from "@ethersproject/bytes"
|
||||
import { createProvider } from "@lumeweb/kernel-eth-client"
|
||||
swarmClient,
|
||||
} from "@/lib/clients";
|
||||
import { ethers } from "ethers";
|
||||
import * as ethersBytes from "@ethersproject/bytes";
|
||||
import { createProvider } from "@lumeweb/kernel-eth-client";
|
||||
// @ts-ignore
|
||||
import jdu from "json-data-uri"
|
||||
import { ERC721_ABI } from "@/lib/erc721-abi"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/components/ui/card"
|
||||
import LogoImg from "@/assets/lume-logo.png"
|
||||
let BOOT_FUNCTIONS: (() => Promise<any>)[] = []
|
||||
import jdu from "json-data-uri";
|
||||
import { ERC721_ABI } from "@/lib/erc721-abi";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import LogoImg from "@/assets/lume-logo.png";
|
||||
let BOOT_FUNCTIONS: (() => Promise<any>)[] = [];
|
||||
|
||||
export const AppContext = createContext<any>(undefined)
|
||||
export const AppContext = createContext<any>(undefined);
|
||||
|
||||
export function useApp() {
|
||||
const context = useContext(AppContext)
|
||||
const context = useContext(AppContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useApp must be used within an AppProvider")
|
||||
throw new Error("useApp must be used within an AppProvider");
|
||||
}
|
||||
|
||||
return context
|
||||
return context;
|
||||
}
|
||||
|
||||
interface AppProviderProps {
|
||||
children: ReactNode
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const provider = createProvider()
|
||||
const provider = createProvider();
|
||||
|
||||
const ERC721_TRANSFER_EVENT_SIGNATURE = ethers.id(
|
||||
"Transfer(address,address,uint256)"
|
||||
)
|
||||
);
|
||||
|
||||
async function findPotentialERC721Contracts(
|
||||
address: string
|
||||
|
@ -79,24 +75,24 @@ async function findPotentialERC721Contracts(
|
|||
topics: [
|
||||
ERC721_TRANSFER_EVENT_SIGNATURE,
|
||||
null,
|
||||
ethersBytes.hexZeroPad(address, 32)
|
||||
]
|
||||
})
|
||||
ethersBytes.hexZeroPad(address, 32),
|
||||
],
|
||||
});
|
||||
|
||||
const potentialContracts = new Set<string>()
|
||||
logs.forEach((log: any) => potentialContracts.add(log.address))
|
||||
const potentialContracts = new Set<string>();
|
||||
logs.forEach((log: any) => potentialContracts.add(log.address));
|
||||
|
||||
const confirmedERC721Contracts: string[] = []
|
||||
const confirmedERC721Contracts: string[] = [];
|
||||
for (let contractAddress of potentialContracts) {
|
||||
if (await isERC721(contractAddress)) {
|
||||
confirmedERC721Contracts.push(contractAddress)
|
||||
confirmedERC721Contracts.push(contractAddress);
|
||||
}
|
||||
}
|
||||
|
||||
return confirmedERC721Contracts
|
||||
return confirmedERC721Contracts;
|
||||
}
|
||||
|
||||
const TRANSFER_EVENT_SIGNATURE = ethers.id("Transfer(address,address,uint256)")
|
||||
const TRANSFER_EVENT_SIGNATURE = ethers.id("Transfer(address,address,uint256)");
|
||||
|
||||
async function fetchTokensViaTransferEvent(
|
||||
address: string,
|
||||
|
@ -109,93 +105,93 @@ async function fetchTokensViaTransferEvent(
|
|||
topics: [
|
||||
TRANSFER_EVENT_SIGNATURE,
|
||||
null,
|
||||
ethersBytes.hexZeroPad(address, 32)
|
||||
]
|
||||
})
|
||||
ethersBytes.hexZeroPad(address, 32),
|
||||
],
|
||||
});
|
||||
|
||||
const tokenIds: number[] = []
|
||||
const tokenIds: number[] = [];
|
||||
logs.forEach((log) => {
|
||||
if (log.topics && log.topics.length === 4) {
|
||||
const tokenIdBigNumber = ethers.toNumber(log.topics[3])
|
||||
tokenIds.push(tokenIdBigNumber)
|
||||
const tokenIdBigNumber = ethers.toNumber(log.topics[3]);
|
||||
tokenIds.push(tokenIdBigNumber);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return tokenIds
|
||||
return tokenIds;
|
||||
}
|
||||
|
||||
async function fetchOwnedNFTs(
|
||||
address: string,
|
||||
confirmedERC721Contracts: string[]
|
||||
): Promise<{ contract: string; tokenId: number; metadata: any }[]> {
|
||||
const ownedNFTs = []
|
||||
const ownedNFTs = [];
|
||||
|
||||
for (let contractAddress of confirmedERC721Contracts) {
|
||||
const contract = new ethers.Contract(contractAddress, ERC721_ABI, provider)
|
||||
let tokenIds: number[] = []
|
||||
const contract = new ethers.Contract(contractAddress, ERC721_ABI, provider);
|
||||
let tokenIds: number[] = [];
|
||||
|
||||
try {
|
||||
const balance = await contract.balanceOf(address)
|
||||
const balance = await contract.balanceOf(address);
|
||||
for (let i = 0; i < balance; i++) {
|
||||
const tokenId = await contract.tokenOfOwnerByIndex(address, i)
|
||||
tokenIds.push(tokenId.toNumber())
|
||||
const tokenId = await contract.tokenOfOwnerByIndex(address, i);
|
||||
tokenIds.push(tokenId.toNumber());
|
||||
}
|
||||
} catch (error) {
|
||||
// If tokenOfOwnerByIndex is not available, fall back to fetchTokensViaTransferEvent
|
||||
tokenIds = await fetchTokensViaTransferEvent(address, contractAddress)
|
||||
tokenIds = await fetchTokensViaTransferEvent(address, contractAddress);
|
||||
}
|
||||
|
||||
for (let tokenId of tokenIds) {
|
||||
try {
|
||||
const uri = await contract.tokenURI(tokenId)
|
||||
const uri = await contract.tokenURI(tokenId);
|
||||
// const metadata = await fetchMetadataFromURI(uri);
|
||||
ownedNFTs.push({
|
||||
contract: contractAddress,
|
||||
tokenId: tokenId,
|
||||
metadata: uri
|
||||
})
|
||||
metadata: uri,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Failed to fetch metadata for token ${tokenId} from contract ${contractAddress}: ${error.message}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ownedNFTs
|
||||
return ownedNFTs;
|
||||
}
|
||||
|
||||
async function isERC721(address: string): Promise<boolean> {
|
||||
const contract = new ethers.Contract(address, ERC721_ABI, provider)
|
||||
const contract = new ethers.Contract(address, ERC721_ABI, provider);
|
||||
try {
|
||||
// Try calling some ERC-721 methods to confirm if this is an ERC-721 contract.
|
||||
await contract.name()
|
||||
await contract.symbol()
|
||||
return true
|
||||
await contract.name();
|
||||
await contract.symbol();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
|
||||
return <AppContext.Provider value={{}}>{children}</AppContext.Provider>
|
||||
}
|
||||
return <AppContext.Provider value={{}}>{children}</AppContext.Provider>;
|
||||
};
|
||||
|
||||
async function boot(status: LumeStatusContextType, auth: AuthContextType) {
|
||||
kernel.init().then(() => {
|
||||
status.setInited(true)
|
||||
})
|
||||
status.setInited(true);
|
||||
});
|
||||
|
||||
await kernelLoaded()
|
||||
await kernelLoaded();
|
||||
|
||||
auth.setIsLoggedIn(true)
|
||||
auth.setIsLoggedIn(true);
|
||||
|
||||
BOOT_FUNCTIONS.push(
|
||||
async () =>
|
||||
await swarmClient.addRelay(
|
||||
"2d7ae1517caf4aae4de73c6d6f400765d2dd00b69d65277a29151437ef1c7d1d"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// IRC
|
||||
BOOT_FUNCTIONS.push(
|
||||
|
@ -203,40 +199,40 @@ async function boot(status: LumeStatusContextType, auth: AuthContextType) {
|
|||
await peerDiscoveryClient.register(
|
||||
"zrjHTx8tSQFWnmZ9JzK7XmJirqJQi2WRBLYp3fASaL2AfBQ"
|
||||
)
|
||||
)
|
||||
);
|
||||
BOOT_FUNCTIONS.push(
|
||||
async () => await networkRegistryClient.registerType("content")
|
||||
)
|
||||
);
|
||||
BOOT_FUNCTIONS.push(
|
||||
async () => await networkRegistryClient.registerType("blockchain")
|
||||
)
|
||||
BOOT_FUNCTIONS.push(async () => await ethClient.register())
|
||||
BOOT_FUNCTIONS.push(async () => await ipfsClient.register())
|
||||
);
|
||||
BOOT_FUNCTIONS.push(async () => await ethClient.register());
|
||||
BOOT_FUNCTIONS.push(async () => await ipfsClient.register());
|
||||
|
||||
const resolvers = [
|
||||
"zrjEYq154PS7boERAbRAKMyRGzAR6CTHVRG6mfi5FV4q9FA" // ENS
|
||||
]
|
||||
"zrjEYq154PS7boERAbRAKMyRGzAR6CTHVRG6mfi5FV4q9FA", // ENS
|
||||
];
|
||||
|
||||
for (const resolver of resolvers) {
|
||||
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver))
|
||||
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver));
|
||||
}
|
||||
BOOT_FUNCTIONS.push(async () => status.setReady(true))
|
||||
BOOT_FUNCTIONS.push(async () => status.setReady(true));
|
||||
|
||||
await bootup()
|
||||
await bootup();
|
||||
|
||||
await Promise.all([ethClient.ready(), ipfsClient.ready()])
|
||||
await Promise.all([ethClient.ready(), ipfsClient.ready()]);
|
||||
}
|
||||
|
||||
async function bootup() {
|
||||
for (const entry of Object.entries(BOOT_FUNCTIONS)) {
|
||||
console.log(entry[1].toString())
|
||||
await entry[1]()
|
||||
console.log(entry[1].toString());
|
||||
await entry[1]();
|
||||
}
|
||||
}
|
||||
|
||||
function LoginDash() {
|
||||
const { isLoggedIn } = useAuth()
|
||||
const { ready, inited } = useLumeStatus()
|
||||
const { isLoggedIn } = useAuth();
|
||||
const { ready, inited } = useLumeStatus();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -259,130 +255,130 @@ function LoginDash() {
|
|||
</LumeDashboard>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function asyncIterableToUint8Array(asyncIterable: any) {
|
||||
const chunks = []
|
||||
let totalLength = 0
|
||||
const chunks = [];
|
||||
let totalLength = 0;
|
||||
|
||||
for await (const chunk of asyncIterable) {
|
||||
chunks.push(chunk)
|
||||
totalLength += chunk.length
|
||||
chunks.push(chunk);
|
||||
totalLength += chunk.length;
|
||||
}
|
||||
|
||||
const result = new Uint8Array(totalLength)
|
||||
let offset = 0
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
function uint8ArrayToBase64(byteArray: Uint8Array) {
|
||||
let base64 = ""
|
||||
let base64 = "";
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
let padding = 0
|
||||
let padding = 0;
|
||||
for (let i = 0; i < byteArray.length; i += 3) {
|
||||
const a = byteArray[i]
|
||||
const b = byteArray[i + 1]
|
||||
const c = byteArray[i + 2]
|
||||
const a = byteArray[i];
|
||||
const b = byteArray[i + 1];
|
||||
const c = byteArray[i + 2];
|
||||
|
||||
const triplet = (a << 16) + ((b || 0) << 8) + (c || 0)
|
||||
const triplet = (a << 16) + ((b || 0) << 8) + (c || 0);
|
||||
|
||||
base64 += characters.charAt((triplet & 0xfc0000) >> 18)
|
||||
base64 += characters.charAt((triplet & 0x03f000) >> 12)
|
||||
base64 += characters.charAt((triplet & 0x000fc0) >> 6)
|
||||
base64 += characters.charAt(triplet & 0x00003f)
|
||||
base64 += characters.charAt((triplet & 0xfc0000) >> 18);
|
||||
base64 += characters.charAt((triplet & 0x03f000) >> 12);
|
||||
base64 += characters.charAt((triplet & 0x000fc0) >> 6);
|
||||
base64 += characters.charAt(triplet & 0x00003f);
|
||||
|
||||
if (byteArray.length - i < 3) {
|
||||
padding = 3 - (byteArray.length - i)
|
||||
padding = 3 - (byteArray.length - i);
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding if necessary
|
||||
if (padding > 0) {
|
||||
base64 = base64.slice(0, -padding) + (padding === 1 ? "=" : "==")
|
||||
base64 = base64.slice(0, -padding) + (padding === 1 ? "=" : "==");
|
||||
}
|
||||
|
||||
return base64
|
||||
return base64;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const status = useLumeStatus()
|
||||
const auth = useAuth()
|
||||
const [nftList, setNftList] = useState<any[]>([])
|
||||
const status = useLumeStatus();
|
||||
const auth = useAuth();
|
||||
const [nftList, setNftList] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
boot(status, auth)
|
||||
}, [])
|
||||
boot(status, auth);
|
||||
}, []);
|
||||
|
||||
const { networks } = useNetworks()
|
||||
const { networks } = useNetworks();
|
||||
|
||||
const ipfsStatus = networks
|
||||
.filter((item) => item.name.toLowerCase() === "ipfs")
|
||||
?.pop()
|
||||
.filter((item) => item.name.toLowerCase() === "ipfs")
|
||||
?.pop();
|
||||
|
||||
const ethStatus = networks
|
||||
.filter((item) => item.name.toLowerCase() === "ethereum")
|
||||
?.pop()
|
||||
?.pop();
|
||||
|
||||
const ready = ethStatus?.ready && status.ready
|
||||
const ready = ethStatus?.ready && status.ready;
|
||||
|
||||
const inputRef = createRef<HTMLInputElement>()
|
||||
const inputRef = createRef<HTMLInputElement>();
|
||||
|
||||
async function search(e: any | Event) {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
|
||||
let address = inputRef?.current?.value as string
|
||||
let address = inputRef?.current?.value as string;
|
||||
|
||||
address = await ethers.resolveAddress(address, provider)
|
||||
address = await ethers.resolveAddress(address, provider);
|
||||
|
||||
const contracts = await findPotentialERC721Contracts(address)
|
||||
const contracts = await findPotentialERC721Contracts(address);
|
||||
|
||||
const nfts = await fetchOwnedNFTs(address, contracts)
|
||||
const nfts = await fetchOwnedNFTs(address, contracts);
|
||||
|
||||
const list = []
|
||||
const list = [];
|
||||
|
||||
for (const nft of nfts) {
|
||||
let meta
|
||||
let meta;
|
||||
if (typeof nft.metadata === "string") {
|
||||
try {
|
||||
meta = await (await fetch(nft.metadata)).json()
|
||||
meta = await (await fetch(nft.metadata)).json();
|
||||
} catch (e) {
|
||||
meta = {
|
||||
image: "", // TODO: Improve this by bringing an actual image
|
||||
name: "Failed to Load",
|
||||
fail: true
|
||||
}
|
||||
fail: true,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
meta = jdu.parse(nft.metadata)
|
||||
meta = jdu.parse(nft.metadata);
|
||||
}
|
||||
|
||||
let image
|
||||
let image;
|
||||
|
||||
if (!meta.fail) {
|
||||
const imageCID = meta.image.replace("ipfs://", "")
|
||||
const imageCID = meta.image.replace("ipfs://", "");
|
||||
|
||||
image = await asyncIterableToUint8Array(
|
||||
ipfsClient.cat(imageCID).iterable()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
image = meta.image
|
||||
image = meta.image;
|
||||
}
|
||||
|
||||
list.push({
|
||||
image,
|
||||
name: meta.name,
|
||||
base64: meta.fail
|
||||
})
|
||||
base64: meta.fail,
|
||||
});
|
||||
|
||||
setNftList(list)
|
||||
setNftList(list);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,7 +414,7 @@ function App() {
|
|||
</svg>
|
||||
<Input
|
||||
className="pl-10 w-full bg-zinc-900 border-zinc-700 text-white ring-offset-primary"
|
||||
placeholder="Introduce ETH Address or ENS. eg: 0x00...ABC or vitalik.ens"
|
||||
placeholder="Introduce ETH Address or ENS. eg: 0x00...ABC or vitalik.eth"
|
||||
type="search"
|
||||
disabled={!ready}
|
||||
ref={inputRef}
|
||||
|
@ -433,7 +429,13 @@ function App() {
|
|||
{auth.isLoggedIn && !ethStatus?.ready ? (
|
||||
<span className="max-w-4xl w-full block my-1 p-4 rounded-lg opacity-80 bg-yellow-900/70 border border-yellow-500 text-yellow-500">
|
||||
You'll need to wait for a couple minutes before we can start
|
||||
searching. You are currently locally syncing to the ETH network. <b className="font-bold">Current Progress: {ethStatus?.sync ? `${ethStatus?.sync.toLocaleString()}%` : 'Initializing...'}</b>
|
||||
searching. You are currently locally syncing to the ETH network.{" "}
|
||||
<b className="font-bold">
|
||||
Current Progress:{" "}
|
||||
{ethStatus?.sync
|
||||
? `${ethStatus?.sync.toLocaleString()}%`
|
||||
: "Initializing..."}
|
||||
</b>
|
||||
</span>
|
||||
) : null}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
|
||||
|
@ -453,7 +455,7 @@ function App() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default function () {
|
||||
|
@ -467,5 +469,5 @@ export default function () {
|
|||
</AuthProvider>
|
||||
</LumeStatusProvider>
|
||||
</AppProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ import '@/styles/global.css'
|
|||
<Card className="max-w-3xl bg-zinc-950 border-zinc-800 shadow-xl">
|
||||
<CardHeader>
|
||||
<img src={LogoImg.src} class="w-20 mb-8" />
|
||||
<CardTitle className='text-white'>Welcome to Web3Toybox</CardTitle>
|
||||
<CardDescription className='text-zinc-500 text-lg'>This is a place where you can play with different famous web3 apps in a truly decentralized way - no infura, no gateways, no censorship.</CardDescription>
|
||||
<CardTitle className='text-white'>Welcome to Web3 Toybox</CardTitle>
|
||||
<CardDescription className='text-zinc-500 text-lg'>This is a place where you can play with different web3 use cases by example in a truly decentralized way - no infura, no gateways, no censorship.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='flex flex-col md:flex-row gap-y-5 md:gap-y-0 justify-between gap-x-5'>
|
||||
<Card className="md:max-w-[400px] w-full bg-zinc-900 border-zinc-800 hover:shadow-lg hover:ring-1 hover:ring-green-400/20 hover:shadow-green-400/20 hover:transform-gpu hover:-translate-y-[3px] transition-all duration-150">
|
||||
|
@ -48,7 +48,7 @@ import '@/styles/global.css'
|
|||
<CardHeader>
|
||||
<CardTitle className='text-zinc-400 mb-3'>More Coming Soon...</CardTitle>
|
||||
<CardDescription className='text-zinc-400'>
|
||||
We're diligently developing more intriguing examples. If you have any suggestions, don't hesitate to join our Discord channel and propose or submit any examples you'd like to see here!
|
||||
We're are definitely interested in new ideas for demos! If you have any suggestions, don't hesitate to join our Discord channel and propose or submit any examples you'd like to see here!
|
||||
<a href="https://discord.com/invite/qpC8ADp3rS">
|
||||
<Button variant="link" className='h-auto p-0 inline pl-1'>
|
||||
Join Discord
|
||||
|
@ -62,7 +62,7 @@ import '@/styles/global.css'
|
|||
<span class="max-w-4xl w-full block my-2 p-4 rounded-lg opacity-80 bg-gray-900/70 border border-gray-600 text-gray-400">
|
||||
For an enhanced experience, we recommend opening the developer console while interacting with these demos. If you're unsure how to do this, you can find instructions <Button variant="link" className="inline p-0 h-auto w-auto text-md"><a href="https://support.google.com/campaignmanager/answer/2828688">here</a></Button>.
|
||||
</span>
|
||||
<p class="text-zinc-700 text-sm">Brought to you with 💚 by the <a href="https://lumeweb.com" class="text-zinc-500 underline">lumeweb.com</a> team alongside the <a href="https://sia.tech/about-sia-foundation" class="text-zinc-500 underline">Sia Foundation</a></p>
|
||||
<p class="text-zinc-700 text-sm">Brought to you with 💚 by the <a href="https://lumeweb.com" class="text-zinc-500 underline">Lume</a> team, and grant sponsored by the <a href="https://sia.tech/about-sia-foundation" class="text-zinc-500 underline">Sia Foundation</a></p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue