Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/3d lib nft #404

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions components/01-atoms/OpenSeaExternalLinkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ExternalLinkIcon } from "@/components/01-atoms";
import { EthereumAddress } from "@/lib/shared/types";
import { useNetwork } from "wagmi";

export const OpenSeaExternalLinkButton = ({
contractAddress,
tokenId,
label,
}: {
contractAddress: EthereumAddress;
tokenId: number;
label?: string;
}) => {
const { chain } = useNetwork();

if (!contractAddress) return null;

const displayEllipsedAddress = tokenId.toString();

const openSeaExplorer = `https://testnets.opensea.io/assets/${chain?.name.toLowerCase()}/${contractAddress.toString()}/${tokenId}`;

return (
<div className="flex">
<a
href={openSeaExplorer}
target="_blank"
className="flex gap-1 items-center justify-start"
>
<h3 className="text-md">{label || `#${displayEllipsedAddress}`}</h3>
<div className="p-1">
<ExternalLinkIcon className="p-medium dark:text-white" />
</div>
</a>
</div>
);
};
85 changes: 85 additions & 0 deletions components/01-atoms/Token3DModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { TokenCard, TokenCardActionType } from "@/components/02-molecules";
import {
BlockExplorerExternalLinkButton,
OpenSeaExternalLinkButton,
} from "@/components/01-atoms";
import { ERC721, EthereumAddress, Token, TokenType } from "@/lib/shared/types";
import cc from "classcat";
import { Atropos } from "atropos/react";

interface Token3DModalProps {
isOpen: boolean;
onClose: () => void;
token: Token;
ownerAddress: EthereumAddress | null;
}

