From 774bc2839c102b33f334a77f7600b78e9fca26b0 Mon Sep 17 00:00:00 2001 From: Juraj Piar Date: Tue, 31 Aug 2021 17:20:20 +0100 Subject: [PATCH] RESOLVE CONFLICTS AND SQUASH ME! When done with conflicts, run: git rebase -i HEAD~ to reword message to: chore(vBump): hotfix v1.4.1 --- package-lock.json | 4 + package.json | 4 + src/components/molecules/ExpirationDate.tsx | 65 ++++ .../notifier/buy/CheckoutPayment.tsx | 154 ++++++++ .../notifier/buy/CheckoutStepper.tsx | 95 +++++ .../buy/NotifierOfferCheckoutPage.tsx | 185 ++++++++++ .../notifier/myoffers/mapActiveContracts.tsx | 124 +++++++ .../mypurchase/NotifierMyPurchasePage.tsx | 335 ++++++++++++++++++ 8 files changed, 966 insertions(+) create mode 100644 src/components/molecules/ExpirationDate.tsx create mode 100644 src/components/organisms/notifier/buy/CheckoutPayment.tsx create mode 100644 src/components/organisms/notifier/buy/CheckoutStepper.tsx create mode 100644 src/components/pages/notifier/buy/NotifierOfferCheckoutPage.tsx create mode 100644 src/components/pages/notifier/myoffers/mapActiveContracts.tsx create mode 100644 src/components/pages/notifier/mypurchase/NotifierMyPurchasePage.tsx diff --git a/package-lock.json b/package-lock.json index 7a29224c3..40fc1f6b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,10 @@ { "name": "rif-marketplace-ui", +<<<<<<< HEAD "version": "1.3.5", +======= + "version": "1.4.1", +>>>>>>> c95a872 (chore(vBump): hotfix v1.4.1) "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5b463b49f..0aaf54a57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "rif-marketplace-ui", +<<<<<<< HEAD "version": "1.3.5", +======= + "version": "1.4.1", +>>>>>>> c95a872 (chore(vBump): hotfix v1.4.1) "description": "RIF Marketplace provides a digital catalogue with a wide range of decentralised services.", "keywords": [ "RIF", diff --git a/src/components/molecules/ExpirationDate.tsx b/src/components/molecules/ExpirationDate.tsx new file mode 100644 index 000000000..1a4f5a200 --- /dev/null +++ b/src/components/molecules/ExpirationDate.tsx @@ -0,0 +1,65 @@ +import Typography, { TypographyProps } from '@material-ui/core/Typography' +import React, { FC } from 'react' +import { getShortDateString } from 'utils/dateUtils' +import { + makeStyles, Theme, +} from '@material-ui/core/styles' +import Grid from '@material-ui/core/Grid' +import WatchLaterIcon from '@material-ui/icons/WatchLater' + +const useStyles = makeStyles(({ palette }: Theme) => ({ + normal: { + color: palette.text.secondary, + }, + warning: { + color: palette.warning.main, + }, + blocked: { + color: palette.error.main, + }, + disabled: { + color: palette.text.disabled, + }, +})) + +export const EXPIRATION_TYPES = { + normal: 'normal', + warning: 'warning', + blocked: 'blocked', + disabled: 'disabled', +} as const + +export type SubscriptionExpirationType = keyof typeof EXPIRATION_TYPES + +type Props = { + className?: string + date: Date + type: SubscriptionExpirationType +} & TypographyProps + +const ExpirationDate: FC = ({ + className = '', date, type, ...typoProps +}) => { + const classes = useStyles() + + return ( + + + + {getShortDateString(date)} + + + + {type === 'warning' && } + + + ) +} + +export default ExpirationDate diff --git a/src/components/organisms/notifier/buy/CheckoutPayment.tsx b/src/components/organisms/notifier/buy/CheckoutPayment.tsx new file mode 100644 index 000000000..f677c42ad --- /dev/null +++ b/src/components/organisms/notifier/buy/CheckoutPayment.tsx @@ -0,0 +1,154 @@ +import RoundBtn from 'components/atoms/RoundBtn' +import React, { + FC, useState, useEffect, useContext, +} from 'react' +import Typography from '@material-ui/core/Typography' +import GridRow from 'components/atoms/GridRow' +import Grid, { GridProps } from '@material-ui/core/Grid' +import { TokenXR } from 'models/Market' +import ExpirationDate from 'components/molecules/ExpirationDate' +import PriceSummary from 'components/molecules/PriceSummary' +import { makeStyles, Theme } from '@material-ui/core/styles' +import Box from '@material-ui/core/Box' +import RoundedCard from 'components/atoms/RoundedCard' +import Big from 'big.js' +import { Web3Store } from '@rsksmart/rif-ui' +import { getBalance } from 'contracts/utils/accountBalance' +import NotEnoughFunds from 'components/atoms/NotEnoughFunds' +import Web3 from 'web3' +import useErrorReporter from 'hooks/useErrorReporter' +import TermsAndConditions from 'components/organisms/TermsAndConditions' +import { TERMS_CONDITIONS_BUY } from 'constants/notifier/strings' + +type Props = { + onBuy: () => void + fiatDisplayName: string + expirationDate: Date + tokenXR: TokenXR + cryptoPrice: Big +} + +const useStyles = makeStyles((theme: Theme) => ({ + priceSummaryCard: { + padding: theme.spacing(1.5, 3), + marginBottom: theme.spacing(1), + }, + expirationDate: { + justifyContent: 'center', + }, +})) + +const CheckoutPayment: FC = ({ + onBuy, fiatDisplayName, expirationDate, tokenXR, cryptoPrice, +}) => { + const classes = useStyles() + const reportError = useErrorReporter() + + const { state: { web3, account } } = useContext(Web3Store) + const [hasEnoughFunds, setHasEnoughFunds] = useState(false) + const [isLoadingBalance, setIsLoadingBalance] = useState(false) + const [termsChecked, setTermsChecked] = useState(false) + + const { + symbol: selectedTokenSymbol, + } = tokenXR + + useEffect(() => { + const calculateBalance = async (): Promise => { + try { + setIsLoadingBalance(true) + const balance = await getBalance( + web3 as Web3, account as string, selectedTokenSymbol, + ) + setHasEnoughFunds(Big(balance).gte(cryptoPrice)) + } catch (error: any) { + reportError({ + error, + id: 'get-balance', + text: 'Could not read account balance', + }) + } finally { + setIsLoadingBalance(false) + } + } + calculateBalance() + }, [account, web3, selectedTokenSymbol, cryptoPrice, reportError]) + + const handleTermsChange = (): void => setTermsChecked((prev) => !prev) + + const colProps: GridProps = { + container: true, + item: true, + direction: 'column', + md: 6, + sm: 12, + alignItems: 'center', + justify: 'center', + } + const isBuyDisabled = !hasEnoughFunds || isLoadingBalance || !termsChecked + + return ( + <> + + + {`To acquire this notification service you have to select + the currency to get the final price.`} + + + You can add more events to this contract before or after renew. + + + + + + + + + Expiration date + + + + + { + !isLoadingBalance && !hasEnoughFunds && ( + + ) + } + + + + + + Buy + + + + {`Your wallet will open and you will be asked + to confirm the transaction for buying the notification plan.`} + + + + + ) +} + +export default CheckoutPayment diff --git a/src/components/organisms/notifier/buy/CheckoutStepper.tsx b/src/components/organisms/notifier/buy/CheckoutStepper.tsx new file mode 100644 index 000000000..b9a9a88d1 --- /dev/null +++ b/src/components/organisms/notifier/buy/CheckoutStepper.tsx @@ -0,0 +1,95 @@ +import React, { FC, useContext, useState } from 'react' +import Stepper from '@material-ui/core/Stepper' +import Step from '@material-ui/core/Step' +import StepLabel from '@material-ui/core/StepLabel' +import StepContent from '@material-ui/core/StepContent' +import { Button } from '@rsksmart/rif-ui' +import MarketContext from 'context/Market' +import { OffersOrder } from 'context/Services/notifier/offers/interfaces' +import { NotifierEventItem } from 'models/marketItems/NotifierEventItem' +import CheckoutPayment from './CheckoutPayment' +import EventsRegistrar from './EventsRegistrar' + +type Props = { + order: OffersOrder + onEventItemAdded: (eventitem: NotifierEventItem) => void + onEventItemRemoved: (eventitem: NotifierEventItem) => void + onBuy: () => void + eventsAdded: NotifierEventItem[] +} + +const getExpirationDate = (daysLeft: number): Date => { + const expirationDate = new Date() + expirationDate.setUTCHours(0, 0, 0, 0) + expirationDate.setUTCDate(expirationDate.getDate() + daysLeft) + return new Date(expirationDate) +} + +const CheckoutStepper: FC = ( + { + onBuy, order, onEventItemAdded, onEventItemRemoved, eventsAdded, + }, +) => { + const [activeStep, setActiveStep] = useState(0) + const { + state: { + exchangeRates: { + crypto: cryptoXRs, + currentFiat: { displayName: fiatDisplayName }, + }, + }, + } = useContext(MarketContext) + + const { + item: { + token: { symbol: tokenSymbol }, + value: tokenValue, + daysLeft, + }, + } = order + + const handleNext = (): void => setActiveStep(1) + const handleBack = (): void => setActiveStep(0) + + const expirationDate = getExpirationDate(daysLeft) + const tokenXR = cryptoXRs[tokenSymbol] + + return ( + + + Notification events + + + + + + Payment + + + + + + + ) +} + +export default CheckoutStepper diff --git a/src/components/pages/notifier/buy/NotifierOfferCheckoutPage.tsx b/src/components/pages/notifier/buy/NotifierOfferCheckoutPage.tsx new file mode 100644 index 000000000..038326423 --- /dev/null +++ b/src/components/pages/notifier/buy/NotifierOfferCheckoutPage.tsx @@ -0,0 +1,185 @@ +import React, { + FC, useContext, useState, +} from 'react' +import Grid from '@material-ui/core/Grid' +import Typography from '@material-ui/core/Typography' +import { + NotifierOffersContextProps as ContextProps, + NotifierOffersContext, +} from 'context/Services/notifier/offers' +import CenteredPageTemplate from 'components/templates/CenteredPageTemplate' +import NotifierPlanDescription from 'components/organisms/notifier/NotifierPlanDescription' +import MarketContext, { MarketContextProps } from 'context/Market' +import CheckoutStepper from 'components/organisms/notifier/buy/CheckoutStepper' +import { NotifierEventItem } from 'models/marketItems/NotifierEventItem' +import NotifierContract from 'contracts/notifier/Notifier' +import Web3 from 'web3' +import { Web3Store } from '@rsksmart/rif-ui' +import useErrorReporter from 'hooks/useErrorReporter' +import ProgressOverlay from 'components/templates/ProgressOverlay' +import RoundBtn from 'components/atoms/RoundBtn' +import ROUTES from 'routes' +import { useHistory } from 'react-router-dom' +import { ConfirmationsContext } from 'context/Confirmations' +import { convertToWeiString } from 'utils/parsers' +import WithLoginCard from 'components/hoc/WithLoginCard' +import { getOrCreateSubscription } from 'api/rif-notifier-service/subscriptionUtils' + +const NotifierOfferCheckoutPage: FC = () => { + const { + state: { account, web3 }, + } = useContext(Web3Store) + const { + state: { + exchangeRates: { + currentFiat: { + displayName: currentFiat, + }, + crypto, + }, + }, + } = useContext(MarketContext) + const { dispatch: confirmationsDispatch } = useContext(ConfirmationsContext) + + const { + state: { + order, + }, + } = useContext(NotifierOffersContext) + const reportError = useErrorReporter() + const history = useHistory() + + const [isProcessingTx, setIsProcessingTx] = useState(false) + const [txOperationDone, setTxOperationDone] = useState(false) + const [eventsAdded, setEventsAdded] = useState([]) + + if (!order?.item) { + history.push(ROUTES.NOTIFIER.BUY.BASE) + return null + } + + const handleEventRemoved = ( + { id: notifierEventId }: NotifierEventItem, + ): void => { + const filteredEvents = eventsAdded.filter( + ({ id }) => id !== notifierEventId, + ) + setEventsAdded(filteredEvents) + } + + const handleEventItemAdded = ( + eventItem: NotifierEventItem, + ): void => { + setEventsAdded([...eventsAdded, eventItem]) + } + + const handleOnBuy = async (): Promise => { + if (!account) return // wrapped with withLoginCard + try { + setIsProcessingTx(true) + + const { item } = order + const { + provider: providerAddress, + value: amount, + token, + planId, + url, + } = item + + const { symbol, tokenAddress } = token + + const { + hash: subscriptionHash, signature, + } = await getOrCreateSubscription({ + planId, symbol, url, value: amount, + }, eventsAdded, account, reportError) + + if (!subscriptionHash) return + + const purchaseReceipt = await NotifierContract.getInstance(web3 as Web3) + .createSubscription( + { + subscriptionHash, + providerAddress, + signature, + amount, + tokenAddress, + }, + { + from: account, + value: convertToWeiString(amount), + }, + ) + + if (purchaseReceipt) { + setTxOperationDone(true) + confirmationsDispatch({ + type: 'NEW_REQUEST', + payload: { + contractAction: 'NOTIFIER_CREATE_SUBSCRIPTION', + txHash: purchaseReceipt.transactionHash, + }, + }) + } + } catch (error: any) { + const { customMessage } = error + reportError({ + error, + id: 'contract-notifier', + text: customMessage || 'Could not complete the order', + }) + } finally { + setIsProcessingTx(false) + } + } + + return ( + + + + Notification plan selected + + + + + history.push(ROUTES.NOTIFIER.MYPURCHASES.BASE) + } + > + View my purchases + , + history.push(ROUTES.NOTIFIER.BUY.BASE) + } + > + View offers listing + , + ]} + + /> + + ) +} + +export default WithLoginCard({ + WrappedComponent: NotifierOfferCheckoutPage, + title: 'Please, connect your wallet.', + contentText: 'Connect your wallet in order to proceed to the checkout.', +}) diff --git a/src/components/pages/notifier/myoffers/mapActiveContracts.tsx b/src/components/pages/notifier/myoffers/mapActiveContracts.tsx new file mode 100644 index 000000000..f75aff64b --- /dev/null +++ b/src/components/pages/notifier/myoffers/mapActiveContracts.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import Grid from '@material-ui/core/Grid' +import Typography from '@material-ui/core/Typography' +import Tooltip from '@material-ui/core/Tooltip' +import MarketplaceAddressCell from 'components/molecules/MarketplaceAddressCell' +import NotificationsBalance from 'components/molecules/notifier/NotificationsBalance' +import { Item, MarketCryptoRecord } from 'models/Market' +import { NotifierOfferItem, NotifierSubscriptionItem } from 'models/marketItems/NotifierItem' +import { BaseFiat } from 'models/Fiat' +import { getFiatPrice } from 'utils/priceUtils' +import RoundBtn from 'components/atoms/RoundBtn' +import { ConfirmationData, SubscriptionWithdrawData } from 'context/Confirmations/interfaces' +import { Spinner } from '@rsksmart/rif-ui' +import ExpirationDate from 'components/molecules/ExpirationDate' + +export const activeContractHeaders = { + customer: 'Customer', + notifBalance: 'Notifications', + expDate: 'Expiration date', + price: 'Price', + actions: '', +} +export type ActiveContractItem = Item & { + [K in keyof typeof activeContractHeaders]: React.ReactElement +} + +const mapActiveContracts = ( + myCustomers: NotifierSubscriptionItem[] = [], + { id: offerId, limit }: Pick, + { currentFiat: { displayName }, crypto }: { + currentFiat: BaseFiat + crypto: MarketCryptoRecord + }, + { onWithdraw, onView }: { + onWithdraw: T + onView: U + }, + withdrawConfs: ConfirmationData[], +): Array => myCustomers + .filter(({ plan: { id } }) => String(id) === offerId) + .map(({ + id, + consumer, + expirationDate, + notificationBalance, + price, + token, + withdrawableFunds, + }) => { + const canWithdraw = withdrawableFunds.gt(0) + const withdrawButton = ( + onWithdraw({ id, token, withdrawableFunds })} + disabled={!canWithdraw} + > + Withdraw + + ) + const withdrawAction = canWithdraw + ? withdrawButton + : ( + + + {withdrawButton} + + + ) + + const isProcessingConfs = withdrawConfs.some( + ({ contractActionData }) => ( + (contractActionData as SubscriptionWithdrawData).subscriptionHash === id + ), + ) + + return ( + { + id, + customer: , + expDate: , + notifBalance: , + price: ( + + + + {getFiatPrice(price, crypto[token.symbol])} + + + + + {displayName} + + + + ), + actions: ( + + { + isProcessingConfs + ? + : ( + <> + + {withdrawAction} + + + onView(id)}>View + + + ) + } + + ), + }) + }) + +export default mapActiveContracts diff --git a/src/components/pages/notifier/mypurchase/NotifierMyPurchasePage.tsx b/src/components/pages/notifier/mypurchase/NotifierMyPurchasePage.tsx new file mode 100644 index 000000000..ac27413c4 --- /dev/null +++ b/src/components/pages/notifier/mypurchase/NotifierMyPurchasePage.tsx @@ -0,0 +1,335 @@ +import { makeStyles, Theme } from '@material-ui/core/styles' +import Typography from '@material-ui/core/Typography' +import { + shortenString, Web3Store, Spinner, +} from '@rsksmart/rif-ui' +import { notifierSubscriptionsAddress } from 'api/rif-marketplace-cache/notifier/subscriptions' +import GridRow from 'components/atoms/GridRow' +import RoundedCard from 'components/atoms/RoundedCard' +import WithLoginCard from 'components/hoc/WithLoginCard' +import InfoBar from 'components/molecules/InfoBar' +import MyPurchasesHeader from 'components/molecules/MyPurchasesHeader' +import PurchasesTable, { MySubscription } from 'components/organisms/notifier/mypurchase/PurchasesTable' +import CenteredPageTemplate from 'components/templates/CenteredPageTemplate' +import AppContext, { AppContextProps } from 'context/App' +import MarketContext from 'context/Market' +import useConfirmations from 'hooks/useConfirmations' +import useErrorReporter from 'hooks/useErrorReporter' +import { NotifierSubscriptionItem } from 'models/marketItems/NotifierItem' +import { UIError } from 'models/UIMessage' +import React, { + FC, useContext, useEffect, useState, + useCallback, +} from 'react' +import { getShortDateString } from 'utils/dateUtils' +import { shortChecksumAddress } from 'utils/stringUtils' +import { getFiatPrice } from 'utils/priceUtils' +import { SubscriptionDetails, subscriptionHeaders } from 'components/organisms/notifier/mypurchase/details' +import NotifierDetails, { SubscriptionEventsDisplayItem } from 'components/organisms/notifier/details/NotifierDetailsModal' +import RoundBtn from 'components/atoms/RoundBtn' +import { eventDisplayItemIterator } from 'components/organisms/notifier/details/utils' +import { SUBSCRIPTION_STATUSES } from 'api/rif-notifier-service/models/subscriptions' +import ProgressOverlay from 'components/templates/ProgressOverlay' +import ROUTES from 'routes' +import NotifierContract from 'contracts/notifier/Notifier' +import Web3 from 'web3' +import { convertToWeiString } from 'utils/parsers' +import { useHistory } from 'react-router-dom' +import { ConfirmationsContext } from 'context/Confirmations' +import { getOrCreateRenewalSubscription } from 'api/rif-notifier-service/subscriptionUtils' +import GridItem from 'components/atoms/GridItem' +import FeatureNotSupportedButton from 'components/atoms/FeatureNotSupportedButton' +import Refresh from 'components/molecules/Refresh' +import mapMyPurchases from './mapMyPurchases' +import { setBrowserSessionCannotRenew } from './utils' + +const useStyles = makeStyles((theme: Theme) => ({ + titleContainer: { + padding: theme.spacing(2, 2, 0, 2), + }, + refreshContainer: { + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + }, +})) + +const NotifierMyPurchasePage: FC = () => { + const classes = useStyles() + + const { + state: { account, web3 }, + } = useContext(Web3Store) + const { + state: { + apis: { + [notifierSubscriptionsAddress]: subscriptionsApi, + }, + }, + } = useContext(AppContext) + const { + state: { + exchangeRates, + }, + } = useContext(MarketContext) + const { dispatch: confirmationsDispatch } = useContext(ConfirmationsContext) + + const history = useHistory() + const reportError = useErrorReporter() + + const [ + subscriptions, + setSubscriptions, + ] = useState>() + const [ + subscriptionDetails, + setSubscriptionDetails, + ] = useState() + const [ + subscriptionEvents, + setSubscriptionEvents, + ] = useState>() + + const [isProcessingTx, setIsProcessingTx] = useState(false) + const [txOperationDone, setTxOperationDone] = useState(false) + const [isLoadingData, setIsLoadingData] = useState(true) + + const numberOfConfs = useConfirmations( + ['NOTIFIER_CREATE_SUBSCRIPTION'], + ).length + const isAwaitingConfs = Boolean(numberOfConfs) + + const fetchSubscriptions = useCallback(() => { + if (account && subscriptionsApi) { + setIsLoadingData(true) + subscriptionsApi.connect(reportError) + subscriptionsApi.fetch({ + consumer: account, + status: { + $ne: SUBSCRIPTION_STATUSES.PENDING, + }, + }) + .then((incomingSubscriptions: Array) => { + const prevSubsMap = incomingSubscriptions.reduce((acc, + sub) => ({ ...acc, [sub.previousSubscription]: sub }), + {}) + + const filtered = incomingSubscriptions.filter( + ({ id }) => !prevSubsMap[id], + ) + + setSubscriptions(Object.values(filtered)) + }) + .catch((error) => reportError(new UIError({ + id: 'service-fetch', + text: 'Error while fetching subscriptions.', + error, + }))) + .finally(() => { + setIsLoadingData(false) + }) + } + }, [subscriptionsApi, account, reportError]) + + useEffect(() => { + fetchSubscriptions() + }, [fetchSubscriptions]) + + const { + crypto, + currentFiat: { + displayName: fiatDisplayName, + }, + } = exchangeRates + + const onView = (subscriptionId: string): void => { + const subscription: NotifierSubscriptionItem = subscriptions + ?.find(({ id }) => id === subscriptionId) as NotifierSubscriptionItem + + if (!subscription) return + + const { + id, + notificationBalance, + plan: { channels }, + expirationDate, + price, + token: { symbol: tokenSymbol }, + events, + provider, + } = subscription + const { provider: providerAddress } = provider + + const viewItem: typeof subscriptionDetails = { + id: shortenString(id), + provider: shortChecksumAddress(providerAddress), + amount: String(notificationBalance), + channels: channels?.map(({ type }) => type).join(',') || '', + expDate: getShortDateString(expirationDate), + price: `${getFiatPrice(price, crypto[tokenSymbol])} ${fiatDisplayName}`, + } + + setSubscriptionDetails(viewItem) + setSubscriptionEvents(events.map( + (event) => eventDisplayItemIterator(event, channels), + )) + } + + const onRenew = async (subscriptionId: string): Promise => { + const subscription: NotifierSubscriptionItem = subscriptions + ?.find(({ id }) => id === subscriptionId) as NotifierSubscriptionItem + + if (!subscription || !account) return + + try { + setIsProcessingTx(true) + const { + id: subscriptionHash, + plan: { planId }, + price, + token: { symbol: tokenSymbol, tokenAddress }, + provider: { provider: providerAddress, url: providerUrl }, + } = subscription + + const response = await getOrCreateRenewalSubscription( + subscriptionHash, { + value: price, symbol: tokenSymbol, planId, url: providerUrl, + }, account, reportError, + ) + + const { hash: renewalHash, signature } = response + + const purchaseReceipt = await NotifierContract.getInstance(web3 as Web3) + .createSubscription( + { + subscriptionHash: renewalHash, + providerAddress, + signature, + amount: price, + tokenAddress, + }, + { + from: account, + value: convertToWeiString(price), + }, + ) + + if (purchaseReceipt) { + setTxOperationDone(true) + setBrowserSessionCannotRenew(subscriptionHash) + confirmationsDispatch({ + type: 'NEW_REQUEST', + payload: { + contractAction: 'NOTIFIER_CREATE_SUBSCRIPTION', + txHash: purchaseReceipt.transactionHash, + }, + }) + } + } catch (error: any) { + const { customMessage } = error + reportError({ + error, + id: 'contract-notifier', + text: customMessage || 'Could not complete the order', + }) + } finally { + setIsProcessingTx(false) + } + } + + const items = subscriptions?.map(mapMyPurchases( + exchangeRates, { onView, onRenew }, + )) || [] as Array + + const onModalClose = (): void => { + setSubscriptionDetails(undefined) + setSubscriptionEvents([]) + } + + return ( + + + <> + + + + + + Active plans + + + + + + + + { + isLoadingData + ? + : + } + + + {subscriptionDetails + && ( + + Cancel Plan + + )} + /> + )} + + setTxOperationDone(false) + } + > + View my purchases + , + history.push(ROUTES.NOTIFIER.BUY.BASE) + } + > + View offers listing + , + ]} + /> + + ) +} + +export default WithLoginCard({ + WrappedComponent: NotifierMyPurchasePage, + title: 'Connect your wallet to see your purchases', + contentText: 'Connect your wallet to get detailed information about your purchases', +})