From 1068f76e400a747fe36a2d67824a02ad82094a5d Mon Sep 17 00:00:00 2001 From: Albert Folch Date: Wed, 15 Jan 2025 15:12:26 +0100 Subject: [PATCH] wip --- .../GeneralProductData.tsx | 11 +- .../common/detail/DetailViewCore.tsx | 105 +++++------------- .../common/detail/TokenGatedItem.tsx | 73 ++++++++++-- .../src/components/ui/ThemedButton.tsx | 10 +- .../hooks/roblox/useRobloxGetItemDetails.ts | 50 +++++++++ packages/react-kit/src/lib/config/config.ts | 6 +- .../src/lib/roblox/getIsOfferRobloxGated.ts | 20 ++++ 7 files changed, 176 insertions(+), 99 deletions(-) create mode 100644 packages/react-kit/src/hooks/roblox/useRobloxGetItemDetails.ts create mode 100644 packages/react-kit/src/lib/roblox/getIsOfferRobloxGated.ts diff --git a/packages/react-kit/src/components/modal/components/common/OfferFullDescription/GeneralProductData.tsx b/packages/react-kit/src/components/modal/components/common/OfferFullDescription/GeneralProductData.tsx index 11d7c9cec..0536bc58a 100644 --- a/packages/react-kit/src/components/modal/components/common/OfferFullDescription/GeneralProductData.tsx +++ b/packages/react-kit/src/components/modal/components/common/OfferFullDescription/GeneralProductData.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; import { useIsPhygital } from "../../../../../hooks/offer/useIsPhygital"; import { breakpoint } from "../../../../../lib/ui/breakpoint"; @@ -21,6 +21,7 @@ import { } from "../detail/useGetOfferDetailData"; import { Exchange } from "../../../../../types/exchange"; import { PhygitalProduct } from "../detail/PhygitalProduct"; +import { getIsOfferRobloxGated } from "../../../../../lib/roblox/getIsOfferRobloxGated"; const StyledPrice = styled(Price)` h3 { @@ -71,6 +72,9 @@ export const GeneralProductData: React.FC = ({ exchangePolicyCheckResult }); const isPhygital = useIsPhygital({ offer }); + const robloxGatedAssetId = useMemo(() => { + return getIsOfferRobloxGated({ offer }); + }, [offer]); return ( = ({ {offer.condition && ( <> - Token gated offer + + {robloxGatedAssetId ? "Roblox gated offer" : "Token gated offer"} + diff --git a/packages/react-kit/src/components/modal/components/common/detail/DetailViewCore.tsx b/packages/react-kit/src/components/modal/components/common/detail/DetailViewCore.tsx index 4274acf6e..967ef6eb8 100644 --- a/packages/react-kit/src/components/modal/components/common/detail/DetailViewCore.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/DetailViewCore.tsx @@ -27,8 +27,7 @@ import { TokenGatedItem } from "./TokenGatedItem"; import { DetailViewProps } from "./types"; import { useGetOfferDetailData } from "./useGetOfferDetailData"; import { SvgImage } from "../../../../ui/SvgImage"; -import { filterRobloxProduct } from "@bosonprotocol/roblox-sdk"; -import { isProductV1 } from "../../../../../lib/offer/filter"; +import { getIsOfferRobloxGated } from "../../../../../lib/roblox/getIsOfferRobloxGated"; const StyledPrice = styled(Price)` h3 { @@ -91,14 +90,7 @@ export const DetailViewCore = forwardRef, Props>( const closeDetailsRef = useRef(true); const isPhygital = useIsPhygital({ offer }); const robloxGatedAssetId = useMemo(() => { - if (offer.metadata && isProductV1(offer) && offer.condition) { - return filterRobloxProduct( - offer.metadata, - offer.condition, - offer.condition?.tokenAddress - ); - } - return ""; + return getIsOfferRobloxGated({ offer }); }, [offer]); return ( @@ -131,78 +123,31 @@ export const DetailViewCore = forwardRef, Props>( {children} {offer.condition && ( <> - {robloxGatedAssetId ? ( - - ) : ( - - ) - } - onSetOpen={(open) => { - if (open && closeDetailsRef.current) { - setDetailsOpen(false); - closeDetailsRef.current = false; - } - }} - > - {isConditionMet ? ( - - You can buy this item because you own - - this roblox item - - + ) : ( - - You need &b - - this roblox item - {" "} - to buy this - - )} - - ) : ( - - ) : ( - - ) + + ) + } + onSetOpen={(open) => { + if (open && closeDetailsRef.current) { + setDetailsOpen(false); + closeDetailsRef.current = false; } - onSetOpen={(open) => { - if (open && closeDetailsRef.current) { - setDetailsOpen(false); - closeDetailsRef.current = false; - } - }} - > - - - )} + }} + > + + )} {isPhygital && ( diff --git a/packages/react-kit/src/components/modal/components/common/detail/TokenGatedItem.tsx b/packages/react-kit/src/components/modal/components/common/detail/TokenGatedItem.tsx index 68fe96e5e..506e6db71 100644 --- a/packages/react-kit/src/components/modal/components/common/detail/TokenGatedItem.tsx +++ b/packages/react-kit/src/components/modal/components/common/detail/TokenGatedItem.tsx @@ -25,10 +25,13 @@ import ThemedButton from "../../../../ui/ThemedButton"; import { BuyOrSwapContainer } from "./BuyOrSwapContainer"; import { useDetailViewContext } from "./DetailViewProvider"; import { OnClickBuyOrSwapHandler } from "./types"; +import { CONFIG } from "../../../../../lib/config/config"; +import { useRobloxGetItemDetails } from "../../../../../hooks/roblox/useRobloxGetItemDetails"; -type Props = OnClickBuyOrSwapHandler & { +type TokenGatedItemProps = OnClickBuyOrSwapHandler & { offer: Offer; isConditionMet: boolean; + robloxGatedAssetId: string | false; }; const Wrapper = styled(Grid)` @@ -61,8 +64,9 @@ const ActionText = ({ children }: { children: ReactNode }) => { export const TokenGatedItem = ({ offer, isConditionMet, - onClickBuyOrSwap -}: Props) => { + onClickBuyOrSwap, + robloxGatedAssetId +}: TokenGatedItemProps) => { const { condition } = offer; const coreSDK = useCoreSDKWithContext(); const { swapParams } = useDetailViewContext(); @@ -76,6 +80,9 @@ export const TokenGatedItem = ({ const [erc1155Info, setErc1155Info] = useState("NFT"); useEffect(() => { + if (!robloxGatedAssetId) { + return; + } (async () => { if ( condition?.tokenAddress && @@ -117,7 +124,7 @@ export const TokenGatedItem = ({ } } })(); - }, [condition, coreSDK]); + }, [condition, coreSDK, robloxGatedAssetId]); const { tokenAddress } = condition ?? {}; const ContractButton = ( @@ -132,6 +139,18 @@ export const TokenGatedItem = ({ ); + const VisitButton = robloxGatedAssetId ? ( + + + Visit + + + ) : null; const BuyButton = !condition ? null : condition.tokenType === TokenType.FungibleToken ? ( @@ -171,7 +190,11 @@ export const TokenGatedItem = ({ ), [isConditionMet] ); - const ActionButton = isConditionMet ? ContractButton : BuyButton; + const ActionButton = robloxGatedAssetId + ? VisitButton + : isConditionMet + ? ContractButton + : BuyButton; const { config } = useConfigContext(); const chainId = config.chainId; @@ -255,7 +278,12 @@ export const TokenGatedItem = ({ }, { enabled: !!(erc1155Uri && erc1155Uri[0] && tokenIdForImage) } ); - + const { data: { Name: RobloxItemName = "" } = {} } = useRobloxGetItemDetails({ + itemId: robloxGatedAssetId || "", + options: { + enabled: !!robloxGatedAssetId + } + }); const Icon = useMemo(() => { return ( <> @@ -265,9 +293,9 @@ export const TokenGatedItem = ({ size={imageSize} currencies={[currency]} /> - ) : erc721Image ? ( + ) : erc721Image?.[0]?.[0] ? ( - ) : erc1155Image ? ( + ) : erc1155Image?.[0]?.[0] ? ( ) : null} @@ -321,7 +349,8 @@ export const TokenGatedItem = ({ flexWrap="wrap" gap="1rem" > - {condition.tokenType === TokenType.FungibleToken ? ( + {condition.tokenType === TokenType.FungibleToken && + !robloxGatedAssetId ? ( {condition.threshold && erc20Info?.decimals && (
@@ -343,7 +372,8 @@ export const TokenGatedItem = ({
{erc20Info.symbol}
- ) : condition.tokenType === TokenType.NonFungibleToken ? ( + ) : condition.tokenType === TokenType.NonFungibleToken && + !robloxGatedAssetId ? ( <> {condition.method === EvaluationMethod.Threshold ? ( @@ -379,7 +409,8 @@ export const TokenGatedItem = ({ <> )} - ) : condition.tokenType === TokenType.MultiToken ? ( + ) : condition.tokenType === TokenType.MultiToken && + !robloxGatedAssetId ? ( <> {Icon} @@ -399,6 +430,26 @@ export const TokenGatedItem = ({ {ActionButton} + ) : robloxGatedAssetId ? ( + <> + + {Icon} +
{condition.threshold}x
+ + + {RobloxItemName || erc1155Info} {rangeText} + + +
+ {ActionButton} + ) : ( <> )} diff --git a/packages/react-kit/src/components/ui/ThemedButton.tsx b/packages/react-kit/src/components/ui/ThemedButton.tsx index c9db78424..1e530acad 100644 --- a/packages/react-kit/src/components/ui/ThemedButton.tsx +++ b/packages/react-kit/src/components/ui/ThemedButton.tsx @@ -159,7 +159,7 @@ export const bosonButtonThemes = () => { hover: { borderColor: colors.violet, background: colors.border, - color: colors.black + color: getCssVar("--main-text-color") } }, blankSecondaryOutline: { @@ -170,7 +170,7 @@ export const bosonButtonThemes = () => { hover: { borderColor: colors.violet, background: colors.border, - color: colors.black + color: getCssVar("--main-text-color") } }, blankOutline: { @@ -206,7 +206,7 @@ export const bosonButtonThemes = () => { } }, warning: { - color: colors.black, + color: getCssVar("--main-text-color"), borderColor: colors.orange, borderWidth: 1, borderRadius: getCssVar("--button-border-radius"), @@ -216,7 +216,7 @@ export const bosonButtonThemes = () => { } }, escalate: { - color: colors.black, + color: getCssVar("--main-text-color"), background: colors.orange, borderColor: colors.orange, borderWidth: 1, @@ -228,7 +228,7 @@ export const bosonButtonThemes = () => { } }, accentFill: { - color: colors.black, + color: getCssVar("--main-text-color"), background: colors.violet, borderColor: "transparent", borderWidth: 1, diff --git a/packages/react-kit/src/hooks/roblox/useRobloxGetItemDetails.ts b/packages/react-kit/src/hooks/roblox/useRobloxGetItemDetails.ts new file mode 100644 index 000000000..af45f3736 --- /dev/null +++ b/packages/react-kit/src/hooks/roblox/useRobloxGetItemDetails.ts @@ -0,0 +1,50 @@ +import { useQuery } from "react-query"; +import * as yup from "yup"; + +type UseRobloxGetItemDetailsProps = { + itemId: string; + options: { + enabled?: boolean; + }; +}; + +const responseSchema = yup.object({ + Name: yup.string().required() +}); +type PayloadResponse = yup.InferType; +export const useRobloxGetItemDetails = ({ + itemId, + options +}: UseRobloxGetItemDetailsProps) => { + const queryKey = ["roblox-item-details", itemId]; + return useQuery( + queryKey, + async (): Promise => { + // TODO: + // const endpoint = CONFIG.roblox.getItemDetailsEndpoint2({ itemId }); + // const response = await fetch(endpoint); + // if (!response.ok) { + // console.error( + // `Error while fetching ${endpoint}, status = ${response.status.toString()}` + // ); + // const endpoint2 = CONFIG.roblox.getItemDetailsEndpoint({ itemId }); + // const response2 = await fetch(endpoint2); + // if (!response2.ok) { + // throw new Error( + // `Error while fetching ${endpoint2}, status = ${response2.status.toString()}` + // ); + // } + // const jsonData = await response2.json(); + // const validatedData = await responseSchema.validate(jsonData); + // return validatedData; + // } + // const jsonData = await response.json(); + // const validatedData = await responseSchema.validate(jsonData); + // return validatedData; + return { + Name: "Name should be returned by the backend and not hardcoded" + }; + }, + options + ); +}; diff --git a/packages/react-kit/src/lib/config/config.ts b/packages/react-kit/src/lib/config/config.ts index ac764f5fd..d8d335053 100644 --- a/packages/react-kit/src/lib/config/config.ts +++ b/packages/react-kit/src/lib/config/config.ts @@ -25,5 +25,9 @@ export const CONFIG = { infuraKey, rpcUrls: getRpcUrls(infuraKey), defaultDisputeResolutionPeriodDays: 15, - minimumDisputePeriodInDays: 30 + minimumDisputePeriodInDays: 30, + roblox: { + getItemDetailsWebsite: ({ itemId }: { itemId: string }) => + `https://www.roblox.com/catalog/${itemId}/` + } } as const; diff --git a/packages/react-kit/src/lib/roblox/getIsOfferRobloxGated.ts b/packages/react-kit/src/lib/roblox/getIsOfferRobloxGated.ts new file mode 100644 index 000000000..377d07fa9 --- /dev/null +++ b/packages/react-kit/src/lib/roblox/getIsOfferRobloxGated.ts @@ -0,0 +1,20 @@ +import { subgraph } from "@bosonprotocol/core-sdk"; +import { isProductV1 } from "../offer/filter"; +import { filterRobloxProduct } from "@bosonprotocol/roblox-sdk"; +type GetIsOfferRobloxGatedProps = { + offer: subgraph.OfferFieldsFragment; +}; + +type RobloxAssetId = string; +export function getIsOfferRobloxGated({ + offer +}: GetIsOfferRobloxGatedProps): RobloxAssetId | false { + if (offer.metadata && isProductV1(offer) && offer.condition) { + return filterRobloxProduct( + offer.metadata, + offer.condition, + offer.condition?.tokenAddress + ); + } + return false; +}