diff --git a/package-lock.json b/package-lock.json index 7a29224c3..59540b81d 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.3", +>>>>>>> 4393fc1 (fix(storage): fixes visual defects + buggy mapping) "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5b463b49f..8d3b29ddd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "rif-marketplace-ui", +<<<<<<< HEAD "version": "1.3.5", +======= + "version": "1.4.3", +>>>>>>> 4393fc1 (fix(storage): fixes visual defects + buggy mapping) "description": "RIF Marketplace provides a digital catalogue with a wide range of decentralised services.", "keywords": [ "RIF", diff --git a/src/components/molecules/RifAddress.tsx b/src/components/molecules/RifAddress.tsx index 61a88cb4d..e59f4a053 100644 --- a/src/components/molecules/RifAddress.tsx +++ b/src/components/molecules/RifAddress.tsx @@ -1,15 +1,23 @@ import Typography, { TypographyProps } from '@material-ui/core/Typography' -import { CopyTextTooltip } from '@rsksmart/rif-ui' +import { CopyTextTooltip, shortenString } from '@rsksmart/rif-ui' import React, { FC } from 'react' import { shortChecksumAddress } from 'utils/stringUtils' export interface AddressItemProps extends TypographyProps { pretext?: string value: string + disableChecksum?: boolean } +<<<<<<< HEAD const RifAddress: FC = ({ pretext, value, ...rest }) => { const address = shortChecksumAddress(value) +======= +const RifAddress: FC = ({ + pretext, value, disableChecksum, ...rest +}) => { + const address = disableChecksum ? shortenString(value) : shortChecksumAddress(value) +>>>>>>> 4393fc1 (fix(storage): fixes visual defects + buggy mapping) const displayElement = ( diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts index b434449c2..b3a1f1bb2 100644 --- a/src/components/molecules/index.ts +++ b/src/components/molecules/index.ts @@ -9,7 +9,7 @@ export { CombinedPriceCell, IconedItem, ReturnButton, - RifAddress as AddressItem, + RifAddress, JobDoneBox, SelectRowButton, } diff --git a/src/components/organisms/rns/sell/MyDomains.tsx b/src/components/organisms/rns/sell/MyDomains.tsx index e65881e00..3bb97fa8f 100644 --- a/src/components/organisms/rns/sell/MyDomains.tsx +++ b/src/components/organisms/rns/sell/MyDomains.tsx @@ -1,4 +1,4 @@ -import { AddressItem, SelectRowButton } from 'components/molecules' +import { RifAddress, SelectRowButton } from 'components/molecules' import DomainFilters from 'components/organisms/filters/DomainFilters' import MarketPageTemplate from 'components/templates/MarketPageTemplate' import { RnsDomain } from 'models/marketItems/DomainItem' @@ -70,7 +70,7 @@ const MyDomains: FC = () => { maxLength={30} /> ) - : + : const isProcessingConfs = pendingConfs.some( ({ contractActionData }) => ( diff --git a/src/components/organisms/rns/sell/MyOffers.tsx b/src/components/organisms/rns/sell/MyOffers.tsx index ad031758e..81095e0f4 100644 --- a/src/components/organisms/rns/sell/MyOffers.tsx +++ b/src/components/organisms/rns/sell/MyOffers.tsx @@ -1,6 +1,6 @@ import IconButton from '@material-ui/core/IconButton' import ClearIcon from '@material-ui/icons/Clear' -import { AddressItem, CombinedPriceCell, SelectRowButton } from 'components/molecules' +import { RifAddress, CombinedPriceCell, SelectRowButton } from 'components/molecules' import DomainFilters from 'components/organisms/filters/DomainFilters' import MarketPageTemplate from 'components/templates/MarketPageTemplate' import { RnsDomain } from 'models/marketItems/DomainItem' @@ -76,7 +76,7 @@ const MyOffers: FC = () => { maxLength={30} /> ) - : + : const isProcessingConfs = pendingConfs.some( ({ contractActionData }) => ( diff --git a/src/components/organisms/rns/sell/SoldDomains.tsx b/src/components/organisms/rns/sell/SoldDomains.tsx index afeb7c727..d6ff33ba2 100644 --- a/src/components/organisms/rns/sell/SoldDomains.tsx +++ b/src/components/organisms/rns/sell/SoldDomains.tsx @@ -1,4 +1,4 @@ -import { AddressItem, CombinedPriceCell } from 'components/molecules' +import { RifAddress, CombinedPriceCell } from 'components/molecules' import DomainFilters from 'components/organisms/filters/DomainFilters' import MarketPageTemplate from 'components/templates/MarketPageTemplate' import { RnsSoldDomain } from 'models/marketItems/DomainItem' @@ -52,12 +52,12 @@ const SoldDomains: FC<{}> = () => { const pseudoResolvedName = name && `${name}.rsk` const displayDomainName = domainName || pseudoResolvedName ? - : + : const displayItem = { id, domainName: displayDomainName, - buyer: , + buyer: , currency: displayName, sellingPrice: ({ }, })) -const DetailsModal: FC = ({ modalProps, itemDetails, actions }) => { +const DetailsModal: FC = ({ + modalProps, itemDetails, actions, +}) => { const modalCardStyleClasses = useModalStyles() return ( = ({ modalProps, itemDetails, actions {itemDetails?.title} - {itemDetails ? Object.keys(itemDetails) @@ -68,6 +72,7 @@ const DetailsModal: FC = ({ modalProps, itemDetails, actions container direction="row" spacing={6} + wrap="nowrap" xs={12} > = ({ modalProps, itemDetails, actions {itemDetails[key]} )) : ''} - + diff --git a/src/components/organisms/storage/agreements/utils.tsx b/src/components/organisms/storage/agreements/utils.tsx index 2d3156a78..5219ac82a 100644 --- a/src/components/organisms/storage/agreements/utils.tsx +++ b/src/components/organisms/storage/agreements/utils.tsx @@ -1,6 +1,6 @@ import { Spinner } from '@rsksmart/rif-ui' import ItemWUnit from 'components/atoms/ItemWUnit' -import { AddressItem, CombinedPriceCell, SelectRowButton } from 'components/molecules' +import { RifAddress, CombinedPriceCell, SelectRowButton } from 'components/molecules' import { MarketplaceItem } from 'components/templates/marketplace/Marketplace' import { AgreementUpdateData, ConfirmationData } from 'context/Confirmations/interfaces' import { BaseFiat } from 'models/Fiat' @@ -33,19 +33,17 @@ export type AgreementCustomerView = AgreementView & { const getCoreItemFields = ( agreement: Agreement, - crypto: MarketCryptoRecord, + { rate, displayName }: TokenXR, currentFiat: BaseFiat, ): AgreementView => { const { monthlyFee, renewalDate, - paymentToken, size, subscriptionPeriod, id, dataReference, } = agreement - const currency: TokenXR = crypto[paymentToken.symbol] const sizeValue = ( ) - const idValue = + const idValue = return { - HASH: , + HASH: , title: idValue, 'PRICE/GB': feeValue, AMOUNT: sizeValue, 'RENEWAL DATE': renewalDate ? getShortDateString(renewalDate) : 'Expired', 'SUBSCRIPTION PERIOD': subscriptionPeriod, - CURRENCY: currency.displayName, + CURRENCY: displayName, SYSTEM: 'IPFS', } } export const getCustomerViewFrom = ( agreement: Agreement, - crypto: MarketCryptoRecord, + currency: TokenXR, currentFiat: BaseFiat, ): AgreementCustomerView => { - const agreementInfo = getCoreItemFields(agreement, crypto, currentFiat) + const agreementInfo = getCoreItemFields(agreement, currency, currentFiat) const { - provider, paymentToken, withdrawableFunds, + provider, withdrawableFunds, } = agreement - const currency: TokenXR = crypto[paymentToken.symbol] - const providerValue = + const providerValue = const withdrawableFundsValue = ( void, withdrawAndRenewConfs: ConfirmationData[], -): MarketplaceItem[] => agreements.map((agreement: Agreement) => { - const { - id, expiresInSeconds, isActive, - } = agreement - const customerView = getCustomerViewFrom(agreement, crypto, currentFiat) - - const isProcessingConfs = withdrawAndRenewConfs.some( - ({ contractActionData }) => ( - (contractActionData as AgreementUpdateData).agreementId === id - ), - ) +): MarketplaceItem[] => agreements + .filter(({ paymentToken: { symbol } }) => crypto[symbol]) + .map((agreement: Agreement) => { + const { + id, expiresInSeconds, isActive, paymentToken: { symbol }, + } = agreement + const customerView = getCustomerViewFrom( + agreement, + crypto[symbol], + currentFiat, + ) - return { - ...customerView, - id, - provider: customerView.PROVIDER, - contentSize: customerView.AMOUNT, - renewalDate: customerView['RENEWAL DATE'], - subscriptionPeriod: customerView['SUBSCRIPTION PERIOD'], - monthlyFee: customerView['PRICE/GB'], - withdrawableFunds: customerView['WITHDRAWABLE FUNDS'], - renew: isProcessingConfs - ? - : ( - { - onItemRenew(event, agreement) - }} - > - Renew - - ), - view: isProcessingConfs - ? <> - : ( - onItemSelect( - event, customerView, agreement, - )} - > - View - + const isProcessingConfs = withdrawAndRenewConfs.some( + ({ contractActionData }) => ( + (contractActionData as AgreementUpdateData).agreementId === id ), - } -}) + ) + + return { + ...customerView, + id, + provider: customerView.PROVIDER, + contentSize: customerView.AMOUNT, + renewalDate: customerView['RENEWAL DATE'], + subscriptionPeriod: customerView['SUBSCRIPTION PERIOD'], + monthlyFee: customerView['PRICE/GB'], + withdrawableFunds: customerView['WITHDRAWABLE FUNDS'], + renew: isProcessingConfs + ? + : ( + { + onItemRenew(event, agreement) + }} + > + Renew + + ), + view: isProcessingConfs + ? <> + : ( + onItemSelect( + event, customerView, agreement, + )} + > + View + + ), + } + }) export const getProviderViewFrom = ( agreement: Agreement, - crypto: MarketCryptoRecord, + currency: TokenXR, currentFiat: BaseFiat, ): AgreementProviderView => { - const agreementInfo = getCoreItemFields(agreement, crypto, currentFiat) const { - consumer, paymentToken, toBePayedOut, + consumer, toBePayedOut, } = agreement - const currency: TokenXR = crypto[paymentToken.symbol] - const consumerValue = + const consumerValue = const toBePayedOutValue = ( ) + const agreementInfo = getCoreItemFields(agreement, currency, currentFiat) + return { ...agreementInfo, CONSUMER: consumerValue, @@ -201,51 +204,59 @@ export const createProviderItemFields = ( agreement: Agreement, ) => void, payoutConfirmations: ConfirmationData[], -): MarketplaceItem[] => agreements.map((agreement: Agreement) => { - const providerView = getProviderViewFrom(agreement, crypto, currentFiat) - const { - id, - } = agreement - - const isProcessingPayoutConfs = payoutConfirmations.some( - ({ contractActionData }) => ( - (contractActionData as AgreementUpdateData).agreementId === id - ), - ) +): MarketplaceItem[] => agreements + .filter(({ paymentToken: { symbol } }) => crypto[symbol]) + .map((agreement: Agreement) => { + const { + id, + paymentToken: { symbol }, + } = agreement - return { - ...providerView, - id, - customer: providerView.CONSUMER, - contentSize: providerView.AMOUNT, - renewalDate: providerView['RENEWAL DATE'], - subscriptionPeriod: providerView['SUBSCRIPTION PERIOD'], - monthlyFee: providerView['PRICE/GB'], - toBePayedOut: providerView['AVAILABLE FUNDS'], - withdraw: isProcessingPayoutConfs - ? - : ( - { - onItemWithdraw(event, agreement) - }} - disabled={Number(agreement.toBePayedOut) <= 0} - > - Withdraw - - ), - view: isProcessingPayoutConfs - ? <> - : ( - onItemSelect( - event, providerView, agreement, - )} - > - View - + const isProcessingPayoutConfs = payoutConfirmations.some( + ({ contractActionData }) => ( + (contractActionData as AgreementUpdateData).agreementId === id ), - } -}) + ) + + const providerView = getProviderViewFrom( + agreement, + crypto[symbol], + currentFiat, + ) + + return { + ...providerView, + id, + customer: providerView.CONSUMER, + contentSize: providerView.AMOUNT, + renewalDate: providerView['RENEWAL DATE'], + subscriptionPeriod: providerView['SUBSCRIPTION PERIOD'], + monthlyFee: providerView['PRICE/GB'], + toBePayedOut: providerView['AVAILABLE FUNDS'], + withdraw: isProcessingPayoutConfs + ? + : ( + { + onItemWithdraw(event, agreement) + }} + disabled={Number(agreement.toBePayedOut) <= 0} + > + Withdraw + + ), + view: isProcessingPayoutConfs + ? <> + : ( + onItemSelect( + event, providerView, agreement, + )} + > + View + + ), + } + }) diff --git a/src/components/pages/notifier/buy/NotifierOffersPage.tsx b/src/components/pages/notifier/buy/NotifierOffersPage.tsx new file mode 100644 index 000000000..cab6abd7e --- /dev/null +++ b/src/components/pages/notifier/buy/NotifierOffersPage.tsx @@ -0,0 +1,253 @@ +import Tooltip from '@material-ui/core/Tooltip' +import Typography from '@material-ui/core/Typography' +import { Spinner, Web3Store } from '@rsksmart/rif-ui' +import { SubscriptionPlanDTO } from 'api/rif-notifier-service/models/subscriptionPlan' +import SubscriptionPlans from 'api/rif-notifier-service/subscriptionPlans' +import ItemWUnit from 'components/atoms/ItemWUnit' +import { RifAddress, SelectRowButton } from 'components/molecules' +import { SelectRowButtonProps } from 'components/molecules/SelectRowButton' +import NotifierOffersFilters from 'components/organisms/filters/notifier/OffersFilters' +import NotifierPlansDraw from 'components/organisms/notifier/NotifierPlansDraw' +import MarketPageTemplate from 'components/templates/MarketPageTemplate' +import { MarketplaceItem, TableHeaders } from 'components/templates/marketplace/Marketplace' +import MarketContext, { MarketContextProps } from 'context/Market' +import { NotifierOffersContext, NotifierOffersContextProps } from 'context/Services/notifier/offers' +import { MarketCryptoRecord } from 'models/Market' +import { NotifierOfferItem, NotifierPlan, PriceOption } from 'models/marketItems/NotifierItem' +import React, { + FC, useCallback, useContext, useEffect, useMemo, useState, +} from 'react' +import { useHistory } from 'react-router-dom' +import ROUTES from 'routes' +import Logger from 'utils/Logger' +import { mapPlansToOffers } from './utils' + +const headers: TableHeaders = { + provider: 'Provider', + notifLimitRange: 'Notifications', + channels: 'Channels', + currencies: 'Currencies', + priceFiatRange: 'Price', + action1: '', +} + +type ProviderItem = { + id: string + plans: Array +} + +const showPlans = ( + { id: selectedItemId, plans }: Partial, + currentFiat: string, + crypto: MarketCryptoRecord, + onPlanSelected: (plan: NotifierOfferItem, priceOption: PriceOption) => void, +): FC => (id): JSX.Element => { + const isSelected = selectedItemId && plans?.length + + return ( + <> + {isSelected && ( + } + isOpen={selectedItemId === id} + {...{ onPlanSelected, currentFiat, crypto }} + /> + )} + + ) +} + +const getProviderPlans = (url: string): Promise => { + const notifierService = new SubscriptionPlans(url) + notifierService.connect((er) => { + Logger.getInstance() + .debug(JSON.stringify(er, null, 2)) + }) + return notifierService.getActivePlans() +} + +const createActionButton = (actionButtonProps: SelectRowButtonProps): JSX.Element => { + const selectButton = ( + + ) + + return actionButtonProps.disabled + ? ( + + + {selectButton} + + + ) : selectButton +} + +const NotifierOffersPage: FC = () => { + const history = useHistory() + const { + state: { + contextID, + listing: { items: cachedPlans }, + }, + dispatch, + } = useContext(NotifierOffersContext) + const { + state: { + exchangeRates: { + currentFiat: { + displayName: currentFiat, + }, + crypto, + }, + }, + } = useContext(MarketContext) + const { + state: { + account, + }, + } = useContext(Web3Store) + + useEffect(() => { + dispatch({ + type: 'FILTER', + payload: { provider: undefined }, + }) + }, [dispatch]) + + const onPlanSelected = ( + plan: NotifierOfferItem, + priceOption: PriceOption, + ): void => { + dispatch({ + type: 'SET_ORDER', + payload: { plan, priceOption }, + }) + history.push(ROUTES.NOTIFIER.BUY.CHECKOUT) + } + + const providers = useMemo(() => Array.from(new Set(cachedPlans + .map(({ provider }) => provider))), [cachedPlans]) + const [selectedProvider, setSelectedProvider] = useState() + const [ + selectedProviderPlans, + setSelectedProviderPlans, + ] = useState([]) + const [collection, setCollection] = useState([]) + const addToCollection = useCallback((newCollectionItem: MarketplaceItem) => { + setCollection((curCollection) => { + const spinnerlessCollection = curCollection.slice(0, -1) + const [spinner] = curCollection.slice(-1) + + return [ + ...spinnerlessCollection, + newCollectionItem, + spinner, + ] + }) + }, [setCollection]) + + useEffect(() => { + setSelectedProviderPlans(cachedPlans + .filter(({ provider }) => provider === selectedProvider)) + }, [cachedPlans, selectedProvider]) + + useEffect(() => { + if (cachedPlans?.length) { + setCollection([ + { + id: 'pending', + action1: (), + }, + ]) + + Promise.all(providers.map(async (provider) => { + const providerPlans = cachedPlans + .filter((item) => item.provider === provider) + const [{ url }] = providerPlans + + const notifierActivePlans = await getProviderPlans(url) + + // filter out inactive plans + const activePlans = providerPlans.filter( + ({ planId }) => notifierActivePlans && notifierActivePlans.some( + ({ id: notifierPlanId }) => notifierPlanId === planId, + ), + ) + const hasActivePlans = activePlans.length + + const isSelected = selectedProvider === provider + + const buttonSelect = createActionButton({ + disabled: !hasActivePlans, + id: provider, + isSelected, + handleSelect: () => setSelectedProvider( + isSelected ? undefined : provider, + ), + }) + + const commonProperties = { + id: provider, + provider: , + action1: account === provider ? 'Your offer' : (buttonSelect), + } + + if (!hasActivePlans) { + const collectionItem: MarketplaceItem = { + ...commonProperties, + channels: 'N/A', + currencies: 'N/A', + notifLimitRange: 'N/A', + priceFiatRange: N/A, + } + addToCollection(collectionItem) + return + } + + const { + priceFiatRange, + ...offerDetails + } = mapPlansToOffers( + activePlans, crypto, + ) + + const resultItem: MarketplaceItem = { + ...commonProperties, + ...offerDetails, + priceFiatRange: ( + + ), + } + + addToCollection(resultItem) + })) + .finally(() => { + setCollection((curCollection) => curCollection + .slice(0, -1)) + }) + } + }, [ + cachedPlans, crypto, + currentFiat, account, selectedProvider, providers, addToCollection, + ]) + + return ( + } + items={collection} + headers={headers} + dispatch={dispatch} + outdatedCt={0} + itemDetail={showPlans( + { id: selectedProvider, plans: selectedProviderPlans }, + currentFiat, crypto, onPlanSelected, + )} + /> + ) +} + +export default NotifierOffersPage diff --git a/src/components/pages/rns/buy/DomainOffersPage.tsx b/src/components/pages/rns/buy/DomainOffersPage.tsx index 00d9019c4..8f08e7bc1 100644 --- a/src/components/pages/rns/buy/DomainOffersPage.tsx +++ b/src/components/pages/rns/buy/DomainOffersPage.tsx @@ -1,7 +1,7 @@ import { Web3Store, ShortenTextTooltip, Spinner } from '@rsksmart/rif-ui' import React, { FC, useContext } from 'react' import { useHistory } from 'react-router-dom' -import { AddressItem, CombinedPriceCell, SelectRowButton } from 'components/molecules' +import { RifAddress, CombinedPriceCell, SelectRowButton } from 'components/molecules' import RifPaging from 'components/molecules/RifPaging' import DomainOfferFilters from 'components/organisms/filters/DomainOffersFilters' import MarketPageTemplate from 'components/templates/MarketPageTemplate' @@ -158,7 +158,7 @@ const DomainOffersPage: FC = () => { maxLength={30} /> ) - : + : const isProcessingConfs = pendingConfs.some( ({ contractActionData }) => ( @@ -183,7 +183,7 @@ const DomainOffersPage: FC = () => { const displayItem = { id, domainName: displayDomainName, - ownerAddress: , + ownerAddress: , expirationDate: expirationDate.toLocaleDateString(), combinedPrice: { return { id, - provider: , + provider: , system, availableSize: , subscriptionOptions: Array.from(new Set(