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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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,