diff --git a/src/features/set-buy-now/SetBuyNow.tsx b/src/features/set-buy-now/SetBuyNow.tsx deleted file mode 100644 index 459cb8b3..00000000 --- a/src/features/set-buy-now/SetBuyNow.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { FC } from 'react'; - -import { useWeb3 } from '../../lib/hooks/useWeb3'; - -import { ConnectWallet } from '../ui/ConnectWallet'; - -export const SetBuyNow: FC = () => { - const { account } = useWeb3(); - - const content = account ? ( - <>Set Buy Now - ) : ( - - ); - - return content; -}; diff --git a/src/features/set-buy-now/SetBuyNowButton.tsx b/src/features/set-buy-now/SetBuyNowButton.tsx deleted file mode 100644 index d96019b4..00000000 --- a/src/features/set-buy-now/SetBuyNowButton.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { FC } from 'react'; - -import { SetBuyNowModal } from './SetBuyNowModal'; - -export const SetBuyNowButton: FC = () => ( - -); diff --git a/src/features/set-buy-now/SetBuyNowButton/SetBuyNowButton.tsx b/src/features/set-buy-now/SetBuyNowButton/SetBuyNowButton.tsx new file mode 100644 index 00000000..d740fd6f --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowButton/SetBuyNowButton.tsx @@ -0,0 +1,12 @@ +import { FC, ReactNode } from 'react'; + +import { SetBuyNowModal } from '..'; + +interface SetBuyNowButtonProps { + zna: string; + trigger: ReactNode; +} + +export const SetBuyNowButton: FC = ({ zna, trigger }) => { + return ; +}; diff --git a/src/features/set-buy-now/SetBuyNowButton/index.ts b/src/features/set-buy-now/SetBuyNowButton/index.ts new file mode 100644 index 00000000..0cf4e966 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowButton/index.ts @@ -0,0 +1 @@ +export * from './SetBuyNowButton'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/Complete.tsx b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/Complete.tsx new file mode 100644 index 00000000..b5f1a35c --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/Complete.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; + +import { Step } from '../hooks'; + +import { NFTDetails } from '../ui'; +import { FormTextContent } from '../../../../ui'; +import { Wizard } from '@zero-tech/zui/components'; + +import styles from '../FormSteps.module.scss'; + +export interface CompleteProps { + zna: string; + step: Step; + onClose: () => void; +} + +export const Complete: FC = ({ zna, step, onClose }) => { + return ( + <> + + +
+ + + +
+ + ); +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/index.ts new file mode 100644 index 00000000..23df34d8 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Complete/index.ts @@ -0,0 +1 @@ +export * from './Complete'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/Confirm.tsx b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/Confirm.tsx new file mode 100644 index 00000000..cc0771e8 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/Confirm.tsx @@ -0,0 +1,61 @@ +import { FC } from 'react'; + +import { Step } from '../hooks'; +import { useSetBuyNowData } from '../../../useSetBuyNowData'; + +import { NFTDetails } from '../ui'; +import { FormTextContent } from '../../../../ui'; +import { Wizard, ButtonsProps } from '@zero-tech/zui/components/Wizard'; + +import styles from '../FormSteps.module.scss'; + +export interface ConfirmProps { + zna: string; + step: Step; + errorText: string; + buyNowAmount: string; + onConfirmSetBuyNow: () => void; + onClose: () => void; +} + +export const Confirm: FC = ({ + zna, + step, + errorText, + buyNowAmount, + onConfirmSetBuyNow, + onClose, +}) => { + const { paymentTokenSymbol } = useSetBuyNowData(zna); + + const primaryButtonText: ButtonsProps['primaryButtonText'] = errorText + ? 'Retry' + : 'Confirm'; + + return ( + <> + + +
+ + + +
+ + ); +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/index.ts new file mode 100644 index 00000000..d8e1c97b --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Confirm/index.ts @@ -0,0 +1 @@ +export * from './Confirm'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/Details.tsx b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/Details.tsx new file mode 100644 index 00000000..93aae4db --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/Details.tsx @@ -0,0 +1,110 @@ +import { FC, useState } from 'react'; + +import { Step } from '../hooks'; +import { formatNumber } from '../../../../../lib/util'; +import { useSetBuyNowData } from '../../../useSetBuyNowData'; + +import { NFTDetails } from '../ui'; +import { ToggleButton } from '../../../../ui'; +import { Input } from '@zero-tech/zui/components/Input'; +import { Wizard, ButtonsProps } from '@zero-tech/zui/components/Wizard'; + +import styles from '../FormSteps.module.scss'; + +export interface DetailsProps { + zna: string; + step: Step; + errorText: string; + buyNowAmount: string; + setBuyNowAmount: (bid: string) => void; + onCheckZAuction: () => void; + onClose: () => void; +} + +export const Details: FC = ({ + zna, + step, + errorText, + buyNowAmount, + setBuyNowAmount, + onCheckZAuction, + onClose, +}) => { + const { + hasExistingBuyNow, + buyNowPriceAsString, + paymentTokenLabel, + paymentTokenSymbol, + paymentTokenPriceInUsd, + } = useSetBuyNowData(zna); + + const [toggledValue, setToggledValue] = useState(hasExistingBuyNow); + + const isInputValueValid = + Number(buyNowAmount) && + !Number.isNaN(parseFloat(buyNowAmount)) && + Number(buyNowAmount) !== 0; + + const isError = buyNowAmount?.length > 0 && !isInputValueValid; + const primaryButtonText: ButtonsProps['primaryButtonText'] = errorText + ? 'Retry' + : 'Next'; + + const bidAmountConversionString = + paymentTokenSymbol && buyNowAmount + ? `$${formatNumber( + Number(paymentTokenPriceInUsd) * Number(buyNowAmount), + )}` + : '-'; + + return ( + <> + + +
+ {step === Step.DETAILS && ( +
+ { + setToggledValue(!toggledValue); + setBuyNowAmount(''); + }} + /> + + setBuyNowAmount(value)} + /> + + {bidAmountConversionString} + + + {hasExistingBuyNow + ? `This NFT has an existing buy now price of ${buyNowPriceAsString}` + : 'This NFT does not have an existing buy now price'} + +
+ )} + + +
+ + ); +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/index.ts new file mode 100644 index 00000000..2201d1f1 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/Details/index.ts @@ -0,0 +1 @@ +export * from './Details'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/FormSteps.module.scss b/src/features/set-buy-now/SetBuyNowForm/FormSteps/FormSteps.module.scss new file mode 100644 index 00000000..b37ba30d --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/FormSteps.module.scss @@ -0,0 +1,25 @@ +@import '../../../../../node_modules/@zero-tech/zui/styles/_colors.scss'; +@import '../../../../../node_modules/@zero-tech/zui/styles/_typography.scss'; + +.Container { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + + .Button { + margin-top: 2.5rem; + } + + .InputWrapper { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + } + + .Subtext { + font-size: $size-tiny; + color: $grey-lighter-2; + } +} diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/index.ts new file mode 100644 index 00000000..668d54f2 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/index.ts @@ -0,0 +1 @@ +export * from './useFormSteps'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/useFormSteps.tsx b/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/useFormSteps.tsx new file mode 100644 index 00000000..4c47af17 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/hooks/useFormSteps.tsx @@ -0,0 +1,95 @@ +import { ReactNode } from 'react'; + +import { Complete, Confirm, Details } from '..'; +import { ApproveZAuction } from '../../../../ui'; +import { Wizard } from '@zero-tech/zui/components'; + +export enum Step { + DETAILS, + ZAUCTION_APPROVE, + CONFIRM, + COMPLETE, + LOADING, +} + +interface UseFormStepsReturn { + content: ReactNode; +} + +export interface useFormStepsProps { + zna: string; + step: Step; + error: string; + statusText: string; + buyNowAmount: string; + setBuyNowAmount: (bid: string) => void; + onCheckZAuction: () => void; + onApproveZAuction: () => void; + onConfirmSetBuyNow: () => void; + onClose: () => void; +} + +export const useFormSteps = ({ + zna, + step, + error, + statusText, + buyNowAmount, + setBuyNowAmount, + onCheckZAuction, + onApproveZAuction, + onConfirmSetBuyNow, + onClose, +}: useFormStepsProps): UseFormStepsReturn => { + let content: ReactNode; + + switch (step) { + case Step.DETAILS: + content = ( +
+ ); + break; + + case Step.ZAUCTION_APPROVE: + content = ( + + ); + break; + + case Step.CONFIRM: + content = ( + + ); + break; + + case Step.COMPLETE: + content = ; + break; + + case Step.LOADING: + content = ; + break; + } + + return { content }; +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/index.ts new file mode 100644 index 00000000..015b8dbf --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/index.ts @@ -0,0 +1,3 @@ +export * from './Complete'; +export * from './Confirm'; +export * from './Details'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.module.scss b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.module.scss new file mode 100644 index 00000000..ce989843 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.module.scss @@ -0,0 +1,67 @@ +@import '../../../../../../../node_modules/@zero-tech/zui/styles/_colors.scss'; +@import '../../../../../../../node_modules/@zero-tech/zui/styles/_typography.scss'; + +.Container { + display: flex; + flex-direction: row; + word-break: break-word; + gap: 1.5rem; + width: 100%; + + // TODO: temporary + .Media { + height: 16.25rem; + width: 16.25rem; + flex-shrink: 0; + border-radius: 0.5rem; + + .Image { + height: 100%; + width: 100%; + border-radius: inherit; + } + } + + .Details { + margin-top: 1rem; + + .TextContent { + margin: 0; + padding: 0; + + li { + display: flex; + flex-direction: column; + list-style: none; + + .Title { + font-size: $size-large; + font-weight: $font-weight-bold; + line-height: 1.816rem; + margin: 0; + } + + .ZNA { + line-height: 1.5rem; + color: $blue; + margin-top: 0.25rem; + } + + .InfoTitle { + font-size: $size-tiny; + line-height: 0.908rem; + color: $grey-lighter-2; + margin-top: 1rem; + } + + .InfoValue { + line-height: 1.5rem; + } + } + } + + .ActionContainer { + margin-top: 1rem; + } + } +} diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.tsx b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.tsx new file mode 100644 index 00000000..c993f0a0 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/NFTDetails.tsx @@ -0,0 +1,141 @@ +import { FC } from 'react'; + +import { Step } from '../../hooks'; +import { useSetBuyNowData } from '../../../../useSetBuyNowData'; +import { HTMLTextElement } from '@zero-tech/zui/lib/types'; +import { truncateAddress, truncateDomain } from '@zero-tech/zui/utils'; + +import { SkeletonText } from '@zero-tech/zui/components'; +import { IpfsMedia } from '@zero-tech/zapp-utils/components'; + +import styles from './NFTDetails.module.scss'; + +export interface NFTDetailsProps { + zna: string; + step?: Step; +} + +export const NFTDetails: FC = ({ zna, step }) => { + const { + title, + creator, + imageAlt, + imageSrc, + highestBidAsString, + buyNowPriceAsString, + hasExistingBuyNow, + isLoading, + } = useSetBuyNowData(zna); + + const truncatedZna = truncateDomain(zna, 20); + const truncatedCreatorAddress = truncateAddress(creator); + + const detailContent: DetailsContentType[] = [ + { + id: 'title', + className: styles.Title, + text: title, + isLoading: isLoading, + as: 'h1' as const, + }, + { + id: 'zna', + className: styles.ZNA, + text: `0://${truncatedZna}`, + isLoading: isLoading, + as: 'span' as const, + }, + { + id: 'creator', + title: 'Creator', + className: styles.InfoValue, + text: truncatedCreatorAddress, + isLoading: isLoading, + as: 'span' as const, + }, + { + id: 'highest-bid', + title: 'Highest Bid', + className: styles.InfoValue, + text: highestBidAsString, + isLoading: isLoading, + as: 'span' as const, + }, + { + id: 'buy-now-price', + title: 'Buy Now Price', + className: styles.InfoValue, + text: buyNowPriceAsString, + isLoading: isLoading, + as: 'span' as const, + }, + ]; + + const content = + !hasExistingBuyNow && (step === Step.DETAILS || step === Step.CONFIRM) + ? detailContent.slice(0, -1) + : detailContent; + + return ( +
+ +
+
+ ); +}; + +/******************* + * Media + *******************/ + +interface MediaProps { + alt: string; + src: string; +} + +const Media = ({ alt, src }: MediaProps) => { + return ( +
+ +
+ ); +}; + +/******************* + * Details + *******************/ + +type DetailsContentType = { + id: string; + title?: string; + className: string; + text: string; + isLoading: boolean; + as: HTMLTextElement; +}; + +interface DetailsProps { + content: DetailsContentType[]; +} + +const Details = ({ content }: DetailsProps) => { + return ( +
+
    + {content.map((e) => ( +
  • + {e?.title && {e?.title}} + +
  • + ))} +
+
+ ); +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/index.ts new file mode 100644 index 00000000..f7371112 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/NFTDetails/index.ts @@ -0,0 +1 @@ +export * from './NFTDetails'; diff --git a/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/index.ts b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/index.ts new file mode 100644 index 00000000..f7371112 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/FormSteps/ui/index.ts @@ -0,0 +1 @@ +export * from './NFTDetails'; diff --git a/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.module.scss b/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.module.scss new file mode 100644 index 00000000..68871377 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.module.scss @@ -0,0 +1,14 @@ +.Container { + white-space: pre-line; + + h2 { + margin: 0 3rem; + } + + .Form { + display: flex; + flex-direction: column; + align-items: center; + gap: 2.5rem; + } +} diff --git a/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.tsx b/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.tsx new file mode 100644 index 00000000..e4632813 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/SetBuyNowForm.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; + +import { useSetBuyNowForm } from './hooks'; +import { useFormSteps } from './FormSteps/hooks'; +import { Wizard } from '@zero-tech/zui/components'; + +import styles from './SetBuyNowForm.module.scss'; + +export interface SetBuyNowFormProps { + zna: string; + onClose: () => void; +} + +export const SetBuyNowForm: FC = ({ zna, onClose }) => { + const { + step, + error, + statusText, + buyNowAmount, + setBuyNowAmount, + onConfirmSetBuyNow, + onCheckZAuction, + onApproveZAuction, + } = useSetBuyNowForm(zna); + + const { content } = useFormSteps({ + zna, + step, + error, + statusText, + buyNowAmount, + setBuyNowAmount, + onCheckZAuction, + onApproveZAuction, + onConfirmSetBuyNow, + onClose, + }); + + return ( + +
{content}
+
+ ); +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/hooks/index.ts b/src/features/set-buy-now/SetBuyNowForm/hooks/index.ts new file mode 100644 index 00000000..e32d33b7 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/hooks/index.ts @@ -0,0 +1 @@ +export * from './useSetBuyNowForm'; diff --git a/src/features/set-buy-now/SetBuyNowForm/hooks/useSetBuyNowForm.tsx b/src/features/set-buy-now/SetBuyNowForm/hooks/useSetBuyNowForm.tsx new file mode 100644 index 00000000..6cb4cba3 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/hooks/useSetBuyNowForm.tsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; + +import { Step } from '../FormSteps/hooks'; +import { useSetBuyNowData } from '../../useSetBuyNowData'; +import { + useWeb3, + useZnsSdk, + useZAuctionCheckTransferNftsByDomain, +} from '../../../../lib/hooks'; +import { ethers } from 'ethers'; +import { useTransaction } from '@zero-tech/zapp-utils/hooks/useTransaction'; + +enum StatusText { + APPROVING_ZAUCTION = 'Approving zAuction. This may take up to 20 mins... Please do not close this window or refresh the page.', + CHECK_ZAUCTION = 'Checking status of zAuction approval...', + PROCESSING_SET_BUY_NOW = 'Setting buy now. This may take up to 20 mins... Please do not close this window or refresh the page.', + WAITING_FOR_APPROVAL = 'Waiting for approval from your wallet...', + WAITING_FOR_SIGNATURE = 'Waiting for set buy now approval to be signed by wallet...', +} + +enum ErrorText { + FAILED_ZAUCTION_CHECK = 'Failed to check zAuction approval status', +} + +export type UseSetBuyNowFormReturn = { + step: Step; + error: string; + statusText: string; + buyNowAmount: string; + setBuyNowAmount: (bidAmount: string) => void; + onCheckZAuction: () => void; + onApproveZAuction: () => void; + onConfirmSetBuyNow: () => void; +}; + +/** + * Drives the logic behind the set buy now form. + */ +export const useSetBuyNowForm = (zna: string): UseSetBuyNowFormReturn => { + const sdk = useZnsSdk(); + + const { account, provider } = useWeb3(); + const { executeTransaction } = useTransaction(); + const { domainId } = useSetBuyNowData(zna); + + const { data: isZAuctionCheckRequired, error: zAuctionCheckError } = + useZAuctionCheckTransferNftsByDomain(domainId, account); + + const [error, setError] = useState(); + const [step, setStep] = useState(Step.DETAILS); + const [buyNowAmount, setBuyNowAmount] = useState(''); + const [statusText, setStatusText] = useState(); + + const onCheckZAuction = async () => { + setError(undefined); + setStep(Step.LOADING); + setStatusText(StatusText.CHECK_ZAUCTION); + + // set timeout to prevent container jolt + await new Promise((r) => setTimeout(r, 1500)); + + if (isZAuctionCheckRequired) { + setStep(Step.ZAUCTION_APPROVE); + } else if (zAuctionCheckError) { + setError(ErrorText.FAILED_ZAUCTION_CHECK); + setStep(Step.DETAILS); + } else { + setStep(Step.CONFIRM); + } + }; + + const onApproveZAuction = () => { + setError(undefined); + return executeTransaction( + sdk.zauction.approveZAuctionToTransferNftsByDomain, + [domainId, provider.getSigner()], + { + onStart: () => { + setStep(Step.LOADING); + setStatusText(StatusText.WAITING_FOR_APPROVAL); + }, + onProcessing: () => setStatusText(StatusText.APPROVING_ZAUCTION), + onSuccess: () => setStep(Step.CONFIRM), + onError: (error: any) => { + setError(error.message); + setStep(Step.ZAUCTION_APPROVE); + }, + + invalidationKeys: [['user', { account, domainId }]], + }, + ); + }; + + const onConfirmSetBuyNow = () => { + const buyNowAmountAsNumber = Number(buyNowAmount); + + setError(undefined); + return executeTransaction( + sdk.zauction.setBuyNowPrice, + [ + { + amount: ethers.utils.parseEther(buyNowAmountAsNumber.toString()), + tokenId: domainId, + }, + provider.getSigner(), + ], + { + onStart: () => { + setStep(Step.LOADING); + setStatusText(StatusText.WAITING_FOR_SIGNATURE); + }, + // buyNowAmountAsNumber + onProcessing: () => setStatusText(StatusText.PROCESSING_SET_BUY_NOW), + onSuccess: () => setStep(Step.COMPLETE), + onError: (error: any) => { + setError(error.message); + setStep(Step.CONFIRM); + }, + + invalidationKeys: [['user', { account, domainId, buyNowAmount }]], + }, + ); + }; + + return { + step, + error, + statusText, + buyNowAmount, + setBuyNowAmount, + onCheckZAuction, + onApproveZAuction, + onConfirmSetBuyNow, + }; +}; diff --git a/src/features/set-buy-now/SetBuyNowForm/index.ts b/src/features/set-buy-now/SetBuyNowForm/index.ts new file mode 100644 index 00000000..44ce1fa1 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowForm/index.ts @@ -0,0 +1 @@ +export * from './SetBuyNowForm'; diff --git a/src/features/set-buy-now/SetBuyNowModal.tsx b/src/features/set-buy-now/SetBuyNowModal.tsx deleted file mode 100644 index 16846550..00000000 --- a/src/features/set-buy-now/SetBuyNowModal.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FC } from 'react'; - -import { BasicModalProps } from '../../lib/types/ui'; - -import { Modal } from '@zero-tech/zui/components'; - -import { SetBuyNow } from './SetBuyNow'; - -export interface SetBuyNowModalProps extends BasicModalProps {} - -export const SetBuyNowModal: FC = ({ ...modalProps }) => ( - - - -); diff --git a/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.module.scss b/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.module.scss new file mode 100644 index 00000000..e6e0e033 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.module.scss @@ -0,0 +1,4 @@ +.Container { + width: 35.625rem; + padding: 3rem; +} diff --git a/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.tsx b/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.tsx new file mode 100644 index 00000000..3a4bcdb0 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowModal/SetBuyNowModal.tsx @@ -0,0 +1,56 @@ +import { FC, useState } from 'react'; + +import { useWeb3 } from '../../../lib/hooks/useWeb3'; +import { BasicModalProps } from '../../../lib/types/ui'; + +import { SetBuyNowForm } from '..'; +import { ConnectWallet } from '../../ui/ConnectWallet'; +import { Modal } from '@zero-tech/zui/components'; + +import styles from './SetBuyNowModal.module.scss'; + +export interface SetBuyNowModalProps extends BasicModalProps { + zna: string; +} + +export const SetBuyNowModal: FC = ({ + zna, + ...modalProps +}) => { + const { account } = useWeb3(); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const onClose = () => { + setIsModalOpen(false); + }; + + return ( + setIsModalOpen(isOpen)} + {...modalProps} + > + + + ); +}; + +/******************* + * ModalContent + *******************/ + +interface ModalContentProps { + account: string; + zna: SetBuyNowModalProps['zna']; + onClose: () => void; +} + +const ModalContent = ({ account, zna, onClose }: ModalContentProps) => { + return account ? ( + + ) : ( + + ); +}; diff --git a/src/features/set-buy-now/SetBuyNowModal/index.ts b/src/features/set-buy-now/SetBuyNowModal/index.ts new file mode 100644 index 00000000..55aa4029 --- /dev/null +++ b/src/features/set-buy-now/SetBuyNowModal/index.ts @@ -0,0 +1 @@ +export * from './SetBuyNowModal'; diff --git a/src/features/set-buy-now/index.ts b/src/features/set-buy-now/index.ts index 55f3a8db..02521d54 100644 --- a/src/features/set-buy-now/index.ts +++ b/src/features/set-buy-now/index.ts @@ -1,2 +1,3 @@ -export * from './SetBuyNowModal'; export * from './SetBuyNowButton'; +export * from './SetBuyNowForm'; +export * from './SetBuyNowModal'; diff --git a/src/features/set-buy-now/useSetBuyNowData.ts b/src/features/set-buy-now/useSetBuyNowData.ts new file mode 100644 index 00000000..95f3d13f --- /dev/null +++ b/src/features/set-buy-now/useSetBuyNowData.ts @@ -0,0 +1,69 @@ +import { + useBidData, + useDomainData, + usePaymentToken, + useDomainMetadata, + useBuyNowListing, +} from '../../lib/hooks'; +import { + formatEthers, + getDomainId, + getParentZna, + sortBidsByAmount, +} from '../../lib/util'; + +export const useSetBuyNowData = (zna: string) => { + const domainId = getDomainId(zna); + const parentZna = getParentZna(zna); + + const { data: paymentToken } = usePaymentToken(parentZna); + const { data: allBids, isLoading: isLoadingBids } = useBidData(domainId); + const { data: domain, isLoading: isLoadingDomain } = useDomainData(domainId); + const { highestBid } = sortBidsByAmount(allBids); + + const { data: metadata, isLoading: isLoadingMetadata } = + useDomainMetadata(domainId); + + const { data: buyNowListingData, isLoading: isLoadingBuyNowListing } = + useBuyNowListing(domainId); + + const title = metadata?.title; + const creator = domain?.minter; + const paymentTokenLabel = paymentToken?.label ?? ''; + const paymentTokenSymbol = paymentToken?.symbol ?? ''; + const paymentTokenPriceInUsd = paymentToken?.priceInUsd ?? ''; + const imageAlt = `${metadata?.title ?? 'loading'} nft image`; + const imageSrc = metadata?.previewImage ?? metadata?.image; + const hasExistingBuyNow = Boolean(buyNowListingData?.price); + + const buyNowPriceAsString = buyNowListingData?.price + ? `${formatEthers( + buyNowListingData?.price?.toString(), + )} ${paymentTokenSymbol}` + : '-'; + + const highestBidAsString = highestBid + ? `${formatEthers(highestBid.amount)} ${paymentTokenSymbol}` + : '-'; + + const isLoading = + isLoadingDomain || + isLoadingBids || + isLoadingMetadata || + isLoadingBuyNowListing; + + return { + title, + creator, + domainId, + imageAlt, + imageSrc, + isLoading, + paymentTokenLabel, + paymentTokenSymbol, + paymentTokenPriceInUsd, + highestBidAsString, + buyNowPriceAsString, + hasExistingBuyNow, + }; +}; diff --git a/src/features/ui/MoreNFTOptions/MoreNFTOptions.tsx b/src/features/ui/MoreNFTOptions/MoreNFTOptions.tsx index a2e6a53e..173a3272 100644 --- a/src/features/ui/MoreNFTOptions/MoreNFTOptions.tsx +++ b/src/features/ui/MoreNFTOptions/MoreNFTOptions.tsx @@ -1,17 +1,23 @@ import { FC, useCallback, ReactNode, useState } from 'react'; import { OptionLabel } from '../OptionLabel'; +import { SetBuyNowModal } from '../../set-buy-now'; import { CreateTokenModal } from '../../create-token'; import { TransferOwnershipModal } from '../../transfer-ownership'; import { DropdownMenu, DropdownMenuProps } from '@zero-tech/zui/components'; -import { IconSend3, IconDatabase2 } from '@zero-tech/zui/components/Icons'; +import { + IconSend3, + IconDatabase2, + IconTag1, +} from '@zero-tech/zui/components/Icons'; export const enum OptionType { TRANSFER = 'transfer', + SET_BUY_NOW = 'set-buy-now', CREATE_TOKEN = 'create-token', } -export type Option = 'transfer' | 'create-token'; +export type Option = 'transfer' | 'set-buy-now' | 'create-token'; type MoreNFTOptionsProps = { zna: string; @@ -23,6 +29,10 @@ const transferOptionLabel = ( } label="Transfer Ownership" /> ); +const setBuyNowOptionLabel = ( + } label="Set Buy Now" /> +); + const createTokenOptionLabel = ( } label="Create Token" /> ); @@ -40,6 +50,12 @@ export const MoreNFTOptions: FC = ({ zna, trigger }) => { label: transferOptionLabel, onSelect: (e: any) => onSelectOption(e), }, + { + className: 'set-buy-now', + id: OptionType.SET_BUY_NOW, + label: setBuyNowOptionLabel, + onSelect: (e: any) => onSelectOption(e), + }, { className: 'create-token', id: OptionType.CREATE_TOKEN, @@ -77,6 +93,12 @@ export const MoreNFTOptions: FC = ({ zna, trigger }) => { onClose={onClose} /> + + *:not(:first-child) { + margin-left: 0.5rem; + } + + .Toggle { + position: relative; + width: 3rem; + height: calc(3rem / 2); + + border-radius: 0.75rem; + border: 1px solid $grey-lighter-2; + + transition: border $time-medium ease; + + overflow: hidden; + cursor: pointer; + + > div { + position: absolute; + top: 2px; + left: 1px; + + height: calc(3rem / 2 - 0.25rem); + width: calc(3rem / 2 - 0.25rem); + border-radius: 50%; + + background-color: $grey-lighter-2; + + transition: left $time-medium ease-in-out, + background-color $time-medium ease; + + > span { + position: absolute; + font-size: 0.5rem; + top: 50%; + font-weight: $font-weight-bold; + text-transform: uppercase; + color: $grey-lighter-2; + transition: color $time-medium ease; + + &:first-of-type { + left: 0; + transform: translateY(-50%) translateX(calc(-100% - 0.25rem)); + } + + &:last-of-type { + left: 100%; + transform: translateY(calc(-50%)) translateX(0.25rem); + } + } + } + + &.On { + background: $ocean-10; + > div { + left: calc(100% - (3rem / 2) + 3px); + } + } + } + + .Toggle.On { + border: 1px solid $blue; + + > div { + background-color: $blue !important; + + > span { + color: $blue; + } + } + } +} diff --git a/src/features/ui/ToggleButton/ToggleButton.tsx b/src/features/ui/ToggleButton/ToggleButton.tsx new file mode 100644 index 00000000..2e657be8 --- /dev/null +++ b/src/features/ui/ToggleButton/ToggleButton.tsx @@ -0,0 +1,38 @@ +import { FC } from 'react'; + +import styles from './ToggleButton.module.scss'; + +type ToggleButtonProps = { + label?: string | React.ReactNode; + toggled: boolean; + hideOnOffLabels?: boolean; + onClick: () => void; +}; + +export const ToggleButton: FC = ({ + hideOnOffLabels, + label, + onClick, + toggled, +}) => ( +
+
+
+ {hideOnOffLabels !== true && ( + <> + On + Off + + )} +
+
+ {typeof label === 'string' ? ( + {label} + ) : ( + { label } + )} +
+); diff --git a/src/features/ui/ToggleButton/index.ts b/src/features/ui/ToggleButton/index.ts new file mode 100644 index 00000000..650fcfda --- /dev/null +++ b/src/features/ui/ToggleButton/index.ts @@ -0,0 +1 @@ +export * from './ToggleButton'; diff --git a/src/features/ui/index.ts b/src/features/ui/index.ts index cc4560e1..56633c04 100644 --- a/src/features/ui/index.ts +++ b/src/features/ui/index.ts @@ -9,3 +9,4 @@ export * from './IconButton'; export * from './OptionLabel'; export * from './StatsList'; export * from './TableCard'; +export * from './ToggleButton'; diff --git a/src/features/view-nft/Actions/Actions.tsx b/src/features/view-nft/Actions/Actions.tsx index fbe83777..b012f047 100644 --- a/src/features/view-nft/Actions/Actions.tsx +++ b/src/features/view-nft/Actions/Actions.tsx @@ -53,7 +53,12 @@ export const Actions = ({ zna }: ActionsProps) => { { label: `Buy Now ${paymentTokenLabel}`, value: buyNowPriceString, - button: , + button: ( + + ), isVisible: isSetBuyNow, }, { diff --git a/src/features/view-subdomains/SubdomainTableCard/SubdomainTableCard.tsx b/src/features/view-subdomains/SubdomainTableCard/SubdomainTableCard.tsx index 9918035d..a1dd8bac 100644 --- a/src/features/view-subdomains/SubdomainTableCard/SubdomainTableCard.tsx +++ b/src/features/view-subdomains/SubdomainTableCard/SubdomainTableCard.tsx @@ -3,6 +3,7 @@ import { useQuery } from 'react-query'; import { useSubdomainTableItem } from '../useSubdomainTableItem'; import { formatEthers } from '../../../lib/util'; + import { getCloudinaryUrlFromIpfs, getCloudinaryVideoPoster, diff --git a/src/lib/hooks/index.ts b/src/lib/hooks/index.ts index 62388296..60a77121 100644 --- a/src/lib/hooks/index.ts +++ b/src/lib/hooks/index.ts @@ -14,4 +14,5 @@ export * from './useViewNavigation'; export * from './useWeb3'; export * from './useZAuctionCheckByBid'; export * from './useZAuctionCheckByPaymentToken'; +export * from './useZAuctionCheckTransferNftsByDomain'; export * from './useZnsSdk'; diff --git a/src/lib/hooks/useZAuctionCheckTransferNftsByDomain.ts b/src/lib/hooks/useZAuctionCheckTransferNftsByDomain.ts new file mode 100644 index 00000000..7e1e7bfa --- /dev/null +++ b/src/lib/hooks/useZAuctionCheckTransferNftsByDomain.ts @@ -0,0 +1,25 @@ +import { useQuery } from 'react-query'; + +import { useZnsSdk } from './useZnsSdk'; + +export const useZAuctionCheckTransferNftsByDomain = ( + domainId: string, + account: string, +) => { + const sdk = useZnsSdk(); + + return useQuery( + ['z-auction', 'check', 'transfer', 'nfts', { domainId, account }], + async () => + await sdk.zauction.needsToApproveZAuctionToTransferNftsByDomain( + domainId, + account, + ), + { + retry: false, + refetchOnMount: false, + refetchOnWindowFocus: false, + enabled: Boolean(domainId && account), + }, + ); +};