diff --git a/package.json b/package.json index 5bd51c6cf..6e268c8c3 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "axios": "^0.19.2", "classnames": "^2.2.6", "date-fns": "^2.15.0", + "decimal.js": "^10.2.0", "dotenv": "^8.2.0", "ethereum-blockies": "github:MyEtherWallet/blockies", "filesize": "^6.1.0", diff --git a/src/components/atoms/Price/index.module.css b/src/components/atoms/Price/index.module.css index 43a071ee5..d2be93aa1 100644 --- a/src/components/atoms/Price/index.module.css +++ b/src/components/atoms/Price/index.module.css @@ -2,6 +2,7 @@ font-weight: var(--font-weight-bold); font-size: var(--font-size-large); color: var(--brand-grey-dark); + line-height: 1; } .price span:first-child { @@ -10,6 +11,11 @@ font-size: var(--font-size-base); } +.empty { + color: var(--color-secondary); + font-weight: var(--font-weight-bold); +} + .small { /* lazy making-conversion-smaller-with-same-markup */ transform: scale(0.8); diff --git a/src/components/atoms/Price/index.tsx b/src/components/atoms/Price/index.tsx index a25eeb7e3..ad4efcd9d 100644 --- a/src/components/atoms/Price/index.tsx +++ b/src/components/atoms/Price/index.tsx @@ -1,20 +1,39 @@ -import React, { ReactElement } from 'react' +import React, { ReactElement, useState, useEffect } from 'react' import classNames from 'classnames/bind' import PriceConversion from './Conversion' import styles from './index.module.css' import { formatCurrency } from '@coingecko/cryptoformat' +import { useMetadata, useOcean } from '@oceanprotocol/react' +import { DDO } from '@oceanprotocol/lib' +import Loader from '../Loader' +import Tooltip from '../Tooltip' const cx = classNames.bind(styles) export default function Price({ - price, + ddo, className, - small + small, + setPriceOutside }: { - price: string // expects price in OCEAN, not wei + ddo: DDO className?: string small?: boolean + setPriceOutside?: (price: string) => void }): ReactElement { + const { chainId } = useOcean() + const { getBestPrice } = useMetadata() + const [price, setPrice] = useState() + + useEffect(() => { + async function init() { + const price = await getBestPrice(ddo.dataToken) + setPrice(price) + setPriceOutside && price !== '' && setPriceOutside(price) + } + init() + }, [chainId]) + const styleClasses = cx({ price: true, small: small, @@ -32,5 +51,14 @@ export default function Price({ ) - return
{displayPrice}
+ return price ? ( +
{displayPrice}
+ ) : price === '' ? ( +
+ No price found{' '} + +
+ ) : ( + + ) } diff --git a/src/components/molecules/AssetTeaser.tsx b/src/components/molecules/AssetTeaser.tsx index c04959abe..4decaea5f 100644 --- a/src/components/molecules/AssetTeaser.tsx +++ b/src/components/molecules/AssetTeaser.tsx @@ -23,17 +23,6 @@ const AssetTeaser: React.FC = ({ const { description } = metadata.additionalInformation const isCompute = Boolean(ddo.findServiceByType('compute')) - const { getBestPrice } = useMetadata(ddo.id) - const [price, setPrice] = useState() - - useEffect(() => { - async function init() { - const price = await getBestPrice(ddo.dataToken) - setPrice(price) - } - init() - }, []) - return (
@@ -47,13 +36,7 @@ const AssetTeaser: React.FC = ({
- {price ? ( - - ) : price === '' ? ( - 'No price found' - ) : ( - - )} +
diff --git a/src/components/molecules/Wallet/Feedback.tsx b/src/components/molecules/Wallet/Feedback.tsx index 17402b682..77f0da55e 100644 --- a/src/components/molecules/Wallet/Feedback.tsx +++ b/src/components/molecules/Wallet/Feedback.tsx @@ -12,15 +12,20 @@ export declare type Web3Error = { } export default function Web3Feedback({ - isBalanceInsufficient + isBalanceSufficient }: { - isBalanceInsufficient?: boolean + isBalanceSufficient?: boolean }): ReactElement { const { appConfig } = useSiteMetadata() const { account, status, chainId } = useOcean() const isOceanConnectionError = status === -1 const correctNetwork = isCorrectNetwork(chainId) - const showFeedback = !account || isOceanConnectionError || !correctNetwork + const showFeedback = + !account || + isOceanConnectionError || + !correctNetwork || + isBalanceSufficient === false + const desiredNetworkName = appConfig.network.replace(/^\w/, (c: string) => c.toUpperCase() ) @@ -29,7 +34,7 @@ export default function Web3Feedback({ ? 'error' : !correctNetwork ? 'warning' - : account && !isBalanceInsufficient + : account && isBalanceSufficient ? 'success' : 'warning' @@ -40,7 +45,7 @@ export default function Web3Feedback({ : !correctNetwork ? 'Wrong Network' : account - ? isBalanceInsufficient === true + ? isBalanceSufficient === false ? 'Insufficient balance' : 'Connected to Ocean' : 'Something went wrong' @@ -51,7 +56,7 @@ export default function Web3Feedback({ ? 'Please try again.' : !correctNetwork ? `Please connect to ${desiredNetworkName}.` - : isBalanceInsufficient === true + : isBalanceSufficient === false ? 'You do not have enough OCEAN in your wallet to purchase this asset.' : 'Something went wrong.' diff --git a/src/components/organisms/AssetActions/Compute.tsx b/src/components/organisms/AssetActions/Compute.tsx index 36bc91bae..0d593c6e6 100644 --- a/src/components/organisms/AssetActions/Compute.tsx +++ b/src/components/organisms/AssetActions/Compute.tsx @@ -1,6 +1,5 @@ -import React, { useState, useEffect, ReactElement } from 'react' +import React, { useState, ReactElement } from 'react' import { DDO } from '@oceanprotocol/lib' -import compareAsBN, { Comparisson } from '../../../utils/compareAsBN' import Loader from '../../atoms/Loader' import Web3Feedback from '../../molecules/Wallet/Feedback' import Dropzone from '../../atoms/Dropzone' @@ -18,10 +17,12 @@ import Alert from '../../atoms/Alert' export default function Compute({ ddo, - price + isBalanceSufficient, + setPrice }: { ddo: DDO - price: string // in OCEAN, not wei + isBalanceSufficient: boolean + setPrice: (price: string) => void }): ReactElement { const { ocean } = useOcean() const { compute, isLoading, computeStepText, computeError } = useCompute() @@ -29,7 +30,6 @@ export default function Compute({ const [isJobStarting, setIsJobStarting] = useState(false) const [, setError] = useState('') - const [isBalanceSufficient, setIsBalanceSufficient] = useState(false) const [computeType, setComputeType] = useState('') const [computeContainer, setComputeContainer] = useState({ entrypoint: '', @@ -39,25 +39,13 @@ export default function Compute({ const [algorithmRawCode, setAlgorithmRawCode] = useState('') const [isPublished, setIsPublished] = useState(false) const [file, setFile] = useState(null) - const [isTermsAgreed, setIsTermsAgreed] = useState(true) - - const isFree = price === '0' const isComputeButtonDisabled = isJobStarting || file === null || computeType === '' || !ocean || - !isBalanceSufficient || - !isTermsAgreed - - // useEffect(() => { - // setIsBalanceSufficient( - // isFree || - // (balance !== null && - // compareAsBN(balance, fromWei(computeService.cost), Comparisson.gte)) - // ) - // }, [balance]) + !isBalanceSufficient const onDrop = async (files: any) => { setFile(files[0]) @@ -101,13 +89,7 @@ export default function Compute({ return (
- {price ? ( - - ) : price === '' ? ( - 'No price found' - ) : ( - - )} +
@@ -133,7 +115,6 @@ export default function Compute({ Start job
- {/* */}
@@ -148,7 +129,7 @@ export default function Compute({ state="success" /> )} - +
diff --git a/src/components/organisms/AssetActions/Consume.module.css b/src/components/organisms/AssetActions/Consume.module.css index 760230d7a..73fda7fe1 100644 --- a/src/components/organisms/AssetActions/Consume.module.css +++ b/src/components/organisms/AssetActions/Consume.module.css @@ -12,16 +12,18 @@ flex-shrink: 0; } -.consume button { - margin-left: calc(var(--spacer) / 4); +.pricewrapper { + display: flex; + flex-wrap: wrap; } -.consume button:first-of-type { - margin-left: 0; +.pricewrapper button { + margin-top: calc(var(--spacer) / 2); + align-self: flex-end; } -.price { - margin-bottom: calc(var(--spacer) / 2); +.pricewrapper > div { + min-height: 30px; } .feedback { diff --git a/src/components/organisms/AssetActions/Consume.tsx b/src/components/organisms/AssetActions/Consume.tsx index 7bc722bd8..64018fe58 100644 --- a/src/components/organisms/AssetActions/Consume.tsx +++ b/src/components/organisms/AssetActions/Consume.tsx @@ -1,7 +1,6 @@ import React, { ReactElement } from 'react' import { toast } from 'react-toastify' import { File as FileMetadata, DDO } from '@oceanprotocol/lib' -import compareAsBN, { Comparisson } from '../../../utils/compareAsBN' import Button from '../../atoms/Button' import File from '../../atoms/File' import Price from '../../atoms/Price' @@ -12,21 +11,19 @@ import { useOcean, useConsume } from '@oceanprotocol/react' export default function Consume({ ddo, - price, - file + file, + isBalanceSufficient, + setPrice }: { ddo: DDO - price: string // in OCEAN, not wei file: FileMetadata + isBalanceSufficient: boolean + setPrice: (price: string) => void }): ReactElement { - const accessService = ddo.findServiceByType('access') const { ocean } = useOcean() const { consumeStepText, consume, consumeError } = useConsume() - const isFree = price === '0' - // const isBalanceSufficient = - // isFree || compareAsBN(balanceInOcean, fromWei(cost), Comparisson.gte) - const isDisabled = !ocean + const isDisabled = !ocean || !isBalanceSufficient if (consumeError) { toast.error(consumeError) @@ -41,7 +38,7 @@ export default function Consume({ onClick={() => consume(ddo.id, ddo.dataToken, 'access')} disabled={isDisabled} > - {isFree ? 'Download' : 'Buy'} + Buy ) @@ -52,19 +49,13 @@ export default function Consume({
- {price ? ( - - ) : price === '' ? ( - 'No price found' - ) : ( - - )} +
- +
) diff --git a/src/components/organisms/AssetActions/index.tsx b/src/components/organisms/AssetActions/index.tsx index b593b908b..0c19746ca 100644 --- a/src/components/organisms/AssetActions/index.tsx +++ b/src/components/organisms/AssetActions/index.tsx @@ -5,7 +5,8 @@ import Consume from './Consume' import { MetadataMarket } from '../../../@types/Metadata' import { DDO } from '@oceanprotocol/lib' import Tabs from '../../atoms/Tabs' -import { useMetadata } from '@oceanprotocol/react' +import { useOcean } from '@oceanprotocol/react' +import compareAsBN from '../../../utils/compareAsBN' export default function AssetActions({ metadata, @@ -14,22 +15,37 @@ export default function AssetActions({ metadata: MetadataMarket ddo: DDO }): ReactElement { - const { getBestPrice } = useMetadata(ddo.id) + const { balance } = useOcean() const [price, setPrice] = useState() - - useEffect(() => { - async function init() { - const price = await getBestPrice(ddo.dataToken) - setPrice(price) - } - init() - }, []) + const [isBalanceSufficient, setIsBalanceSufficient] = useState() const isCompute = Boolean(ddo.findServiceByType('compute')) + + // Check user balance against price + useEffect(() => { + if (!price || !balance || !balance.ocean) return + + const isFree = price === '0' + setIsBalanceSufficient(isFree ? true : compareAsBN(balance.ocean, price)) + + return () => { + setIsBalanceSufficient(false) + } + }, [balance, price]) + const UseContent = isCompute ? ( - + ) : ( - + ) const tabs = [ diff --git a/src/utils/compareAsBN.ts b/src/utils/compareAsBN.ts index 1aae82edf..8038773d9 100644 --- a/src/utils/compareAsBN.ts +++ b/src/utils/compareAsBN.ts @@ -1,21 +1,16 @@ -import BN from 'bn.js' +import { Decimal } from 'decimal.js' -export enum Comparisson { - 'lt' = 'lt', - 'lte' = 'lte', - 'gt' = 'gt', - 'gte' = 'gte', - 'eq' = 'eq' -} +// Run decimal.js comparison +// http://mikemcl.github.io/decimal.js/#cmp +export default function compareAsBN(balance: string, price: string): boolean { + const aBN = new Decimal(balance) + const bBN = new Decimal(price) + const compare = aBN.comparedTo(bBN) -// Run the corresponding bn.js comparisson: -// https://github.com/indutny/bn.js/#utilities -export default function compareAsBN( - a: string, - b: string, - comparisson: Comparisson -) { - const aBN = new BN(a) - const bBN = new BN(b) - return aBN[comparisson](bBN) + switch (compare) { + case 1 || 0: // balance is greater or equal to price + return true + default: + return false + } }