From 2944de9c29b99deadf3e8046742e4e7ad8111491 Mon Sep 17 00:00:00 2001 From: Jay Welsh Date: Tue, 16 Jul 2024 01:07:19 +0200 Subject: [PATCH 1/5] feat(PropyKeysHomeListingLikeZone): support likes on listings --- src/components/FullScreenGallery.tsx | 21 +- src/components/ListingGallery.tsx | 1 + src/components/PageContainer.tsx | 8 +- .../PropyKeysHomeListingLikeZone.tsx | 227 ++++++++++++++++++ src/containers/PageContainerContainer.tsx | 8 +- .../PropyKeysHomeListingLikeZoneContainer.tsx | 21 ++ src/pages/SingleListingPage.tsx | 9 +- src/services/api.ts | 11 + 8 files changed, 297 insertions(+), 9 deletions(-) create mode 100644 src/components/PropyKeysHomeListingLikeZone.tsx create mode 100644 src/containers/PropyKeysHomeListingLikeZoneContainer.tsx diff --git a/src/components/FullScreenGallery.tsx b/src/components/FullScreenGallery.tsx index 4bedd1c..cf67d3d 100644 --- a/src/components/FullScreenGallery.tsx +++ b/src/components/FullScreenGallery.tsx @@ -40,6 +40,10 @@ const useStyles = makeStyles((theme: Theme) => backgroundColor: 'black', height: 'calc(100% - 100px)' }, + primaryImage: { + maxHeight: '100%', + maxWidth: '100%', + }, primaryImageBackgroundContainer: { backgroundSize: 'cover', width: '110%', @@ -56,11 +60,21 @@ const useStyles = makeStyles((theme: Theme) => backgroundPosition: 'center', display: 'flex', flexDirection: 'column', - justifyContent: 'space-between', + justifyContent: 'center', alignItems: 'center', position: 'relative', height: '100%', }, + controlsOverlayContainer: { + top: 0, + position: 'absolute', + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + }, primaryImageControlsTopRow: { display: 'flex', width: '100%', @@ -192,7 +206,10 @@ const FullScreenGallery = (props: PropsFromRedux & IFullScreenGallery) => { // onClick={() => handleFullscreenImageSelection(selectedImageIndex)} >
0 ? { backgroundImage: `url(${fullScreenGalleryConfig?.images[fullScreenGalleryConfig?.selectedImageIndex]})` } : {}} /> -
0 ? { backgroundImage: `url(${fullScreenGalleryConfig?.images[fullScreenGalleryConfig?.selectedImageIndex]})` } : {}}> +
+ {`fullscreen 0 ? fullScreenGalleryConfig?.images[fullScreenGalleryConfig?.selectedImageIndex] : ""}/> +
+
{fullScreenGalleryConfig?.selectedImageIndex + 1} / {fullScreenGalleryConfig?.images?.length} diff --git a/src/components/ListingGallery.tsx b/src/components/ListingGallery.tsx index 5f3d5b8..ea490c5 100644 --- a/src/components/ListingGallery.tsx +++ b/src/components/ListingGallery.tsx @@ -81,6 +81,7 @@ const useStyles = makeStyles((theme: Theme) => overflowX: 'scroll', padding: 4, border: '1px solid #d5d5d5', + height: 110, }, previewImageRowInner: { display: 'flex', diff --git a/src/components/PageContainer.tsx b/src/components/PageContainer.tsx index 00aafa8..14b4e81 100644 --- a/src/components/PageContainer.tsx +++ b/src/components/PageContainer.tsx @@ -31,6 +31,8 @@ import useWindowSize from '../hooks/useWindowSize'; import { PropsFromRedux } from '../containers/PageContainerContainer'; +import { defaultFullScreenGalleryConfig } from '../state/reducers/fullScreenGalleryConfig'; + import { IS_GLOBAL_TOP_BANNER_ENABLED, GLOBAL_TOP_BANNER_HEIGHT, @@ -54,6 +56,7 @@ const PageContainer = (props: PropsFromRedux) => { const { isConsideredMobile, darkMode, + setFullScreenGalleryConfig, } = props; const { pathname } = useLocation(); @@ -78,8 +81,9 @@ const PageContainer = (props: PropsFromRedux) => { }, [windowSize.width, windowSize.height, setShowDesktopMenu]); useEffect(() => { - window.scrollTo(0, 0); - }, [pathname]); + window.scrollTo(0, 0); + setFullScreenGalleryConfig(defaultFullScreenGalleryConfig); + }, [pathname, setFullScreenGalleryConfig]); return ( diff --git a/src/components/PropyKeysHomeListingLikeZone.tsx b/src/components/PropyKeysHomeListingLikeZone.tsx new file mode 100644 index 0000000..0cc8f0c --- /dev/null +++ b/src/components/PropyKeysHomeListingLikeZone.tsx @@ -0,0 +1,227 @@ +import React, { useState, useEffect } from 'react'; + +import { useAccount, useSignMessage } from 'wagmi'; + +import { toast } from 'sonner'; + +import { Theme } from '@mui/material/styles'; + +import makeStyles from '@mui/styles/makeStyles'; +import createStyles from '@mui/styles/createStyles'; + +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; + +import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; +import FavoriteIcon from '@mui/icons-material/Favorite'; + +import Typography from '@mui/material/Typography'; + +import { Web3ModalButtonWagmi } from './Web3ModalButtonWagmi'; + +import { PropsFromRedux } from '../containers/PropyKeysHomeListingLikeZoneContainer'; + +import { + INonceResponse, +} from '../interfaces'; + +import { + SignerService, + PropyKeysListingService, +} from '../services/api'; + +import { + constructSignerMessage, +} from '../utils'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: 'auto', + display: 'flex', + alignItems: 'center', + }, + likeButton: { + border: '1px solid #efefef', + marginRight: theme.spacing(1), + }, + likeButtonCompact: { + border: '1px solid #efefef', + marginRight: theme.spacing(0.5), + }, + }), +); + +interface IPropyKeysHomeListingLikeZone { + title?: string, + propyKeysHomeListingId: string, + onSuccess?: () => void, + compact?: boolean, +} + +const PropyKeysHomeListingLikeZone = (props: PropsFromRedux & IPropyKeysHomeListingLikeZone) => { + + const [likeCount, setLikeCount] = useState(0); + const [isLiked, setIsLiked] = useState(false); + const [reloadIndex, setReloadIndex] = useState(0); + + const classes = useStyles(); + + const { address } = useAccount(); + + const { + // data, + // isError, + // isLoading, + // isSuccess, + signMessageAsync + } = useSignMessage(); + + const { + darkMode, + propyKeysHomeListingId, + onSuccess, + compact = false, + } = props; + + useEffect(() => { + let isMounted = true; + const getLikeStatus = async () => { + if(address) { + let [likeStatusResponse, likeCountResponse] = await Promise.all([ + PropyKeysListingService.getLikedByStatus(propyKeysHomeListingId, address), + PropyKeysListingService.getLikeCount(propyKeysHomeListingId), + ]); + if(isMounted) { + if(likeStatusResponse?.data?.like_status) { + setIsLiked(true); + } else { + setIsLiked(false); + } + console.log({likeCountResponse}) + if(!isNaN(likeCountResponse?.data?.like_count)) { + setLikeCount(likeCountResponse?.data?.like_count); + } + } + } else { + let [likeCountResponse] = await Promise.all([ + PropyKeysListingService.getLikeCount(propyKeysHomeListingId), + ]); + if(isMounted) { + if(!isNaN(likeCountResponse?.data?.like_count)) { + setLikeCount(likeCountResponse?.data?.like_count); + } + } + setIsLiked(false); + } + } + getLikeStatus(); + return () => { + isMounted = false; + } + }, [propyKeysHomeListingId, address, reloadIndex]) + + const signLike = async (type: 'add_like_propykeys_listing' | 'remove_like_propykeys_listing') => { + if(signMessageAsync && address) { + let signerAccount = address; + let nonceResponse : INonceResponse = await SignerService.getSignerNonce(address); + let { + data, + } = nonceResponse; + if(data && propyKeysHomeListingId) { + let { + nonce, + salt, + } = data; + let messageForSigning = constructSignerMessage( + signerAccount, + nonce, + salt, + type, + { + listing_id: propyKeysHomeListingId, + } + ); + if(messageForSigning) { + try { + let signedMessage = await signMessageAsync({message: messageForSigning}) + console.log({signedMessage, messageForSigning}) + if(typeof signedMessage === "string") { + let triggerSignedMessageActionResponse = await SignerService.validateSignedMessageAndPerformAction(messageForSigning, signedMessage, signerAccount); + console.log({triggerSignedMessageActionResponse}); + if(triggerSignedMessageActionResponse.status) { + if(onSuccess) { + onSuccess(); + } + setReloadIndex(reloadIndex + 1); + if(type === 'add_like_propykeys_listing') { + setIsLiked(true); + setLikeCount(likeCount + 1); + } else { + setIsLiked(false); + setLikeCount(likeCount - 1); + } + toast.success(`Like ${type === 'add_like_propykeys_listing' ? "added" : "removed"} successfully!`); + } else { + toast.error(`Unable to ${type === 'add_like_propykeys_listing' ? "add" : "remove"} like`); + } + } + } catch (e) { + //@ts-ignore + toast.error(e?.shortMessage ? e.shortMessage : "Failed to sign message"); + } + } else { + toast.error("Unable to generate message for signing"); + } + } else { + toast.error("Unable to fetch account nonce"); + } + } + } + + return ( +
+ {!address && + void) => ( + + { + e.stopPropagation(); + e.preventDefault(); + onClickFn(); + }} + > + {isLiked ? : } + + + )} variant="contained" color="secondary" darkMode={darkMode} overrideConnectText="Connect wallet" hideNetworkSwitch={true} /> + } + {address && + + { + e.stopPropagation(); + e.preventDefault(); + signLike(isLiked ? 'remove_like_propykeys_listing' : 'add_like_propykeys_listing') + }} + onTouchStart={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + {isLiked ? : } + + + } + + {likeCount} + {!compact && <>{(likeCount && (likeCount === 1)) ? ' Like' : ' Likes'}} + +
+ ) +} + +export default PropyKeysHomeListingLikeZone; \ No newline at end of file diff --git a/src/containers/PageContainerContainer.tsx b/src/containers/PageContainerContainer.tsx index 6845bd8..dde4bc5 100644 --- a/src/containers/PageContainerContainer.tsx +++ b/src/containers/PageContainerContainer.tsx @@ -2,6 +2,8 @@ import { connect, ConnectedProps } from 'react-redux'; import PageContainer from '../components/PageContainer'; +import { setFullScreenGalleryConfig } from '../state/actions'; + interface RootState { isConsideredMobile: boolean isConsideredMedium: boolean @@ -13,8 +15,12 @@ const mapStateToProps = (state: RootState) => ({ isConsideredMedium: state.isConsideredMedium, darkMode: state.darkMode, }) + +const mapDispatchToProps = { + setFullScreenGalleryConfig, +} -const connector = connect(mapStateToProps, {}) +const connector = connect(mapStateToProps, mapDispatchToProps) export type PropsFromRedux = ConnectedProps diff --git a/src/containers/PropyKeysHomeListingLikeZoneContainer.tsx b/src/containers/PropyKeysHomeListingLikeZoneContainer.tsx new file mode 100644 index 0000000..304fc36 --- /dev/null +++ b/src/containers/PropyKeysHomeListingLikeZoneContainer.tsx @@ -0,0 +1,21 @@ +import { connect, ConnectedProps } from 'react-redux'; + +import PropyKeysHomeListingLikeZone from '../components/PropyKeysHomeListingLikeZone'; + +interface RootState { + isConsideredMobile: boolean + isConsideredMedium: boolean + darkMode: boolean +} + +const mapStateToProps = (state: RootState) => ({ + isConsideredMobile: state.isConsideredMobile, + isConsideredMedium: state.isConsideredMedium, + darkMode: state.darkMode, +}) + +const connector = connect(mapStateToProps, {}) + +export type PropsFromRedux = ConnectedProps + +export default connector(PropyKeysHomeListingLikeZone) \ No newline at end of file diff --git a/src/pages/SingleListingPage.tsx b/src/pages/SingleListingPage.tsx index 3b7f075..6cacc21 100644 --- a/src/pages/SingleListingPage.tsx +++ b/src/pages/SingleListingPage.tsx @@ -19,6 +19,7 @@ import GenericPageContainer from '../containers/GenericPageContainer'; import GenericTitleContainer from '../containers/GenericTitleContainer'; import ListingGalleryContainer from '../containers/ListingGalleryContainer'; import PropyKeysHomeListingContactFormContainer from '../containers/PropyKeysHomeListingContactFormContainer'; +import PropyKeysHomeListingLikeZoneContainer from '../containers/PropyKeysHomeListingLikeZoneContainer'; import SingleTokenCardBaseline from '../components/SingleTokenCardBaseline'; @@ -252,11 +253,11 @@ const SingleListingPage = (props: ISingleListingPage) => { if(listingRecord) { return ( <> - {/* {tokenId && tokenAddress && network && + {listingRecord?.id &&
- setFetchIndex(fetchIndex + 1)} tokenId={tokenId} tokenAddress={tokenAddress} tokenNetwork={network} /> + setFetchIndex(fetchIndex + 1)} propyKeysHomeListingId={listingRecord?.id.toString()} />
- } */} + } {listingRecord?.description && <> @@ -282,7 +283,7 @@ const SingleListingPage = (props: ISingleListingPage) => { } */} {nftRecord && <> - +
{ return ApiService.get(`/listing/${network}`, `${contractNameOrCollectionNameOrAddress}?perPage=${perPage}&page=${page}${(additionalFilters && additionalFilters?.length > 0) ? `&${additionalFilters.map((queryEntry) => queryEntry["filter_type"] + "=" + queryEntry["value"]).join("&")}` : ''}`) }, + async getLikedByStatus( + propyKeysHomeListingId: string, + likerAddress: string, + ) : Promise { + return ApiService.get(`/listing/liked-by-status/${propyKeysHomeListingId}`, `${likerAddress}`) + }, + async getLikeCount( + propyKeysHomeListingId: string, + ) : Promise { + return ApiService.get(`/listing/like-count/${propyKeysHomeListingId}`) + }, } \ No newline at end of file From a3d317f2786fbacc9384c1d7fff0fc02adb3c3c5 Mon Sep 17 00:00:00 2001 From: Jay Welsh Date: Wed, 17 Jul 2024 16:13:59 +0200 Subject: [PATCH 2/5] fix(NetworkSelectDropdown): test adjusted network detection --- src/components/NetworkSelectDropdown.tsx | 11 ++++++----- src/components/SingleListingCardBaseline.tsx | 18 ++++++++---------- src/components/SingleTokenCardBaseline.tsx | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/NetworkSelectDropdown.tsx b/src/components/NetworkSelectDropdown.tsx index 11725f2..1786bb6 100644 --- a/src/components/NetworkSelectDropdown.tsx +++ b/src/components/NetworkSelectDropdown.tsx @@ -42,21 +42,22 @@ const NetworkSelectDropdown = (props: PropsFromRedux & INetworkSelectButton) => showCompactNetworkSwitch = false, } = props; - const { chain } = useAccount(); + const { chainId, address } = useAccount(); const { open } = useWeb3Modal(); useEffect(() => { - if(chain) { - console.log({chain}) - let networkName = NETWORK_ID_TO_NAME[chain.id]; + if(chainId) { + let networkName = NETWORK_ID_TO_NAME[chainId]; if(networkName) { setActiveNetwork(networkName as SupportedNetworks); } else { setActiveNetwork('unsupported'); } + } else if (!chainId && address) { + setActiveNetwork('unsupported'); } - }, [chain, setActiveNetwork, open]); + }, [chainId, address, setActiveNetwork, open]); const activeNetworkToImage = (network: SupportedNetworks, showCompactNetworkSwitch: boolean) => { let networkImage; diff --git a/src/components/SingleListingCardBaseline.tsx b/src/components/SingleListingCardBaseline.tsx index de09ecd..e6badc0 100644 --- a/src/components/SingleListingCardBaseline.tsx +++ b/src/components/SingleListingCardBaseline.tsx @@ -11,7 +11,7 @@ import CardActionArea from '@mui/material/CardActionArea'; import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; -import NFTLikeZoneContainer from '../containers/NFTLikeZoneContainer'; +import PropyKeysHomeListingLikeZoneContainer from '../containers/PropyKeysHomeListingLikeZoneContainer'; import PlaceholderImage from '../assets/img/placeholder.webp'; @@ -86,7 +86,7 @@ const useStyles = makeStyles((theme: Theme) => textFirstLine: { display: 'flex', justifyContent: 'space-between', - alignItems: 'end', + alignItems: 'center', }, disabledActionArea: { opacity: 0.5, @@ -151,9 +151,6 @@ const SingleTokenCardBaseline = (props: ISingleTokenCardBaselineProps) => { subtleDisableInteraction, tokenLink, tokenImage, - tokenId, - tokenContractAddress, - tokenNetwork, tokenTitle, listingRecord, } = props; @@ -246,15 +243,16 @@ const SingleTokenCardBaseline = (props: ISingleTokenCardBaselineProps) => { {tokenCollectionName} } */} - {tokenId && tokenContractAddress && tokenNetwork && !selectable && + + {tokenTitle} + + {listingRecord?.id && !selectable &&
- +
}
- - {tokenTitle} - + {/* {listingRecord?.price && {priceFormat(`${listingRecord?.price}`, 2, "$")} diff --git a/src/components/SingleTokenCardBaseline.tsx b/src/components/SingleTokenCardBaseline.tsx index 37ed023..ec6b8e9 100644 --- a/src/components/SingleTokenCardBaseline.tsx +++ b/src/components/SingleTokenCardBaseline.tsx @@ -74,7 +74,7 @@ const useStyles = makeStyles((theme: Theme) => textFirstLine: { display: 'flex', justifyContent: 'space-between', - alignItems: 'end', + alignItems: 'center', }, disabledActionArea: { opacity: 0.5, From d6db014cce308aa24a8679dc8306f3919aac0b9c Mon Sep 17 00:00:00 2001 From: Jay Welsh Date: Wed, 17 Jul 2024 16:43:05 +0200 Subject: [PATCH 3/5] feat(BridgeFinalizeWithdrawalForm): adds helper context to estimated time to finalization --- .../BridgeFinalizeWithdrawalForm.tsx | 20 +++++++++++++++++-- src/hooks/usePrepareFinalizeWithdrawal.ts | 13 ++++++++---- src/interfaces/index.ts | 1 + 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/components/BridgeFinalizeWithdrawalForm.tsx b/src/components/BridgeFinalizeWithdrawalForm.tsx index e854517..c4a9123 100644 --- a/src/components/BridgeFinalizeWithdrawalForm.tsx +++ b/src/components/BridgeFinalizeWithdrawalForm.tsx @@ -4,6 +4,8 @@ import { animated, useSpring } from '@react-spring/web'; import { utils } from "ethers"; +import dayjs from 'dayjs'; + import BigNumber from 'bignumber.js'; import { toast } from 'sonner'; @@ -32,10 +34,16 @@ import BaseLogo from '../assets/img/base-logo-transparent-bg.png'; import FloatingActionButton from './FloatingActionButton'; -import { SupportedNetworks } from '../interfaces'; +import { + SupportedNetworks, + NetworkName, +} from '../interfaces'; + +import LinkWrapper from './LinkWrapper'; import { priceFormat, + getEtherscanLinkByNetworkName, } from '../utils'; import { @@ -44,6 +52,7 @@ import { OPTIMISM_PORTAL_ADDRESS, BASE_BRIDGE_L1_NETWORK, BASE_BRIDGE_L2_NETWORK, + PROPY_LIGHT_BLUE, } from '../utils/constants'; import { @@ -381,7 +390,14 @@ const BridgeFinalizeWithdrawalForm = (props: PropsFromRedux & IBridgeFinalizeWit showLoadingIcon={isAwaitingWalletInteraction || isAwaitingFinalizeTx || isAwaitingValidPreparation || isFinalizationPeriodNotElapsed} text={getBridgeFinalizedWithdrawalButtonText(isAwaitingWalletInteraction, isAwaitingFinalizeTx, isAwaitingValidPreparation, isWithdrawalAlreadyFinalized, showSuccessMessage, isFinalizationPeriodNotElapsed)} /> - {isAwaitingValidPreparation && Waiting for your withdrawal proof to make it through the challenge period, this will take around ~ 1 week from the time of submitting your withdrawal proof.} + {isAwaitingValidPreparation && + <> + Waiting for your withdrawal proof to make it through the challenge period, this will take around ~ 1 week from the time of submitting your withdrawal proof. + {transactionData?.withdrawal_proven_event?.evm_transaction?.block_timestamp && + Detected withdrawal proof at {dayjs.unix(Number(transactionData?.withdrawal_proven_event?.evm_transaction?.block_timestamp)).format('hh:mm A MMM-D-YYYY')}, therefore withdrawal finalization should be possible at approximately {dayjs.unix(Number(transactionData?.withdrawal_proven_event?.evm_transaction?.block_timestamp)).add(7, 'day').format('hh:mm A MMM-D-YYYY')}. + } + + } {(isWithdrawalAlreadyFinalized || showSuccessMessage) && Withdrawal finalized! Tokens have been withdrawn to L1.}
} diff --git a/src/hooks/usePrepareFinalizeWithdrawal.ts b/src/hooks/usePrepareFinalizeWithdrawal.ts index 2eb5c0e..1704fa8 100644 --- a/src/hooks/usePrepareFinalizeWithdrawal.ts +++ b/src/hooks/usePrepareFinalizeWithdrawal.ts @@ -24,7 +24,7 @@ export function usePrepareFinalizeWithdrawal( const shouldPrepare = withdrawalForTx; - const { data, error: useSimulateContractError, refetch } = useSimulateContract({ + const { data, error: useSimulateContractError, isFetching, isRefetching, refetch } = useSimulateContract({ address: shouldPrepare ? l1OptimismPortalProxyAddress : undefined, abi: OptimismPortal, functionName: 'finalizeWithdrawalTransaction', @@ -75,10 +75,15 @@ export function usePrepareFinalizeWithdrawal( } } else { alreadyFinalizedHandler(false); - prepErrorHandler(false); - finalizationPeriodHasNotElapsed(false); + if(!isFetching && !isRefetching && shouldPrepare) { + finalizationPeriodHasNotElapsed(false); + prepErrorHandler(false); + } else { + finalizationPeriodHasNotElapsed(true); + prepErrorHandler(true); + } } - }, [useSimulateContractError, prepErrorHandler, alreadyFinalizedHandler, finalizationPeriodHasNotElapsed]) + }, [useSimulateContractError, shouldPrepare, l1OptimismPortalProxyAddress, isFetching, isRefetching, prepErrorHandler, alreadyFinalizedHandler, finalizationPeriodHasNotElapsed]) useEffect(() => { if (withdrawalReceipt) { diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 34901be..10f9db9 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -300,6 +300,7 @@ export interface IBaseWithdrawalProvenEvent { event_fingerprint: string; created_at: Date; updated_at: Date; + evm_transaction?: IEVMTransactionRecord; } export interface IBaseWithdrawalFinalizedEvent { From 503493cd604585b45f4a3c1dff3fa3b34dcb74ef Mon Sep 17 00:00:00 2001 From: Jay Welsh Date: Fri, 19 Jul 2024 11:14:29 +0200 Subject: [PATCH 4/5] feat(constructSignerMessage): include chain ID --- src/components/NFTLikeZone.tsx | 17 ++++++++++++----- src/components/PropyKeysHomeListingLikeZone.tsx | 17 ++++++++++++----- src/components/ReserveAnAddressHomeBanner.tsx | 2 +- src/components/SignalInterest.tsx | 5 +++-- src/utils/index.ts | 2 ++ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/components/NFTLikeZone.tsx b/src/components/NFTLikeZone.tsx index e09f1ed..7a4dbcb 100644 --- a/src/components/NFTLikeZone.tsx +++ b/src/components/NFTLikeZone.tsx @@ -69,7 +69,7 @@ const NFTLikeZone = (props: PropsFromRedux & INFTLikeZone) => { const classes = useStyles(); - const { address } = useAccount(); + const { address, chainId } = useAccount(); const { // data, @@ -125,7 +125,7 @@ const NFTLikeZone = (props: PropsFromRedux & INFTLikeZone) => { }, [tokenNetwork, tokenAddress, tokenId, address, reloadIndex]) const signLike = async (type: 'add_like_nft' | 'remove_like_nft') => { - if(signMessageAsync && address) { + if(signMessageAsync && address && chainId) { let signerAccount = address; let nonceResponse : INonceResponse = await SignerService.getSignerNonce(address); let { @@ -140,6 +140,7 @@ const NFTLikeZone = (props: PropsFromRedux & INFTLikeZone) => { signerAccount, nonce, salt, + chainId, type, { token_address: tokenAddress, @@ -171,9 +172,15 @@ const NFTLikeZone = (props: PropsFromRedux & INFTLikeZone) => { toast.error(`Unable to ${type === 'add_like_nft' ? "add" : "remove"} like`); } } - } catch (e) { - //@ts-ignore - toast.error(e?.shortMessage ? e.shortMessage : "Failed to sign message"); + } catch (e: any) { + let errorMessage; + if(e?.shortMessage) { + errorMessage = e?.shortMessage; + } + if(e?.data?.message) { + errorMessage = e?.data?.message; + } + toast.error(errorMessage ? errorMessage : "Failed to sign message"); } } else { toast.error("Unable to generate message for signing"); diff --git a/src/components/PropyKeysHomeListingLikeZone.tsx b/src/components/PropyKeysHomeListingLikeZone.tsx index 0cc8f0c..2d1fac1 100644 --- a/src/components/PropyKeysHomeListingLikeZone.tsx +++ b/src/components/PropyKeysHomeListingLikeZone.tsx @@ -67,7 +67,7 @@ const PropyKeysHomeListingLikeZone = (props: PropsFromRedux & IPropyKeysHomeList const classes = useStyles(); - const { address } = useAccount(); + const { address, chainId } = useAccount(); const { // data, @@ -122,7 +122,7 @@ const PropyKeysHomeListingLikeZone = (props: PropsFromRedux & IPropyKeysHomeList }, [propyKeysHomeListingId, address, reloadIndex]) const signLike = async (type: 'add_like_propykeys_listing' | 'remove_like_propykeys_listing') => { - if(signMessageAsync && address) { + if(signMessageAsync && address && chainId) { let signerAccount = address; let nonceResponse : INonceResponse = await SignerService.getSignerNonce(address); let { @@ -137,6 +137,7 @@ const PropyKeysHomeListingLikeZone = (props: PropsFromRedux & IPropyKeysHomeList signerAccount, nonce, salt, + chainId, type, { listing_id: propyKeysHomeListingId, @@ -166,9 +167,15 @@ const PropyKeysHomeListingLikeZone = (props: PropsFromRedux & IPropyKeysHomeList toast.error(`Unable to ${type === 'add_like_propykeys_listing' ? "add" : "remove"} like`); } } - } catch (e) { - //@ts-ignore - toast.error(e?.shortMessage ? e.shortMessage : "Failed to sign message"); + } catch (e: any) { + let errorMessage; + if(e?.shortMessage) { + errorMessage = e?.shortMessage; + } + if(e?.data?.message) { + errorMessage = e?.data?.message; + } + toast.error(errorMessage ? errorMessage : "Failed to sign message"); } } else { toast.error("Unable to generate message for signing"); diff --git a/src/components/ReserveAnAddressHomeBanner.tsx b/src/components/ReserveAnAddressHomeBanner.tsx index 4fe37af..90503a3 100644 --- a/src/components/ReserveAnAddressHomeBanner.tsx +++ b/src/components/ReserveAnAddressHomeBanner.tsx @@ -182,7 +182,7 @@ const ReserveAnAddressHomeBanner = (props: PropsFromRedux) => { Tier 3 - RWA + RWA NFT Speed up the buying & selling process, potentially use NFT as onchain collateral diff --git a/src/components/SignalInterest.tsx b/src/components/SignalInterest.tsx index fcb6efe..6e845a0 100644 --- a/src/components/SignalInterest.tsx +++ b/src/components/SignalInterest.tsx @@ -84,7 +84,7 @@ const SignalInterest = (props: PropsFromRedux & ISignalInterest) => { const classes = useStyles(); - const { address } = useAccount(); + const { address, chainId } = useAccount(); const { // data, @@ -106,7 +106,7 @@ const SignalInterest = (props: PropsFromRedux & ISignalInterest) => { } = props; const signOfferMessage = async (proAmount: string) => { - if(signMessageAsync && address) { + if(signMessageAsync && address && chainId) { let signerAccount = address; let nonceResponse : INonceResponse = await SignerService.getSignerNonce(address); let { @@ -121,6 +121,7 @@ const SignalInterest = (props: PropsFromRedux & ISignalInterest) => { signerAccount, nonce, salt, + chainId, "make_offchain_offer", { token_address: tokenAddress, diff --git a/src/utils/index.ts b/src/utils/index.ts index f4ac44e..3b9d444 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -275,6 +275,7 @@ export const constructSignerMessage = ( signerAccount: string, nonce: number, salt: string, + chainId: number, actionType: string, metadata: any ) => { @@ -284,6 +285,7 @@ export const constructSignerMessage = ( action: actionType, metadata, timestamp: Math.floor(new Date().getTime() / 1000), + chain_id: chainId, nonce, salt, }, null, 4); From 0d76148ba817c653e1b747e43b642193998ce486 Mon Sep 17 00:00:00 2001 From: Jay Welsh Date: Fri, 19 Jul 2024 12:51:37 +0200 Subject: [PATCH 5/5] feat(StakePortal): handle large token amounts --- src/components/StakePortal.tsx | 71 ++++++++++++++++++++++++++++------ src/interfaces/index.ts | 8 ++++ src/services/api.ts | 6 ++- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/components/StakePortal.tsx b/src/components/StakePortal.tsx index fb524de..c2e4eaf 100644 --- a/src/components/StakePortal.tsx +++ b/src/components/StakePortal.tsx @@ -60,6 +60,7 @@ import { import { IAssetRecord, IBalanceRecord, + IPaginationNoOptional, } from '../interfaces'; import ERC20ABI from '../abi/ERC20ABI.json'; @@ -155,7 +156,13 @@ const useStyles = makeStyles((theme: Theme) => }, loadingZone: { opacity: 0.5, - } + }, + loadMoreButtonContainer: { + marginTop: theme.spacing(4), + width: '100%', + display: 'flex', + justifyContent: 'center', + }, }), ); @@ -316,7 +323,11 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { const [nftAssets, setNftAssets] = useState({}); const [propyKeysNFT, setPropyKeysNFT] = useState(false); + const [propyKeysNFTPaginationData, setPropyKeysNFTPaginationData] = useState(false); + const [maxStakedLoadCount, setMaxStakedLoadCount] = useState(100); + const [maxUnstakedLoadCount, setMaxUnstakedLoadCount] = useState(100); const [ogKeysNFT, setOGKeysNFT] = useState(false); + const [ogKeysNFTPaginationData, setOGKeysNFTPaginationData] = useState(); const [selectedTokenIds, setSelectedPropyKeyTokenIds] = useState([]); const [selectedTokenAddress, setSelectedTokenAddress] = useState(false); const [isAwaitingUnstakeTx, setIsAwaitingUnstakeTx] = useState(false); @@ -383,13 +394,13 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { let results; if (mode === "enter") { results = await Promise.all([ - AccountBalanceService.getAccountBalancesByAssetIncludeStakingStatus(address, BASE_PROPYKEYS_STAKING_NFT), - AccountBalanceService.getAccountBalancesByAssetIncludeStakingStatus(address, BASE_OG_STAKING_NFT), + AccountBalanceService.getAccountBalancesByAssetIncludeStakingStatus(address, BASE_PROPYKEYS_STAKING_NFT, maxUnstakedLoadCount), + AccountBalanceService.getAccountBalancesByAssetIncludeStakingStatus(address, BASE_OG_STAKING_NFT, maxUnstakedLoadCount), ]) } else if(mode === "leave") { results = await Promise.all([ - AccountBalanceService.getAccountBalancesByAssetOnlyStaked(address, BASE_PROPYKEYS_STAKING_NFT, version === 1 ? BASE_PROPYKEYS_STAKING_CONTRACT_V1 : BASE_PROPYKEYS_STAKING_CONTRACT_V2), - AccountBalanceService.getAccountBalancesByAssetOnlyStaked(address, BASE_OG_STAKING_NFT, version === 1 ? BASE_PROPYKEYS_STAKING_CONTRACT_V1 : BASE_PROPYKEYS_STAKING_CONTRACT_V2), + AccountBalanceService.getAccountBalancesByAssetOnlyStaked(address, BASE_PROPYKEYS_STAKING_NFT, version === 1 ? BASE_PROPYKEYS_STAKING_CONTRACT_V1 : BASE_PROPYKEYS_STAKING_CONTRACT_V2, maxStakedLoadCount), + AccountBalanceService.getAccountBalancesByAssetOnlyStaked(address, BASE_OG_STAKING_NFT, version === 1 ? BASE_PROPYKEYS_STAKING_CONTRACT_V1 : BASE_PROPYKEYS_STAKING_CONTRACT_V2, maxStakedLoadCount), ]) } if(isMounted) { @@ -415,7 +426,13 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { if(isMounted) { setNftAssets(assetResults); setPropyKeysNFT(propykeysRenderResults); + if(results?.[0]?.data?.metadata?.pagination) { + setPropyKeysNFTPaginationData(results?.[0]?.data?.metadata?.pagination); + } setOGKeysNFT(ogRenderResults); + if(results?.[1]?.data?.metadata?.pagination) { + setOGKeysNFTPaginationData(results?.[1]?.data?.metadata?.pagination); + } setIsLoading(false); } } @@ -425,7 +442,7 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { return () => { isMounted = false; } - }, [address, mode, triggerUpdateIndex, version]) + }, [address, mode, triggerUpdateIndex, version, maxUnstakedLoadCount, maxStakedLoadCount]) const handleBalanceRecordSelected = (balanceRecord: IBalanceRecord) => { let useCurrentSelection = balanceRecord.asset_address === selectedTokenAddress ? [...selectedTokenIds] : []; @@ -495,7 +512,7 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { } }, [clientCountry]) - console.log({propyKeysNFT, nftAssets, selectedTokenIds, clientCountry}) + console.log({clientCountry}) const { data: dataPropyKeysIsStakingContractApproved, @@ -1092,14 +1109,14 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { {!(isLoading || isLoadingGeoLocation) && ((ogKeysNFT && ogKeysNFT.length > 0) || (propyKeysNFT && propyKeysNFT.length > 0)) && - {(propyKeysNFT && propyKeysNFT.length > 0) && + {(propyKeysNFTPaginationData && propyKeysNFTPaginationData?.total > 0) && <> - {`Found ${propyKeysNFT && propyKeysNFT.length > 0 ? propyKeysNFT.length : 0} PropyKeys`}
+ {`Found ${propyKeysNFTPaginationData && propyKeysNFTPaginationData?.total > 0 ? propyKeysNFTPaginationData.total : 0} PropyKeys`}
} - {(ogKeysNFT && ogKeysNFT.length > 0) && + {(ogKeysNFTPaginationData && ogKeysNFTPaginationData?.total > 0) && <> - {`Found ${ogKeysNFT && ogKeysNFT.length > 0 ? ogKeysNFT.length : 0} PropyOG tokens`}
+ {`Found ${ogKeysNFTPaginationData && ogKeysNFTPaginationData?.total > 0 ? ogKeysNFTPaginationData.total : 0} PropyOG tokens`}
} Please click on the token(s) that you would like to {mode === "enter" ? "stake" : "unstake"} @@ -1132,6 +1149,38 @@ const StakeEnter = (props: PropsFromRedux & IStakeEnter) => { } } + { + (mode === "enter") && ((ogKeysNFT && ogKeysNFT?.length > 0) || (propyKeysNFT && propyKeysNFT?.length > 0)) && +
+ setMaxUnstakedLoadCount(maxUnstakedLoadCount + 100)} + showLoadingIcon={isLoading || isLoadingGeoLocation} + text={( + (propyKeysNFTPaginationData && (propyKeysNFTPaginationData?.total === propyKeysNFTPaginationData?.count)) + && (ogKeysNFTPaginationData && (ogKeysNFTPaginationData?.total === ogKeysNFTPaginationData?.count)) + ) ? "All Records Loaded" : "Load More"} + /> +
+ } + { + (mode === "leave") && ((ogKeysNFT && ogKeysNFT?.length > 0) || (propyKeysNFT && propyKeysNFT?.length > 0)) && +
+ setMaxStakedLoadCount(maxStakedLoadCount + 100)} + showLoadingIcon={isLoading || isLoadingGeoLocation} + text={( + (propyKeysNFTPaginationData && (propyKeysNFTPaginationData?.total === propyKeysNFTPaginationData?.count)) + && (ogKeysNFTPaginationData && (ogKeysNFTPaginationData?.total === ogKeysNFTPaginationData?.count)) + ) ? "All Records Loaded" : "Load More"} + /> +
+ } {!(isLoading || isLoadingGeoLocation) && (ogKeysNFT && ogKeysNFT.length === 0) && (propyKeysNFT && propyKeysNFT.length === 0) && diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 10f9db9..830a222 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -161,6 +161,14 @@ export interface IPagination { totalPages?: number } +export interface IPaginationNoOptional { + total: number + count: number + perPage: number + currentPage: number + totalPages: number +} + export interface IMixedBalancesResult { [key: string]: { [key: string]: { diff --git a/src/services/api.ts b/src/services/api.ts index a94eac4..c637c37 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -244,15 +244,17 @@ export const AccountBalanceService = { async getAccountBalancesByAssetIncludeStakingStatus( account: `0x${string}`, assetAddress: `0x${string}`, + maxRecords: number, ) : Promise { - return ApiService.get(`/balances`, `${account}/${assetAddress}?includeStakingStatus=true`) + return ApiService.get(`/balances`, `${account}/${assetAddress}?includeStakingStatus=true&perPage=${maxRecords}`) }, async getAccountBalancesByAssetOnlyStaked( account: `0x${string}`, assetAddress: `0x${string}`, stakingContractAddress: `0x${string}`, + maxRecords: number, ) : Promise { - return ApiService.get(`/balances`, `${account}/${assetAddress}?includeStakingStatus=true&includeLastStakerRecords=true&onlyLastStakerRecords=true&stakingContractAddress=${stakingContractAddress}`) + return ApiService.get(`/balances`, `${account}/${assetAddress}?includeStakingStatus=true&includeLastStakerRecords=true&onlyLastStakerRecords=true&stakingContractAddress=${stakingContractAddress}&perPage=${maxRecords}`) }, }