Browse Source

💸 Fiat prices (#4370)

* Add service for token amount

* Rework hook and NumberFormat.tsx

* Implement denomination in new places

* Always align fiat value to right

* Add new places with denomination

* Initial CR fixes

* Adjust skeleton sizes

* Add denomination for new component
WRadoslaw 1 year ago
parent
commit
822b68926d
21 changed files with 300 additions and 170 deletions
  1. 1 1
      packages/atlas/atlas.config.yml
  2. 1 0
      packages/atlas/src/components/Fee/Fee.tsx
  3. 3 0
      packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/NftCarouselDetails.tsx
  4. 130 12
      packages/atlas/src/components/NumberFormat/NumberFormat.tsx
  5. 9 12
      packages/atlas/src/components/TablePaymentsHistory/TablePaymentsHistory.tsx
  6. 3 1
      packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.tsx
  7. 1 1
      packages/atlas/src/components/WidgetTile/WidgetTile.styles.ts
  8. 1 0
      packages/atlas/src/components/_nft/NftTile/NftTile.styles.ts
  9. 1 0
      packages/atlas/src/components/_nft/NftTile/NftTileDetails.styles.ts
  10. 10 2
      packages/atlas/src/components/_nft/NftTile/NftTileDetails.tsx
  11. 0 15
      packages/atlas/src/components/_nft/NftWidget/NftHistory.styles.ts
  12. 9 22
      packages/atlas/src/components/_nft/NftWidget/NftHistory.tsx
  13. 25 37
      packages/atlas/src/components/_nft/NftWidget/NftWidgetContent.tsx
  14. 12 12
      packages/atlas/src/components/_overlays/AcceptBidDialog/AcceptBidList.tsx
  15. 17 5
      packages/atlas/src/components/_overlays/MemberDropdown/MemberDropdownNav.tsx
  16. 9 0
      packages/atlas/src/providers/joystream/joystream.hooks.ts
  17. 15 5
      packages/atlas/src/views/global/NftPurchaseBottomDrawer/NftPurchaseBottomDrawer.tsx
  18. 19 11
      packages/atlas/src/views/studio/MyPaymentsView/PaymentsOverview/PaymentsOverView.tsx
  19. 8 25
      packages/atlas/src/views/studio/MyPaymentsView/PaymentsOverview/PaymentsOverview.styles.ts
  20. 8 1
      packages/atlas/src/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.styles.ts
  21. 18 8
      packages/atlas/src/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.tsx

+ 1 - 1
packages/atlas/atlas.config.yml

@@ -24,7 +24,7 @@ storage:
 
 joystream:
   tokenTicker: 'JOY' # Ticker for the token used in the app
-  tokenPriceFeedUrl: null # URL for the token price feed - used to display token price in the app
+  tokenPriceFeedUrl: 'https://status.joystream.org/price' # URL for the token price feed - used to display token price in the app
   alternativeNodes:
     - name: 'Jsgenesis (Europe/UK)'
       url: 'wss://testnet-rpc-3-uk.joystream.org'

+ 1 - 0
packages/atlas/src/components/Fee/Fee.tsx

@@ -49,6 +49,7 @@ export const Fee: FC<FeeProps> = ({
             variant={variant}
             color={loading ? 'colorTextMuted' : color}
             withTooltip
+            withDenomination="after"
             format="short"
             margin={{ right: 1 }}
           />

+ 3 - 0
packages/atlas/src/components/NftCarousel/components/MarketplaceCarouselCard/NftCarouselDetails.tsx

@@ -191,6 +191,7 @@ export const NftCarouselDetails = ({
                     caption="BUY NOW"
                     content={nftDetails.buyNow}
                     icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
+                    withDenomination
                   />
                 )}
                 {nftDetails.topBid && (
@@ -200,6 +201,7 @@ export const NftCarouselDetails = ({
                     caption="TOP BID"
                     content={nftDetails.topBid}
                     icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
+                    withDenomination
                   />
                 )}
                 {nftDetails.minBid && (
@@ -209,6 +211,7 @@ export const NftCarouselDetails = ({
                     caption="MIN BID"
                     content={nftDetails.minBid}
                     icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
+                    withDenomination
                   />
                 )}
                 {nftStatus?.status === 'idle' && (

+ 130 - 12
packages/atlas/src/components/NumberFormat/NumberFormat.tsx

@@ -1,11 +1,14 @@
+import { css } from '@emotion/react'
 import styled from '@emotion/styled'
 import BN from 'bn.js'
-import { forwardRef, useRef } from 'react'
+import { ReactNode, forwardRef, useRef } from 'react'
 import { mergeRefs } from 'react-merge-refs'
 
 import { Text, TextProps, TextVariant } from '@/components/Text'
 import { atlasConfig } from '@/config'
 import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
+import { useTokenPrice } from '@/providers/joystream/joystream.hooks'
+import { sizes } from '@/styles'
 import { formatNumber } from '@/utils/number'
 
 import { Tooltip } from '../Tooltip'
@@ -19,8 +22,13 @@ export type NumberFormatProps = {
   variant?: TextVariant
   displayedValue?: string | number
   isNegative?: boolean
+  icon?: ReactNode
+  withDenomination?: boolean | 'horizontal' | 'vertical' | 'before' | 'after'
+  denominationAlign?: 'left' | 'right'
 } & Omit<TextProps, 'children' | 'variant'>
 
+const TEXT_DENOMINATION_ALIGNMENTS: NumberFormatProps['withDenomination'][] = ['before', 'after']
+
 export const NumberFormat = forwardRef<HTMLHeadingElement, NumberFormatProps>(
   (
     {
@@ -32,25 +40,37 @@ export const NumberFormat = forwardRef<HTMLHeadingElement, NumberFormatProps>(
       displayedValue,
       isNegative,
       color,
+      withDenomination: _withDenomination,
+      denominationAlign = 'left',
+      icon,
       ...textProps
     },
     ref
   ) => {
+    const withDenomination = atlasConfig.joystream.tokenPriceFeedUrl ? _withDenomination : undefined
+    const { convertTokensToUSD } = useTokenPrice()
     const internalValue = BN.isBN(value) ? hapiBnToTokenNumber(value) : value
+    const fiatValue = convertTokensToUSD(internalValue)
     const textRef = useRef<HTMLHeadingElement>(null)
+    const denominationRef = useRef<HTMLHeadingElement>(null)
     const bnValue = new BN(value)
     let formattedValue
+    let formattedDenominatedValue
     let tooltipText
     switch (isNegative || bnValue.isNeg() ? 'full' : format) {
       case 'short':
-        formattedValue = internalValue ? (internalValue > 0.01 ? formatNumberShort(internalValue) : `< 0.01`) : 0
+        formattedValue = internalValue ? (internalValue > 0.01 ? formatNumberShort(internalValue) : `<0.01`) : 0
+        formattedDenominatedValue = fiatValue ? (fiatValue > 0.01 ? formatNumberShort(fiatValue) : `<$0.01`) : 0
         tooltipText = formatNumber(internalValue)
         break
       case 'full':
         formattedValue = tooltipText = formatNumber(internalValue)
+        formattedDenominatedValue = fiatValue ? formatNumber(fiatValue) : 0
         break
       case 'dollar':
         formattedValue = formatDollars(internalValue)
+        formattedDenominatedValue = fiatValue ? formatDollars(fiatValue) : 0
+
         tooltipText = new Intl.NumberFormat('en-US', { maximumSignificantDigits, ...currencyFormatOptions })
           .format(internalValue)
           .replaceAll(',', ' ')
@@ -61,32 +81,130 @@ export const NumberFormat = forwardRef<HTMLHeadingElement, NumberFormatProps>(
     const hasTooltip =
       withTooltip &&
       ((format === 'short' && (internalValue > 999 || hasDecimals)) || (format === 'dollar' && hasDecimals))
+    const shouldShowDenominationTooltip = fiatValue && fiatValue <= 0.01
     const content = (
-      <StyledText
-        {...textProps}
-        color={bnValue.isNeg() || isNegative ? 'colorTextError' : color}
-        variant={variant}
-        ref={mergeRefs([ref, textRef])}
-      >
-        {displayedValue || formattedValue}
-        {withToken && ` ${atlasConfig.joystream.tokenTicker}`}
-      </StyledText>
+      <ContentContainer>
+        {withDenomination === 'before' && (
+          <Text
+            className="denomination"
+            as="span"
+            color={bnValue.isNeg() || isNegative ? 'colorTextError' : 'colorTextMuted'}
+            variant={variant}
+            ref={denominationRef}
+          >
+            ({formattedDenominatedValue !== '<$0.01' ? '$' : ''}
+            {formattedDenominatedValue}){' '}
+          </Text>
+        )}
+        <StyledText
+          {...textProps}
+          color={bnValue.isNeg() || isNegative ? 'colorTextError' : color}
+          variant={variant}
+          ref={mergeRefs([ref, textRef])}
+        >
+          {displayedValue || formattedValue}
+          {withToken && ` ${atlasConfig.joystream.tokenTicker}`}
+        </StyledText>
+        {withDenomination === 'after' && (
+          <Text
+            className="denomination"
+            as="span"
+            color={bnValue.isNeg() || isNegative ? 'colorTextError' : 'colorTextMuted'}
+            variant={variant}
+            ref={denominationRef}
+          >
+            {' '}
+            ({formattedDenominatedValue !== '<$0.01' ? '$' : ''}
+            {formattedDenominatedValue}){' '}
+          </Text>
+        )}
+      </ContentContainer>
     )
 
     return (
       <>
-        {content}
+        {withDenomination ? (
+          <Container orientation={withDenomination}>
+            {icon ? (
+              <IconContainer>
+                {icon}
+                {content}
+              </IconContainer>
+            ) : (
+              content
+            )}
+            {!TEXT_DENOMINATION_ALIGNMENTS.includes(withDenomination) && (
+              <Denomination
+                align={denominationAlign}
+                className="denomination"
+                as="span"
+                color={bnValue.isNeg() || isNegative ? 'colorTextError' : 'colorTextMuted'}
+                variant="t100"
+                ref={denominationRef}
+              >
+                {formattedDenominatedValue !== '<$0.01' ? '$' : ''}
+                {formattedDenominatedValue}
+              </Denomination>
+            )}
+          </Container>
+        ) : icon ? (
+          <IconContainer>
+            {icon}
+            {content}
+          </IconContainer>
+        ) : (
+          content
+        )}
+
         <Tooltip reference={textRef} placement="top" delay={[500, null]} text={hasTooltip ? tooltipText : undefined} />
+        <Tooltip
+          reference={denominationRef}
+          placement="top"
+          delay={[500, null]}
+          text={shouldShowDenominationTooltip ? `$${fiatValue?.toPrecision(2)}` : undefined}
+        />
       </>
     )
   }
 )
 NumberFormat.displayName = 'Number'
 
+export const ContentContainer = styled.div`
+  display: inline-block;
+`
+
 const StyledText = styled(Text)`
   display: inline-block;
 `
 
+const Denomination = styled(Text)<{ align: 'right' | 'left' }>`
+  display: inline-block;
+  text-align: ${(props) => props.align};
+`
+
+const Container = styled.div<{ orientation: NumberFormatProps['withDenomination'] }>`
+  display: flex;
+  flex-direction: column;
+  gap: ${sizes(1)};
+  width: fit-content;
+  ${(props) =>
+    props.orientation === 'horizontal' &&
+    css`
+      width: 100%;
+      flex-direction: row;
+      align-items: center;
+      justify-content: space-between;
+    `}
+`
+
+const IconContainer = styled.div`
+  display: inline-grid;
+  grid-auto-flow: column;
+  grid-auto-columns: max-content;
+  align-items: center;
+  gap: ${sizes(1)};
+`
+
 const maximumSignificantDigits = 21
 
 const currencyFormatOptions = {

+ 9 - 12
packages/atlas/src/components/TablePaymentsHistory/TablePaymentsHistory.tsx

@@ -18,7 +18,6 @@ import { formatDateTime } from '@/utils/time'
 
 import {
   DialogText,
-  JoyAmountWrapper,
   JoystreamSvgWrapper,
   SenderItem,
   StyledJoyTokenIcon,
@@ -174,16 +173,14 @@ const Type = ({ type }: { type: PaymentType }) => {
 const TokenAmount = ({ tokenAmount }: { tokenAmount: BN }) => {
   const isNegative = tokenAmount.isNeg()
   return (
-    <JoyAmountWrapper>
-      <StyledJoyTokenIcon variant="gray" error={isNegative} />
-      <StyledNumberFormat
-        variant="t200-strong"
-        as="p"
-        value={tokenAmount}
-        margin={{ left: 1 }}
-        format="short"
-        color={isNegative ? 'colorTextError' : 'colorTextStrong'}
-      />
-    </JoyAmountWrapper>
+    <StyledNumberFormat
+      icon={<StyledJoyTokenIcon variant="gray" error={isNegative} />}
+      variant="t200-strong"
+      as="p"
+      value={tokenAmount}
+      format="short"
+      color={isNegative ? 'colorTextError' : 'colorTextStrong'}
+      withDenomination
+    />
   )
 }

+ 3 - 1
packages/atlas/src/components/TopSellingChannelsTable/TopSellingChannelsTable.tsx

@@ -100,13 +100,15 @@ export const TopSellingChannelsTable = () => {
           ),
           salesVolume: (
             <JoyAmountWrapper>
-              <JoyTokenIcon variant="gray" />
               <NumberFormat
+                icon={<JoyTokenIcon variant="gray" />}
                 variant="t200-strong"
                 as="p"
                 value={new BN(data.amount)}
                 margin={{ left: 1 }}
                 format="short"
+                withDenomination
+                denominationAlign="right"
               />
             </JoyAmountWrapper>
           ),

+ 1 - 1
packages/atlas/src/components/WidgetTile/WidgetTile.styles.ts

@@ -24,7 +24,7 @@ export const Content = styled.div`
   align-self: flex-end;
   gap: ${sizes(4)};
 
-  ${media.lg} {
+  ${media.md} {
     gap: ${sizes(6)};
     grid-template-columns: 1fr auto;
     align-items: center;

+ 1 - 0
packages/atlas/src/components/_nft/NftTile/NftTile.styles.ts

@@ -6,4 +6,5 @@ type ContainerProps = {
 
 export const Container = styled.div<ContainerProps>`
   width: ${({ fullWidth }) => (fullWidth ? '100%' : '320px')};
+  overflow: hidden;
 `

+ 1 - 0
packages/atlas/src/components/_nft/NftTile/NftTileDetails.styles.ts

@@ -62,6 +62,7 @@ const tileSizeVariants = ({ tileSize }: ContentProps) => css`
 const isContentPropValid = (prop: string) => prop !== 'loading' && prop !== 'tileSize' && prop !== 'shouldHover'
 export const Content = styled(Link, { shouldForwardProp: isContentPropValid })<ContentProps>`
   display: block;
+  height: 100%;
   text-decoration: none;
   background-color: ${cVar('colorBackgroundMuted')};
   transition: background-color ${cVar('animationTransitionFast')};

+ 10 - 2
packages/atlas/src/components/_nft/NftTile/NftTileDetails.tsx

@@ -118,6 +118,7 @@ export const NftTileDetails: FC<NftTileDetailsProps> = memo(
               content="Not for sale"
               icon={<SvgActionNotForSale />}
               secondary
+              withDenomination
             />
           )
         case 'buy-now':
@@ -127,6 +128,7 @@ export const NftTileDetails: FC<NftTileDetailsProps> = memo(
               caption="Buy now"
               content={buyNowPrice ?? 0}
               icon={<JoyTokenIcon size={16} variant="regular" />}
+              withDenomination
             />
           )
         case 'auction':
@@ -138,6 +140,7 @@ export const NftTileDetails: FC<NftTileDetailsProps> = memo(
                   caption="Top bid"
                   content={topBid}
                   icon={<JoyTokenIcon size={16} variant="regular" />}
+                  withDenomination
                 />
               ) : (
                 <DetailsContent
@@ -145,6 +148,7 @@ export const NftTileDetails: FC<NftTileDetailsProps> = memo(
                   caption="Min bid"
                   content={startingPrice ?? 0}
                   icon={<JoyTokenIcon size={16} variant="regular" />}
+                  withDenomination
                 />
               )}
               {!!buyNowPrice && (
@@ -153,6 +157,7 @@ export const NftTileDetails: FC<NftTileDetailsProps> = memo(
                   caption="Buy now"
                   content={buyNowPrice}
                   icon={<JoyTokenIcon size={16} variant="regular" />}
+                  withDenomination
                 />
               )}
             </>
@@ -254,9 +259,10 @@ type DetailsContentProps = {
   content: number | string | ReactElement | ReactElement[]
   secondary?: boolean
   tileSize: TileSize | undefined
+  withDenomination?: boolean
 }
 export const DetailsContent: FC<DetailsContentProps> = memo(
-  ({ tileSize, caption, icon, content, secondary, avoidIconStyling }) => {
+  ({ tileSize, caption, icon, content, secondary, avoidIconStyling, withDenomination }) => {
     const getSize = () => {
       switch (tileSize) {
         case 'small':
@@ -277,7 +283,7 @@ export const DetailsContent: FC<DetailsContentProps> = memo(
           {caption}
         </Text>
         <DetailsContentWrapper avoidIconStyling={avoidIconStyling} secondary={secondary}>
-          {icon}{' '}
+          {typeof content === 'string' && icon}{' '}
           {typeof content === 'string' ? (
             <Text as="span" variant={getSize().content} color={secondary ? 'colorText' : undefined}>
               {content}
@@ -285,10 +291,12 @@ export const DetailsContent: FC<DetailsContentProps> = memo(
           ) : typeof content === 'number' ? (
             <NumberFormat
               as="span"
+              icon={icon}
               value={content}
               format="short"
               variant={getSize().content}
               color={secondary ? 'colorText' : undefined}
+              withDenomination={withDenomination}
             />
           ) : (
             content

+ 0 - 15
packages/atlas/src/components/_nft/NftWidget/NftHistory.styles.ts

@@ -57,21 +57,6 @@ export const CopyContainer = styled.div`
   display: flex;
 `
 
-export const ValueContainer = styled.div`
-  display: grid;
-  gap: ${sizes(1)};
-  grid-auto-rows: max-content;
-`
-
-export const JoyPlusIcon = styled.div`
-  display: grid;
-  gap: ${sizes(1)};
-  align-items: center;
-  grid-auto-flow: column;
-  grid-auto-columns: max-content;
-  justify-content: end;
-`
-
 export const HistoryPanelContainer = styled.div`
   position: relative;
 `

+ 9 - 22
packages/atlas/src/components/_nft/NftWidget/NftHistory.tsx

@@ -9,7 +9,6 @@ import { NumberFormat } from '@/components/NumberFormat'
 import { Text } from '@/components/Text'
 import { absoluteRoutes } from '@/config/routes'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
-import { useTokenPrice } from '@/providers/joystream/joystream.hooks'
 import { formatDateTime } from '@/utils/time'
 
 import {
@@ -17,10 +16,8 @@ import {
   HistoryItemContainer,
   HistoryPanel,
   HistoryPanelContainer,
-  JoyPlusIcon,
   NftHistoryHeader,
   TextContainer,
-  ValueContainer,
 } from './NftHistory.styles'
 import { OwnerHandle, Size } from './NftWidget.styles'
 
@@ -56,9 +53,6 @@ type HistoryItemProps = {
 export const HistoryItem: FC<HistoryItemProps> = ({ size, member, date, joyAmount, text }) => {
   const navigate = useNavigate()
   const { urls, isLoadingAsset } = getMemberAvatar(member)
-  const { convertHapiToUSD } = useTokenPrice()
-
-  const dollarValue = joyAmount ? convertHapiToUSD(joyAmount) : null
 
   return (
     <HistoryItemContainer data-size={size}>
@@ -85,22 +79,15 @@ export const HistoryItem: FC<HistoryItemProps> = ({ size, member, date, joyAmoun
         </Text>
       </TextContainer>
       {!!joyAmount && (
-        <ValueContainer>
-          <JoyPlusIcon>
-            <JoyTokenIcon size={16} variant="silver" />
-            <NumberFormat as="span" format="short" value={joyAmount} variant={size === 'medium' ? 'h300' : 'h200'} />
-          </JoyPlusIcon>
-          {dollarValue !== null && (
-            <NumberFormat
-              as="span"
-              format="dollar"
-              variant="t100"
-              color="colorText"
-              value={dollarValue || 0}
-              align="end"
-            />
-          )}
-        </ValueContainer>
+        <NumberFormat
+          as="span"
+          icon={<JoyTokenIcon size={16} variant="silver" />}
+          format="short"
+          value={joyAmount}
+          variant={size === 'medium' ? 'h300' : 'h200'}
+          withDenomination
+          denominationAlign="right"
+        />
       )}
     </HistoryItemContainer>
   )

+ 25 - 37
packages/atlas/src/components/_nft/NftWidget/NftWidgetContent.tsx

@@ -84,16 +84,15 @@ export const NftWidgetContent: FC<NftWidgetContentProps> = memo(
                 size={size}
                 label="Last price"
                 content={
-                  <>
-                    <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
-                    <NumberFormat
-                      as="span"
-                      value={nftStatus.lastSalePrice}
-                      format="short"
-                      variant={contentTextVariant}
-                      color="colorText"
-                    />
-                  </>
+                  <NumberFormat
+                    as="span"
+                    value={nftStatus.lastSalePrice}
+                    format="short"
+                    variant={contentTextVariant}
+                    icon={<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />}
+                    color="colorText"
+                    withDenomination
+                  />
                 }
                 secondaryText={nftStatus.lastSaleDate && formatDateTime(nftStatus.lastSaleDate)}
               />
@@ -256,7 +255,6 @@ export const NftWidgetContent: FC<NftWidgetContentProps> = memo(
         )
 
         const topBidAmountInUsd = nftStatus.topBidAmount && convertHapiToUSD(nftStatus.topBidAmount)
-        const startingPriceInUsd = convertHapiToUSD(nftStatus.startingPrice)
 
         return (
           <>
@@ -301,21 +299,14 @@ export const NftWidgetContent: FC<NftWidgetContentProps> = memo(
                 size={size}
                 label="Starting Price"
                 content={
-                  <>
-                    <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
-                    <NumberFormat
-                      as="span"
-                      format="short"
-                      value={nftStatus.startingPrice}
-                      variant={contentTextVariant}
-                    />
-                  </>
-                }
-                disableSecondary={startingPriceInUsd === null}
-                secondaryText={
-                  startingPriceInUsd && (
-                    <NumberFormat as="span" color="colorText" format="dollar" value={startingPriceInUsd ?? 0} />
-                  )
+                  <NumberFormat
+                    as="span"
+                    format="short"
+                    value={nftStatus.startingPrice}
+                    variant={contentTextVariant}
+                    icon={<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />}
+                    withDenomination
+                  />
                 }
               />
             )}
@@ -474,23 +465,20 @@ const BidPlacingInfoText = () => (
 )
 
 export const BuyNow = memo(({ buyNowPrice, size }: { buyNowPrice?: BN; size: Size }) => {
-  const { convertHapiToUSD } = useTokenPrice()
-  const buyNowPriceInUsd = buyNowPrice && convertHapiToUSD(buyNowPrice)
-
   const contentTextVariant = size === 'small' ? 'h400' : 'h600'
   return buyNowPrice?.gtn(0) ? (
     <NftInfoItem
       size={size}
       label="Buy now"
-      disableSecondary={buyNowPriceInUsd === null}
       content={
-        <>
-          <JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
-          <NumberFormat as="span" value={buyNowPrice} format="short" variant={contentTextVariant} />
-        </>
-      }
-      secondaryText={
-        buyNowPriceInUsd && <NumberFormat as="span" color="colorText" format="dollar" value={buyNowPriceInUsd} />
+        <NumberFormat
+          icon={<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />}
+          withDenomination
+          as="span"
+          value={buyNowPrice}
+          format="short"
+          variant={contentTextVariant}
+        />
       }
     />
   ) : null

+ 12 - 12
packages/atlas/src/components/_overlays/AcceptBidDialog/AcceptBidList.tsx

@@ -7,12 +7,11 @@ import { NumberFormat } from '@/components/NumberFormat'
 import { Text } from '@/components/Text'
 import { RadioInput } from '@/components/_inputs/RadioInput'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
-import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
 import { formatDateTime } from '@/utils/time'
 
 import { Bid, SelectedBid } from './AcceptBidDialog.types'
-import { BidRowWrapper, Price, TokenPrice } from './AcceptBidList.styles'
+import { BidRowWrapper, Price } from './AcceptBidList.styles'
 
 type BidRowProps = {
   selectedBid?: SelectedBid
@@ -43,7 +42,7 @@ export const AcceptBidList: FC<AcceptBidListProps> = ({ items, onSelect, selecte
   )
 }
 
-export const BidRow: FC<BidRowProps> = ({ bidder, createdAt, amount, amountUSD, selectedBid, onSelect }) => {
+export const BidRow: FC<BidRowProps> = ({ bidder, createdAt, amount, selectedBid, onSelect }) => {
   const xsMatch = useMediaMatch('xs')
   const selected = selectedBid?.bidderId === bidder.id
   const { urls, isLoadingAsset } = getMemberAvatar(bidder)
@@ -64,15 +63,16 @@ export const BidRow: FC<BidRowProps> = ({ bidder, createdAt, amount, amountUSD,
         </Text>
       </div>
       <Price>
-        <TokenPrice>
-          <JoyTokenIcon variant={selected ? 'regular' : 'gray'} />
-          <Text as="p" variant="h300" margin={{ left: 1 }} color={!selected ? 'colorText' : undefined}>
-            {hapiBnToTokenNumber(amount)}
-          </Text>
-        </TokenPrice>
-        {amountUSD !== null && (
-          <NumberFormat value={amountUSD} format="dollar" as="p" variant="t100" color="colorText" />
-        )}
+        <NumberFormat
+          value={amount}
+          icon={<JoyTokenIcon variant={selected ? 'regular' : 'gray'} />}
+          format="short"
+          as="p"
+          variant="h300"
+          color={!selected ? 'colorText' : undefined}
+          withDenomination
+          denominationAlign="right"
+        />
       </Price>
     </BidRowWrapper>
   )

+ 17 - 5
packages/atlas/src/components/_overlays/MemberDropdown/MemberDropdownNav.tsx

@@ -193,10 +193,14 @@ export const MemberDropdownNav: FC<MemberDropdownNavProps> = ({
                       {selectedChannel?.title}
                     </MemberHandleText>
                     {channelBalance !== undefined ? (
-                      <UserBalance>
-                        <JoyTokenIcon size={16} variant="regular" />
-                        <NumberFormat as="span" variant="t200-strong" value={channelBalance} format="short" />
-                      </UserBalance>
+                      <NumberFormat
+                        icon={<JoyTokenIcon size={16} variant="regular" />}
+                        as="span"
+                        variant="t200-strong"
+                        value={channelBalance}
+                        format="short"
+                        withDenomination
+                      />
                     ) : (
                       <SkeletonLoader width={30} height={20} />
                     )}
@@ -213,14 +217,22 @@ export const MemberDropdownNav: FC<MemberDropdownNavProps> = ({
                     >
                       {accountBalance !== undefined ? (
                         <UserBalance>
-                          <JoyTokenIcon isNegative={isInDebt} size={16} variant="regular" withoutInformationTooltip />
                           <NumberFormat
+                            icon={
+                              <JoyTokenIcon
+                                isNegative={isInDebt}
+                                size={16}
+                                variant="regular"
+                                withoutInformationTooltip
+                              />
+                            }
                             withTooltip={false}
                             as="span"
                             variant="t200-strong"
                             value={accountBalance}
                             format="short"
                             isNegative={isInDebt}
+                            withDenomination
                           />
                         </UserBalance>
                       ) : (

+ 9 - 0
packages/atlas/src/providers/joystream/joystream.hooks.ts

@@ -45,6 +45,14 @@ export const useTokenPrice = () => {
     },
     [tokenPrice]
   )
+
+  const convertTokensToUSD = useCallback(
+    (tokens: number) => {
+      if (!tokenPrice) return null
+      return tokens * tokenPrice
+    },
+    [tokenPrice]
+  )
   const convertUSDToHapi = useCallback(
     (dollars: number) => {
       if (!tokenPrice) return new BN(0)
@@ -56,6 +64,7 @@ export const useTokenPrice = () => {
 
   return {
     convertHapiToUSD,
+    convertTokensToUSD,
     convertUSDToHapi,
     isLoadingPrice,
   }

+ 15 - 5
packages/atlas/src/views/global/NftPurchaseBottomDrawer/NftPurchaseBottomDrawer.tsx

@@ -575,6 +575,7 @@ export const NftPurchaseBottomDrawer: FC = () => {
                   withToken
                   variant="t100"
                   color={hasInsufficientFunds ? 'colorTextError' : 'colorText'}
+                  withDenomination="before"
                 />
               ) : (
                 <SkeletonLoader width={82} height={16} />
@@ -591,6 +592,7 @@ export const NftPurchaseBottomDrawer: FC = () => {
                   withToken
                   variant="t100"
                   color="colorText"
+                  withDenomination="before"
                 />
               )}
             </Row>
@@ -603,19 +605,26 @@ export const NftPurchaseBottomDrawer: FC = () => {
                   {(canBuyNow ? !buyNowFee.toNumber() : !makeBidFee.toNumber()) ||
                   buyNowFeeLoading ||
                   makeBidFeeLoading ? (
-                    <SkeletonLoader width={80} height={16} />
+                    <SkeletonLoader width={80} height={18} />
                   ) : (
-                    <NumberFormat as="span" value={transactionFee} withToken variant="t100" color="colorText" />
+                    <NumberFormat
+                      as="span"
+                      value={transactionFee}
+                      withToken
+                      variant="t100"
+                      color="colorText"
+                      withDenomination="before"
+                    />
                   )}
                 </Row>
                 <Row>
-                  <Text as="span" variant="h500" color="colorText">
+                  <Text as="span" variant="h300">
                     You will pay
                   </Text>
                   {(canBuyNow ? !buyNowFee.toNumber() : !makeBidFee.toNumber()) ||
                   buyNowFeeLoading ||
                   makeBidFeeLoading ? (
-                    <SkeletonLoader width={112} height={32} />
+                    <SkeletonLoader width={112} height={24} />
                   ) : (
                     <NumberFormat
                       as="span"
@@ -626,7 +635,8 @@ export const NftPurchaseBottomDrawer: FC = () => {
                       withToken
                       format="short"
                       withTooltip
-                      variant="h500"
+                      variant="h300"
+                      withDenomination="before"
                     />
                   )}
                 </Row>

+ 19 - 11
packages/atlas/src/views/studio/MyPaymentsView/PaymentsOverview/PaymentsOverView.tsx

@@ -2,20 +2,19 @@ import { BN } from 'bn.js'
 import { useMemo, useState } from 'react'
 
 import { useFullChannel } from '@/api/hooks/channel'
-import { SvgAlertsInformative24, SvgJoyTokenMonochrome24 } from '@/assets/icons'
+import { SvgAlertsInformative24 } from '@/assets/icons'
+import { NumberFormat } from '@/components/NumberFormat'
 import { Text } from '@/components/Text'
 import { WidgetTile } from '@/components/WidgetTile'
 import { ClaimChannelPaymentsDialog } from '@/components/_overlays/ClaimChannelPaymentsDialog'
 import { WithdrawFundsDialog } from '@/components/_overlays/SendTransferDialogs'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
-import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
 import { useSubscribeAccountBalance } from '@/providers/joystream/joystream.hooks'
 import { useUser } from '@/providers/user/user.hooks'
-import { formatNumber } from '@/utils/number'
 
 import { useChannelPayout } from './PaymentsOverview.hooks'
-import { CustomNodeWrapper, TilesWrapper } from './PaymentsOverview.styles'
+import { CustomNodeWrapper, StyledSvgJoyTokenMonochrome24, TilesWrapper } from './PaymentsOverview.styles'
 
 export const PaymentsOverView = () => {
   const { channelId, activeMembership } = useUser()
@@ -35,9 +34,6 @@ export const PaymentsOverView = () => {
       channelStateBloatBond: memoizedChannelStateBloatBond,
     }) || new BN(0)
 
-  const formattedChannelBalance = formatNumber(hapiBnToTokenNumber(channelBalance || new BN(0)))
-  const formattedReward = formatNumber(availableAward || 0)
-
   const mdMatch = useMediaMatch('md')
 
   return (
@@ -56,7 +52,6 @@ export const PaymentsOverView = () => {
         <WidgetTile
           title="Claimable Rewards"
           loading={isAwardLoading || loading}
-          text={availableAward ? formattedReward : undefined}
           button={
             availableAward
               ? {
@@ -67,7 +62,14 @@ export const PaymentsOverView = () => {
               : undefined
           }
           customNode={
-            availableAward ? undefined : (
+            availableAward ? (
+              <NumberFormat
+                value={availableAward}
+                as="span"
+                icon={<StyledSvgJoyTokenMonochrome24 />}
+                withDenomination
+              />
+            ) : (
               <CustomNodeWrapper>
                 <SvgAlertsInformative24 />
                 <Text variant="t100" as="p" color="colorText">
@@ -80,8 +82,14 @@ export const PaymentsOverView = () => {
         />
         <WidgetTile
           title="Channel balance"
-          icon={<SvgJoyTokenMonochrome24 />}
-          text={formattedChannelBalance}
+          customNode={
+            <NumberFormat
+              value={channelBalance || new BN(0)}
+              as="span"
+              icon={<StyledSvgJoyTokenMonochrome24 />}
+              withDenomination
+            />
+          }
           loading={loading || channelBalance === undefined}
           button={{
             text: 'Withdraw',

+ 8 - 25
packages/atlas/src/views/studio/MyPaymentsView/PaymentsOverview/PaymentsOverview.styles.ts

@@ -1,7 +1,8 @@
 import styled from '@emotion/styled'
 
+import { SvgJoyTokenMonochrome24 } from '@/assets/icons'
 import { JoyTokenIcon } from '@/components/JoyTokenIcon'
-import { cVar, media, sizes, zIndex } from '@/styles'
+import { cVar, media, sizes } from '@/styles'
 
 export const CustomNodeWrapper = styled.div`
   display: grid;
@@ -10,30 +11,6 @@ export const CustomNodeWrapper = styled.div`
   gap: ${sizes(2)};
 `
 
-export const AvatarAndTokenWrapper = styled.div`
-  display: flex;
-  align-items: center;
-`
-
-export const TokenWrapper = styled.div`
-  position: relative;
-  left: -4px;
-  z-index: ${zIndex.overlay};
-  margin-right: ${sizes(2)};
-
-  /* token background */
-
-  &::before {
-    content: '';
-    position: absolute;
-    width: 28px;
-    height: 28px;
-    background-color: ${cVar('colorBackgroundMuted')};
-    border-radius: 100%;
-    left: -2px;
-    top: -2px;
-  }
-`
 export const StyledJoyTokenIcon = styled(JoyTokenIcon)`
   position: relative;
 `
@@ -54,3 +31,9 @@ export const TilesWrapper = styled.div`
     margin-bottom: ${sizes(6)};
   }
 `
+
+export const StyledSvgJoyTokenMonochrome24 = styled(SvgJoyTokenMonochrome24)`
+  path {
+    fill: ${cVar('colorText')};
+  }
+`

+ 8 - 1
packages/atlas/src/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.styles.ts

@@ -1,7 +1,8 @@
 import { css } from '@emotion/react'
 import styled from '@emotion/styled'
 
-import { media, sizes } from '@/styles'
+import { SvgJoyTokenMonochrome24 } from '@/assets/icons'
+import { cVar, media, sizes } from '@/styles'
 
 export const TilesWrapper = styled.div`
   display: grid;
@@ -30,3 +31,9 @@ export const TableWrapper = styled.div<{ isEmpty?: boolean }>`
         `
       : ''}
 `
+
+export const StyledSvgJoyTokenMonochrome24 = styled(SvgJoyTokenMonochrome24)`
+  path {
+    fill: ${cVar('colorText')};
+  }
+`

+ 18 - 8
packages/atlas/src/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.tsx

@@ -1,15 +1,13 @@
 import { useMemo } from 'react'
 
-import { SvgJoyTokenMonochrome24 } from '@/assets/icons'
+import { NumberFormat } from '@/components/NumberFormat'
 import { TablePaymentsHistory } from '@/components/TablePaymentsHistory'
 import { WidgetTile } from '@/components/WidgetTile'
-import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
 import { useUser } from '@/providers/user/user.hooks'
-import { formatNumber } from '@/utils/number'
 import { useChannelPaymentsHistory } from '@/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.hooks'
 import { aggregatePaymentHistory } from '@/views/studio/MyPaymentsView/PaymentsTransactions/PaymentTransactions.utils'
 
-import { TableWrapper, TilesWrapper } from './PaymentTransactions.styles'
+import { StyledSvgJoyTokenMonochrome24, TableWrapper, TilesWrapper } from './PaymentTransactions.styles'
 
 export const PaymentTransactions = () => {
   const { channelId } = useUser()
@@ -22,15 +20,27 @@ export const PaymentTransactions = () => {
       <TilesWrapper>
         <WidgetTile
           title="Total earned"
-          text={formatNumber(hapiBnToTokenNumber(paymentHistoryOverview.totalEarned))}
+          customNode={
+            <NumberFormat
+              value={paymentHistoryOverview.totalEarned}
+              as="span"
+              icon={<StyledSvgJoyTokenMonochrome24 />}
+              withDenomination
+            />
+          }
           loading={loading}
-          icon={<SvgJoyTokenMonochrome24 />}
         />
         <WidgetTile
           title="Total withdrawn"
           loading={loading}
-          text={formatNumber(hapiBnToTokenNumber(paymentHistoryOverview.totalWithdrawn.abs()))}
-          icon={<SvgJoyTokenMonochrome24 />}
+          customNode={
+            <NumberFormat
+              value={paymentHistoryOverview.totalWithdrawn.abs()}
+              as="span"
+              icon={<StyledSvgJoyTokenMonochrome24 />}
+              withDenomination
+            />
+          }
         />
       </TilesWrapper>
       <TableWrapper isEmpty={!paymentData?.length}>