diff --git a/packages/react-kit/src/assets/svg/blank_token.svg b/packages/react-kit/src/assets/svg/blank_token.svg index 8d0e03824..7a33111b3 100644 --- a/packages/react-kit/src/assets/svg/blank_token.svg +++ b/packages/react-kit/src/assets/svg/blank_token.svg @@ -1,3 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/packages/react-kit/src/assets/wallets/browser-wallet-light.svg b/packages/react-kit/src/assets/wallets/browser-wallet-light.svg index d557a1f4e..6910a1e11 100644 --- a/packages/react-kit/src/assets/wallets/browser-wallet-light.svg +++ b/packages/react-kit/src/assets/wallets/browser-wallet-light.svg @@ -1,5 +1,8 @@ - - - - + + + + \ No newline at end of file diff --git a/packages/react-kit/src/assets/wallets/metamask-icon.svg b/packages/react-kit/src/assets/wallets/metamask-icon.svg index d574d33e7..7fee8284b 100644 --- a/packages/react-kit/src/assets/wallets/metamask-icon.svg +++ b/packages/react-kit/src/assets/wallets/metamask-icon.svg @@ -1,15 +1,49 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/assets/wallets/trustwallet-icon.svg b/packages/react-kit/src/assets/wallets/trustwallet-icon.svg index b574317d9..7e6d7632d 100644 --- a/packages/react-kit/src/assets/wallets/trustwallet-icon.svg +++ b/packages/react-kit/src/assets/wallets/trustwallet-icon.svg @@ -1,17 +1,23 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/components/modal/components/RequestShipment/RequestShipmentModal.tsx b/packages/react-kit/src/components/modal/components/RequestShipment/RequestShipmentModal.tsx index 3adf6cefb..24fae32c6 100644 --- a/packages/react-kit/src/components/modal/components/RequestShipment/RequestShipmentModal.tsx +++ b/packages/react-kit/src/components/modal/components/RequestShipment/RequestShipmentModal.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { styled } from "styled-components"; import OfferPolicyDetails from "../../../offerPolicy/OfferPolicyDetails"; -import { subgraph } from "@bosonprotocol/core-sdk"; import ContractualAgreement from "../../../contractualAgreement/ContractualAgreement"; import License from "../../../license/License"; import * as Yup from "yup"; @@ -27,6 +26,7 @@ import { mockedDeliveryAddress } from "../../../widgets/redemption/const"; import { checkSignatures } from "../Redeem/checkSignatures"; import Loading from "../../../ui/loading/LoadingWrapper"; import { useCurrentSellers } from "../../../../hooks/useCurrentSellers"; +import { BosonRobloxExchange } from "../../../../hooks/roblox/backend.types"; const Wrapper = styled.div``; @@ -46,15 +46,13 @@ export type RequestShipmentModalProps = Pick< | "redemptionSubmittedHandler" | "redemptionConfirmedHandler" > & { - offer: subgraph.OfferFieldsFragment; - exchange: subgraph.ExchangeFieldsFragment; + exchange: BosonRobloxExchange; forcedAccount?: string; parentOrigin?: string | null; signatures?: string[] | null; }; const initialStep = ActiveStep.EXCHANGE_POLICY_OVERVIEW; export const RequestShipmentModal = ({ - offer, exchange, parentOrigin, signatures, @@ -64,6 +62,7 @@ export const RequestShipmentModal = ({ redemptionConfirmedHandler, redemptionSubmittedHandler }: RequestShipmentModalProps) => { + const { offer } = exchange; const offerId = offer.id; const offerName = exchange?.offer?.metadata?.name || ""; const buyerId = exchange?.buyer.id || ""; diff --git a/packages/react-kit/src/components/ui/Grid.tsx b/packages/react-kit/src/components/ui/Grid.tsx index 59fa4ecf4..cc6fb02bb 100644 --- a/packages/react-kit/src/components/ui/Grid.tsx +++ b/packages/react-kit/src/components/ui/Grid.tsx @@ -22,6 +22,10 @@ const pickedProps = { marginRight: true, marginBottom: true, marginLeft: true, + paddingTop: true, + paddingRight: true, + paddingBottom: true, + paddingLeft: true, width: true, height: true, maxWidth: true, @@ -72,6 +76,14 @@ const Container = styled.div` isDefined($marginBottom) ? `margin-bottom:${$marginBottom};` : ""} ${({ $marginLeft }) => isDefined($marginLeft) ? `margin-left:${$marginLeft};` : ""} + ${({ $paddingTop }) => + isDefined($paddingTop) ? `padding-top:${$paddingTop};` : ""} + ${({ $paddingRight }) => + isDefined($paddingRight) ? `padding-right:${$paddingRight};` : ""} + ${({ $paddingBottom }) => + isDefined($paddingBottom) ? `padding-bottom:${$paddingBottom};` : ""} + ${({ $paddingLeft }) => + isDefined($paddingLeft) ? `padding-left:${$paddingLeft};` : ""} ${({ $alignSelf }) => isDefined($alignSelf) ? `align-self:${$alignSelf};` : ""} ${({ $justifySelf }) => diff --git a/packages/react-kit/src/components/widgets/roblox/RobloxWidget.tsx b/packages/react-kit/src/components/widgets/roblox/RobloxWidget.tsx index b3c45cdb8..751d2eb07 100644 --- a/packages/react-kit/src/components/widgets/roblox/RobloxWidget.tsx +++ b/packages/react-kit/src/components/widgets/roblox/RobloxWidget.tsx @@ -1,4 +1,4 @@ -import { CSSProperties, styled } from "styled-components"; +import { CSSProperties } from "styled-components"; import { ConnectRoblox, ConnectRobloxProps } from "./components/ConnectRoblox"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { Grid } from "../../ui/Grid"; @@ -8,8 +8,11 @@ import { } from "./components/ProductsRoblox"; import { CommitWidgetProviders } from "../commit/CommitWidgetProviders"; import { CommitWidgetProps } from "../commit/CommitWidget"; +import { Typography } from "../../ui/Typography"; +import { AccountDrawer } from "../../wallet2/accountDrawer"; +import { Portal } from "../../portal/Portal"; -const Wrapper = styled(Grid)``; +const Wrapper = Grid; const numSteps = 3; const numGaps = numSteps - 1; export type RobloxWidgetProps = { @@ -21,6 +24,7 @@ export type RobloxWidgetProps = { | "envName" | "sellerId" | "requestShipmentProps" + | "walletButtonTheme" >; configProps: Omit< CommitWidgetProps, @@ -77,11 +81,72 @@ export const RobloxWidget = ({ + + ( + + By connecting a wallet, you agree to Boson App 's{" "} + + Terms & Conditions + {" "} + and consent to its{" "} + + Privacy Policy + + . (Last Updated 18 August 2023) + + ) + }} + /> + ); }; diff --git a/packages/react-kit/src/components/widgets/roblox/components/ConnectRoblox.tsx b/packages/react-kit/src/components/widgets/roblox/components/ConnectRoblox.tsx index 390eb58ce..6eb9b1d54 100644 --- a/packages/react-kit/src/components/widgets/roblox/components/ConnectRoblox.tsx +++ b/packages/react-kit/src/components/widgets/roblox/components/ConnectRoblox.tsx @@ -8,16 +8,15 @@ import React, { import { CSSProperties, css, styled } from "styled-components"; import { Grid } from "../../../ui/Grid"; import { Typography } from "../../../ui/Typography"; -import { BaseButton, BaseButtonTheme } from "../../../buttons/BaseButton"; -import { ConnectWallet } from "../../../wallet2/web3Status"; -import { Portal } from "../../../portal/Portal"; -import { AccountDrawer } from "../../../wallet2/accountDrawer"; +import { BaseButton } from "../../../buttons/BaseButton"; import { useAccount } from "../../../../hooks"; import { CheckCircle, Power } from "phosphor-react"; import { useDisconnect } from "../../../../hooks/connection/useDisconnect"; import { useIsRobloxLoggedIn } from "../../../../hooks/roblox/useIsRobloxLoggedIn"; import { useRobloxLogout } from "../../../../hooks/roblox/useRobloxLogout"; import { useRobloxConfigContext } from "../../../../hooks/roblox/context/useRobloxConfigContext"; +import { ButtonThemeProps } from "./types"; +import { ConnectWalletWithLogic } from "./ConnectWalletWithLogic"; const Wrapper = styled(Grid)` container-type: inline-size; @@ -226,12 +225,7 @@ type CardThemeProps = { stroke: CSSProperties["color"]; }; }; - button: { - active: Omit & - Required>; - inactive: Omit & - Required>; - }; + button: ButtonThemeProps; }; export type ConnectRobloxProps = { brand: string; @@ -247,7 +241,6 @@ type ActiveStep = 0 | 1 | 2; export const ConnectRoblox = forwardRef( ({ brand, theme }, ref) => { const { address } = useAccount(); - const disconnect = useDisconnect(); const [isSignUpDone, setSignUpDone] = useState(false); const [activeStep, setActiveStep] = useState(0); const { data: robloxLoggedInData, refetch: getIsRobloxLoggedInAsync } = @@ -372,81 +365,10 @@ export const ConnectRoblox = forwardRef( title="Create an account" subtitle="Linking your Roblox account to your wallet to signal your permission." button={ - <> - {address ? ( - disconnect({ isUserDisconnecting: true })} - > - Disconnect Account - - ) : ( - - )} - - ( - - By connecting a wallet, you agree to Boson App 's{" "} - - Terms & Conditions - {" "} - and consent to its{" "} - - Privacy Policy - - . (Last Updated 18 August 2023) - - ) - }} - /> - - + } /> { + const { address } = useAccount(); + const disconnect = useDisconnect(); + + return address ? ( + disconnect({ isUserDisconnecting: true })} + > + Disconnect Account + + ) : ( + + ); +}; diff --git a/packages/react-kit/src/components/widgets/roblox/components/ProductsGrid.tsx b/packages/react-kit/src/components/widgets/roblox/components/ProductsGrid.tsx deleted file mode 100644 index 2897ea00d..000000000 --- a/packages/react-kit/src/components/widgets/roblox/components/ProductsGrid.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { subgraph } from "@bosonprotocol/core-sdk"; -import { GridContainer } from "../../../ui/GridContainer"; -import { ProductCard } from "../../../productCard/ProductCard"; -import styled, { css } from "styled-components"; -import { Currencies } from "../../../currencyDisplay/CurrencyDisplay"; -import { useAccount, useIpfsContext } from "../../../../hooks"; -import { Button } from "../../../buttons/Button"; -import React from "react"; -import { utils } from "ethers"; -import { CameraSlash } from "phosphor-react"; -import { theme } from "../../../../theme"; -import { - getFallbackImageUrl, - getImageUrl -} from "../../../../lib/images/images"; -import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; -import { isBundle, isProductV1 } from "../../../../lib/offer/filter"; -import { ProductCardSkeleton } from "../../../skeleton/ProductCardSkeleton"; -import { CommitButtonView } from "../../../buttons/CommitButtonView"; -import { LabelType, ProductType } from "../../../productCard/const"; - -const colors = theme.colors.light; - -const commonCardStyles = css` - background: transparent; - box-shadow: unset; - padding: 0; - > * { - padding-top: 0; - } -`; -const TransparentProductCard = styled(ProductCard)` - ${commonCardStyles} -`; -const TransparentSkeletonProductCard = styled(ProductCardSkeleton)` - ${commonCardStyles} -`; - -export type ProductsGridProps = { - products: subgraph.OfferFieldsFragment[]; - isLoading: boolean; - numProducts?: number; -} & ( - | { - cta: "buy"; - handleSetProductUuid: (uuid: string) => void; - handleSetBundleUuid: (uuid: string) => void; - handleRequestShipment?: never; - } - | { - cta: "request-shipment"; - handleRequestShipment: (offer: subgraph.OfferFieldsFragment) => void; - handleSetProductUuid?: never; - handleSetBundleUuid?: never; - } -); -export const ProductsGrid = ({ - isLoading, - numProducts, - products, - cta, - handleSetBundleUuid, - handleSetProductUuid, - handleRequestShipment -}: ProductsGridProps) => { - const { address } = useAccount(); - const { ipfsImageGateway } = useIpfsContext(); - return ( - - {isLoading - ? new Array(numProducts || 3).fill(null).map(() => { - return ; - }) - : products - .filter((offer) => offer.metadata) - .map((offer) => { - if (!offer.metadata || !(isProductV1(offer) || isBundle(offer))) { - return null; - } - const metadata = offer.metadata; - const { mainImage } = getOfferDetails(offer); - const imageOptimizationOptions = { - height: 500 - }; - const imageSrc = getImageUrl( - (mainImage || metadata?.imageUrl) ?? "", - ipfsImageGateway, - imageOptimizationOptions - ); - - return ( - - } - }} - CTAOnHover={ - !address ? ( - - ) : cta === "buy" ? ( - { - if (isProductV1(offer) && offer.metadata?.uuid) { - handleSetProductUuid(offer.metadata.uuid); - } else if ( - isBundle(offer) && - offer.metadata?.bundleUuid - ) { - handleSetBundleUuid(offer.metadata.bundleUuid); - } - }} - /> - ) : ( - - ) - } - /> - ); - })} - - ); -}; diff --git a/packages/react-kit/src/components/widgets/roblox/components/ProductsRoblox.tsx b/packages/react-kit/src/components/widgets/roblox/components/ProductsRoblox.tsx index 68d80e06f..c19f60361 100644 --- a/packages/react-kit/src/components/widgets/roblox/components/ProductsRoblox.tsx +++ b/packages/react-kit/src/components/widgets/roblox/components/ProductsRoblox.tsx @@ -1,14 +1,16 @@ import { CSSProperties } from "styled-components"; -import React, { useMemo, useState } from "react"; +import React, { useState } from "react"; import { Typography, TypographyProps } from "../../../ui/Typography"; import { Grid } from "../../../ui/Grid"; -import { subgraph } from "@bosonprotocol/core-sdk"; import { CommitModalWithOffer } from "../../commit/CommitModalWithOffer"; -import { ProductsGrid } from "./ProductsGrid"; -import useProductByOfferId from "../../../../hooks/products/useProductByOfferId"; -import { isTruthy } from "../../../../types/helpers"; +import { RobloxProductsGrid } from "./RobloxProductsGrid"; import { useModal } from "../../../modal/useModal"; import { RequestShipmentModalProps } from "../../../modal/components/RequestShipment/RequestShipmentModal"; +import { ButtonThemeProps } from "./types"; +import { useRobloxProducts } from "../../../../hooks/roblox/useRobloxProducts"; +import { useRobloxExchanges } from "../../../../hooks/roblox/useRobloxExchanges"; +import { useAccount } from "../../../../hooks"; +import { RobloxExchangesGrid } from "./RobloxExchangesGrid"; const Wrapper = Grid; const ContentWrapper = Grid; @@ -32,6 +34,7 @@ export type ProductsRobloxProps = { | "parentOrigin" | "signatures" >; + walletButtonTheme: ButtonThemeProps; sellerId: string; theme?: Partial<{ style: Partial; @@ -42,14 +45,11 @@ export type ProductsRobloxProps = { maxWidth?: CSSProperties["maxWidth"]; }; -// const purchasedProducts = [{}] as any[]; -// const purchasedProductsLoading = false; -const unavailableProducts = [{}] as any[]; // TODO: get from API instead -const unavailableProductsLoading = false; // TODO: get from API instead export const ProductsRoblox = ({ sellerId, theme, maxWidth, + walletButtonTheme, requestShipmentProps: { deliveryInfoHandler, postDeliveryInfoUrl, @@ -60,26 +60,22 @@ export const ProductsRoblox = ({ signatures } }: ProductsRobloxProps) => { + const { address } = useAccount(); const { showModal } = useModal(); const { - data, + data: availableProducts, isLoading: availableProductLoading, ...rest - } = useProductByOfferId("17", { - // TODO: get from API instead - enabled: true - }); - console.log({ data, ...rest }); // TODO: remove - const availableProducts = useMemo( - () => - data?.variants.at(0)?.offer - ? [data?.variants.at(0)?.offer].filter(isTruthy) || [] - : [], - [data?.variants] - ); - const purchasedProducts = availableProducts; - const purchasedProductsLoading = availableProductLoading; - + } = useRobloxProducts({ sellerId }); + const unavailableProducts = availableProducts; // TODO: change + const unavailableProductsLoading = availableProductLoading; // TODO: change + console.log({ availableProducts, ...rest }); + const { data: purchasedProducts, isLoading: purchasedProductsLoading } = + useRobloxExchanges({ + sellerId, + userWallet: address ?? "", + options: { enabled: !!address } + }); const [productUuid, setProductUuid] = useState(""); const [bundleUuid, setBundleUuid] = useState(""); @@ -121,18 +117,13 @@ export const ProductsRoblox = ({ > Purchased Products - { + + { showModal("REQUEST_SHIPMENT", { - offer, - exchange: { - // TODO: should not be hardcoded - offer, - buyer: { id: "3" }, - seller: { id: sellerId, assistant: "0x123" } - } as unknown as subgraph.ExchangeFieldsFragment, // TODO: remove cast + exchange: robloxExchange, deliveryInfoHandler, parentOrigin, postDeliveryInfoUrl, @@ -156,12 +147,12 @@ export const ProductsRoblox = ({ Following products are available for you based on the Roblox inventory you have - @@ -175,12 +166,12 @@ export const ProductsRoblox = ({ Other products that can be purchased when you have the right Roblox inventory item. - diff --git a/packages/react-kit/src/components/widgets/roblox/components/RobloxExchangesGrid.tsx b/packages/react-kit/src/components/widgets/roblox/components/RobloxExchangesGrid.tsx new file mode 100644 index 000000000..595f9e2fb --- /dev/null +++ b/packages/react-kit/src/components/widgets/roblox/components/RobloxExchangesGrid.tsx @@ -0,0 +1,137 @@ +import { GridContainer } from "../../../ui/GridContainer"; +import { ProductCard } from "../../../productCard/ProductCard"; +import styled, { css } from "styled-components"; +import { Currencies } from "../../../currencyDisplay/CurrencyDisplay"; +import { useAccount, useIpfsContext } from "../../../../hooks"; +import { Button } from "../../../buttons/Button"; +import React from "react"; +import { utils } from "ethers"; +import { CameraSlash } from "phosphor-react"; +import { theme } from "../../../../theme"; +import { + getFallbackImageUrl, + getImageUrl +} from "../../../../lib/images/images"; +import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; +import { isBundle, isProductV1 } from "../../../../lib/offer/filter"; +import { ProductCardSkeleton } from "../../../skeleton/ProductCardSkeleton"; +import { LabelType, ProductType } from "../../../productCard/const"; +import { ConnectWalletWithLogic } from "./ConnectWalletWithLogic"; +import { ButtonThemeProps } from "./types"; +import { BosonRobloxExchange } from "../../../../hooks/roblox/backend.types"; +import { Typography } from "../../../ui/Typography"; +import { isTruthy } from "../../../../types/helpers"; + +const colors = theme.colors.light; + +const commonCardStyles = css` + background: transparent; + box-shadow: unset; + padding: 0; + > * { + padding-top: 0; + } +`; +const TransparentProductCard = styled(ProductCard)` + ${commonCardStyles} +`; +const TransparentSkeletonProductCard = styled(ProductCardSkeleton)` + ${commonCardStyles} +`; + +export type RobloxExchangesGridProps = { + isLoading: boolean; + numProducts?: number; + walletButtonTheme: ButtonThemeProps; + handleRequestShipment: (robloxProduct: BosonRobloxExchange) => void; + exchanges: BosonRobloxExchange[] | undefined; +}; +export const RobloxExchangesGrid = ({ + isLoading, + numProducts, + exchanges, + walletButtonTheme, + handleRequestShipment +}: RobloxExchangesGridProps) => { + const { address } = useAccount(); + const { ipfsImageGateway } = useIpfsContext(); + return ( + + {isLoading ? ( + new Array(numProducts || 3).fill(null).map((_, index) => { + return ; + }) + ) : exchanges?.length ? ( + exchanges + .filter((robloxExchange) => robloxExchange.offer.metadata) + .map((robloxExchange) => { + const { offer } = robloxExchange; + console.log({ robloxExchange }); + const { price, metadata } = offer; + if (!(isProductV1(offer) || isBundle(offer))) { + return null; + } + const { exchangeToken } = offer; + const { mainImage } = getOfferDetails(offer); + const imageOptimizationOptions = { + height: 500 + }; + const imageSrc = getImageUrl( + (mainImage || metadata?.image) ?? "", + ipfsImageGateway, + imageOptimizationOptions + ); + + return ( + + } + }} + CTAOnHover={ + !address ? ( + + ) : ( + + ) + } + /> + ); + }) + .filter(isTruthy) + ) : ( + No products found + )} + + ); +}; diff --git a/packages/react-kit/src/components/widgets/roblox/components/RobloxProductsGrid.tsx b/packages/react-kit/src/components/widgets/roblox/components/RobloxProductsGrid.tsx new file mode 100644 index 000000000..9daec769b --- /dev/null +++ b/packages/react-kit/src/components/widgets/roblox/components/RobloxProductsGrid.tsx @@ -0,0 +1,145 @@ +import { GridContainer } from "../../../ui/GridContainer"; +import { ProductCard } from "../../../productCard/ProductCard"; +import styled, { css } from "styled-components"; +import { Currencies } from "../../../currencyDisplay/CurrencyDisplay"; +import { useAccount, useIpfsContext } from "../../../../hooks"; +import { Button } from "../../../buttons/Button"; +import React from "react"; +import { utils } from "ethers"; +import { CameraSlash } from "phosphor-react"; +import { theme } from "../../../../theme"; +import { + getFallbackImageUrl, + getImageUrl +} from "../../../../lib/images/images"; +import { getOfferDetails } from "../../../../lib/offer/getOfferDetails"; +import { isBundle, isProductV1 } from "../../../../lib/offer/filter"; +import { ProductCardSkeleton } from "../../../skeleton/ProductCardSkeleton"; +import { CommitButtonView } from "../../../buttons/CommitButtonView"; +import { ProductType } from "../../../productCard/const"; +import { ConnectWalletWithLogic } from "./ConnectWalletWithLogic"; +import { ButtonThemeProps } from "./types"; +import { BosonRobloxProductWithAvailability } from "../../../../hooks/roblox/backend.types"; +import { Typography } from "../../../ui/Typography"; +import { isTruthy } from "../../../../types/helpers"; + +const colors = theme.colors.light; + +const commonCardStyles = css` + background: transparent; + box-shadow: unset; + padding: 0; + > * { + padding-top: 0; + } +`; +const TransparentProductCard = styled(ProductCard)` + ${commonCardStyles} +`; +const TransparentSkeletonProductCard = styled(ProductCardSkeleton)` + ${commonCardStyles} +`; + +export type RobloxProductsGridProps = { + isLoading: boolean; + products: BosonRobloxProductWithAvailability[] | undefined; + numProducts?: number; + walletButtonTheme: ButtonThemeProps; + handleSetProductUuid: (uuid: string) => void; + handleSetBundleUuid: (uuid: string) => void; +}; +export const RobloxProductsGrid = ({ + isLoading, + numProducts, + products, + walletButtonTheme, + handleSetBundleUuid, + handleSetProductUuid +}: RobloxProductsGridProps) => { + const { address } = useAccount(); + const { ipfsImageGateway } = useIpfsContext(); + return ( + + {isLoading ? ( + new Array(numProducts || 3).fill(null).map((_, index) => { + return ; + }) + ) : products?.length ? ( + products + .filter((robloxProduct) => robloxProduct.offer.metadata) + .map((robloxProduct) => { + const { exchangeToken, uuid, offer } = robloxProduct; + console.log({ robloxProduct }); + const { price, metadata } = offer; + if (!(isProductV1(offer) || isBundle(offer))) { + return null; + } + const bundleUuid = isBundle(offer) ? offer.metadata.bundleUuid : ""; + + const { mainImage } = getOfferDetails(offer); + const imageOptimizationOptions = { + height: 500 + }; + const imageSrc = getImageUrl( + (mainImage || metadata?.image) ?? "", + ipfsImageGateway, + imageOptimizationOptions + ); + + return ( + + } + }} + CTAOnHover={ + !address ? ( + + ) : ( + { + if (isProductV1(offer) && uuid) { + handleSetProductUuid(uuid); + } else if (isBundle(offer) && bundleUuid) { + handleSetBundleUuid(bundleUuid); + } + }} + /> + ) + } + /> + ); + }) + .filter(isTruthy) + ) : ( + No products found + )} + + ); +}; diff --git a/packages/react-kit/src/components/widgets/roblox/components/styles.tsx b/packages/react-kit/src/components/widgets/roblox/components/styles.tsx new file mode 100644 index 000000000..e851445af --- /dev/null +++ b/packages/react-kit/src/components/widgets/roblox/components/styles.tsx @@ -0,0 +1,8 @@ +import { Power } from "phosphor-react"; +import styled from "styled-components"; + +export const StyledPower = styled(Power)` + && { + stroke: initial; + } +`; diff --git a/packages/react-kit/src/components/widgets/roblox/components/types.ts b/packages/react-kit/src/components/widgets/roblox/components/types.ts new file mode 100644 index 000000000..2bfe2c582 --- /dev/null +++ b/packages/react-kit/src/components/widgets/roblox/components/types.ts @@ -0,0 +1,8 @@ +import { BaseButtonTheme } from "../../../buttons/BaseButton"; + +export type ButtonThemeProps = { + active: Omit & + Required>; + inactive: Omit & + Required>; +}; diff --git a/packages/react-kit/src/hooks/roblox/backend.types.ts b/packages/react-kit/src/hooks/roblox/backend.types.ts new file mode 100644 index 000000000..ff4d177f7 --- /dev/null +++ b/packages/react-kit/src/hooks/roblox/backend.types.ts @@ -0,0 +1,81 @@ +import { subgraph } from "@bosonprotocol/core-sdk"; + +export type RobloxClaims = { + sub: string; + name: string; + nickname?: string; + picture?: string; + preferred_username?: string; + email?: string; + profile?: string; +}; + +export type RobloxUser = Omit & { userId: string }; + +/** + * Product Metadata subgraph, with related Roblox assetId + */ +export type BosonRobloxProduct = + subgraph.ProductV1MetadataEntityFieldsFragment & { + robloxAssetId: string; + }; + +export type BosonRobloxExchange = subgraph.ExchangeFieldsFragment & { + robloxAssetId: string; +}; + +export class ApiError extends Error { + constructor( + public status: number, + message: string + ) { + super(message); + } +} + +export type RobloxInventoryItem = { + path: string; + assetDetails: { + assetId: string; + }; +}; + +export type GetInfoResponse = { + userId: string; + claims: RobloxClaims; + inventoryItems: RobloxInventoryItem[]; +}; + +export type GetLoggedInResponse = { + isLoggedIn: boolean; + claims: RobloxClaims | null; + nonce: string | null; +}; + +export type GetWalletAuthResponse = { + walletAuth: boolean; + address: string | null; + sellerId: string | null; +}; + +export type PendingRequest = { txHash: string; txId: string }; + +/** + * A Product can be: + * - Unknown: the user is not authenticated on roblox + * - Not available: the roblox user is authenticated and it doesn't have the required Roblox asset + * - Potentially: the roblox user is authenticated and it has the required Roblox asset, the user wallet is not known yet + * - Pending: the roblox user is authenticated and it has the required Roblox asset and their wallet doesn't own the required NFT yet (minting is pending) + * - Available: the roblox user is authenticated and it has the required Roblox asset and their wallet owns the required NFT + * */ +export type ProductAvailabilityStatus = + | { status: "UNKNOWN" | "POTENTIALLY" | "NOT_AVAILABLE" | "AVAILABLE" } + | { status: "PENDING"; pendingData: PendingRequest }; + +export type BosonRobloxProductWithAvailability = BosonRobloxProduct & { + availability: ProductAvailabilityStatus; +}; + +export type GetProductsResponse = BosonRobloxProductWithAvailability[]; + +export type GetExchangesResponse = BosonRobloxExchange[]; diff --git a/packages/react-kit/src/hooks/roblox/useRobloxExchanges.ts b/packages/react-kit/src/hooks/roblox/useRobloxExchanges.ts new file mode 100644 index 000000000..b15ada767 --- /dev/null +++ b/packages/react-kit/src/hooks/roblox/useRobloxExchanges.ts @@ -0,0 +1,35 @@ +import { useQuery } from "react-query"; +import { GetExchangesResponse } from "./backend.types"; +import { useRobloxConfigContext } from "./context/useRobloxConfigContext"; + +type UseRobloxProductsProps = { + sellerId: string; + userWallet: string; + options: { + enabled: boolean; + }; +}; +export const useRobloxExchanges = ({ + sellerId, + userWallet, + options +}: UseRobloxProductsProps) => { + const { backendOrigin } = useRobloxConfigContext(); + const queryKey = ["roblox-exchanges", backendOrigin, sellerId, userWallet]; + return useQuery( + queryKey, + async () => { + const response = await fetch( + `${backendOrigin}/exchanges?bosonSellerId=${sellerId}&userWallet=${userWallet}` + ); + if (!response.ok) { + throw new Error( + `Error while fetching roblox exchanges, status = ${response.status.toString()}` + ); + } + const data = (await response.json()) as GetExchangesResponse; + return data; + }, + options + ); +}; diff --git a/packages/react-kit/src/hooks/roblox/useRobloxProducts.ts b/packages/react-kit/src/hooks/roblox/useRobloxProducts.ts new file mode 100644 index 000000000..5f129b375 --- /dev/null +++ b/packages/react-kit/src/hooks/roblox/useRobloxProducts.ts @@ -0,0 +1,32 @@ +import { useQuery, useQueryClient } from "react-query"; +import { GetProductsResponse } from "./backend.types"; +import { useRobloxConfigContext } from "./context/useRobloxConfigContext"; + +type UseRobloxProductsProps = { + sellerId: string; +}; +export const useRobloxProducts = ({ sellerId }: UseRobloxProductsProps) => { + const queryClient = useQueryClient(); + const { backendOrigin } = useRobloxConfigContext(); + const queryKey = ["roblox-products", backendOrigin, sellerId]; + return useQuery(queryKey, async () => { + const response = await fetch( + `${backendOrigin}/products?bosonSellerId=${sellerId}`, + { credentials: "include" } // required to include Cookie in the request + ); + if (!response.ok) { + throw new Error( + `Error while fetching roblox products, status = ${response.status.toString()}` + ); + } + const data = (await response.json()) as GetProductsResponse; + if (data.some((product) => product.availability.status === "PENDING")) { + console.log( + "Some products are in PENDING state. Refresh products in a short while..." + ); + // Wait a short delay and refresh products + setTimeout(() => queryClient.invalidateQueries(queryKey), 1000); + } + return data; + }); +}; diff --git a/packages/react-kit/src/stories/assets/code-brackets.svg b/packages/react-kit/src/stories/assets/code-brackets.svg index 73de94776..f3d53d8b4 100644 --- a/packages/react-kit/src/stories/assets/code-brackets.svg +++ b/packages/react-kit/src/stories/assets/code-brackets.svg @@ -1 +1,11 @@ -illustration/code-brackets \ No newline at end of file + + illustration/code-brackets + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/colors.svg b/packages/react-kit/src/stories/assets/colors.svg index 17d58d516..8aa142834 100644 --- a/packages/react-kit/src/stories/assets/colors.svg +++ b/packages/react-kit/src/stories/assets/colors.svg @@ -1 +1,17 @@ -illustration/colors \ No newline at end of file + + illustration/colors + + + + + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/comments.svg b/packages/react-kit/src/stories/assets/comments.svg index 6493a139f..cb947d6f5 100644 --- a/packages/react-kit/src/stories/assets/comments.svg +++ b/packages/react-kit/src/stories/assets/comments.svg @@ -1 +1,12 @@ -illustration/comments \ No newline at end of file + + illustration/comments + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/direction.svg b/packages/react-kit/src/stories/assets/direction.svg index 65676ac27..070cbd53d 100644 --- a/packages/react-kit/src/stories/assets/direction.svg +++ b/packages/react-kit/src/stories/assets/direction.svg @@ -1 +1,10 @@ -illustration/direction \ No newline at end of file + + illustration/direction + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/flow.svg b/packages/react-kit/src/stories/assets/flow.svg index 8ac27db40..608328c81 100644 --- a/packages/react-kit/src/stories/assets/flow.svg +++ b/packages/react-kit/src/stories/assets/flow.svg @@ -1 +1,11 @@ -illustration/flow \ No newline at end of file + + illustration/flow + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/plugin.svg b/packages/react-kit/src/stories/assets/plugin.svg index 29e5c690c..ec1563314 100644 --- a/packages/react-kit/src/stories/assets/plugin.svg +++ b/packages/react-kit/src/stories/assets/plugin.svg @@ -1 +1,10 @@ -illustration/plugin \ No newline at end of file + + illustration/plugin + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/repo.svg b/packages/react-kit/src/stories/assets/repo.svg index f386ee902..c1c1802bd 100644 --- a/packages/react-kit/src/stories/assets/repo.svg +++ b/packages/react-kit/src/stories/assets/repo.svg @@ -1 +1,13 @@ -illustration/repo \ No newline at end of file + + illustration/repo + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/assets/stackalt.svg b/packages/react-kit/src/stories/assets/stackalt.svg index 9b7ad2743..0cf032bbc 100644 --- a/packages/react-kit/src/stories/assets/stackalt.svg +++ b/packages/react-kit/src/stories/assets/stackalt.svg @@ -1 +1,21 @@ -illustration/stackalt \ No newline at end of file + + illustration/stackalt + + + + + + + \ No newline at end of file diff --git a/packages/react-kit/src/stories/widgets/Roblox.stories.tsx b/packages/react-kit/src/stories/widgets/Roblox.stories.tsx index ef816c3f4..13adec001 100644 --- a/packages/react-kit/src/stories/widgets/Roblox.stories.tsx +++ b/packages/react-kit/src/stories/widgets/Roblox.stories.tsx @@ -108,7 +108,7 @@ export const Base = { sellerId: "20", walletConnectProjectId: process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID ?? "", - backendOrigin: "http://localhost:3000", + backendOrigin: "http://localhost:3336", ipfsGateway: process.env.STORYBOOK_DATA_IPFS_GATEWAY, ipfsProjectId: process.env.STORYBOOK_DATA_IPFS_PROJECT_ID, ipfsProjectSecret: process.env.STORYBOOK_DATA_IPFS_PROJECT_SECRET,