Skip to content

Commit

Permalink
⚙️ apps/premier: Add NFTs retrieval + Add ReactThreeFiber + Add model…
Browse files Browse the repository at this point in the history
…s + Add Mint UI
  • Loading branch information
sshmaxime committed Oct 30, 2024
1 parent 9d0b201 commit d2b5ada
Show file tree
Hide file tree
Showing 38 changed files with 2,952 additions and 256 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ yarn-error.log*
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.env*.local
3 changes: 3 additions & 0 deletions apps/premier/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
const nextConfig = {
transpilePackages: ["@web-playground/ui"],
reactStrictMode: true,
images: {
domains: ["res.cloudinary.com"],
},

/**
* @dev https://docs.reown.com/appkit/next/core/installation#extra-configuration
Expand Down
9 changes: 9 additions & 0 deletions apps/premier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@
"@rainbow-me/rainbowkit": "^2.1.6",
"@react-three/drei": "^9.114.5",
"@react-three/fiber": "^8.17.10",
"@react-three/postprocessing": "^2.16.3",
"@simplewebauthn/browser": "^9.0.1",
"@tanstack/react-query": "^5.56.2",
"@types/three": "^0.169.0",
"@vercel/analytics": "^1.2.2",
"@web-playground/contracts-premier": "workspace:^",
"@web-playground/ui": "workspace:^",
"@web-playground/utils": "workspace:^",
"@web3icons/core": "^3.8.0",
"@web3icons/react": "^3.8.0",
"alchemy-sdk": "^3.4.4",
"clsx": "^2.1.0",
"html-react-parser": "^5.1.18",
"kubo-rpc-client": "^5.0.1",
"next": "^14.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loading-skeleton": "^3.5.0",
"react-spring": "^9.7.4",
"tailwind-merge": "^2.2.1",
"thirdweb": "^5.63.2",
"three": "^0.169.0",
"uint8arrays": "^5.1.0",
"viem": "2.x",
Expand All @@ -36,6 +44,7 @@
},
"devDependencies": {
"@biomejs/biome": "^1.6.4",
"@types/lodash": "^4.17.12",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file modified apps/premier/public/placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions apps/premier/src/app/drop/[dropId]/_components/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"use client";

import { Separator } from "@web-playground/ui/shadcn/components/separator";
import { Box } from "@web-playground/ui/system/base/box";
import { H4, H6 } from "@web-playground/ui/system/typography";

import { ConnectButton } from "@components/buttons/connectButton";
import { useAccountNfts } from "@hooks/query/useNfts";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@web-playground/ui/shadcn/components/tabs";
import { Network, type OwnedNft } from "alchemy-sdk";
import Image from "next/image";
import { animated, useSpring } from "react-spring";
import { useDropStore } from "src/app/drop/[dropId]/store";
import { useAccount } from "wagmi";

type NftsBoxProps = {
collectionName: string;
nfts: OwnedNft[];
};
const NftsCollectionBox = ({ collectionName, nfts }: NftsBoxProps) => {
const sceneRef = useDropStore((s) => s.sceneRef);

return (
<Box className="flex flex-col rounded gap-2">
<Box>
<H6 className="font-medium inline-block tracking-tighter rounded bg-border px-2 py-1">
{collectionName}
</H6>
</Box>

<Box className="grid grid-cols-7 rounded gap-2">
{nfts.map((item) => (
<Box
key={item.tokenId}
className="relative h-full w-full aspect-square rounded overflow-hidden hover:cursor-pointer"
onClick={() => sceneRef.current.updateItem(item.image.pngUrl ?? "")}
>
<Image alt={item.name ?? ""} src={item.image.pngUrl ?? ""} fill />
</Box>
))}
</Box>
</Box>
);
};

const Drawer = () => {
const openDrawer = useDropStore((s) => s.openDrawer);

const props = useSpring({
left: 0,
position: "absolute" as const,
top: 0,
backgroundColor: "white",
height: "100%",
width: "100%",
transform: openDrawer ? "translate(0%)" : "translate(100%)",
overflow: "auto",
zIndex: 10,
});

const { address, isConnected, isDisconnected } = useAccount();

const nftsOwned = useAccountNfts({
address: address ?? "",
network: Network.ETH_MAINNET,
enabled: isConnected,
});

return (
<animated.div style={props}>
<Box className="relative p-4 h-full">
<H4 className="font-extrabold tracking-tighter mb-2">Customization.</H4>

{isDisconnected && (
<Box className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex items-center flex-col gap-2">
<ConnectButton />
<H6 className="text-center tracking-tighter font-medium">You need to be connected.</H6>
</Box>
)}

{isConnected && nftsOwned && (
<Tabs defaultValue="placeholder">
<TabsList className="flex justify-start gap-2 px-2">
<TabsTrigger className="text-xs" value="placeholder">
Placeholder
</TabsTrigger>
</TabsList>

<TabsContent value="placeholder">
<Box className="space-y-2 p-2 bg-muted rounded">
{Object.keys(nftsOwned).map((collectionName) => {
return (
<Box key={collectionName}>
<NftsCollectionBox
collectionName={collectionName}
nfts={nftsOwned[collectionName]}
/>

<Separator className="mt-2" />
</Box>
);
})}
</Box>
</TabsContent>
</Tabs>
)}
</Box>
</animated.div>
);
};

export { Drawer };
221 changes: 221 additions & 0 deletions apps/premier/src/app/drop/[dropId]/_components/product.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
"use client";

import { Badge } from "@web-playground/ui/shadcn/components/badge";
import { Button } from "@web-playground/ui/shadcn/components/button";
import { Separator } from "@web-playground/ui/shadcn/components/separator";
import { Box } from "@web-playground/ui/system/base/box";
import { H0, H1, H4, H6 } from "@web-playground/ui/system/typography";
import { TokenETH } from "@web3icons/react";
import { WandSparkles, Zap } from "lucide-react";

import { ExternalLink } from "lucide-react";
import Skeleton from "react-loading-skeleton";

import "react-loading-skeleton/dist/skeleton.css";
import { chainIdAsChainName } from "@configs/schemas/chains";
import { ipfsUrl } from "@server/config/ipfs";
import { useQueryClient } from "@tanstack/react-query";
import { useWriteStoreMint } from "@web-playground/contracts-premier/wagmi.ts";
import parse from "html-react-parser";
import Link from "next/link";
import { Drawer } from "src/app/drop/[dropId]/_components/drawer";
import { useDropStore } from "src/app/drop/[dropId]/store";
import { formatEther } from "viem";
import { useChainId, useSwitchChain } from "wagmi";

const Product = () => {
return (
<>
<Header />
<Separator className="mt-2 bg-border/50" />
<Box className="flex-1 relative [mask-image:linear-gradient(to_bottom,_transparent_10px,_black_24px,_black_calc(100%_-_24px),_transparent_100%)]">
<Drawer />
<Description />
</Box>

<Footer />
</>
);
};

const Header = () => {
const drop = useDropStore((s) => s.drop);
const chainId = chainIdAsChainName(useChainId());

const buttonDisabled = !drop;

return (
<header className="p-4 pb-0 pr-[var(--app-px)]">
<Box>
<Box className="space-y-1">
<H6 className="text-muted-foreground tracking-tighter">{drop?.symbol ?? <Skeleton />}</H6>

<Box className="flex justify-between items-center gap-10">
<H0 className="font-black tracking-tighter grow">{drop?.name ?? <Skeleton />}</H0>

{drop ? (
<Box>
<Badge className="rounded">{chainId}</Badge>
</Box>
) : (
<Skeleton width={100} />
)}
</Box>
</Box>

<Box className="flex gap-2 mt-3">
<Button
variant="secondary"
disabled={buttonDisabled}
className="h-6 p-2"
asChild={!buttonDisabled}
>
<Link
className="flex items-center gap-1"
href={ipfsUrl(drop?.dropURI ?? "")}
target="_blank"
>
<H6>Contract</H6>
<ExternalLink className="w-3 h-3" />
</Link>
</Button>

<Button
variant="secondary"
disabled={buttonDisabled}
className="h-6 p-2"
asChild={!buttonDisabled}
>
<Link
className="flex items-center gap-1 "
href={ipfsUrl(drop?.dropURI ?? "")}
target="_blank"
>
<H6>IPFS</H6>
<ExternalLink className="w-3 h-3" />
</Link>
</Button>
</Box>
</Box>
</header>
);
};

const Description = () => {
const drop = useDropStore((s) => s.drop);

const LoadingPlaceholder = () =>
[5, 2, 1].map((count, index) => (
<section key={count}>
<H4 className="font-extrabold tracking-tighter mb-2">
<Skeleton />
</H4>

<Box className="prose prose-sm prose-slate">
<Skeleton count={count} />
</Box>

{index !== 3 - 1 && <Separator className="mt-4 bg-border/50" />}
</section>
));

return (
<Box className="absolute h-full w-full overflow-auto">
<Box className="[&>*:first-child]:pt-4 [&>*:last-child]:pb-6 space-y-4 text-wrap px-4 pr-[var(--app-px)]">
{!drop && <LoadingPlaceholder />}

{drop?.metadata.informations.map((informationItem, index) => (
<section key={informationItem.title}>
<H4 className="font-extrabold tracking-tighter mb-2">{informationItem.title}.</H4>
<Box className="prose prose-sm prose-slate">{parse(informationItem.html)}</Box>

{index !== drop.metadata.informations.length - 1 && (
<Separator className="mt-4 bg-border/50" />
)}
</section>
))}
</Box>
</Box>
);
};

const Footer = () => {
const drop = useDropStore((s) => s.drop);

const toggleOpenDrawer = useDropStore((s) => s.toggleOpenDrawer);

const dropPriceAsEth = drop?.price ? formatEther(drop?.price) : undefined;

const { writeContract: writeStoreMint } = useWriteStoreMint();
const { switchChain } = useSwitchChain();
const queryClient = useQueryClient();

const chainId = useChainId();

const mint = drop
? () => {
writeStoreMint(
{ args: [drop?.id, 0], chainId: 31337, value: drop.price },
{ onSettled: () => queryClient.invalidateQueries({ queryKey: ["useDrop"] }) },
);
}
: undefined;

return (
<footer className="p-4 pt-0 pr-[var(--app-px)]">
<Box className="flex justify-between items-center">
<Box className="flex items-center py-3 grow">
<TokenETH variant="branded" className="w-8 h-8" />

<H1 className="font-bold tracking-tighter">
{dropPriceAsEth ?? <Skeleton width={100} />}
</H1>
</Box>

<Box>
{drop ? (
<Badge>
<H6>
{drop?.currentSupply.toString()} / {drop?.maxSupply.toString()}
</H6>
</Badge>
) : (
<Skeleton width={100} />
)}
</Box>
</Box>

<Box className="flex gap-2">
<Button
variant="outline"
className="rounded tracking-tighter w-full gap-2 shrink-1"
size="sm"
onClick={toggleOpenDrawer}
>
<WandSparkles className="w-4 h-4" />
<Box>Customize</Box>
</Button>

<Button
className="rounded tracking-tighter w-full gap-2"
size="sm"
onClick={() => {
if (drop) {
if (chainId !== 31337) switchChain({ chainId: 31337 }, { onSuccess: () => mint?.() });
else mint?.();
}
}}
>
<Zap className="w-4 h-4" />
<Box>
Mint {"{ "}
<span className="font-extrabold">{drop?.name ?? "Placeholder"}</span>
{" }"}
</Box>
</Button>
</Box>
</footer>
);
};

export { Product };
Loading

0 comments on commit d2b5ada

Please sign in to comment.