diff --git a/src/components/atoms/ButtonBuy.tsx b/src/components/atoms/ButtonBuy.tsx index 245c9228b..6bdcec09e 100644 --- a/src/components/atoms/ButtonBuy.tsx +++ b/src/components/atoms/ButtonBuy.tsx @@ -10,33 +10,66 @@ interface ButtonBuyProps { hasDatatoken: boolean dtSymbol: string dtBalance: string - isLoading: boolean + assetType: string assetTimeout: string + hasPreviousOrderSelectedComputeAsset?: boolean + hasDatatokenSelectedComputeAsset?: boolean + dtSymbolSelectedComputeAsset?: string + dtBalanceSelectedComputeAsset?: string + selectedComputeAssetType?: string + isLoading: boolean onClick?: (e: FormEvent) => void stepText?: string type?: 'submit' } -function getHelpText( - token: { - dtBalance: string - dtSymbol: string - }, +function getConsumeHelpText( + dtBalance: string, + dtSymbol: string, hasDatatoken: boolean, hasPreviousOrder: boolean, - timeout: string + assetType: string ) { - const { dtBalance, dtSymbol } = token - const assetTimeout = timeout === 'Forever' ? '' : ` for ${timeout}` const text = hasPreviousOrder - ? `You bought this data set already allowing you to use it without paying again${assetTimeout}.` + ? `You bought this ${assetType} already allowing you to use it without paying again.` : hasDatatoken ? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.` - : `For using this data set, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.` + : `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.` return text } +function getComputeAssetHelpText( + hasPreviousOrder: boolean, + hasDatatoken: boolean, + dtSymbol: string, + dtBalance: string, + assetType: string, + hasPreviousOrderSelectedComputeAsset?: boolean, + hasDatatokenSelectedComputeAsset?: boolean, + dtSymbolSelectedComputeAsset?: string, + dtBalanceSelectedComputeAsset?: string, + selectedComputeAssetType?: string +) { + const computeAssetHelpText = getConsumeHelpText( + dtBalance, + dtSymbol, + hasDatatoken, + hasPreviousOrder, + assetType + ) + const text = + !dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset + ? '' + : hasPreviousOrderSelectedComputeAsset + ? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.` + : hasDatatokenSelectedComputeAsset + ? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying OCEAN again.` + : `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.` + + return `${computeAssetHelpText} ${text}` +} + export default function ButtonBuy({ action, disabled, @@ -44,8 +77,14 @@ export default function ButtonBuy({ hasDatatoken, dtSymbol, dtBalance, - onClick, + assetType, assetTimeout, + hasPreviousOrderSelectedComputeAsset, + hasDatatokenSelectedComputeAsset, + dtSymbolSelectedComputeAsset, + dtBalanceSelectedComputeAsset, + selectedComputeAssetType, + onClick, stepText, isLoading, type @@ -55,11 +94,9 @@ export default function ButtonBuy({ ? hasPreviousOrder ? 'Download' : `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}` - : hasPreviousOrder + : hasPreviousOrder && hasPreviousOrderSelectedComputeAsset ? 'Start Compute Job' - : `Buy Compute Job ${ - assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}` - }` + : `Buy Compute Job` return (
@@ -76,12 +113,26 @@ export default function ButtonBuy({ {buttonText}
- {getHelpText( - { dtBalance, dtSymbol }, - hasDatatoken, - hasPreviousOrder, - assetTimeout - )} + {action === 'download' + ? getConsumeHelpText( + dtBalance, + dtSymbol, + hasDatatoken, + hasPreviousOrder, + assetType + ) + : getComputeAssetHelpText( + hasPreviousOrder, + hasDatatoken, + dtSymbol, + dtBalance, + assetType, + hasPreviousOrderSelectedComputeAsset, + hasDatatokenSelectedComputeAsset, + dtSymbolSelectedComputeAsset, + dtBalanceSelectedComputeAsset, + selectedComputeAssetType + )}
)} diff --git a/src/components/molecules/FormFields/AssetSelection.module.css b/src/components/molecules/FormFields/AssetSelection.module.css index e29a1648a..496cf5407 100644 --- a/src/components/molecules/FormFields/AssetSelection.module.css +++ b/src/components/molecules/FormFields/AssetSelection.module.css @@ -92,10 +92,14 @@ div [class*='loaderWrap'] { .price { white-space: pre; - font-size: var(--font-size-small) !important; + font-size: calc(var(--font-size-small) / 1.1) !important; padding-left: calc(var(--spacer) / 4); } +.price [class*='symbol'] { + font-size: calc(var(--font-size-small) / 1.2) !important; +} + .search { margin: calc(var(--spacer) / 4) calc(var(--spacer) / 2); width: calc(100% - var(--spacer)); diff --git a/src/components/organisms/AssetActions/Compute/FormComputeDataset.module.css b/src/components/organisms/AssetActions/Compute/FormComputeDataset.module.css index 5319f1758..57936ae3a 100644 --- a/src/components/organisms/AssetActions/Compute/FormComputeDataset.module.css +++ b/src/components/organisms/AssetActions/Compute/FormComputeDataset.module.css @@ -1,11 +1,10 @@ .form { padding: 0; border: none; - margin-top: -0.8rem; } .form > div > label, -[class*='ButtonBuy-module--actions'] { +.form [class*='ButtonBuy-module--actions'] { text-align: center; } @@ -23,15 +22,3 @@ border-right: 0; padding: 0; } - -.actions { - display: flex; - justify-content: center; - max-height: 100%; -} - -.actions a, -.actions button { - margin-left: calc(var(--spacer) / 2); - margin-right: calc(var(--spacer) / 2); -} diff --git a/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx b/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx index 53a51a1fa..58a5122f4 100644 --- a/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx @@ -1,12 +1,14 @@ -import React, { ReactElement, useEffect } from 'react' +import React, { ReactElement, useEffect, useState } from 'react' import styles from './FormComputeDataset.module.css' import { Field, Form, FormikContextType, useFormikContext } from 'formik' import Input from '../../../atoms/Input' import { FormFieldProps } from '../../../../@types/Form' import { useStaticQuery, graphql } from 'gatsby' -import { DDO } from '@oceanprotocol/lib' +import { DDO, BestPrice } from '@oceanprotocol/lib' import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection' import ButtonBuy from '../../../atoms/ButtonBuy' +import PriceOutput from './PriceOutput' +import { useAsset } from '../../../../providers/Asset' const contentQuery = graphql` query StartComputeDatasetQuery { @@ -46,10 +48,17 @@ export default function FormStartCompute({ isComputeButtonDisabled, hasPreviousOrder, hasDatatoken, - dtSymbol, dtBalance, + assetType, + assetTimeout, + hasPreviousOrderSelectedComputeAsset, + hasDatatokenSelectedComputeAsset, + dtSymbolSelectedComputeAsset, + dtBalanceSelectedComputeAsset, + selectedComputeAssetType, + selectedComputeAssetTimeout, stepText, - datasetTimeout + algorithmPrice }: { algorithms: AssetSelectionAsset[] ddoListAlgorithms: DDO[] @@ -58,10 +67,17 @@ export default function FormStartCompute({ isComputeButtonDisabled: boolean hasPreviousOrder: boolean hasDatatoken: boolean - dtSymbol: string dtBalance: string + assetType: string + assetTimeout: string + hasPreviousOrderSelectedComputeAsset?: boolean + hasDatatokenSelectedComputeAsset?: boolean + dtSymbolSelectedComputeAsset?: string + dtBalanceSelectedComputeAsset?: string + selectedComputeAssetType?: string + selectedComputeAssetTimeout?: string stepText: string - datasetTimeout: string + algorithmPrice: BestPrice }): ReactElement { const data = useStaticQuery(contentQuery) const content = data.content.edges[0].node.childPagesJson @@ -70,6 +86,8 @@ export default function FormStartCompute({ isValid, values }: FormikContextType<{ algorithm: string }> = useFormikContext() + const { price, ddo } = useAsset() + const [totalPrice, setTotalPrice] = useState(price?.value) function getAlgorithmAsset(algorithmId: string): DDO { let assetDdo = null @@ -84,6 +102,25 @@ export default function FormStartCompute({ setSelectedAlgorithm(getAlgorithmAsset(values.algorithm)) }, [values.algorithm]) + // + // Set price for calculation output + // + useEffect(() => { + if (!price || !algorithmPrice) return + + const priceDataset = hasPreviousOrder ? 0 : Number(price.value) + const priceAlgo = hasPreviousOrderSelectedComputeAsset + ? 0 + : Number(algorithmPrice.value) + + setTotalPrice(priceDataset + priceAlgo) + }, [ + price, + algorithmPrice, + hasPreviousOrder, + hasPreviousOrderSelectedComputeAsset + ]) + return (
{content.form.data.map((field: FormFieldProps) => ( @@ -95,17 +132,36 @@ export default function FormStartCompute({ /> ))} + + ) diff --git a/src/components/organisms/AssetActions/Compute/PriceOutput.module.css b/src/components/organisms/AssetActions/Compute/PriceOutput.module.css new file mode 100644 index 000000000..e5c30b0b1 --- /dev/null +++ b/src/components/organisms/AssetActions/Compute/PriceOutput.module.css @@ -0,0 +1,54 @@ +.priceComponent { + margin-left: -2rem; + margin-right: -2rem; + margin-top: -1rem; + margin-bottom: calc(var(--spacer) / 1.5); + padding-left: calc(var(--spacer) / 2); + padding-right: calc(var(--spacer) / 2); + border-bottom: 1px solid var(--border-color); + padding-bottom: calc(var(--spacer) / 3); + text-align: center; + color: var(--color-secondary); + font-size: var(--font-size-small); +} + +.priceComponent > * { + display: inline-block !important; +} + +.calculation { + min-width: 12rem; +} + +.timeout { + display: block; + text-align: right; + font-size: var(--font-size-mini); + color: var(--color-secondary); +} + +.calculation .price { + font-size: var(--font-size-small) !important; +} + +.priceRow { + width: 100%; + border-bottom: 1px solid var(--border-color); + padding-top: calc(var(--spacer) / 7); + padding-bottom: calc(var(--spacer) / 7); + display: flex; + justify-content: space-between; +} + +.priceRow:last-child { + border-bottom: none; + border-top: 1px solid var(--border-color); +} + +.sign { + display: inline-block; + width: 5%; + text-align: left; + color: var(--color-secondary); + font-size: var(--font-size-base); +} diff --git a/src/components/organisms/AssetActions/Compute/PriceOutput.tsx b/src/components/organisms/AssetActions/Compute/PriceOutput.tsx new file mode 100644 index 000000000..51b61c55d --- /dev/null +++ b/src/components/organisms/AssetActions/Compute/PriceOutput.tsx @@ -0,0 +1,81 @@ +import { BestPrice } from '@oceanprotocol/lib' +import React, { ReactElement } from 'react' +import { useAsset } from '../../../../providers/Asset' +import PriceUnit from '../../../atoms/Price/PriceUnit' +import Tooltip from '../../../atoms/Tooltip' +import styles from './PriceOutput.module.css' + +interface PriceOutputProps { + totalPrice: number + hasPreviousOrder: boolean + assetTimeout: string + hasPreviousOrderSelectedComputeAsset: boolean + algorithmPrice: BestPrice + selectedComputeAssetTimeout: string +} + +function Row({ + price, + hasPreviousOrder, + timeout, + sign +}: { + price: number + hasPreviousOrder?: boolean + timeout?: string + sign?: string +}) { + return ( +
+
{sign}
+
+ + + {timeout && + timeout !== 'Forever' && + !hasPreviousOrder && + `for ${timeout}`} + +
+
+ ) +} + +export default function PriceOutput({ + totalPrice, + hasPreviousOrder, + assetTimeout, + hasPreviousOrderSelectedComputeAsset, + algorithmPrice, + selectedComputeAssetTimeout +}: PriceOutputProps): ReactElement { + const { price } = useAsset() + + return ( +
+ You will pay + + + + +
+ } + /> +
+ ) +} diff --git a/src/components/organisms/AssetActions/Compute/index.module.css b/src/components/organisms/AssetActions/Compute/index.module.css index 2dd952c7b..b1c7e162a 100644 --- a/src/components/organisms/AssetActions/Compute/index.module.css +++ b/src/components/organisms/AssetActions/Compute/index.module.css @@ -2,21 +2,13 @@ display: flex; align-items: center; width: auto; - margin-bottom: var(--spacer); + margin-bottom: calc(var(--spacer) / 2); border-bottom: 1px solid var(--border-color); margin-top: -1rem; margin-left: -2rem; margin-right: -2rem; - padding: 0 var(--spacer) calc(var(--spacer) / 2) var(--spacer); -} - -.filewrapper { - flex-shrink: 0; -} - -.actions { - margin-top: var(--spacer); - text-align: center; + padding: 0 calc(var(--spacer) / 2) calc(var(--spacer) / 2) + calc(var(--spacer) * 1.5); } .feedback { @@ -24,6 +16,6 @@ margin-top: calc(var(--spacer) / 2); } -.help { - composes: help from '../index.module.css'; +.feedback:empty { + margin-top: 0; } diff --git a/src/components/organisms/AssetActions/Compute/index.stories.tsx b/src/components/organisms/AssetActions/Compute/index.stories.tsx index 756741938..0200b6820 100644 --- a/src/components/organisms/AssetActions/Compute/index.stories.tsx +++ b/src/components/organisms/AssetActions/Compute/index.stories.tsx @@ -1,7 +1,6 @@ import React, { ReactElement } from 'react' import Compute from '.' import ddo from '../../../../../tests/unit/__fixtures__/ddo' -import { DDO } from '@oceanprotocol/lib' export default { title: 'Organisms/Compute', @@ -14,7 +13,6 @@ export default { export const Default = (): ReactElement => ( () @@ -90,6 +88,7 @@ export default function Compute({ const [hasPreviousAlgorithmOrder, setHasPreviousAlgorithmOrder] = useState( false ) + const [algorithmDTBalance, setalgorithmDTBalance] = useState() const [algorithmPrice, setAlgorithmPrice] = useState() const [variables, setVariables] = useState({}) const [ @@ -97,6 +96,7 @@ export default function Compute({ setPreviousAlgorithmOrderId ] = useState() const [datasetTimeout, setDatasetTimeout] = useState() + const [algorithmTimeout, setAlgorithmTimeout] = useState() /* eslint-disable @typescript-eslint/no-unused-vars */ const { @@ -138,6 +138,7 @@ export default function Compute({ asset.dataToken, accountId ) + setalgorithmDTBalance(AssetDtBalance) setHasAlgoAssetDatatoken(Number(AssetDtBalance) >= 1) } @@ -264,6 +265,10 @@ export default function Compute({ } checkAssetDTBalance(selectedAlgorithmAsset) initMetadata(selectedAlgorithmAsset) + const { timeout } = ( + ddo.findServiceByType('access') || ddo.findServiceByType('compute') + ).attributes.main + setAlgorithmTimeout(secondsToString(timeout)) }, [selectedAlgorithmAsset, ocean, accountId, hasPreviousAlgorithmOrder]) // Output errors in toast UI @@ -415,7 +420,7 @@ export default function Compute({ <>
- +
{type === 'algorithm' ? ( @@ -436,14 +441,21 @@ export default function Compute({ setSelectedAlgorithm={setSelectedAlgorithmAsset} isLoading={isJobStarting} isComputeButtonDisabled={isComputeButtonDisabled} - hasPreviousOrder={ - hasPreviousDatasetOrder || hasPreviousAlgorithmOrder - } + hasPreviousOrder={hasPreviousDatasetOrder} hasDatatoken={hasDatatoken} - dtSymbol={ddo.dataTokenInfo?.symbol} dtBalance={dtBalance} + assetType={type} + assetTimeout={datasetTimeout} + hasPreviousOrderSelectedComputeAsset={hasPreviousAlgorithmOrder} + hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken} + dtSymbolSelectedComputeAsset={ + selectedAlgorithmAsset?.dataTokenInfo?.symbol + } + dtBalanceSelectedComputeAsset={algorithmDTBalance} + selectedComputeAssetType="algorithm" + selectedComputeAssetTimeout={algorithmTimeout} stepText={pricingStepText || 'Starting Compute Job...'} - datasetTimeout={datasetTimeout} + algorithmPrice={algorithmPrice} /> )} diff --git a/src/components/organisms/AssetActions/Consume.tsx b/src/components/organisms/AssetActions/Consume.tsx index 4b6db0045..30ba32d08 100644 --- a/src/components/organisms/AssetActions/Consume.tsx +++ b/src/components/organisms/AssetActions/Consume.tsx @@ -47,7 +47,7 @@ export default function Consume({ const { marketFeeAddress } = useSiteMetadata() const [hasPreviousOrder, setHasPreviousOrder] = useState(false) const [previousOrderId, setPreviousOrderId] = useState() - const { isInPurgatory, price } = useAsset() + const { isInPurgatory, price, type } = useAsset() const { buyDT, pricingStepText, @@ -153,6 +153,7 @@ export default function Consume({ dtBalance={dtBalance} onClick={handleConsume} assetTimeout={assetTimeout} + assetType={type} stepText={consumeStepText || pricingStepText} isLoading={pricingIsLoading} /> diff --git a/src/components/organisms/AssetActions/index.tsx b/src/components/organisms/AssetActions/index.tsx index 5a0a98ef6..41ac03ed5 100644 --- a/src/components/organisms/AssetActions/index.tsx +++ b/src/components/organisms/AssetActions/index.tsx @@ -54,7 +54,6 @@ export default function AssetActions(): ReactElement { const UseContent = isCompute ? (