export const Token3DModal = ({
isOpen,
onClose,
token,
ownerAddress,
}: Token3DModalProps) => {
return (
<>
{isOpen && (
<div
className="fixed inset-0 w-full h-full z-40 bg-black/30 backdrop-blur-sm transition-opacity duration-300 cursor-default"
role="button"
onClick={onClose}
/>
)}
<div
className={cc([
"fixed z-50 flex justify-center items-center transition-transform duration-300 ease-in-out p-6",
"rounded-[20px] border dark:bg-[#212322] dark:border-[#353836] dark:shadow-sidebarDark bg-[#F6F6F6] border-[#F0EEEE] shadow-sidebarLight",
isOpen ? "opacity-100 scale-100 " : "opacity-0 scale-75",
isOpen
? "top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[800px] h-[500px]"
: "translate-y-full",
])}
>
<div className="flex flex-row w-full h-full gap-2">
<div className="flex w-[500px] h-full justify-center">
<Atropos className="flex w-full h-full">
<TokenCard
tokenData={token}
ownerAddress={ownerAddress}
styleType="fit"
withSelectionValidation={false}
onClickAction={TokenCardActionType.SHOW_NFT_DETAILS}
key={token.id}
/>
</Atropos>
</div>
<div className="flex h-full flex-col mt-4">
<p className="text-lg font-bold">
{token.name} (
{token.tokenType === TokenType.ERC721 &&
(token as ERC721).contractMetadata?.name &&
(token as ERC721).contractMetadata?.symbol}
)
</p>
<p className="text-md">
{token.id && token.contract && (
<>
<OpenSeaExternalLinkButton
contractAddress={new EthereumAddress(token.contract)}
tokenId={Number(token.id)}
/>
</>
)}
</p>
<p className="text-md">
{token.contract && (
<BlockExplorerExternalLinkButton
address={new EthereumAddress(token.contract)}
/>
)}
</p>
</div>
</div>
</div>
</>
);
};
2 changes: 2 additions & 0 deletions components/01-atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from "./NetworkDropdown";
export * from "./MobileNotSupported";
export * from "./OfferExpiryConfirmSwap";
export * from "./OfferTag";
export * from "./OpenSeaExternalLinkButton";
export * from "./ProgressBar";
export * from "./SearchBar";
export * from "./SearchItemsShelf";
Expand All @@ -21,6 +22,7 @@ export * from "./SwapModalLayout";
export * from "./SwappingIcons";
export * from "./SwappingSearchTab";
export * from "./ThreeDotsCardOffersOptions";
export * from "./Token3DModal";
export * from "./TokenCardProperties";
export * from "./TokenCardsPlaceholder";
export * from "./TokenOfferDetails";
Expand Down
108 changes: 103 additions & 5 deletions components/02-molecules/TokenCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { SwaplaceIcon } from "@/components/01-atoms";
import { SwaplaceIcon, Token3DModal } from "@/components/01-atoms";
import { useAuthenticatedUser } from "@/lib/client/hooks/useAuthenticatedUser";
import {
ERC20,
Expand All @@ -10,9 +10,11 @@ import {
} from "@/lib/shared/types";
import { getTokenName } from "@/lib/client/ui-utils";
import { SwapContext } from "@/lib/client/contexts";
import useLongPress from "@/lib/client/hooks/useLongPress";
import React, { useContext, useEffect, useState } from "react";
import cc from "classcat";
import toast from "react-hot-toast";
import { Atropos } from "atropos/react";

interface TokenCardProps {
tokenData: Token;
Expand All @@ -31,6 +33,7 @@ interface TokenCardProps {
displayERC20TokensAmount?: boolean;
withSelectionValidation?: boolean;
styleType?: StyleVariant;
isToken3D?: boolean; // If true, the token card will be displayed in 3D using the AtroposLibrary
}

export enum TokenCardActionType {
Expand All @@ -45,20 +48,23 @@ export enum TokenCardStyleType {
NORMAL = "normal",
MEDIUM = "medium",
LARGE = "large",
FIT = "fit",
}

type StyleVariant =
| TokenCardStyleType
| "small"
| "normal"
| "medium"
| "large";
| "large"
| "fit";

export const TokenSizeClassNames = {
[TokenCardStyleType.SMALL]: "card-token-small",
[TokenCardStyleType.NORMAL]: "card-token-normal",
[TokenCardStyleType.MEDIUM]: "card-token-medium",
[TokenCardStyleType.LARGE]: "card-token-large",
[TokenCardStyleType.FIT]: "w-full h-full",
};

/**
Expand All @@ -85,6 +91,7 @@ export const TokenCard = ({
displayERC20TokensAmount = false,
styleType = TokenCardStyleType.NORMAL,
onClickAction = TokenCardActionType.SELECT_TOKEN_FOR_SWAP,
isToken3D = false,
}: TokenCardProps) => {
const { authenticatedUserAddress } = useAuthenticatedUser();
const {
Expand All @@ -95,11 +102,42 @@ export const TokenCard = ({
} = useContext(SwapContext);
const [currentNftIsSelected, setCurrentNftIsSelected] = useState(false);
const [couldntLoadNftImage, setCouldntLoadNftImage] = useState(false);

const [tokenDisplayableData, setDisplayableData] = useState({
id: "",
symbol: "",
});
const [isPressed, setIsPressed] = useState(false);

interface TokenToClipboard {
tokenType: TokenType;
id?: string;
name?: string;
image: string;
contract: string;
}

/**
* Parses the token data into a format suitable for copying to the clipboard.
* @param tokenData The token data to be parsed.
* @returns The parsed token data in the form of TokenToClipboard object.
*/
const parseTokenDataToClipboard = (tokenData: Token): TokenToClipboard => {
const tokenCopyToClipboard: TokenToClipboard = {
tokenType: tokenData.tokenType,
id: tokenData.id,
name: tokenData.name,
image:
tokenData.tokenType === TokenType.ERC721
? ((tokenData as ERC721).metadata?.image as string)
: "",
contract:
tokenData.tokenType === TokenType.ERC721
? ((tokenData as ERC721).contract as string)
: "",
};

return tokenCopyToClipboard;
};

useEffect(() => {
const displayableData = { ...tokenDisplayableData };
Expand Down Expand Up @@ -207,7 +245,8 @@ export const TokenCard = ({
}
}
} else if (onClickAction === TokenCardActionType.SHOW_NFT_DETAILS) {
navigator.clipboard.writeText(JSON.stringify(tokenData));
const tokenDataToClipboard = parseTokenDataToClipboard(tokenData);
navigator.clipboard.writeText(JSON.stringify(tokenDataToClipboard));
toast.success("NFT data copied to your clipboard!");
}
};
Expand All @@ -216,8 +255,47 @@ export const TokenCard = ({
setCouldntLoadNftImage(true);
};

const handleClick = () => {
setIsPressed(false);
};

const handleClickLong = () => {
setIsPressed(true);
};

const { onMouseDown, onMouseUp, onMouseLeave } = useLongPress(
handleClickLong,
onCardClick, // when the user clicks in Button card
);

const ButtonLayout = (children: React.ReactNode) => {
return (
return isToken3D ? (
<Atropos shadowScale={0.5} scaleClassName="atropos-scale" scaleChildren>
<button
onClick={handleClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onMouseLeave={onMouseLeave}
className={cc([
TokenSizeClassNames[styleType],
{
"border-green-500":
currentNftIsSelected && withSelectionValidation,
"cursor-auto": onClickAction === TokenCardActionType.NO_ACTION,
},
])}
>
{currentNftIsSelected && withSelectionValidation && (
<div className="flex items-end justify-end absolute bottom-0 right-0 w-full h-full rounded-xl z-20">
<div className=" dark:bg-[#212322] bg-[#F6F6F6] translate-x-[1px] translate-y-[1px] p-1 rounded-tl-xl">
<SwaplaceIcon className="text-[#AABE13] dark:text-[#DDF23D] w-4 h-4" />
</div>
</div>
)}
{children}
</button>
</Atropos>
) : (
<button
onClick={onCardClick}
className={cc([
Expand Down Expand Up @@ -250,6 +328,16 @@ export const TokenCard = ({
className="dark:text-[#707572] text-[#707572] text-center static z-10 w-full h-full overflow-y-auto rounded-xl"
/>,
)}
{isPressed && (
<Token3DModal
token={tokenData}
ownerAddress={ownerAddress}
isOpen={isPressed}
onClose={() => {
setIsPressed(false);
}}
/>
)}
</>
) : (
<>
Expand All @@ -261,6 +349,16 @@ export const TokenCard = ({
})}
</div>,
)}
{isPressed && (
<Token3DModal
token={tokenData}
ownerAddress={ownerAddress}
isOpen={isPressed}
onClose={() => {
setIsPressed(false);
}}
/>
)}
</>
);
};
3 changes: 3 additions & 0 deletions components/02-molecules/TokensList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface TokensListProps {
tokenCardClickAction?: TokenCardActionType;
variant: ForWhom;
gridClassNames?: string;
isToken3D?: boolean; // If true, the token card will be displayed in 3D using the AtroposLibrary
}

/**
Expand Down Expand Up @@ -62,6 +63,7 @@ export const TokensList = ({
tokenCardStyleType = TokenCardStyleType.NORMAL,
tokenCardClickAction = TokenCardActionType.SELECT_TOKEN_FOR_SWAP,
gridClassNames = "w-full h-full grid grid-cols-3 md:grid-cols-6 lg:grid-cols-6 gap-3",
isToken3D = false,
}: TokensListProps) => {
const [selectTokenAmountOf, setSelectTokenAmountOf] =
useState<EthereumAddress | null>(null);
Expand Down Expand Up @@ -113,6 +115,7 @@ export const TokensList = ({
withSelectionValidation={withSelectionValidation}
ownerAddress={ownerAddress}
tokenData={token}
isToken3D={isToken3D}
/>
));

Expand Down
1 change: 1 addition & 0 deletions components/03-organisms/TokensShelf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export const TokensShelf = ({ variant }: { variant: ForWhom }) => {
ownerAddress={address}
tokensList={allTokensList}
variant={variant}
isToken3D={true}
/>
</div>
) : tokensQueryStatus == TokensQueryStatus.EMPTY_QUERY || !address ? (
Expand Down
Loading