diff --git a/packages/react-kit/src/components/modal/components/Commit/CommitNonModal.tsx b/packages/react-kit/src/components/modal/components/Commit/CommitNonModal.tsx new file mode 100644 index 000000000..6689ae9fe --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/CommitNonModal.tsx @@ -0,0 +1,236 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { theme } from "../../../../theme"; +import { useAccount } from "../../../../hooks/connection/connection"; +import { useDisconnect } from "../../../../hooks/connection/useDisconnect"; +import { OfferFullDescriptionView } from "./OfferFullDescriptionView/OfferFullDescriptionView"; +import { ReturnUseProductByUuid } from "../../../../hooks/products/useProductByUuid"; +import { Offer } from "../../../../types/offer"; +import { VariantV1 } from "../../../../types/variants"; +import { OfferVariantView, OfferVariantViewProps } from "./OfferVariantView"; +import NonModal, { NonModalProps } from "../../nonModal/NonModal"; +import Typography from "../../../ui/Typography"; +import { BosonFooter } from "../common/BosonFooter"; +import { useConfigContext } from "../../../config/ConfigContext"; +import { Loading } from "../../../Loading"; +import { PurchaseOverviewView } from "../common/StepsOverview/PurchaseOverviewView"; +import { CommitOfferPolicyView } from "./OfferPolicyView/CommitOfferPolicyView"; +import useCheckExchangePolicy from "../../../../hooks/useCheckExchangePolicy"; +import { useConvertionRate } from "../../../widgets/finance/convertion-rate/useConvertionRate"; +import { ContractualAgreementView } from "./ContractualAgreementView/ContractualAgreementView"; +import { LicenseAgreementView } from "./LicenseAgreementView/LicenseAgreementView"; +import { CommitSuccess } from "./CommitSuccess"; +import { Exchange } from "../../../../types/exchange"; + +const colors = theme.colors.light; +enum ActiveStep { + OFFER_VIEW, + PURCHASE_OVERVIEW, + COMMIT_SUCESS, + EXCHANGE_POLICY, + CONTRACTUAL_AGREEMENT, + LICENSE_AGREEMENT, + OFFER_FULL_DESCRIPTION +} + +export type CommitNonModalProps = { + product?: ReturnUseProductByUuid; + singleOffer?: Offer; + isLoading: boolean; + fairExchangePolicyRules: string; + hideModal?: NonModalProps["hideModal"]; + offerViewOnExchangePolicyClick?: OfferVariantViewProps["onExchangePolicyClick"]; + offerViewOnPurchaseOverview?: OfferVariantViewProps["onPurchaseOverview"]; + offerViewOnViewFullDescription?: OfferVariantViewProps["onViewFullDescription"]; + forcedAccount?: string; +}; + +export default function CommitWrapper({ + hideModal, + ...props +}: CommitNonModalProps) { + return ( + + Commit + + ), + footerComponent: , + contentStyle: { + background: colors.white + } + }} + > + + + ); +} + +function CommitNonModal({ + singleOffer, + product: productResult, + isLoading, + fairExchangePolicyRules, + offerViewOnExchangePolicyClick, + offerViewOnPurchaseOverview, + offerViewOnViewFullDescription, + forcedAccount, + hideModal +}: CommitNonModalProps) { + const variants = productResult?.variants; + const variantsWithV1 = variants?.filter( + ({ offer: { metadata } }) => metadata?.type === "PRODUCT_V1" + ) as VariantV1[] | undefined; + const singleOfferVariant = singleOffer + ? { offer: singleOffer, variations: [] } + : undefined; + const defaultVariant = + variantsWithV1?.find((variant) => !variant.offer.voided) ?? + variantsWithV1?.[0] ?? + singleOfferVariant; + + const [exchange, setExchange] = useState(null); + const [selectedVariant, setSelectedVariant] = useState( + defaultVariant + ); + useEffect(() => { + if (defaultVariant) { + setSelectedVariant(defaultVariant); + } + }, [defaultVariant]); + const { + store: { tokens: defaultTokens } + } = useConvertionRate(); + const { config: coreConfig } = useConfigContext(); + const defaultDisputeResolverId = coreConfig?.defaultDisputeResolverId; + + const [{ currentStep }, setStep] = useState<{ + previousStep: ActiveStep[]; + currentStep: ActiveStep; + }>({ + previousStep: [], + currentStep: ActiveStep.OFFER_VIEW + }); + + const setActiveStep = (newCurrentStep: ActiveStep) => { + setStep((prev) => ({ + previousStep: [...prev.previousStep, prev.currentStep], + currentStep: newCurrentStep + })); + }; + const goToPreviousStep = useCallback(() => { + setStep((prev) => { + const { previousStep } = prev; + const currentStep = previousStep.length + ? (previousStep.pop() as ActiveStep) + : prev.currentStep; + const previousWithoutLast = previousStep; + return { + previousStep: previousWithoutLast, + currentStep: currentStep + }; + }); + }, []); + const { address } = useAccount(); + const disconnect = useDisconnect(); + const exchangePolicyCheckResult = useCheckExchangePolicy({ + offerId: selectedVariant?.offer?.id, + fairExchangePolicyRules, + defaultDisputeResolverId: defaultDisputeResolverId || "unknown", + defaultTokens: defaultTokens || [] + }); + if ( + forcedAccount && + address && + forcedAccount.toLowerCase() !== address.toLowerCase() + ) { + // force disconnection as the current connected wallet is not the forced one + disconnect(); + } + + if (!address) { + return ( + <> +

Please connect your wallet

+ {forcedAccount &&

(expected account: {forcedAccount})

} + + ); + } + if (isLoading) { + return ; + } + + if (!selectedVariant) { + return

No variant has been selected

; + } + + return ( + <> + {currentStep === ActiveStep.OFFER_VIEW ? ( + { + setActiveStep(ActiveStep.EXCHANGE_POLICY); + offerViewOnExchangePolicyClick?.(); + }} + onPurchaseOverview={() => { + setActiveStep(ActiveStep.PURCHASE_OVERVIEW); + offerViewOnPurchaseOverview?.(); + }} + onViewFullDescription={() => { + setActiveStep(ActiveStep.OFFER_FULL_DESCRIPTION); + offerViewOnViewFullDescription?.(); + }} + onNextClick={() => { + setActiveStep(ActiveStep.COMMIT_SUCESS); + // TODO: call to setExchange(exchange) + }} + fairExchangePolicyRules={fairExchangePolicyRules} + defaultDisputeResolverId={defaultDisputeResolverId} + /> + ) : currentStep === ActiveStep.OFFER_FULL_DESCRIPTION ? ( + + ) : currentStep === ActiveStep.PURCHASE_OVERVIEW ? ( + + ) : currentStep === ActiveStep.EXCHANGE_POLICY ? ( + + setActiveStep(ActiveStep.CONTRACTUAL_AGREEMENT) + } + onLicenseAgreementClick={() => + setActiveStep(ActiveStep.LICENSE_AGREEMENT) + } + exchangePolicyCheckResult={exchangePolicyCheckResult} + /> + ) : currentStep === ActiveStep.CONTRACTUAL_AGREEMENT ? ( + + ) : currentStep === ActiveStep.LICENSE_AGREEMENT ? ( + + ) : currentStep === ActiveStep.COMMIT_SUCESS ? ( + setActiveStep(ActiveStep.OFFER_VIEW)} + onClickDone={() => hideModal?.()} + onExchangePolicyClick={() => + setActiveStep(ActiveStep.EXCHANGE_POLICY) + } + exchangeId={exchange?.id || ""} + /> + ) : ( +

Wrong step...something went wrong

+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Commit/CommitSuccess.tsx b/packages/react-kit/src/components/modal/components/Commit/CommitSuccess.tsx new file mode 100644 index 000000000..bdcb74b62 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/CommitSuccess.tsx @@ -0,0 +1,162 @@ +import { CheckCircle, Fire, House } from "phosphor-react"; +import React, { useEffect } from "react"; +import styled from "styled-components"; +import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; +import Grid from "../../../ui/Grid"; +import IpfsImage from "../../../ui/IpfsImage"; +import Loading from "../../../ui/loading/Loading"; +import Typography from "../../../ui/Typography"; +import { theme } from "../../../../theme"; +import Video from "../../../ui/Video"; +import { Button } from "../../../buttons/Button"; +import GridContainer from "../../../ui/GridContainer"; +import { useNonModalContext } from "../../nonModal/NonModal"; +import { useExchanges } from "../../../../hooks/useExchanges"; +import DetailOpenSea from "../common/DetailOpenSea"; + +const colors = theme.colors.light; + +const ImageWrapper = styled.div` + position: relative; + max-width: 35rem !important; + min-width: 50%; + width: -webkit-fill-available; +`; + +type Props = { + onClickDone: () => void; + onHouseClick: () => void; + onExchangePolicyClick: () => void; + exchangeId: string; +}; + +export function CommitSuccess({ + onClickDone, + onHouseClick, + exchangeId +}: Props) { + const { + data: exchanges, + isError, + isFetching + } = useExchanges( + { + id: exchangeId + }, + { + enabled: !!exchangeId + } + ); + const exchange = exchanges?.[0]; + const offer = exchange?.offer; + + const offerDetails = offer ? getOfferDetails(offer) : undefined; + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + + + Sucess! + + + ), + contentStyle: { + background: colors.lightGrey + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]); + return ( + <> + {isFetching ? ( + + ) : !offer ? ( +
This exchange does not exist
+ ) : isError || !exchangeId || !offerDetails ? ( +
+ There has been an error, please try again later... +
+ ) : ( + + + {offerDetails.animationUrl ? ( + + + + + + + Congratulations! + + + + + +
+ What's next? + Redeem it! +
+
+ + + You got an rNFT + +
+ + + + +
+
+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Commit/ContractualAgreementView/ContractualAgreementView.tsx b/packages/react-kit/src/components/modal/components/Commit/ContractualAgreementView/ContractualAgreementView.tsx new file mode 100644 index 000000000..1c4a179c9 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/ContractualAgreementView/ContractualAgreementView.tsx @@ -0,0 +1,48 @@ +import React, { useEffect } from "react"; +import Grid from "../../../../ui/Grid"; +import Typography from "../../../../ui/Typography"; +import { ArrowLeft } from "phosphor-react"; +import ContractualAgreement from "../../../../contractualAgreement/ContractualAgreement"; +import { useNonModalContext } from "../../../nonModal/NonModal"; +import { theme } from "../../../../../theme"; +import { Offer } from "../../../../../types/offer"; + +const colors = theme.colors.light; +interface Props { + onBackClick: () => void; + offer: Offer | null; +} + +export function ContractualAgreementView({ onBackClick, offer }: Props) { + const offerId = offer?.id; + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + + Contractual Agreement + + ), + contentStyle: { + background: colors.white + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]); + return ( + <> + {offer ? ( + + ) : ( +

Offer could not be retrieved

+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Commit/LicenseAgreementView/LicenseAgreementView.tsx b/packages/react-kit/src/components/modal/components/Commit/LicenseAgreementView/LicenseAgreementView.tsx new file mode 100644 index 000000000..1a6110515 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/LicenseAgreementView/LicenseAgreementView.tsx @@ -0,0 +1,47 @@ +import React, { useEffect } from "react"; +import Grid from "../../../../ui/Grid"; +import Typography from "../../../../ui/Typography"; +import { ArrowLeft } from "phosphor-react"; +import { Exchange } from "../../../../../types/exchange"; +import License from "../../../../license/License"; +import { useNonModalContext } from "../../../nonModal/NonModal"; +import { theme } from "../../../../../theme"; + +const colors = theme.colors.light; +interface Props { + onBackClick: () => void; + offer: Exchange["offer"] | null; +} + +export function LicenseAgreementView({ onBackClick, offer }: Props) { + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + + License Agreement + + ), + contentStyle: { + background: colors.white + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]); + return ( + <> + {offer ? ( + + ) : ( +

Offer could not be retrieved

+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Commit/OfferFullDescriptionView/OfferFullDescriptionView.tsx b/packages/react-kit/src/components/modal/components/Commit/OfferFullDescriptionView/OfferFullDescriptionView.tsx new file mode 100644 index 000000000..65bd71ca3 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/OfferFullDescriptionView/OfferFullDescriptionView.tsx @@ -0,0 +1,39 @@ +import React, { useEffect } from "react"; +import { theme } from "../../../../../theme"; +import { Offer } from "../../../../../types/offer"; +import { useNonModalContext } from "../../../nonModal/NonModal"; +import { OfferFullDescription } from "../../common/OfferFullDescription"; +import Grid from "../../../../ui/Grid"; +import { ArrowLeft } from "phosphor-react"; +import Typography from "../../../../ui/Typography"; + +const colors = theme.colors.light; +interface Props { + onBackClick: () => void; + offer: Offer; +} + +export function OfferFullDescriptionView({ onBackClick, offer }: Props) { + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + + {offer.metadata.name || ""} + + ), + contentStyle: { + background: colors.white + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, offer.metadata.name]); + return ; +} diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangePolicy/ExchangePolicy.tsx b/packages/react-kit/src/components/modal/components/Commit/OfferPolicyView/CommitOfferPolicyView.tsx similarity index 70% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangePolicy/ExchangePolicy.tsx rename to packages/react-kit/src/components/modal/components/Commit/OfferPolicyView/CommitOfferPolicyView.tsx index 0d7b698b1..ef8fc8a01 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangePolicy/ExchangePolicy.tsx +++ b/packages/react-kit/src/components/modal/components/Commit/OfferPolicyView/CommitOfferPolicyView.tsx @@ -4,29 +4,29 @@ import Typography from "../../../../ui/Typography"; import { ArrowLeft } from "phosphor-react"; import { Exchange } from "../../../../../types/exchange"; import { offers } from "@bosonprotocol/core-sdk"; -import ExchangePolicyDetails, { - ExchangePolicyDetailsProps -} from "../../../../exchangePolicy/ExchangePolicyDetails"; +import OfferPolicyDetails, { + OfferPolicyDetailsProps +} from "../../../../offerPolicy/OfferPolicyDetails"; import { useNonModalContext } from "../../../nonModal/NonModal"; import { theme } from "../../../../../theme"; const colors = theme.colors.light; interface Props { onBackClick: () => void; - exchange: Exchange | null; - onContractualAgreementClick: ExchangePolicyDetailsProps["onContractualAgreementClick"]; - onLicenseAgreementClick: ExchangePolicyDetailsProps["onLicenseAgreementClick"]; + offer: Exchange["offer"] | null | undefined; + onContractualAgreementClick: OfferPolicyDetailsProps["onContractualAgreementClick"]; + onLicenseAgreementClick: OfferPolicyDetailsProps["onLicenseAgreementClick"]; exchangePolicyCheckResult?: offers.CheckExchangePolicyResult; } -export function ExchangePolicy({ +export function CommitOfferPolicyView({ onBackClick, - exchange, + offer, onContractualAgreementClick, onLicenseAgreementClick, exchangePolicyCheckResult }: Props) { - const exchangeName = exchange?.offer.metadata.name || ""; + const offerName = offer?.metadata.name || ""; const dispatch = useNonModalContext(); useEffect(() => { dispatch({ @@ -39,7 +39,7 @@ export function ExchangePolicy({ style={{ cursor: "pointer", flexShrink: 0 }} /> - {exchangeName} + {offerName} ), @@ -49,18 +49,18 @@ export function ExchangePolicy({ } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dispatch, exchangeName]); + }, [dispatch, offerName]); return ( <> - {exchange ? ( - ) : ( -

Exchange could not be retrieved

+

Offer could not be retrieved

)} ); diff --git a/packages/react-kit/src/components/modal/components/Commit/OfferVariantView.tsx b/packages/react-kit/src/components/modal/components/Commit/OfferVariantView.tsx new file mode 100644 index 000000000..d75cf29c3 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Commit/OfferVariantView.tsx @@ -0,0 +1,182 @@ +import React, { useEffect, useMemo } from "react"; +import styled from "styled-components"; +import { VariantV1 } from "../../../../types/variants"; +import { theme } from "../../../../theme"; +import { useSellers } from "../../../../hooks/useSellers"; +import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; +import { isTruthy } from "../../../../types/helpers"; +import { useConvertionRate } from "../../../widgets/finance/convertion-rate/useConvertionRate"; +import useCheckExchangePolicy from "../../../../hooks/useCheckExchangePolicy"; +import { useNonModalContext } from "../../nonModal/NonModal"; +import Grid from "../../../ui/Grid"; +import Typography from "../../../ui/Typography"; +import { Loading } from "../../../Loading"; +import DetailSlider from "../common/detail/DetailSlider"; +import GridContainer from "../../../ui/GridContainer"; +import { SellerAndDescription } from "../common/detail/SellerAndDescription"; +import VariationSelects from "../Redeem/ExchangeView/VariationSelects"; +import DetailView from "../Redeem/ExchangeView/DetailView/DetailView"; + +const colors = theme.colors.light; + +const ImageWrapper = styled.div` + position: relative; + max-width: 35rem !important; + min-width: 50%; + width: -webkit-fill-available; +`; + +export type OfferVariantViewProps = { + onNextClick: () => void; + onExchangePolicyClick: () => void; + onPurchaseOverview: () => void; + onViewFullDescription: () => void; + variant: VariantV1; + fairExchangePolicyRules: string; + defaultDisputeResolverId: string; +}; + +const SLIDER_OPTIONS = { + type: "carousel" as const, + startAt: 0, + gap: 20, + perView: 1 +}; + +export function OfferVariantView({ + variant, + onNextClick, + onExchangePolicyClick, + onPurchaseOverview, + onViewFullDescription, + fairExchangePolicyRules, + defaultDisputeResolverId +}: OfferVariantViewProps) { + const hasVariations = !!variant.variations?.length; + const { offer } = variant; + + const { + data: sellers, + isLoading, + isError + } = useSellers( + { + id: offer?.seller.id, + includeFunds: true + }, + { + enabled: !!offer?.seller.id + } + ); + + const sellerAvailableDeposit = sellers?.[0]?.funds?.find( + (fund) => fund.token.address === offer?.exchangeToken.address + )?.availableAmount; + const offerRequiredDeposit = Number(offer?.sellerDeposit || 0); + const hasSellerEnoughFunds = + offerRequiredDeposit > 0 + ? Number(sellerAvailableDeposit) >= offerRequiredDeposit + : true; + const { offerImg, animationUrl, images } = offer + ? getOfferDetails(offer) + : ({} as ReturnType); + const allImages = useMemo(() => { + return Array.from(new Set([offerImg || "", ...(images || [])])).filter( + isTruthy + ); + }, [offerImg, images]); + const { + store: { tokens: defaultTokens } + } = useConvertionRate(); + + const exchangePolicyCheckResult = useCheckExchangePolicy({ + offerId: offer.id, + fairExchangePolicyRules, + defaultDisputeResolverId: defaultDisputeResolverId || "unknown", + defaultTokens: defaultTokens || [] + }); + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + {offer && ( + + {offer.metadata.name} + + )} + + ), + contentStyle: { + background: colors.lightGrey + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, offer]); + return ( + <> + {isLoading ? ( + + ) : !offer ? ( +
This exchange does not exist
+ ) : isError ? ( +
+ There has been an error, please try again later... +
+ ) : ( + + + + <> + {(allImages.length > 0 || animationUrl) && ( + + )} + + + + + + + {hasVariations && ( +
+ +
+ )} + +
+
+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/DetailView/DetailView.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/DetailView/DetailView.tsx index 038bd7643..83b3a2aae 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/DetailView/DetailView.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/DetailView/DetailView.tsx @@ -33,7 +33,7 @@ import { StyledCancelButton, Widget, WidgetUpperGrid -} from "../detail/Detail.style"; +} from "../../../common/detail/Detail.style"; import TokenGated from "./TokenGated"; import { exchanges, offers, subgraph } from "@bosonprotocol/core-sdk"; import Price from "../../../../../price/Price"; @@ -43,7 +43,7 @@ import { } from "../../../../../../lib/price/prices"; import { useConfigContext } from "../../../../../config/ConfigContext"; import { titleCase } from "../../../../../../lib/string/formatText"; -import DetailTable from "../detail/DetailTable"; +import DetailTable from "../../../common/detail/DetailTable"; import { DetailDisputeResolver } from "./DetailDisputeResolver"; import { IPrice } from "../../../../../../lib/price/convertPrice"; import useCheckTokenGatedOffer from "../../../../../../hooks/tokenGated/useCheckTokenGatedOffer"; @@ -149,7 +149,7 @@ const getOfferDetailData = ( Redeemable - If you don’t redeem your NFT during the redemption period, it + If you don't redeem your NFT during the redemption period, it will expire and you will receive back the price minus the Buyer cancel penalty @@ -169,7 +169,7 @@ const getOfferDetailData = ( Redeemable - If you don’t redeem your NFT during the redemption period, it will + If you don't redeem your NFT during the redemption period, it will expire and you will receive back the price minus the Buyer cancel penalty @@ -305,13 +305,14 @@ interface IDetailWidget { exchange?: Exchange; hasSellerEnoughFunds: boolean; isPreview?: boolean; - onCancelExchange: () => void; + onCancelExchange?: () => void; hasMultipleVariants?: boolean; onExchangePolicyClick: () => void; - onRedeem: () => void; + onRedeem?: () => void; + onCommit?: () => void; onPurchaseOverview: () => void; - onExpireVoucherClick: () => void; - onRaiseDisputeClick: () => void; + onExpireVoucherClick?: () => void; + onRaiseDisputeClick?: () => void; exchangePolicyCheckResult?: offers.CheckExchangePolicyResult; } @@ -328,6 +329,7 @@ const DetailView: React.FC = ({ onPurchaseOverview, onRaiseDisputeClick, onRedeem, + onCommit, exchangePolicyCheckResult }) => { const core = useCoreSDKWithContext(); @@ -427,7 +429,7 @@ const DetailView: React.FC = ({ if (!exchange) { return; } - onCancelExchange(); + onCancelExchange?.(); }; const isOfferEmpty = quantity < 1; @@ -477,207 +479,205 @@ const DetailView: React.FC = ({ offer }); return ( - <> - - {isExchange && isToRedeem && ( + + {isExchange && isToRedeem && ( + + {redeemableDays > 0 + ? `${redeemableDays} days left to Redeem` + : `${redeemableHours} hours left to Redeem`} + + )} +
+ {isExchange && isExchangeExpired && ( - {redeemableDays > 0 - ? `${redeemableDays} days left to Redeem` - : `${redeemableHours} hours left to Redeem`} + { + if (exchange) { + onExpireVoucherClick?.(); + } + }} + > + + You can withdraw your funds here + + + )} -
- {isExchange && isExchangeExpired && ( - - { - if (exchange) { - onExpireVoucherClick(); - } - }} - > - - You can withdraw your funds here - - - - - )} - - + + - {isOffer && !isNotCommittableOffer && ( - - )} + {isOffer && !isNotCommittableOffer && ( + + )} - {isOffer && isNotCommittableOffer && ( - - {!isPreview && notCommittableOfferStatus} - - )} - {isToRedeem && ( - { - onRedeem(); - }} - > - Redeem - - )} - {!isToRedeem && ( - - {disabledRedeemText} - - - )} - -
- - {isBeforeRedeem ? ( - - How it works? - - ) : ( - + {!isPreview && notCommittableOfferStatus} + + )} + {isToRedeem && ( + { + onRedeem?.(); + }} > -   - + Redeem + )} - - - {offer.condition && ( - - )} -
- -
- {isExchange && ( - <> - - - { - const contactSellerForExchangeUrlWithId = - contactSellerForExchangeUrl.replace( - "{id}", - exchange?.id || "" - ); - window.open(contactSellerForExchangeUrlWithId, "_blank"); - }} - theme="blank" - type="button" - style={{ fontSize: "0.875rem" }} - disabled={ - isInWrongChain || !isBuyer || !contactSellerForExchangeUrl - } - > - Contact seller - - {isBeforeRedeem ? ( - <> - {![ - exchanges.ExtendedExchangeState.Expired, - subgraph.ExchangeState.Cancelled, - subgraph.ExchangeState.Revoked, - subgraph.ExchangeState.Completed - ].includes( - exchangeStatus as - | exchanges.ExtendedExchangeState - | subgraph.ExchangeState - ) && ( - - Cancel - - )} - - ) : ( - <> - {!exchange?.disputed && ( - { - onRaiseDisputeClick(); - }} - type="button" - theme="blank" - style={{ fontSize: "0.875rem" }} - disabled={ - exchange?.state !== "REDEEMED" || - !isBuyer || - isInWrongChain - } - > - Raise a problem - - - )} - - )} - - + {!isToRedeem && ( + + {disabledRedeemText} + + + )} + +
+ + {isBeforeRedeem ? ( + + How it works? + + ) : ( + +   + )} -
- + + + {offer.condition && ( + + )} +
+ +
+ {isExchange && ( + <> + + + { + const contactSellerForExchangeUrlWithId = + contactSellerForExchangeUrl.replace( + "{id}", + exchange?.id || "" + ); + window.open(contactSellerForExchangeUrlWithId, "_blank"); + }} + theme="blank" + type="button" + style={{ fontSize: "0.875rem" }} + disabled={ + isInWrongChain || !isBuyer || !contactSellerForExchangeUrl + } + > + Contact seller + + {isBeforeRedeem ? ( + <> + {![ + exchanges.ExtendedExchangeState.Expired, + subgraph.ExchangeState.Cancelled, + subgraph.ExchangeState.Revoked, + subgraph.ExchangeState.Completed + ].includes( + exchangeStatus as + | exchanges.ExtendedExchangeState + | subgraph.ExchangeState + ) && ( + + Cancel + + )} + + ) : ( + <> + {!exchange?.disputed && ( + { + onRaiseDisputeClick?.(); + }} + type="button" + theme="blank" + style={{ fontSize: "0.875rem" }} + disabled={ + exchange?.state !== "REDEEMED" || + !isBuyer || + isInWrongChain + } + > + Raise a problem + + + )} + + )} + + + )} +
); }; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeFullDescriptionView/ExchangeFullDescription.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeFullDescriptionView/ExchangeFullDescription.tsx index 84e7e6997..cdc6bc021 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeFullDescriptionView/ExchangeFullDescription.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeFullDescriptionView/ExchangeFullDescription.tsx @@ -1,14 +1,7 @@ import React from "react"; -import { getOfferDetails } from "../../../../../../lib/offer/getOfferDetails"; -import { theme } from "../../../../../../theme"; import { Exchange } from "../../../../../../types/exchange"; -import Grid from "../../../../../ui/Grid"; -import GridContainer from "../../../../../ui/GridContainer"; -import Typography from "../../../../../ui/Typography"; -import DetailTable from "../detail/DetailTable"; -import DetailTransactions from "../detail/DetailTransactions"; - -const colors = theme.colors.light; +import DetailTransactions from "../../../common/detail/DetailTransactions"; +import { OfferFullDescription } from "../../../common/OfferFullDescription"; interface ExchangeFullDescriptionProps { exchange: Exchange; @@ -18,58 +11,16 @@ export const ExchangeFullDescription: React.FC< ExchangeFullDescriptionProps > = ({ exchange }) => { const { offer } = exchange; - const { description, artistDescription, shippingInfo } = - getOfferDetails(offer); const buyerAddress = exchange.buyer.wallet; return ( - <> - -
- Product description - - {description} - -
- -
- About the creator - - {artistDescription} - -
-
- - - {(shippingInfo.returnPeriodInDays !== undefined || - !!shippingInfo.shippingTable.length) && ( -
- Shipping information - - Return period: {shippingInfo.returnPeriodInDays}{" "} - {shippingInfo.returnPeriodInDays === 1 ? "day" : "days"} - - -
- )} -
- + + + ); }; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeView.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeView.tsx index 119e7186f..5df4f662d 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeView.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/ExchangeView.tsx @@ -8,17 +8,17 @@ import { VariantV1 } from "../../../../../types/variants"; import Grid from "../../../../ui/Grid"; import Loading from "../../../../ui/loading/Loading"; import Typography from "../../../../ui/Typography"; -import DetailOpenSea from "./detail/DetailOpenSea"; +import DetailOpenSea from "../../common/DetailOpenSea"; import DetailView from "./DetailView/DetailView"; import VariationSelects from "./VariationSelects"; -import DetailSlider from "./detail/DetailSlider"; import { isTruthy } from "../../../../../types/helpers"; import GridContainer from "../../../../ui/GridContainer"; import { theme } from "../../../../../theme"; -import { SellerAndDescription } from "./detail/SellerAndDescription"; import { useConvertionRate } from "../../../../widgets/finance/convertion-rate/useConvertionRate"; import useCheckExchangePolicy from "../../../../../hooks/useCheckExchangePolicy"; import { useNonModalContext } from "../../../nonModal/NonModal"; +import { SellerAndDescription } from "../../common/detail/SellerAndDescription"; +import DetailSlider from "../../common/detail/DetailSlider"; const colors = theme.colors.light; @@ -182,7 +182,7 @@ export function ExchangeView({ diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/RedeemSuccess.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/RedeemSuccess.tsx index b1f364bdd..ba1a46d0a 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/RedeemSuccess.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/RedeemSuccess.tsx @@ -7,7 +7,7 @@ import Grid from "../../../../ui/Grid"; import IpfsImage from "../../../../ui/IpfsImage"; import Loading from "../../../../ui/loading/Loading"; import Typography from "../../../../ui/Typography"; -import DetailOpenSea from "./detail/DetailOpenSea"; +import DetailOpenSea from "../../common/DetailOpenSea"; import { useFormikContext } from "formik"; import { FormType } from "../RedeemFormModel"; import { theme } from "../../../../../theme"; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/cancellation/CancelExchange.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/cancellation/CancelExchange.tsx index 038932652..d934deab6 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/cancellation/CancelExchange.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/cancellation/CancelExchange.tsx @@ -21,7 +21,7 @@ import SuccessTransactionToast from "../../../../../toasts/SuccessTransactionToa import Grid from "../../../../../ui/Grid"; import { Spinner } from "../../../../../ui/loading/Spinner"; import ThemedButton from "../../../../../ui/ThemedButton"; -import DetailTable from "../detail/DetailTable"; +import DetailTable from "../../../common/detail/DetailTable"; import { useSigner } from "../../../../../../hooks/connection/connection"; import { RedemptionWidgetAction, diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/expireVoucher/ExpireVoucher.tsx b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/expireVoucher/ExpireVoucher.tsx index 52a414094..f744ad1b7 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/expireVoucher/ExpireVoucher.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/expireVoucher/ExpireVoucher.tsx @@ -19,7 +19,7 @@ import Grid from "../../../../../ui/Grid"; import { Spinner } from "../../../../../ui/loading/Spinner"; import ThemedButton from "../../../../../ui/ThemedButton"; import Typography from "../../../../../ui/Typography"; -import DetailTable from "../detail/DetailTable"; +import DetailTable from "../../../common/detail/DetailTable"; import { useSigner } from "../../../../../../hooks/connection/connection"; import { extractUserFriendlyError } from "../../../../../../lib/errors/transactions"; diff --git a/packages/react-kit/src/components/modal/components/Redeem/LicenseAgreementView/LicenseAgreementView.tsx b/packages/react-kit/src/components/modal/components/Redeem/LicenseAgreementView/LicenseAgreementView.tsx index c9bdf9a20..1e9a63ec4 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/LicenseAgreementView/LicenseAgreementView.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/LicenseAgreementView/LicenseAgreementView.tsx @@ -10,10 +10,10 @@ import { theme } from "../../../../../theme"; const colors = theme.colors.light; interface Props { onBackClick: () => void; - exchange: Exchange | null; + offer: Exchange["offer"] | null | undefined; } -export function LicenseAgreementView({ onBackClick, exchange }: Props) { +export function LicenseAgreementView({ onBackClick, offer }: Props) { const dispatch = useNonModalContext(); useEffect(() => { dispatch({ @@ -37,8 +37,8 @@ export function LicenseAgreementView({ onBackClick, exchange }: Props) { }, [dispatch]); return ( <> - {exchange ? ( - + {offer ? ( + ) : (

Exchange could not be retrieved

)} diff --git a/packages/react-kit/src/components/modal/components/Redeem/OfferPolicyView/RedeemOfferPolicyView.tsx b/packages/react-kit/src/components/modal/components/Redeem/OfferPolicyView/RedeemOfferPolicyView.tsx new file mode 100644 index 000000000..b5790e76d --- /dev/null +++ b/packages/react-kit/src/components/modal/components/Redeem/OfferPolicyView/RedeemOfferPolicyView.tsx @@ -0,0 +1,67 @@ +import React, { useEffect } from "react"; +import Grid from "../../../../ui/Grid"; +import Typography from "../../../../ui/Typography"; +import { ArrowLeft } from "phosphor-react"; +import { Exchange } from "../../../../../types/exchange"; +import { offers } from "@bosonprotocol/core-sdk"; +import OfferPolicyDetails, { + OfferPolicyDetailsProps +} from "../../../../offerPolicy/OfferPolicyDetails"; +import { useNonModalContext } from "../../../nonModal/NonModal"; +import { theme } from "../../../../../theme"; + +const colors = theme.colors.light; +interface Props { + onBackClick: () => void; + offer: Exchange["offer"] | null | undefined; + onContractualAgreementClick: OfferPolicyDetailsProps["onContractualAgreementClick"]; + onLicenseAgreementClick: OfferPolicyDetailsProps["onLicenseAgreementClick"]; + exchangePolicyCheckResult?: offers.CheckExchangePolicyResult; +} + +export function RedeemOfferPolicyView({ + onBackClick, + offer, + onContractualAgreementClick, + onLicenseAgreementClick, + exchangePolicyCheckResult +}: Props) { + const offerName = offer?.metadata.name || ""; + const dispatch = useNonModalContext(); + useEffect(() => { + dispatch({ + payload: { + headerComponent: ( + + + + {offerName} + + + ), + contentStyle: { + background: colors.white + } + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, offerName]); + return ( + <> + {offer ? ( + + ) : ( +

Offer could not be retrieved

+ )} + + ); +} diff --git a/packages/react-kit/src/components/modal/components/Redeem/RedeemNonModal.tsx b/packages/react-kit/src/components/modal/components/Redeem/RedeemNonModal.tsx index 12e0b67ae..a72e1d9b6 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/RedeemNonModal.tsx +++ b/packages/react-kit/src/components/modal/components/Redeem/RedeemNonModal.tsx @@ -8,10 +8,9 @@ import React, { } from "react"; import { useDisconnect } from "wagmi"; import * as Yup from "yup"; -import { ExchangePolicy } from "./ExchangePolicy/ExchangePolicy"; +import { RedeemOfferPolicyView } from "./OfferPolicyView/RedeemOfferPolicyView"; import { MyItems, MyItemsProps } from "./MyItems/MyItems"; import { FormModel } from "./RedeemFormModel"; -import StepsOverview from "./StepsOverview/StepsOverview"; import { Exchange } from "../../../../types/exchange"; import { ContractualAgreementView } from "./ContractualAgreementView/ContractualAgreementView"; import { LicenseAgreementView } from "./LicenseAgreementView/LicenseAgreementView"; @@ -20,7 +19,6 @@ import { ConfirmationViewProps } from "./Confirmation/ConfirmationView"; import RedeemFormView from "./RedeemForm/RedeemFormView"; -import { PurchaseOverviewView } from "./StepsOverview/PurchaseOverviewView"; import { RedeemSuccess } from "./ExchangeView/RedeemSuccess"; import { @@ -45,9 +43,11 @@ import { RedemptionWidgetAction, useRedemptionContext } from "../../../widgets/redemption/provider/RedemptionContext"; -import { BosonFooter } from "./BosonFooter"; +import { BosonFooter } from "../common/BosonFooter"; import { theme } from "../../../../theme"; import { useAccount } from "../../../../hooks/connection/connection"; +import StepsOverview from "../common/StepsOverview/StepsOverview"; +import { PurchaseOverviewView } from "../common/StepsOverview/PurchaseOverviewView"; const colors = theme.colors.light; enum ActiveStep { @@ -463,8 +463,8 @@ function RedeemNonModal({ isValid={isRedeemFormOK} /> ) : currentStep === ActiveStep.EXCHANGE_POLICY ? ( - setActiveStep(ActiveStep.CONTRACTUAL_AGREEMENT) @@ -481,7 +481,7 @@ function RedeemNonModal({ /> ) : currentStep === ActiveStep.LICENSE_AGREEMENT ? ( ) : currentStep === ActiveStep.REDEEM_FORM_CONFIRMATION ? ( diff --git a/packages/react-kit/src/components/modal/components/Redeem/BosonFooter.tsx b/packages/react-kit/src/components/modal/components/common/BosonFooter.tsx similarity index 100% rename from packages/react-kit/src/components/modal/components/Redeem/BosonFooter.tsx rename to packages/react-kit/src/components/modal/components/common/BosonFooter.tsx diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailOpenSea.tsx b/packages/react-kit/src/components/modal/components/common/DetailOpenSea.tsx similarity index 89% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailOpenSea.tsx rename to packages/react-kit/src/components/modal/components/common/DetailOpenSea.tsx index 18e062d72..d2d97c1f5 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailOpenSea.tsx +++ b/packages/react-kit/src/components/modal/components/common/DetailOpenSea.tsx @@ -1,11 +1,11 @@ import { exchanges, subgraph } from "@bosonprotocol/core-sdk"; import { ArrowSquareOut } from "phosphor-react"; import React, { useMemo } from "react"; -import { Exchange } from "../../../../../../types/exchange"; -import { useEnvContext } from "../../../../../environment/EnvironmentContext"; +import { Exchange } from "../../../../types/exchange"; +import { useEnvContext } from "../../../environment/EnvironmentContext"; -import { OpenSeaButton } from "./Detail.style"; -import { getExchangeTokenId } from "../../../../../../lib/utils/exchange"; +import { getExchangeTokenId } from "../../../../lib/utils/exchange"; +import { OpenSeaButton } from "./detail/Detail.style"; interface Props { exchange?: Exchange; diff --git a/packages/react-kit/src/components/modal/components/common/OfferFullDescription.tsx b/packages/react-kit/src/components/modal/components/common/OfferFullDescription.tsx new file mode 100644 index 000000000..452583f80 --- /dev/null +++ b/packages/react-kit/src/components/modal/components/common/OfferFullDescription.tsx @@ -0,0 +1,69 @@ +import React, { ReactNode } from "react"; +import { Offer } from "../../../../types/offer"; +import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; +import { theme } from "../../../../theme"; +import GridContainer from "../../../ui/GridContainer"; +import Typography from "../../../ui/Typography"; +import Grid from "../../../ui/Grid"; +import DetailTable from "./detail/DetailTable"; + +const colors = theme.colors.light; + +interface OfferFullDescriptionProps { + offer: Offer; + children?: ReactNode; +} + +export const OfferFullDescription: React.FC = ({ + offer, + children +}) => { + const { description, artistDescription, shippingInfo } = + getOfferDetails(offer); + + return ( + <> + +
+ Product description + + {description} + +
+ +
+ About the creator + + {artistDescription} + +
+
+ + {children} + {(shippingInfo.returnPeriodInDays !== undefined || + !!shippingInfo.shippingTable.length) && ( +
+ Shipping information + + Return period: {shippingInfo.returnPeriodInDays}{" "} + {shippingInfo.returnPeriodInDays === 1 ? "day" : "days"} + + +
+ )} +
+ + ); +}; diff --git a/packages/react-kit/src/components/modal/components/Redeem/StepsOverview/PurchaseOverview.tsx b/packages/react-kit/src/components/modal/components/common/StepsOverview/PurchaseOverview.tsx similarity index 97% rename from packages/react-kit/src/components/modal/components/Redeem/StepsOverview/PurchaseOverview.tsx rename to packages/react-kit/src/components/modal/components/common/StepsOverview/PurchaseOverview.tsx index e144dd79b..daf6acd1e 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/StepsOverview/PurchaseOverview.tsx +++ b/packages/react-kit/src/components/modal/components/common/StepsOverview/PurchaseOverview.tsx @@ -8,10 +8,7 @@ import { import { useBreakpoints } from "../../../../../hooks/useBreakpoints"; import Grid from "../../../../ui/Grid"; import Typography from "../../../../ui/Typography"; -import { - LearnMore, - ModalBackground -} from "../ExchangeView/detail/Detail.style"; +import { LearnMore, ModalBackground } from "../detail/Detail.style"; import { CommitStep } from "./style"; import styled from "styled-components"; import { breakpoint } from "../../../../../lib/ui/breakpoint"; diff --git a/packages/react-kit/src/components/modal/components/Redeem/StepsOverview/PurchaseOverviewView.tsx b/packages/react-kit/src/components/modal/components/common/StepsOverview/PurchaseOverviewView.tsx similarity index 100% rename from packages/react-kit/src/components/modal/components/Redeem/StepsOverview/PurchaseOverviewView.tsx rename to packages/react-kit/src/components/modal/components/common/StepsOverview/PurchaseOverviewView.tsx diff --git a/packages/react-kit/src/components/modal/components/Redeem/StepsOverview/StepsOverview.tsx b/packages/react-kit/src/components/modal/components/common/StepsOverview/StepsOverview.tsx similarity index 100% rename from packages/react-kit/src/components/modal/components/Redeem/StepsOverview/StepsOverview.tsx rename to packages/react-kit/src/components/modal/components/common/StepsOverview/StepsOverview.tsx diff --git a/packages/react-kit/src/components/modal/components/Redeem/StepsOverview/style.tsx b/packages/react-kit/src/components/modal/components/common/StepsOverview/style.tsx similarity index 100% rename from packages/react-kit/src/components/modal/components/Redeem/StepsOverview/style.tsx rename to packages/react-kit/src/components/modal/components/common/StepsOverview/style.tsx diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/Detail.style.tsx b/packages/react-kit/src/components/modal/components/common/detail/Detail.style.tsx similarity index 96% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/Detail.style.tsx rename to packages/react-kit/src/components/modal/components/common/detail/Detail.style.tsx index 9b7cd077e..de125a945 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/Detail.style.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/Detail.style.tsx @@ -1,13 +1,13 @@ import styled, { css } from "styled-components"; -import { breakpoint } from "../../../../../../lib/ui/breakpoint"; -import { theme } from "../../../../../../theme"; -import Grid from "../../../../../ui/Grid"; -import ThemedButton from "../../../../../ui/ThemedButton"; -import { zIndex } from "../../../../../ui/zIndex"; - -import frameImage from "../../../../../../assets/frame.png"; -import { buttonText } from "../../../../../ui/styles"; -import Typography from "../../../../../ui/Typography"; +import { breakpoint } from "../../../../../lib/ui/breakpoint"; +import { theme } from "../../../../../theme"; +import Grid from "../../../../ui/Grid"; +import ThemedButton from "../../../../ui/ThemedButton"; +import { zIndex } from "../../../../ui/zIndex"; + +import frameImage from "../../../../../assets/frame.png"; +import { buttonText } from "../../../../ui/styles"; +import Typography from "../../../../ui/Typography"; const colors = theme.colors.light; export const ChartWrapper = styled.div` @@ -498,6 +498,7 @@ export const Widget = styled.div` width: 100%; background: ${colors.white}; font-family: "Plus Jakarta Sans"; + padding-top: 2rem; > div { padding-left: 2rem; padding-right: 2rem; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailSlider.tsx b/packages/react-kit/src/components/modal/components/common/detail/DetailSlider.tsx similarity index 91% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailSlider.tsx rename to packages/react-kit/src/components/modal/components/common/detail/DetailSlider.tsx index b14b44e1c..2f778ef42 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailSlider.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/DetailSlider.tsx @@ -5,13 +5,13 @@ import { CaretLeft, CaretRight } from "phosphor-react"; import React, { useEffect, useMemo, useReducer, useRef } from "react"; import { GlideSlide, GlideWrapper } from "./Detail.style"; -import { breakpointNumbers } from "../../../../../../lib/ui/breakpoint"; -import Grid from "../../../../../ui/Grid"; -import ThemedButton from "../../../../../ui/ThemedButton"; -import IpfsImage from "../../../../../ui/IpfsImage"; -import { zIndex } from "../../../../../ui/zIndex"; -import Video from "../../../../../ui/Video"; -import { theme } from "../../../../../../theme"; +import { breakpointNumbers } from "../../../../../lib/ui/breakpoint"; +import Grid from "../../../../ui/Grid"; +import ThemedButton from "../../../../ui/ThemedButton"; +import IpfsImage from "../../../../ui/IpfsImage"; +import { zIndex } from "../../../../ui/zIndex"; +import Video from "../../../../ui/Video"; +import { theme } from "../../../../../theme"; import styled from "styled-components"; const colors = theme.colors.light; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTable.tsx b/packages/react-kit/src/components/modal/components/common/detail/DetailTable.tsx similarity index 91% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTable.tsx rename to packages/react-kit/src/components/modal/components/common/detail/DetailTable.tsx index 2e62cb171..adaef5925 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTable.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/DetailTable.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from "react"; -import { Tooltip } from "../../../../../tooltip/Tooltip"; -import Grid from "../../../../../ui/Grid"; -import Typography from "../../../../../ui/Typography"; +import { Tooltip } from "../../../../tooltip/Tooltip"; +import Grid from "../../../../ui/Grid"; +import Typography from "../../../../ui/Typography"; import { Table } from "./Detail.style"; export interface Data { diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTransactions.tsx b/packages/react-kit/src/components/modal/components/common/detail/DetailTransactions.tsx similarity index 84% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTransactions.tsx rename to packages/react-kit/src/components/modal/components/common/detail/DetailTransactions.tsx index 2cee85bd0..16898cb8d 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/DetailTransactions.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/DetailTransactions.tsx @@ -1,12 +1,12 @@ import dayjs from "dayjs"; import React, { useMemo } from "react"; -import useTransactionHistory from "../../../../../../hooks/useTransactionHistory"; -import { getDateTimestamp } from "../../../../../../lib/dates/getDateTimestamp"; -import { IPrice } from "../../../../../../lib/price/convertPrice"; -import { Exchange } from "../../../../../../types/exchange"; -import { Offer } from "../../../../../../types/offer"; -import { useConvertedPrice } from "../../../../../price/useConvertedPrice"; -import Typography from "../../../../../ui/Typography"; +import useTransactionHistory from "../../../../../hooks/useTransactionHistory"; +import { getDateTimestamp } from "../../../../../lib/dates/getDateTimestamp"; +import { IPrice } from "../../../../../lib/price/convertPrice"; +import { Exchange } from "../../../../../types/exchange"; +import { Offer } from "../../../../../types/offer"; +import { useConvertedPrice } from "../../../../price/useConvertedPrice"; +import Typography from "../../../../ui/Typography"; import { Transactions } from "./Detail.style"; const HEADER = ["Event", "From", "To", "Price", "Date"]; diff --git a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/SellerAndDescription.tsx b/packages/react-kit/src/components/modal/components/common/detail/SellerAndDescription.tsx similarity index 72% rename from packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/SellerAndDescription.tsx rename to packages/react-kit/src/components/modal/components/common/detail/SellerAndDescription.tsx index 14d79bcb0..e0e458fe5 100644 --- a/packages/react-kit/src/components/modal/components/Redeem/ExchangeView/detail/SellerAndDescription.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/SellerAndDescription.tsx @@ -1,12 +1,12 @@ import { TextAlignLeft } from "phosphor-react"; import React from "react"; import styled from "styled-components"; -import { theme } from "../../../../../../theme"; -import { Exchange } from "../../../../../../types/exchange"; -import SellerID from "../../../../../avatar/SellerID"; -import Grid from "../../../../../ui/Grid"; -import ThemedButton from "../../../../../ui/ThemedButton"; -import Typography from "../../../../../ui/Typography"; +import { theme } from "../../../../../theme"; +import SellerID from "../../../../avatar/SellerID"; +import Grid from "../../../../ui/Grid"; +import ThemedButton from "../../../../ui/ThemedButton"; +import Typography from "../../../../ui/Typography"; +import { Offer } from "../../../../../types/offer"; const colors = theme.colors.light; @@ -26,15 +26,11 @@ const Container = styled(Grid)` `; type Props = { - exchange: Exchange; + offer: Offer; onViewFullDescription: () => void; }; -export function SellerAndDescription({ - exchange, - onViewFullDescription -}: Props) { - const { offer } = exchange; +export function SellerAndDescription({ offer, onViewFullDescription }: Props) { return ( void; onLicenseAgreementClick: () => void; } -export default function ExchangePolicyDetails({ - exchange, +export default function OfferPolicyDetails({ + offer: offerData, exchangePolicyCheckResult, onContractualAgreementClick, onLicenseAgreementClick -}: ExchangePolicyDetailsProps) { - const offerData: subgraph.OfferFieldsFragment = exchange.offer; +}: OfferPolicyDetailsProps) { const isExchangePolicyValid = exchangePolicyCheckResult && (exchangePolicyCheckResult.isValid || diff --git a/packages/react-kit/src/components/widgets/commit/CommitModalWithOffer.tsx b/packages/react-kit/src/components/widgets/commit/CommitModalWithOffer.tsx new file mode 100644 index 000000000..58e0f02a2 --- /dev/null +++ b/packages/react-kit/src/components/widgets/commit/CommitModalWithOffer.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import CommitNonModal, { + CommitNonModalProps +} from "../../modal/components/Commit/CommitNonModal"; +import useProductByUuid from "../../../hooks/products/useProductByUuid"; +// TODO: implement hook in this repo +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +const useOffer = (a, b) => ({ data: undefined } as any); + +function WithProductOrOffer( + WrappedComponent: React.ComponentType +) { + const ComponentWithProductOrOffer = ( + props: Omit< + CommitNonModalProps, + "product" | "singleOffer" | "isLoading" + > & { + offerId?: string | undefined; + sellerId?: string | undefined; + productUuid?: string | undefined; + } + ) => { + const allProductByUuidParamsDefined = + !!props.sellerId && !!props.productUuid; + const { data: product, isLoading: isProductLoading } = useProductByUuid( + props.sellerId, + props.productUuid, + { + enabled: + (!props.offerId && allProductByUuidParamsDefined) || + !!(props.offerId && allProductByUuidParamsDefined) + } + ); + const { data: offer, isLoading: isOfferLoading } = useOffer( + { + id: props.offerId + }, + { + enabled: !props.productUuid && props.offerId + } + ); + return ( + + ); + }; + return ComponentWithProductOrOffer; +} + +export const CommitModalWithOffer = WithProductOrOffer(CommitNonModal); diff --git a/packages/react-kit/src/components/widgets/commit/CommitWidget.tsx b/packages/react-kit/src/components/widgets/commit/CommitWidget.tsx new file mode 100644 index 000000000..10e8310cb --- /dev/null +++ b/packages/react-kit/src/components/widgets/commit/CommitWidget.tsx @@ -0,0 +1,98 @@ +import React, { ComponentType } from "react"; +import { ButtonProps } from "../../buttons/Button"; +import { + EnvironmentProvider, + EnvironmentProviderProps +} from "../../environment/EnvironmentProvider"; +import { IpfsProvider, IpfsProviderProps } from "../../ipfs/IpfsProvider"; +import ModalProvider from "../../modal/ModalProvider"; +import GlobalStyle from "../../styles/GlobalStyle"; +import WalletConnectionProvider, { + WalletConnectionProviderProps +} from "../../wallet/WalletConnectionProvider"; +import { QueryClient, QueryClientProvider } from "react-query"; +import { + ConfigProvider, + ConfigProviderProps +} from "../../config/ConfigProvider"; +import ChatProvider from "../../chat/ChatProvider/ChatProvider"; +import ConvertionRateProvider, { + ConvertionRateProviderProps +} from "../finance/convertion-rate/ConvertionRateProvider"; +import { CommitNonModalProps } from "../../modal/components/Commit/CommitNonModal"; +import { CommitModalWithOffer } from "./CommitModalWithOffer"; +import { MagicProvider } from "../../magicLink/MagicContext"; +import { CONFIG } from "../../../lib/config/config"; + +type CommitProps = { + buttonProps?: Omit; + trigger?: ComponentType<{ onClick: () => unknown }> | undefined; +} & Omit< + CommitNonModalProps, + "product" | "singleOffer" | "isLoading" | "hideModal" +> & { + closeWidgetClick?: () => void; + modalMargin?: string; + offerId?: string; + sellerId?: string; + productUuid?: string; + }; + +type WidgetProps = CommitProps & + IpfsProviderProps & + Omit & + EnvironmentProviderProps & + ConvertionRateProviderProps & + Omit; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false + } + } +}); + +const { infuraKey, magicLinkKey } = CONFIG; + +export function CommitWidget(props: WidgetProps) { + return ( +
+ + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, prettier/prettier + /* @ts-ignore */} + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/packages/react-kit/src/hooks/products/useProductByUuid.ts b/packages/react-kit/src/hooks/products/useProductByUuid.ts new file mode 100644 index 000000000..360beda12 --- /dev/null +++ b/packages/react-kit/src/hooks/products/useProductByUuid.ts @@ -0,0 +1,30 @@ +import { useQuery } from "react-query"; +import { useCoreSDKWithContext } from "../useCoreSdkWithContext"; + +export default function useProductByUuid( + sellerId: string | undefined | null, + uuid: string | undefined | null, + options: { + enabled?: boolean; + } = {} +) { + const coreSDK = useCoreSDKWithContext(); + + return useQuery( + ["get-product-by-uuid", uuid, coreSDK, sellerId], + async () => { + if (!uuid || !sellerId) { + return; + } + return await coreSDK?.getProductWithVariants(sellerId, uuid); + }, + { + ...options, + enabled: options.enabled && !!coreSDK + } + ); +} + +export type ReturnUseProductByUuid = ReturnType< + typeof useProductByUuid +>["data"]; diff --git a/packages/react-kit/src/stories/widgets/Commit.stories.tsx b/packages/react-kit/src/stories/widgets/Commit.stories.tsx new file mode 100644 index 000000000..431061f4a --- /dev/null +++ b/packages/react-kit/src/stories/widgets/Commit.stories.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { ComponentMeta, ComponentStory, Story } from "@storybook/react"; + +import { CommitWidget } from "../../components/widgets/commit/CommitWidget"; +import { CtaButtonWrapper } from "../helpers/CtaButtonWrapper"; +import { EnvironmentType, getEnvConfigs } from "@bosonprotocol/core-sdk"; +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: "Widgets/Commit", + component: CommitWidget +} as ComponentMeta; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Template: ComponentStory = (args) => ( + + + +); + +const wrapper = (Story: Story) => ( +
+ +
+); + +export const Commit: ComponentStory = Template.bind({}); + +const envName = + (process.env.STORYBOOK_DATA_ENV_NAME as EnvironmentType) || "testing"; +const envConfig = getEnvConfigs(envName); +// More on args: https://storybook.js.org/docs/react/writing-stories/args +Commit.args = { + envName, + configId: envConfig[0].configId, + walletConnectProjectId: process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID, + dateFormat: "YYYY/MM/DD", + defaultCurrencySymbol: "$", + defaultCurrencyTicker: "USD", + contactSellerForExchangeUrl: "https://bosonapp.io/#/chat/{id}", + fairExchangePolicyRules: + "ipfs://QmV3Wy2wmrFdEXzhyhvvaW25Q8w2wTd2UypFVyhwsdBE8T", + ipfsGateway: process.env.STORYBOOK_DATA_IPFS_GATEWAY, + ipfsProjectId: process.env.STORYBOOK_DATA_IPFS_PROJECT_ID, + ipfsProjectSecret: process.env.STORYBOOK_DATA_IPFS_PROJECT_SECRET, + offerId: undefined, + productUuid: "28f220-3c44-d7f4-cf0-0c72702cd4", + sellerId: "26", + metaTx: { + apiKey: process.env.STORYBOOK_DATA_META_TX_API_KEY as string, + apiIds: process.env.STORYBOOK_DATA_META_TX_API_IDS as string + }, + closeWidgetClick: () => { + console.log("closeWidgetClick()"); + }, + modalMargin: "2%", + forcedAccount: "" +}; + +Commit.decorators = [(Story) => wrapper(Story)];