diff --git a/.env.example b/.env.example index c6e6bb302..fe81917cb 100644 --- a/.env.example +++ b/.env.example @@ -45,8 +45,6 @@ REACT_APP_RELEASE_NAME= REACT_APP_WALLET_CONNECT_PROJECT_ID= -REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP='{"production-137-0":"248", "testing-80001-0":"26"}' - REACT_APP_VIEW_MODE=dapp # REACT_APP_VIEW_MODE=dr_center # REACT_APP_VIEW_MODE=dapp,dr_center diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8493e8ef6..661486fd3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,6 @@ jobs: REACT_APP_MOONPAY_EXTERNAL_LINK: "https://www.moonpay.com/buy" REACT_APP_META_TX_API_KEY_MAP: ${{ vars.REACT_APP_META_TX_API_KEY_MAP_TESTING }} REACT_APP_META_TX_API_IDS_MAP: ${{ vars.REACT_APP_META_TX_API_IDS_MAP_TESTING }} - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: ${{ vars.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP }} secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -94,7 +93,6 @@ jobs: REACT_APP_MOONPAY_EXTERNAL_LINK: "https://www.moonpay.com/buy" REACT_APP_META_TX_API_KEY_MAP: ${{ vars.REACT_APP_META_TX_API_KEY_MAP_TESTING }} REACT_APP_META_TX_API_IDS_MAP: ${{ vars.REACT_APP_META_TX_API_IDS_MAP_TESTING }} - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: ${{ vars.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP }} secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} FLEEK_API_KEY: ${{ secrets.FLEEK_API_KEY }} @@ -134,7 +132,6 @@ jobs: REACT_APP_MOONPAY_EXTERNAL_LINK: "https://www.moonpay.com/buy" REACT_APP_META_TX_API_KEY_MAP: ${{ vars.REACT_APP_META_TX_API_KEY_MAP_STAGING }} REACT_APP_META_TX_API_IDS_MAP: ${{ vars.REACT_APP_META_TX_API_IDS_MAP_STAGING }} - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: ${{ vars.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP }} secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} FLEEK_API_KEY: ${{ secrets.FLEEK_API_KEY }} @@ -174,7 +171,6 @@ jobs: REACT_APP_MOONPAY_EXTERNAL_LINK: "https://www.moonpay.com/buy" REACT_APP_META_TX_API_KEY_MAP: ${{ vars.REACT_APP_META_TX_API_KEY_MAP_PRODUCTION }} REACT_APP_META_TX_API_IDS_MAP: ${{ vars.REACT_APP_META_TX_API_IDS_MAP_PRODUCTION }} - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: ${{ vars.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP }} secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} FLEEK_API_KEY: ${{ secrets.FLEEK_API_KEY }} diff --git a/.github/workflows/ci_reusable.yaml b/.github/workflows/ci_reusable.yaml index 31a21b8c0..2c1d32921 100644 --- a/.github/workflows/ci_reusable.yaml +++ b/.github/workflows/ci_reusable.yaml @@ -66,9 +66,6 @@ on: REACT_APP_META_TX_API_IDS_MAP: required: true type: string - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: - required: true - type: string secrets: NPM_TOKEN: required: true @@ -121,7 +118,6 @@ jobs: REACT_APP_DEFAULT_RESOLUTION_PERIOD_DAYS: ${{ secrets.REACT_APP_DEFAULT_RESOLUTION_PERIOD_DAYS }} REACT_APP_INFURA_IPFS_PROJECT_ID: ${{ secrets.REACT_APP_INFURA_IPFS_PROJECT_ID }} REACT_APP_INFURA_IPFS_PROJECT_SECRET: ${{ secrets.REACT_APP_INFURA_IPFS_PROJECT_SECRET }} - REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP: ${{ inputs.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP }} REACT_APP_GOOGLE_TAG_ID: ${{ secrets.REACT_APP_GOOGLE_TAG_ID }} REACT_APP_META_TX_API_KEY_MAP: ${{ inputs.REACT_APP_META_TX_API_KEY_MAP }} REACT_APP_META_TX_API_IDS_MAP: ${{ inputs.REACT_APP_META_TX_API_IDS_MAP }} diff --git a/package-lock.json b/package-lock.json index 67c95ac77..47748b71a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@apollo/client": "^3.8.1", "@bosonprotocol/chat-sdk": "^1.3.1-alpha.9", - "@bosonprotocol/react-kit": "^0.34.0-alpha.20", + "@bosonprotocol/react-kit": "^0.34.0-alpha.23", "@davatar/react": "^1.10.4", "@ethersproject/address": "^5.6.1", "@ethersproject/units": "^5.7.0", @@ -2488,9 +2488,9 @@ } }, "node_modules/@bosonprotocol/common": { - "version": "1.29.0-alpha.8", - "resolved": "https://registry.npmjs.org/@bosonprotocol/common/-/common-1.29.0-alpha.8.tgz", - "integrity": "sha512-k2CX7D/NfYi/7a8wERirokPhXzWhnJFSzdjfW3VmoxnhgvuIOvW1jQ9e/UPrPfJhxaqEI05OLVdG4JiJcu5w4Q==", + "version": "1.29.0-alpha.11", + "resolved": "https://registry.npmjs.org/@bosonprotocol/common/-/common-1.29.0-alpha.11.tgz", + "integrity": "sha512-JjcCkQmvbkPuKi2Z+fquX/UWOEkL9YZTGn19XbPHUFq8Saz6i1OQC+3ArQ2ZyeRSfVx03yrZwcMiY5QdQfTKbg==", "dependencies": { "@bosonprotocol/metadata": "^1.16.2", "@ethersproject/abi": "^5.5.0", @@ -2502,11 +2502,11 @@ } }, "node_modules/@bosonprotocol/core-sdk": { - "version": "1.41.0-alpha.22", - "resolved": "https://registry.npmjs.org/@bosonprotocol/core-sdk/-/core-sdk-1.41.0-alpha.22.tgz", - "integrity": "sha512-IyZT0Totig9Yyvww79r/suKQ7KiM8wW1QDuzH7OsM3GQRO6gOMEqfGyYRarevwPnb+yLFymst0mAN5hCSbZ/JA==", + "version": "1.41.0-alpha.25", + "resolved": "https://registry.npmjs.org/@bosonprotocol/core-sdk/-/core-sdk-1.41.0-alpha.25.tgz", + "integrity": "sha512-g9kEz9n2SZ+y/QdRRE/coPdivcDPi5+TtAgNITMbAQXSotpJLGvPRieteCUSgQpnlH96BVEmMbSCj8BoI3TfMg==", "dependencies": { - "@bosonprotocol/common": "^1.29.0-alpha.8", + "@bosonprotocol/common": "^1.29.0-alpha.11", "@ethersproject/abi": "^5.5.0", "@ethersproject/address": "^5.5.0", "@ethersproject/bignumber": "^5.5.0", @@ -2523,20 +2523,20 @@ } }, "node_modules/@bosonprotocol/ethers-sdk": { - "version": "1.15.0-alpha.8", - "resolved": "https://registry.npmjs.org/@bosonprotocol/ethers-sdk/-/ethers-sdk-1.15.0-alpha.8.tgz", - "integrity": "sha512-ySi5/RKKN2baTBntO3kXtkhWtB39WULcUaX64tyAm+M0wZwx3eNYLxqU4jjzOXFgW9Skjovs/X6yJB8YNcpE2w==", + "version": "1.15.0-alpha.11", + "resolved": "https://registry.npmjs.org/@bosonprotocol/ethers-sdk/-/ethers-sdk-1.15.0-alpha.11.tgz", + "integrity": "sha512-fCKBsVmQKbPK5kFq5mCtdIGS8tWGmfZE/8frjP5J7PV1RjUriBZ47xiu0S19EKBJCNWP+RM9c8aJYKv3oO2WIA==", "dependencies": { - "@bosonprotocol/common": "^1.29.0-alpha.8" + "@bosonprotocol/common": "^1.29.0-alpha.11" }, "peerDependencies": { "ethers": "^5.5.0" } }, "node_modules/@bosonprotocol/ipfs-storage": { - "version": "1.12.0-alpha.20", - "resolved": "https://registry.npmjs.org/@bosonprotocol/ipfs-storage/-/ipfs-storage-1.12.0-alpha.20.tgz", - "integrity": "sha512-W+apiSphIhYA25NW6lUhbaAmrkTvaQmmLEgeX2ls9411todpHx4ikco56/ps3+eptYHho3a3yPjpUn1H5w+n+Q==", + "version": "1.12.0-alpha.23", + "resolved": "https://registry.npmjs.org/@bosonprotocol/ipfs-storage/-/ipfs-storage-1.12.0-alpha.23.tgz", + "integrity": "sha512-SQXXX9OAP2XQ0esG50tG//hNjXvXK4zVNP2R6v6BOve3loP8WhA/7juWjuCl3rE+nuGgr6lEgEZkG9rgr5ZPYw==", "dependencies": { "@bosonprotocol/metadata-storage": "^1.0.1", "ipfs-http-client": "^56.0.1", @@ -2559,14 +2559,14 @@ "integrity": "sha512-f2W2SQAvY5IKD6L9JwaiNye7gGRIIPd/HOB0i+otWLzMPBlwQtbN4JeWSuKeJvuaqu8tyMy7CHzN8EkhrJDB+A==" }, "node_modules/@bosonprotocol/react-kit": { - "version": "0.34.0-alpha.20", - "resolved": "https://registry.npmjs.org/@bosonprotocol/react-kit/-/react-kit-0.34.0-alpha.20.tgz", - "integrity": "sha512-5ofqFjWVATMS0zA63eX9GOk5sSNy0tAnLGf+YLQEkAo+RZngP+nVB7ZHCtxVWDKRmV6Gy2sJrUh/w6HDfenMTw==", + "version": "0.34.0-alpha.23", + "resolved": "https://registry.npmjs.org/@bosonprotocol/react-kit/-/react-kit-0.34.0-alpha.23.tgz", + "integrity": "sha512-sQWlnPVdZ2/YYE/kjKl5m7nJpiZTYl7uvPc7kXURCbwMIeD/5nL5fRCpRDsxH7ZmR5doCsSUKLG1xIXT/pTKLw==", "dependencies": { "@bosonprotocol/chat-sdk": "^1.3.1-alpha.9", - "@bosonprotocol/core-sdk": "^1.41.0-alpha.22", - "@bosonprotocol/ethers-sdk": "^1.15.0-alpha.8", - "@bosonprotocol/ipfs-storage": "^1.12.0-alpha.20", + "@bosonprotocol/core-sdk": "^1.41.0-alpha.25", + "@bosonprotocol/ethers-sdk": "^1.15.0-alpha.11", + "@bosonprotocol/ipfs-storage": "^1.12.0-alpha.23", "@davatar/react": "1.11.1", "@ethersproject/units": "5.6.0", "@glidejs/glide": "3.6.0", diff --git a/package.json b/package.json index 6fc38b6d5..6855c11eb 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "dependencies": { "@apollo/client": "^3.8.1", "@bosonprotocol/chat-sdk": "^1.3.1-alpha.9", - "@bosonprotocol/react-kit": "^0.34.0-alpha.20", + "@bosonprotocol/react-kit": "^0.34.0-alpha.23", "@davatar/react": "^1.10.4", "@ethersproject/address": "^5.6.1", "@ethersproject/units": "^5.7.0", diff --git a/src/components/config/ConfigProvider.tsx b/src/components/config/ConfigProvider.tsx index c45db94c7..f455690ae 100644 --- a/src/components/config/ConfigProvider.tsx +++ b/src/components/config/ConfigProvider.tsx @@ -1,6 +1,10 @@ -import { ProtocolConfig } from "@bosonprotocol/react-kit"; +import { + ConfigProvider as BosonConfigProvider, + ProtocolConfig +} from "@bosonprotocol/react-kit"; import { MagicProvider } from "components/magicLink/MagicContext"; import { + CONFIG, defaultEnvConfig, envConfigsFilteredByEnv, getDappConfig @@ -35,9 +39,29 @@ export function ConfigProvider({ children }: ConfigProviderProps) { const dappConfig = getDappConfig(envConfig || defaultEnvConfig); return ( - - {children} - + + + {children} + + ); } diff --git a/src/components/detail/Detail.style.tsx b/src/components/detail/Detail.style.tsx index 3657c3102..d669b2bab 100644 --- a/src/components/detail/Detail.style.tsx +++ b/src/components/detail/Detail.style.tsx @@ -197,29 +197,6 @@ export const ImageWrapper = styled.div` width: -webkit-fill-available; `; -export const GlideWrapper = styled.div<{ $afterBackground: string }>` - &:after { - content: ""; - position: absolute; - height: 100%; - width: 4rem; - z-index: ${zIndex.Carousel}; - top: 0; - bottom: 0; - right: 0; - background: linear-gradient( - -90deg, - ${({ $afterBackground }) => $afterBackground} 0%, - transparent 100% - ); - pointer-events: none; - } -`; - -export const GlideSlide = styled.div` - overflow: hidden; -`; - const tableBorder = css` tbody { tr { diff --git a/src/components/detail/const.ts b/src/components/detail/const.ts deleted file mode 100644 index 0faf94c0c..000000000 --- a/src/components/detail/const.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { CurrencyCircleDollar, Package, ShoppingCart } from "phosphor-react"; - -import { breakpointNumbers } from "../../lib/styles/breakpoint"; - -export const COMMIT_STEPS = [ - { - icon: ShoppingCart, - header: "Commit to Buy", - description: - "Commit to buy an offer to receive a Redeemable NFT (rNFT) that can be exchanged for the real-world item it represents" - }, - { - icon: CurrencyCircleDollar, - header: "Hold, Trade or Transfer ", - description: - "You can hold, transfer or easily trade your rNFT on the secondary market" - }, - { - icon: Package, - header: "Redeem", - description: - "Redeem your rNFT to receive the underlying item. The rNFT will be destroyed in the process." - } -]; - -export const SLIDER_OPTIONS = { - type: "carousel" as const, - startAt: 0, - gap: 20, - perView: 3, - breakpoints: { - [breakpointNumbers.l]: { - perView: 3 - }, - [breakpointNumbers.m]: { - perView: 2 - }, - [breakpointNumbers.xs]: { - perView: 1 - } - } -}; - -export const HEADER = ["Event", "From", "To", "Price", "Date"]; diff --git a/src/components/offer/OfferCard.tsx b/src/components/offer/OfferCard.tsx deleted file mode 100644 index c015a33c2..000000000 --- a/src/components/offer/OfferCard.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { generatePath, useLocation } from "react-router-dom"; -import styled, { css } from "styled-components"; - -import RootPrice from "../../components/price"; -import { clamp } from "../../components/ui/styles"; -import { UrlParameters } from "../../lib/routing/parameters"; -import { BosonRoutes, OffersRoutes } from "../../lib/routing/routes"; -import { colors } from "../../lib/styles/colors"; -import { Offer } from "../../lib/types/offer"; -import { Exchange } from "../../lib/utils/hooks/useExchanges"; -import { useKeepQueryParamsNavigate } from "../../lib/utils/hooks/useKeepQueryParamsNavigate"; -import Image from "../ui/Image"; -import SellerID from "../ui/SellerID"; -import { Typography } from "../ui/Typography"; -import ExchangeStatuses from "./ExchangeStatuses"; -import OfferBanner from "./OfferBanner"; -import OfferStatuses from "./OfferStatuses"; - -const Card = styled.div<{ isCarousel: boolean }>` - display: inline-block; - background-color: ${colors.white}; - position: relative; - width: 100%; - cursor: pointer; - border: 1px solid ${colors.black}20; - color: ${colors.black}; - - ${({ isCarousel }) => - !isCarousel - ? css` - transition: all 300ms ease-in-out; - transition: box-shadow 300ms; - - &:hover { - box-shadow: - 0px 0px 0px rgba(0, 0, 0, 0.05), - 4px 4px 4px rgba(0, 0, 0, 0.05), - 8px 8px 8px rgba(0, 0, 0, 0.05), - 16px 16px 16px rgba(0, 0, 0, 0.05); - - img[data-testid] { - transform: translate(-50%, -50%) scale(1.05); - } - } - ` - : ""} -`; - -const Content = styled.div` - padding: 1rem 1.5rem; -`; - -const BasicInfoContainer = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - gap: 4px; - margin: 4px 0; - min-height: 4rem; -`; - -const Name = styled(Typography)` - ${clamp} - font-weight: 600; - margin: 0; -`; - -const Price = styled(RootPrice)` - font-size: 1rem; - font-weight: 600; -`; - -const PriceText = styled(Typography)` - color: ${colors.darkGrey}; - font-size: 0.875rem; -`; - -const getCTAPath = ( - action: Props["action"], - { - offerId, - exchangeId - }: { offerId: Props["offer"]["id"]; exchangeId: string | undefined } -) => { - if ( - (["redeem", "contact-seller"] as Action[]).includes(action as Action) && - exchangeId - ) { - return generatePath(BosonRoutes.Exchange, { - [UrlParameters.exchangeId]: exchangeId - }); - } - return generatePath(OffersRoutes.OfferDetail, { - [UrlParameters.offerId]: offerId - }); -}; - -export type Action = "commit" | "redeem" | "contact-seller" | null; - -interface Props { - offer: Offer; - exchange?: Exchange; - showSeller?: boolean; - action?: Action; - dataTestId: string; - isPrivateProfile?: boolean; - isCarousel?: boolean; - type?: "gone" | "hot" | "soon" | undefined; -} -export default function OfferCard({ - offer, - exchange, - showSeller, - action, - dataTestId, - isPrivateProfile, - isCarousel = false, - type -}: Props) { - const offerId = offer.id; - const isSellerVisible = showSeller === undefined ? true : showSeller; - const offerImg = offer.metadata?.imageUrl ?? ""; - const name = offer.metadata?.name || "Untitled"; - const sellerAddress = offer.seller?.assistant; - - const location = useLocation(); - const navigate = useKeepQueryParamsNavigate(); - - if (!offer) { - return null; - } - const path = getCTAPath(action, { offerId, exchangeId: exchange?.id }); - - const isClickable = !!path; - const onClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - isClickable && - navigate( - { pathname: path }, - { - state: { - from: location.pathname - } - } - ); - }; - - const Status = isPrivateProfile ? ( - exchange ? ( - - ) : ( - - ) - ) : ( - <> - ); - - return ( - - {Status} - - {!isCarousel && } - - - {isSellerVisible && sellerAddress && ( - - Price - - )} - - - - {name || "Untitled"} - - {offer.exchangeToken && ( - - )} - - - - ); -} diff --git a/src/components/offers/OfferList.tsx b/src/components/offers/OfferList.tsx index 5a7db8b72..3ae6d98fc 100644 --- a/src/components/offers/OfferList.tsx +++ b/src/components/offers/OfferList.tsx @@ -12,8 +12,8 @@ import { useKeepQueryParamsNavigate } from "../../lib/utils/hooks/useKeepQueryPa import { useIsCustomStoreValueChanged } from "../../pages/custom-store/useIsCustomStoreValueChanged"; import { ExtendedOffer } from "../../pages/explore/WithAllOffers"; import { ProductGridContainer } from "../../pages/profile/ProfilePage.styles"; +import { Action } from "../../pages/profile/seller/const"; import Breadcrumbs from "../breadcrumbs/Breadcrumbs"; -import { Action } from "../offer/OfferCard"; import ProductCard from "../productCard/ProductCard"; import { Grid } from "../ui/Grid"; import { ItemsPerRow } from "../ui/GridContainer"; diff --git a/src/components/product/utils/usePreviewOffers.ts b/src/components/product/utils/usePreviewOffers.ts index 5d65c33fc..7ccf3b8fe 100644 --- a/src/components/product/utils/usePreviewOffers.ts +++ b/src/components/product/utils/usePreviewOffers.ts @@ -35,6 +35,7 @@ export const usePreviewOffers = ({ const { values } = useForm(); const chainId = useChainId(); const { config } = useConfigContext(); + const chainIdToUse = chainId || config.envConfig.chainId; const disputeResolverId = config.envConfig.defaultDisputeResolverId; const { disputeResolver } = useDisputeResolver(disputeResolverId); const escalationResponsePeriod = @@ -224,7 +225,7 @@ export const usePreviewOffers = ({ productV1Seller, shipping }, - ...getDigitalMetadatas({ chainId, values }) + ...getDigitalMetadatas({ chainId: chainIdToUse, values }) ] }), product, @@ -244,7 +245,6 @@ export const usePreviewOffers = ({ return offer; }, [ - chainId, config.envConfig.defaultTokens, config.envConfig.nativeCoin?.decimals, disputeResolver, @@ -260,7 +260,8 @@ export const usePreviewOffers = ({ values, voucherRedeemableFromDateInMS, voucherRedeemableUntilDateInMS, - voucherValidDurationInMS + voucherValidDurationInMS, + chainIdToUse ] ); const offerData: Offer[] = useMemo(() => { diff --git a/src/lib/config.ts b/src/lib/config.ts index 7877b1f3d..4612d1bc1 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -81,25 +81,6 @@ function getMetaTxApiKey(envConfig: ProtocolConfig) { return apiKey; } -function getCarouselPromotedSellerId( - envConfig: ProtocolConfig -): string | undefined { - let carouselPromotedSellerId: string | undefined; - const sellerMap = process.env.REACT_APP_CAROUSEL_PROMOTED_SELLER_ID_MAP; - if (sellerMap) { - try { - const carouselPromotedSellerIdMap = JSON.parse(sellerMap || "{}"); - carouselPromotedSellerId = - carouselPromotedSellerIdMap[envConfig.configId]; - } catch (error) { - console.error(error); - Sentry.captureException(error); - } - } - - return carouselPromotedSellerId; -} - export const envConfigsFilteredByEnv: ProtocolConfig[] = getEnvConfigs(envName); export const envChainIds = envConfigsFilteredByEnv.map( (envConf) => envConf.chainId @@ -222,8 +203,7 @@ export const getDappConfig = (envConfig: ProtocolConfig) => { LENS_PROFILES_CONTRACT_PARTIAL_ABI: envConfig.lens?.LENS_PROFILES_CONTRACT_PARTIAL_ABI, LENS_FOLLOW_NFT_ABI: lensFollowNftContractAbi - }, - carouselPromotedSellerId: getCarouselPromotedSellerId(envConfig) + } }; }; diff --git a/src/lib/styles/zIndex.ts b/src/lib/styles/zIndex.ts index 4e3a6a52a..ddc0afb61 100644 --- a/src/lib/styles/zIndex.ts +++ b/src/lib/styles/zIndex.ts @@ -6,7 +6,6 @@ export const zIndex = { ExchangeSidePreview: 2, OfferStatus: 5, ChatSeparator: 9, - Carousel: 10, LandingTitle: 20, Footer: 30, Select: 40, diff --git a/src/lib/utils/hooks/offers/types.ts b/src/lib/utils/hooks/offers/types.ts index 901245532..b1a43120e 100644 --- a/src/lib/utils/hooks/offers/types.ts +++ b/src/lib/utils/hooks/offers/types.ts @@ -32,3 +32,10 @@ export interface UseOffersProps extends CommonProps { export interface UseOfferProps extends CommonProps { offerId: string; } + +export interface UseOfferOptionsProps { + enabled?: boolean; + overrides?: Partial< + Pick + >; +} diff --git a/src/lib/utils/hooks/offers/useOffers.ts b/src/lib/utils/hooks/offers/useOffers.ts index 8e2b07681..7ec25fec5 100644 --- a/src/lib/utils/hooks/offers/useOffers.ts +++ b/src/lib/utils/hooks/offers/useOffers.ts @@ -4,31 +4,40 @@ import { useQuery } from "react-query"; import { useConvertedPriceFunction } from "../../../../components/price/useConvertedPriceFunction"; import { useCurationLists } from "../useCurationLists"; import { getOffers } from "./getOffers"; -import { UseOffersProps } from "./types"; +import { UseOfferOptionsProps, UseOffersProps } from "./types"; export function useOffers( props: UseOffersProps, - options: { - enabled?: boolean; - } = {} + options: UseOfferOptionsProps = {} ) { const { config } = useConfigContext(); const { subgraphUrl, defaultDisputeResolverId } = config.envConfig; const curationLists = useCurationLists(); const convertPrice = useConvertedPriceFunction(); - - props = { + const offerlistFromCuration = curationLists.offerCurationList; + const newProps = { ...props, - ...curationLists - }; + ...curationLists, + ...options.overrides, + // if there is a offer list defined at .env level, then use that or the intersection with the overrides + // otherwise, use the overrides offer list + offerCurationList: offerlistFromCuration?.length + ? options.overrides?.offerCurationList + ? options.overrides.offerCurationList.filter((o) => + offerlistFromCuration.includes(o) + ) + : offerlistFromCuration + : options.overrides?.offerCurationList + } satisfies UseOffersProps; + return useQuery( - ["offers", props, subgraphUrl, defaultDisputeResolverId], + ["offers", newProps, subgraphUrl, defaultDisputeResolverId], async () => { const offersList = !curationLists.sellerCurationList || curationLists.sellerCurationList.length > 0 - ? await getOffers(subgraphUrl, defaultDisputeResolverId, props) + ? await getOffers(subgraphUrl, defaultDisputeResolverId, newProps) : []; // sort the offers by price @@ -43,6 +52,8 @@ export function useOffers( return orderedOffers; }, - options + { + enabled: options.enabled + } ); } diff --git a/src/lib/utils/hooks/offers/useOffersWhitelist.ts b/src/lib/utils/hooks/offers/useOffersWhitelist.ts new file mode 100644 index 000000000..144d83d01 --- /dev/null +++ b/src/lib/utils/hooks/offers/useOffersWhitelist.ts @@ -0,0 +1,22 @@ +import { useConfigContext } from "components/config/ConfigContext"; +import { useQuery } from "react-query"; +import * as Yup from "yup"; +const offersWhitelistSchema = Yup.array(Yup.string().required()).required(); +export const useOffersWhitelist = () => { + const { + config: { envConfig } + } = useConfigContext(); + return useQuery( + ["useOffersWhitelist", envConfig.configId, envConfig.offersWhiteList], + async () => { + const response = await fetch(envConfig.offersWhiteList); + if (!response.ok) { + throw new Error(`Error fetching offers list: ${response.statusText}`); + } + const data = await response.json(); + const offersWhiteList = await offersWhitelistSchema.validate(data); + return Array.from(new Set(offersWhiteList)); + }, + { enabled: true } + ); +}; diff --git a/src/lib/utils/hooks/product/useProductsByFilteredOffers.ts b/src/lib/utils/hooks/product/useProductsByFilteredOffers.ts index 567b8bd18..b99150dba 100644 --- a/src/lib/utils/hooks/product/useProductsByFilteredOffers.ts +++ b/src/lib/utils/hooks/product/useProductsByFilteredOffers.ts @@ -2,12 +2,14 @@ import { useMemo } from "react"; import { isTruthy } from "../../../types/helpers"; import { useOffers } from "../offers"; +import { UseOfferOptionsProps } from "../offers/types"; import useProducts from "./useProducts"; export default function useProductsByFilteredOffers( - props: Parameters[0] = {} + props: Parameters[0] = {}, + options?: UseOfferOptionsProps ) { - const { data, isLoading, isError } = useOffers({ ...props, first: 200 }); + const { data, isLoading, isError } = useOffers(props, options); const productsIds = useMemo( // use product ids instead of uuids () => diff --git a/src/lib/utils/hooks/useResolveUrlFromIPFS.ts b/src/lib/utils/hooks/useResolveUrlFromIPFS.ts deleted file mode 100644 index 34152a009..000000000 --- a/src/lib/utils/hooks/useResolveUrlFromIPFS.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { getIpfsGatewayUrl } from "../ipfs"; - -export function resolveUrlFromIPFS(uri: string, ipfsGateway: string): string { - return getIpfsGatewayUrl(uri, { gateway: ipfsGateway }); -} diff --git a/src/pages/about/AboutPage.tsx b/src/pages/about/AboutPage.tsx index 09b6de991..01726349f 100644 --- a/src/pages/about/AboutPage.tsx +++ b/src/pages/about/AboutPage.tsx @@ -159,10 +159,6 @@ function AboutPage() { {CONFIG.ipfsImageGateway} - - Carousel Promoted SellerId: - {config.carouselPromotedSellerId || "-"} - dApp View Mode Url: {CONFIG.envViewMode?.dappViewModeUrl || "-"} diff --git a/src/pages/create-product/CreateProductInner.tsx b/src/pages/create-product/CreateProductInner.tsx index 21ee13044..ed983f29b 100644 --- a/src/pages/create-product/CreateProductInner.tsx +++ b/src/pages/create-product/CreateProductInner.tsx @@ -125,8 +125,9 @@ function CreateProductInner({ isDraftModalClosed }: Props) { const chainId = useChainId(); - const signer = useSigner(); const { config } = useConfigContext(); + const chainIdToUse = chainId || config.envConfig.chainId; + const signer = useSigner(); const history = useNavigate(); const location = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); @@ -526,7 +527,7 @@ function CreateProductInner({ const offersToCreate: offers.CreateOfferArgs[] = []; const productAnimation = values.productAnimation?.[0]; const newNftMetadatas = getDigitalMetadatas({ - chainId, + chainId: chainIdToUse, values }); const nftMetadataIpfsLinks: string[] = ( diff --git a/src/pages/landing/AnimatedImageGrid.tsx b/src/pages/landing/AnimatedImageGrid.tsx index ad42dfdbe..897a2e96a 100644 --- a/src/pages/landing/AnimatedImageGrid.tsx +++ b/src/pages/landing/AnimatedImageGrid.tsx @@ -1,6 +1,7 @@ -import { Grid } from "@bosonprotocol/react-kit"; +import { isTruthy } from "@bosonprotocol/react-kit"; +import Loading from "components/ui/Loading"; import { CONFIG } from "lib/config"; -import { resolveUrlFromIPFS } from "lib/utils/hooks/useResolveUrlFromIPFS"; +import { getImageUrl } from "lib/utils/images"; import React, { useEffect, useMemo, useRef, useState } from "react"; import styled, { css, keyframes } from "styled-components"; @@ -49,12 +50,6 @@ const StyledImage = styled.img` border-radius: 8px; `; -const LoadingContainer = styled(Grid)` - height: 100%; - font-size: 1.125rem; - color: #333; -`; - interface ImageItemProps { top: string; left: string; @@ -93,42 +88,33 @@ const AnimatedImageGrid: React.FC = ({ images }) => { useEffect(() => { const loadImages = async () => { - const resolvedUrls = images.map((url) => - resolveUrlFromIPFS(url, CONFIG.ipfsImageGateway) - ); const loadedUrls = await Promise.all( - resolvedUrls.map(async (url) => { - try { - const response = await fetch(url); - if (!response.ok) throw new Error("Network response was not ok"); - const blob = await response.blob(); - return URL.createObjectURL(blob); - } catch (error) { - console.error("Error loading image:", error); - return null; - } + images.map(async (url) => { + return getImageUrl(url, { gateway: CONFIG.ipfsImageGateway }); }) ); - setLoadedImages(loadedUrls.filter(Boolean) as string[]); + setLoadedImages(loadedUrls.filter(isTruthy)); }; loadImages(); }, [images]); useEffect(() => { + function runTransition() { + setActiveIndex((prevIndex) => { + setTransitioningIndex(prevIndex); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setTransitioningIndex(null); + }, 50); + return (prevIndex + 1) % imageItems.length; + }); + } if (loadedImages.length === images.length) { - const interval = setInterval(() => { - setActiveIndex((prevIndex) => { - setTransitioningIndex(prevIndex); - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - setTransitioningIndex(null); - }, 50); - return (prevIndex + 1) % imageItems.length; - }); - }, 2000); + runTransition(); + const interval = setInterval(() => runTransition(), 2000); return () => { clearInterval(interval); @@ -140,15 +126,7 @@ const AnimatedImageGrid: React.FC = ({ images }) => { }, [loadedImages, images.length, imageItems.length]); if (loadedImages.length !== images.length) { - return ( - - Loading images... - - ); + return ; } return ( diff --git a/src/pages/landing/Carousel.tsx b/src/pages/landing/Carousel.tsx deleted file mode 100644 index a71cec5dd..000000000 --- a/src/pages/landing/Carousel.tsx +++ /dev/null @@ -1,335 +0,0 @@ -import { ProductCardSkeleton } from "@bosonprotocol/react-kit"; -import * as Sentry from "@sentry/browser"; -import { useConfigContext } from "components/config/ConfigContext"; -// inspired by https://3dtransforms.desandro.com/carousel -import { CaretLeft, CaretRight } from "phosphor-react"; -import { useMemo, useRef, useState } from "react"; -import { useSwipeable } from "react-swipeable"; -import styled, { css } from "styled-components"; - -import ProductCard from "../../components/productCard/ProductCard"; -import { breakpoint } from "../../lib/styles/breakpoint"; -import { zIndex } from "../../lib/styles/zIndex"; -import { Offer } from "../../lib/types/offer"; -import useProductsByFilteredOffers from "../../lib/utils/hooks/product/useProductsByFilteredOffers"; -import extractUniqueRandomProducts from "../../lib/utils/product/extractUniqueRandomProducts"; -import { ExtendedOffer } from "../explore/WithAllOffers"; - -const cellSize = 300; -const numCells = 8; // or number of max offers -const tz = Math.round(cellSize / 2 / Math.tan(Math.PI / numCells)); -const translateZValue = `${tz}px`; - -const VIEWER_DISTANCE = 1000; -const ITEM_WIDTH = cellSize; -const ITEM_PADDING = cellSize / 10; -const ANIMATION_TIME_MS = 500; - -const Scene = styled.div` - transform: scale(0.8); - min-width: 150%; - min-height: 540px; - margin-top: -4rem; - - ${breakpoint.xs} { - margin-top: 0; - overflow: hidden; - min-height: 650px; - min-width: 120%; - transform: scale(1); - } - ${breakpoint.xs} { - min-width: 100%; - } - ${breakpoint.m} { - min-width: 60%; - } - ${breakpoint.l} { - min-width: 50%; - } - ${breakpoint.xl} { - min-width: 50%; - } - - perspective: ${VIEWER_DISTANCE}px; - display: flex; - flex-direction: column; - align-items: center; - > * { - flex: 0 0 auto; - } - - &:after, - &:before { - content: ""; - position: absolute; - width: 20vw; - ${breakpoint.s} { - width: 15vw; - } - ${breakpoint.l} { - width: 12vw; - } - height: 100%; - z-index: ${zIndex.Carousel}; - top: 0; - } - &:after { - right: -1vw; - ${breakpoint.m} { - right: -5vw; - } - background: linear-gradient( - -90deg, - var(--primaryBgColor) 50%, - transparent 100% - ); - } - &:before { - left: -1vw; - ${breakpoint.m} { - left: -5vw; - } - background: linear-gradient( - 90deg, - var(--primaryBgColor) 50%, - transparent 100% - ); - } -`; -const CarouselContainer = styled.div` - margin: 0; - - width: ${ITEM_WIDTH}px; - transform-style: preserve-3d; - transition: transform ${ANIMATION_TIME_MS}ms; - - transform: translateZ(-${translateZValue}) rotateY(0deg); - - > div { - width: 100%; - box-sizing: border-box; - padding: 0 calc(${ITEM_PADDING}px / 2); - } -`; -const CarouselNav = styled.div` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 90%; - z-index: ${zIndex.Carousel + 2}; -`; - -const nthChilds = new Array(numCells) - .fill(0) - .map((_, idx) => { - return ` - &:nth-child(${idx + 1}) { - transform: rotateY(${ - (360 / numCells) * idx - }deg) translateZ(${translateZValue}) translateY(50px); - } - `; - }) - .join(""); - -const CarouselCell = styled.div<{ - $isNext: boolean; - $isPrevious: boolean; - $isCurrent: boolean; -}>` - position: absolute; - pointer-events: ${(props) => (props.$isCurrent ? "all" : "none")}; - left: 0; - top: 0; - transition: opacity ${ANIMATION_TIME_MS}ms; - - opacity: ${({ $isCurrent, $isPrevious, $isNext }) => - !$isCurrent && !$isPrevious && !$isNext ? "0" : "1"}; - > div { - transition: transform ${ANIMATION_TIME_MS}ms; - &:hover { - box-shadow: none; - } - } - - ${nthChilds} - ${(props) => - props.$isCurrent - ? css` - > div { - transform: scale(1.1); - } - ` - : ""}; - [data-testid="offer"] { - box-shadow: none; - } -`; - -const navButtons = css` - cursor: pointer; - position: absolute; - top: calc(50% - 48px / 2); -`; - -const PreviousButton = styled(CaretLeft)` - ${navButtons} - left: 1rem; - ${breakpoint.s} { - left: 0; - } -`; - -const NextButton = styled(CaretRight)` - ${navButtons} - right: 1rem; - ${breakpoint.s} { - right: 0; - } -`; - -const theta = 360 / numCells; -export default function Carousel() { - const { config } = useConfigContext(); - const [selectedIndex, setSelectedIndex] = useState(0); - const carouselRef = useRef(null); - - const { products, isLoading } = useProductsByFilteredOffers({ - voided: false, - valid: true, - first: numCells, - quantityAvailable_gte: 1, - sellerId: config.carouselPromotedSellerId - }); - - const uiOffers = useMemo(() => { - if (products?.length) { - let uiOffers: ExtendedOffer[] = []; - try { - uiOffers = extractUniqueRandomProducts({ - products, - quantity: numCells - }); - } catch (error) { - console.error(error); - Sentry.captureException(error); - } - const numOffersToAdd = numCells - uiOffers.length; - let offerIdx = 0; - // if we need more products than what we got from the request, we add some duplicate products - for (let index = 0; index < numOffersToAdd; index++) { - const offerIdxToAdd = offerIdx % products.length; - uiOffers.push(products[offerIdxToAdd]); - - offerIdx++; - } - return uiOffers; - } - return []; - }, [products]); - - const onPreviousClick = () => { - const newIndex = selectedIndex - 1; - setSelectedIndex(newIndex); - rotateCarousel(newIndex); - }; - const onNextClick = () => { - const newIndex = selectedIndex + 1; - setSelectedIndex(newIndex); - rotateCarousel(newIndex); - }; - - function rotateCarousel(newIndex: number) { - const angle = theta * newIndex * -1; - if (carouselRef.current) { - const rotateFn = "rotateY"; - const cellWidth = carouselRef.current.offsetWidth; - const cellSize = cellWidth; - - const radius = Math.round(cellSize / 2 / Math.tan(Math.PI / numCells)); - - carouselRef.current.style.transform = - "translateZ(" + -radius + "px) " + rotateFn + "(" + angle + "deg)"; - } - } - const handlers = useSwipeable({ - onSwipedLeft: () => onNextClick(), - onSwipedRight: () => onPreviousClick(), - swipeDuration: 500, - preventScrollOnSwipe: true, - trackMouse: true - }); - - if (!uiOffers?.length && !isLoading) { - return <>; - } - - const getCells = (idx: number) => { - const clampedSelectedIndex = - ((selectedIndex % numCells) + numCells) % numCells; - const previousCell = - clampedSelectedIndex - 1 < 0 - ? numCells - 1 === idx - : idx === clampedSelectedIndex - 1; - const currentCell = idx === clampedSelectedIndex; - const nextCell = - idx === clampedSelectedIndex + 1 || - (clampedSelectedIndex + 1 === numCells && idx === 0); - return { - previousCell, - currentCell, - nextCell - }; - }; - return ( - - - {isLoading - ? new Array(numCells).fill(0).map((_, idx) => { - const { currentCell, nextCell, previousCell } = getCells(idx); - return ( - - - - ); - }) - : uiOffers?.map((offer: Offer, idx: number) => { - const { currentCell, nextCell, previousCell } = getCells(idx); - return ( - - - - ); - })} - - - - - - - ); -} diff --git a/src/pages/landing/Landing.tsx b/src/pages/landing/Landing.tsx index 8f7e3da1f..0dafd67c0 100644 --- a/src/pages/landing/Landing.tsx +++ b/src/pages/landing/Landing.tsx @@ -1,8 +1,10 @@ -import { CollectionsCardSkeleton } from "@bosonprotocol/react-kit"; +import { CollectionsCardSkeleton, isTruthy } from "@bosonprotocol/react-kit"; import CollectionsCard from "components/modal/components/Explore/Collections/CollectionsCard"; import { useSortOffers } from "components/price/useSortOffers"; +import Loading from "components/ui/Loading"; import { colors } from "lib/styles/colors"; import { Profile } from "lib/utils/hooks/lens/graphql/generated"; +import { useOffersWhitelist } from "lib/utils/hooks/offers/useOffersWhitelist"; import useProducts from "lib/utils/hooks/product/useProducts"; import useProductsByFilteredOffers from "lib/utils/hooks/product/useProductsByFilteredOffers"; import { getOfferDetails } from "lib/utils/offer/getOfferDetails"; @@ -128,9 +130,9 @@ export default function Landing() { const LayoutWrapper = isSideNavBar ? Grid : DarkerBackground; const { products, isLoading, isError, sellerLensProfilePerSellerId } = useProductsByFilteredOffers({ + first: 200, voided: false, valid: true, - first: numOffers, quantityAvailable_gte: 1 }); @@ -146,15 +148,51 @@ export default function Landing() { } }, [products]); + const { data: offersWhitelisted } = useOffersWhitelist(); + const { products: validOffersWhitelisted } = useProductsByFilteredOffers( + { + voided: false, + valid: true, + quantityAvailable_gte: 1, + first: offersWhitelisted?.length || 0 + }, + { + enabled: !!offersWhitelisted?.length, + overrides: { + enableCurationLists: true, + offerCurationList: offersWhitelisted + } + } + ); const offerImages = useMemo(() => { - return shuffledOffers - ?.slice(0, 8) + function getOffersForAnimatedGrid() { + const numOffersInAnimatedGrid = 8; + if (validOffersWhitelisted.length >= numOffersInAnimatedGrid) { + return validOffersWhitelisted; + } + const validOfferWhitelistedIdMap = new Map( + validOffersWhitelisted.map((offer) => [offer.id, true]) + ); + + const offersToAdd = [...validOffersWhitelisted]; + for (const offer of shuffledOffers) { + if (offersToAdd.length >= numOffersInAnimatedGrid) break; + + if (!validOfferWhitelistedIdMap.has(offer.id)) { + offersToAdd.push(offer); + } + } + return offersToAdd; + } + const offers = getOffersForAnimatedGrid(); + return offers .map((offer) => { const { mainImage } = getOfferDetails(offer.metadata); - return mainImage || offer?.metadata?.imageUrl; + const img = mainImage || offer?.metadata?.imageUrl; + return img; }) - .filter((image): image is string => !!image); - }, [shuffledOffers]); + .filter(isTruthy); + }, [validOffersWhitelisted, shuffledOffers]); const allProducts = useProducts( { @@ -244,8 +282,7 @@ export default function Landing() { padding="0.9375rem 2.5rem 2.5rem 2.5rem" > - Tokenize, transfer and trade any physical asset - as an NFT + Tokenize, transfer and trade any physical asset as an NFT The first decentralized marketplace built on Boson Protocol @@ -261,8 +298,10 @@ export default function Landing() { - {offerImages.length > 0 && ( + {offerImages.length > 0 ? ( + ) : ( + )} diff --git a/src/pages/landing/Step.tsx b/src/pages/landing/Step.tsx deleted file mode 100644 index 7b2d83183..000000000 --- a/src/pages/landing/Step.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import styled from "styled-components"; - -import { Typography } from "../../components/ui/Typography"; -import { breakpoint } from "../../lib/styles/breakpoint"; -import { colors } from "../../lib/styles/colors"; -import { zIndex } from "../../lib/styles/zIndex"; - -const StepWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 0.75rem; - ${breakpoint.xxs} { - gap: 1.5rem; - } - flex: 1; - width: 100%; - [data-testid="number"] { - font-size: 3.5rem; - font-weight: 600; - line-height: 1.2; - position: relative; - color: var(--accent); - margin-bottom: 0.5rem !important; - z-index: ${zIndex.CommitStep}; - &:after { - content: ""; - position: absolute; - z-index: ${zIndex.CommitStep - 1}; - width: 100%; - height: 50%; - top: 50%; - left: 0%; - background: var(--primary, ${colors.green}); - } - } - [data-testid="step-title"] { - all: unset; - font-size: 1.5rem; - font-weight: 600; - line-height: 1.5; - } - [data-testid="number"], - [data-testid="step-title"], - p { - text-align: center; - margin: 0; - } -`; - -interface IStep { - children?: string | React.ReactNode; - number: number; - title: string; -} - -const Step: React.FC = ({ children, number, title, ...props }) => { - return ( - - - 0{number} - - - {title} - - {children} - - ); -}; - -export default Step; diff --git a/src/pages/profile/seller/Offers.tsx b/src/pages/profile/seller/Offers.tsx index 4e4610726..d537cdd79 100644 --- a/src/pages/profile/seller/Offers.tsx +++ b/src/pages/profile/seller/Offers.tsx @@ -1,9 +1,9 @@ import { useMemo } from "react"; -import { Action } from "../../../components/offer/OfferCard"; import OfferList from "../../../components/offers/OfferList"; import { Profile } from "../../../lib/utils/hooks/lens/graphql/generated"; import { ExtendedSeller } from "../../explore/WithAllOffers"; +import { Action } from "./const"; interface Props { products: ExtendedSeller; diff --git a/src/pages/profile/seller/const.ts b/src/pages/profile/seller/const.ts new file mode 100644 index 000000000..7ceec8d6a --- /dev/null +++ b/src/pages/profile/seller/const.ts @@ -0,0 +1 @@ +export type Action = "commit" | "redeem" | "contact-seller" | null;