diff --git a/.changeset/mighty-jokes-joke.md b/.changeset/mighty-jokes-joke.md new file mode 100644 index 000000000..c3a6bbbf7 --- /dev/null +++ b/.changeset/mighty-jokes-joke.md @@ -0,0 +1,7 @@ +--- +'hostd': minor +'renterd': minor +'walletd': minor +--- + +Currency display can now be configured to siacoin, fiat, or both along with a preference for when only one can be displayed. diff --git a/.changeset/seven-cars-nail.md b/.changeset/seven-cars-nail.md new file mode 100644 index 000000000..2d3c094bd --- /dev/null +++ b/.changeset/seven-cars-nail.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +Fixed an issue with file search where selecting a file would navigate to the path without the bucket. diff --git a/apps/explorer/components/Layout/Footer.tsx b/apps/explorer/components/Layout/Footer.tsx index cf295348a..35b8c63ff 100644 --- a/apps/explorer/components/Layout/Footer.tsx +++ b/apps/explorer/components/Layout/Footer.tsx @@ -4,7 +4,7 @@ import { webLinks, Logo, ThemeRadio, - CurrencySelector, + CurrencyFiatSelector, } from '@siafoundation/design-system' export function Footer() { @@ -48,7 +48,7 @@ export function Footer() {
- +
diff --git a/apps/explorer/components/Layout/NavDropdownMenu.tsx b/apps/explorer/components/Layout/NavDropdownMenu.tsx index 27b13535c..7f3f05d59 100644 --- a/apps/explorer/components/Layout/NavDropdownMenu.tsx +++ b/apps/explorer/components/Layout/NavDropdownMenu.tsx @@ -7,7 +7,7 @@ import { Link, DropdownMenuGroup, DropdownMenuLabel, - CurrencySelector, + CurrencyFiatSelector, ThemeRadio, } from '@siafoundation/design-system' import { Menu24 } from '@siafoundation/react-icons' @@ -53,7 +53,7 @@ export function NavDropdownMenu({ trigger, children, ...props }: Props) { Settings
- +
diff --git a/apps/hostd/contexts/contracts/columns.tsx b/apps/hostd/contexts/contracts/columns.tsx index 2a04fc8a5..bc019e590 100644 --- a/apps/hostd/contexts/contracts/columns.tsx +++ b/apps/hostd/contexts/contracts/columns.tsx @@ -1,6 +1,5 @@ import { Text, - ValueSc, TableColumn, ValueCopyable, stripPrefix, @@ -9,6 +8,7 @@ import { ContractTimeline, ValueNum, blockHeightToTime, + ValueScFiat, } from '@siafoundation/design-system' import { ArrowUpLeft16, @@ -201,7 +201,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { revision } }) => ( - + ), }, { @@ -210,7 +210,11 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { revision } }) => ( - + ), }, { @@ -219,7 +223,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { lockedCollateral } }) => ( - + ), }, { @@ -228,7 +232,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -237,7 +241,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -246,7 +250,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -255,7 +259,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -264,7 +268,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -273,7 +277,7 @@ export const columns: ContractsTableColumn[] = ( category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { usage } }) => ( - + ), }, { @@ -281,7 +285,9 @@ export const columns: ContractsTableColumn[] = ( label: 'RPC usage', category: 'financial', contentClassName: 'w-[120px] justify-end', - render: ({ data: { usage } }) => , + render: ({ data: { usage } }) => ( + + ), }, ] as ContractsTableColumn[] ).map( diff --git a/apps/hostd/contexts/metrics/index.tsx b/apps/hostd/contexts/metrics/index.tsx index eee1631d2..e3776d2ed 100644 --- a/apps/hostd/contexts/metrics/index.tsx +++ b/apps/hostd/contexts/metrics/index.tsx @@ -7,8 +7,9 @@ import { getTimeRange, MiBToBytes, minutesInMilliseconds, + ValueScFiat, } from '@siafoundation/design-system' -import { humanBytes, humanNumber, humanSiacoin } from '@siafoundation/sia-js' +import { humanBytes, humanNumber } from '@siafoundation/sia-js' import { useCallback, useMemo } from 'react' import { chartConfigs } from '../../config/charts' import { useMetricsPeriod } from '@siafoundation/react-hostd' @@ -249,7 +250,9 @@ function useMetricsMain() { 'total' ), }, - format: (v) => humanSiacoin(v), + formatComponent: function ({ value }) { + return + }, formatTimestamp, disableAnimations, }, @@ -282,7 +285,9 @@ function useMetricsMain() { locked: chartConfigs.locked, risked: chartConfigs.risked, }, - format: (v) => humanSiacoin(v), + formatComponent: function ({ value }) { + return + }, formatTimestamp, disableAnimations, }, @@ -345,7 +350,9 @@ function useMetricsMain() { ingress: chartConfigs.ingress, storage: chartConfigs.storage, }, - format: (v) => humanSiacoin(v), + formatComponent: function ({ value }) { + return + }, formatTimestamp, disableAnimations, }, diff --git a/apps/renterd/components/Config/ConfigStats.tsx b/apps/renterd/components/Config/ConfigStats.tsx index 7c7a01305..4b1f04445 100644 --- a/apps/renterd/components/Config/ConfigStats.tsx +++ b/apps/renterd/components/Config/ConfigStats.tsx @@ -10,7 +10,7 @@ import { humanBytes, toHastings } from '@siafoundation/sia-js' import { useConfig } from '../../contexts/config' import { useApp } from '../../contexts/app' -export function AutopilotStats() { +export function ConfigStats() { const { autopilot } = useApp() const { canEstimate, @@ -35,7 +35,7 @@ export function AutopilotStats() { ) : ( -
+
Estimate: diff --git a/apps/renterd/components/Config/index.tsx b/apps/renterd/components/Config/index.tsx index 010ae12da..68be946ed 100644 --- a/apps/renterd/components/Config/index.tsx +++ b/apps/renterd/components/Config/index.tsx @@ -4,7 +4,7 @@ import { routes } from '../../config/routes' import { useDialog } from '../../contexts/dialog' import { RenterdAuthedLayout } from '../RenterdAuthedLayout' import { useConfig } from '../../contexts/config' -import { AutopilotStats } from './ConfigStats' +import { ConfigStats } from './ConfigStats' import { ConfigActions } from './ConfigActions' import { ConfigNav } from './ConfigNav' @@ -18,7 +18,7 @@ export function Config() { routes={routes} nav={} sidenav={} - stats={} + stats={} actions={} openSettings={() => openDialog('settings')} > diff --git a/apps/renterd/components/Files/FilesCmd/FilesSearchCmd/index.tsx b/apps/renterd/components/Files/FilesCmd/FilesSearchCmd/index.tsx index e316bb349..2927c9603 100644 --- a/apps/renterd/components/Files/FilesCmd/FilesSearchCmd/index.tsx +++ b/apps/renterd/components/Files/FilesCmd/FilesSearchCmd/index.tsx @@ -64,7 +64,10 @@ export function FilesSearchCmd({ key={path} onSelect={() => { beforeSelect() - setActiveDirectory(() => getDirectorySegmentsFromPath(path)) + setActiveDirectory(() => [ + activeBucket, + ...getDirectorySegmentsFromPath(path), + ]) afterSelect() }} value={path} diff --git a/apps/renterd/contexts/contracts/columns.tsx b/apps/renterd/contexts/contracts/columns.tsx index 02de883d4..aa2b11b6c 100644 --- a/apps/renterd/contexts/contracts/columns.tsx +++ b/apps/renterd/contexts/contracts/columns.tsx @@ -1,6 +1,6 @@ import { Text, - ValueSc, + ValueScFiat, TableColumn, ContractTimeline, ValueCopyable, @@ -172,7 +172,7 @@ export const columns: ContractsTableColumn[] = [ category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { totalCost } }) => ( - + ), }, { @@ -181,7 +181,7 @@ export const columns: ContractsTableColumn[] = [ category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { spendingUploads } }) => ( - + ), }, { @@ -190,7 +190,7 @@ export const columns: ContractsTableColumn[] = [ category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { spendingDownloads } }) => ( - + ), }, { @@ -199,7 +199,11 @@ export const columns: ContractsTableColumn[] = [ category: 'financial', contentClassName: 'w-[120px] justify-end', render: ({ data: { spendingFundAccount } }) => ( - + ), }, ] diff --git a/apps/renterd/contexts/hosts/columns.tsx b/apps/renterd/contexts/hosts/columns.tsx index 3e47794fb..0b8f440c0 100644 --- a/apps/renterd/contexts/hosts/columns.tsx +++ b/apps/renterd/contexts/hosts/columns.tsx @@ -5,7 +5,7 @@ import { ValueNum, Tooltip, LoadingDots, - ValueSc, + ValueScFiat, } from '@siafoundation/design-system' import { WarningSquareFilled16, @@ -27,7 +27,7 @@ import { workerRhpScanRoute, } from '@siafoundation/react-renterd' import BigNumber from 'bignumber.js' -import React from 'react' +import React, { memo } from 'react' type HostsTableColumn = TableColumn< TableColumnId, @@ -233,7 +233,7 @@ export const columns: HostsTableColumn[] = ( if (isPending) { return } - const ago = formatDistance(new Date(data.lastScan), new Date(), { + const ago = formatDistance(new Date(data.lastScan || 0), new Date(), { addSuffix: true, }) let message = '' @@ -1087,18 +1087,20 @@ type Key = | keyof AutopilotHost['host']['settings'] function makeRenderSc(section: 'priceTable' | 'settings', name: Key) { - return function RenderPriceTableNumber({ data }: { data: HostData }) { + return memo(function RenderPriceTableNumber({ data }: { data: HostData }) { if (!data[section]) { return null } return ( - ) - } + }) } function makeRenderNumber( diff --git a/apps/walletd/contexts/events/columns.tsx b/apps/walletd/contexts/events/columns.tsx index e5eebdb2f..5f04ae66d 100644 --- a/apps/walletd/contexts/events/columns.tsx +++ b/apps/walletd/contexts/events/columns.tsx @@ -2,8 +2,8 @@ import { Text, TableColumn, ValueCopyable, - ValueSc, LoadingDots, + ValueScFiat, } from '@siafoundation/design-system' import { humanDate } from '@siafoundation/sia-js' import { EventData, TableColumnId } from './types' @@ -105,22 +105,24 @@ export const columns: EventsTableColumn[] = [ id: 'amount', label: 'amount', category: 'general', + contentClassName: 'w-[120px] justify-end', render: ({ data: { amount } }) => { if (!amount) { return null } - return + return }, }, { id: 'fee', label: 'fee', category: 'general', + contentClassName: 'w-[120px] justify-end', render: ({ data: { fee } }) => { if (!fee) { return null } - return + return }, }, { diff --git a/apps/walletd/contexts/events/index.tsx b/apps/walletd/contexts/events/index.tsx index 8e6edaca7..de6fabac5 100644 --- a/apps/walletd/contexts/events/index.tsx +++ b/apps/walletd/contexts/events/index.tsx @@ -82,14 +82,14 @@ export function useEventsMain() { })) const dataEvents: EventData[] = responseEvents.data.map((e, index) => { let amount = new BigNumber(0) - if (e.Type === 'siacoin transfer') { + if (e.type === 'siacoin transfer') { const inputsTotal = - e.Val?.Inputs?.reduce( + e.val?.inputs?.reduce( (acc, o) => acc.plus(o.value), new BigNumber(0) ) || new BigNumber(0) const outputsTotal = - e.Val?.Outputs?.reduce( + e.val?.outputs?.reduce( (acc, o) => acc.plus(o.value), new BigNumber(0) ) || new BigNumber(0) @@ -99,34 +99,34 @@ export function useEventsMain() { const id = String(index) const res: EventData = { id, - type: e.Type, - timestamp: new Date(e.Timestamp).getTime(), - height: e.Index.height, + type: e.type, + timestamp: new Date(e.timestamp).getTime(), + height: e.index.height, pending: false, amount, } - if ('MaturityHeight' in e.Val) { - res.maturityHeight = e.Val.MaturityHeight + if ('maturityHeight' in e.val) { + res.maturityHeight = e.val.maturityHeight } - if ('Fee' in e.Val) { - res.fee = new BigNumber(e.Val.Fee) + if ('fee' in e.val) { + res.fee = new BigNumber(e.val.fee) } - if ('ContractID' in e.Val) { - res.contractId = e.Val.ContractID + if ('contractID' in e.val) { + res.contractId = e.val.contractID } - if ('TransactionID' in e.Val) { - res.id += e.Val.TransactionID - res.transactionId = e.Val.TransactionID + if ('transactionID' in e.val) { + res.id += e.val.transactionID + res.transactionId = e.val.transactionID } - if ('OutputID' in e.Val) { - res.id += e.Val.OutputID - res.outputId = e.Val.OutputID + if ('outputID' in e.val) { + res.id += e.val.outputID + res.outputId = e.val.outputID } - if ('NetAddress' in e.Val) { - res.netAddress = e.Val.NetAddress + if ('netAddress' in e.val) { + res.netAddress = e.val.netAddress } - if ('PublicKey' in e.Val) { - res.publicKey = e.Val.PublicKey + if ('publicKey' in e.val) { + res.publicKey = e.val.publicKey } return res }) diff --git a/apps/walletd/contexts/wallets/columns.tsx b/apps/walletd/contexts/wallets/columns.tsx index 06dd6c0f1..d9d27f54d 100644 --- a/apps/walletd/contexts/wallets/columns.tsx +++ b/apps/walletd/contexts/wallets/columns.tsx @@ -5,8 +5,8 @@ import { Paragraph, Tooltip, Button, - ValueSc, ValueSf, + ValueScFiat, } from '@siafoundation/design-system' import { Locked16, Unlocked16, Draggable16 } from '@siafoundation/react-icons' import { humanDate } from '@siafoundation/sia-js' @@ -87,8 +87,9 @@ export const columns: WalletsTableColumn[] = [ return null } return ( -
- +
{stats && ( -
+
{stats}
)} diff --git a/libs/design-system/src/app/CurrencyDisplaySelector.tsx b/libs/design-system/src/app/CurrencyDisplaySelector.tsx new file mode 100644 index 000000000..5c3951d01 --- /dev/null +++ b/libs/design-system/src/app/CurrencyDisplaySelector.tsx @@ -0,0 +1,33 @@ +'use client' + +import { Option, Select } from '../core/Select' +import { CurrencyDisplay, useAppSettings } from '@siafoundation/react-core' + +const displayOptions = [ + { id: 'sc', label: 'Siacoin' }, + { id: 'fiat', label: 'Fiat' }, + { id: 'bothPreferSc', label: 'Both - prefer siacoin' }, + { id: 'bothPreferFiat', label: 'Both - prefer fiat' }, +] as const + +export function CurrencyDisplaySelector() { + const { settings, setSettings } = useAppSettings() + + return ( + + ) +} diff --git a/libs/design-system/src/app/CurrencySelector.tsx b/libs/design-system/src/app/CurrencyFiatSelector.tsx similarity index 92% rename from libs/design-system/src/app/CurrencySelector.tsx rename to libs/design-system/src/app/CurrencyFiatSelector.tsx index aaad00222..af4ae5b69 100644 --- a/libs/design-system/src/app/CurrencySelector.tsx +++ b/libs/design-system/src/app/CurrencyFiatSelector.tsx @@ -3,7 +3,7 @@ import { Option, Select } from '../core/Select' import { CurrencyId, useAppSettings } from '@siafoundation/react-core' -export function CurrencySelector() { +export function CurrencyFiatSelector() { const { settings, setCurrency, currencyOptions } = useAppSettings() return ( diff --git a/libs/design-system/src/app/DatumCardConfigurable.tsx b/libs/design-system/src/app/DatumCardConfigurable.tsx index 7be911047..f1b7cf66c 100644 --- a/libs/design-system/src/app/DatumCardConfigurable.tsx +++ b/libs/design-system/src/app/DatumCardConfigurable.tsx @@ -4,11 +4,11 @@ import { Option, Select } from '../core/Select' import { Text } from '../core/Text' import { DatumCard } from '../components/DatumCard' import { ValueNum } from '../components/ValueNum' -import { ValueSc } from '../components/ValueSc' import { DataLabel } from './DataLabel' import BigNumber from 'bignumber.js' import { Tooltip } from '../core/Tooltip' import useLocalStorageState from 'use-local-storage-state' +import { ValueScFiat } from '../components/ValueScFiat' type Mode = 'total' | 'average' | 'latest' @@ -87,8 +87,9 @@ export function DatumCardConfigurable({ comment={ sc ? (
- {showChange && sc.change !== undefined && ( diff --git a/libs/design-system/src/app/SettingsDialog.tsx b/libs/design-system/src/app/SettingsDialog.tsx index d1fc3ac8a..1e4e12a3c 100644 --- a/libs/design-system/src/app/SettingsDialog.tsx +++ b/libs/design-system/src/app/SettingsDialog.tsx @@ -17,7 +17,8 @@ import { webLinks } from '../data/webLinks' import { useAppSettings } from '@siafoundation/react-core' import { Dialog } from '../core/Dialog' import { minutesInMilliseconds } from '../lib/time' -import { CurrencySelector } from './CurrencySelector' +import { CurrencyFiatSelector } from './CurrencyFiatSelector' +import { CurrencyDisplaySelector } from './CurrencyDisplaySelector' type Props = { open: boolean @@ -51,13 +52,31 @@ export function SettingsDialog({ open, onOpenChange, securityEl }: Props) { - Currency + Currency display - +
- Select a currency for price conversions from Siacoin. Requires - Sia Central third-party data enabled under Privacy. + Select whether you would like to see currency values in + siacoin, fiat, or both. Fiat requires Sia Central third-party + data enabled under Privacy. + +
+ + +
+
+ + + + + Fiat + + +
+ + Select a fiat currency for price conversions from Siacoin. + Requires Sia Central third-party data enabled under Privacy.
diff --git a/libs/design-system/src/app/WalletBalance.tsx b/libs/design-system/src/app/WalletBalance.tsx index be98f60d3..b5aaa71ef 100644 --- a/libs/design-system/src/app/WalletBalance.tsx +++ b/libs/design-system/src/app/WalletBalance.tsx @@ -1,10 +1,10 @@ import { Panel } from '../core/Panel' import { Text } from '../core/Text' -import { humanSiacoin } from '@siafoundation/sia-js' import BigNumber from 'bignumber.js' import { Warning16 } from '@siafoundation/react-icons' import { Tooltip } from '../core/Tooltip' -import { ValueSc } from '../components/ValueSc' +import { WalletBalanceTip } from './WalletBalanceTip' +import { ValueScFiat } from '../components/ValueScFiat' export function WalletBalance({ balanceSc, @@ -32,55 +32,29 @@ export function WalletBalance({ > - - - - {humanSiacoin(balanceSc.spendable.plus(balanceSc.unconfirmed))} + + ) } return ( - -
-
- spendable - All confirmed outputs not in-use. -
-
- -
-
-
-
- confirmed - All confirmed outputs. -
-
- -
-
-
-
- unconfirmed - All unconfirmed outputs not in-use. -
-
- -
-
-
- } - > + - - {humanSiacoin(balanceSc.spendable.plus(balanceSc.unconfirmed))} - + - + ) } diff --git a/libs/design-system/src/app/WalletBalanceSideNav.tsx b/libs/design-system/src/app/WalletBalanceSideNav.tsx index 494eb8009..4b13c0a82 100644 --- a/libs/design-system/src/app/WalletBalanceSideNav.tsx +++ b/libs/design-system/src/app/WalletBalanceSideNav.tsx @@ -1,8 +1,7 @@ -import { Text } from '../core/Text' -import { humanSiacoin } from '@siafoundation/sia-js' import BigNumber from 'bignumber.js' import { Tooltip } from '../core/Tooltip' -import { ValueSc } from '../components/ValueSc' +import { ValueScFiat } from '../components/ValueScFiat' +import { WalletBalanceTip } from './WalletBalanceTip' export function WalletBalanceSideNav({ balanceSc, @@ -21,6 +20,17 @@ export function WalletBalanceSideNav({ return null } + const el = ( + + ) + if (!isSynced) { return ( - - {humanSiacoin(balanceSc.spendable.plus(balanceSc.unconfirmed), { - fixed: 0, - })} - +
{el}
) } return ( - -
-
- spendable - All confirmed outputs not in-use. -
-
- -
-
-
-
- confirmed - All confirmed outputs. -
-
- -
-
-
-
- unconfirmed - All unconfirmed outputs not in-use. -
-
- -
-
-
- } - > - - {humanSiacoin(balanceSc.spendable.plus(balanceSc.unconfirmed), { - fixed: 0, - })} - - + + {el} + ) } diff --git a/libs/design-system/src/app/WalletBalanceTip.tsx b/libs/design-system/src/app/WalletBalanceTip.tsx new file mode 100644 index 000000000..b889fad52 --- /dev/null +++ b/libs/design-system/src/app/WalletBalanceTip.tsx @@ -0,0 +1,76 @@ +import { Text } from '../core/Text' +import BigNumber from 'bignumber.js' +import { Tooltip } from '../core/Tooltip' +import { ValueScFiat } from '../components/ValueScFiat' +import { Separator } from '../core/Separator' + +export function WalletBalanceTip({ + side, + balanceSc, + children, +}: { + side?: 'left' | 'right' | 'top' | 'bottom' + balanceSc?: { + unconfirmed: BigNumber + confirmed: BigNumber + spendable: BigNumber + } + children: React.ReactNode +}) { + if (!balanceSc) { + return null + } + + return ( + +
+
+ spendable + All confirmed outputs not in-use. +
+
+ +
+
+ +
+
+ confirmed + All confirmed outputs. +
+
+ +
+
+ +
+
+ unconfirmed + All unconfirmed outputs not in-use. +
+
+ +
+
+
+ } + > +
{children}
+ + ) +} diff --git a/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx b/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx index b975c093d..0bd7531d4 100644 --- a/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx +++ b/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx @@ -296,6 +296,7 @@ export function ChartXYGraph({ return acc + val }, 0) : 0 + const Component = config?.formatComponent return (
({ nearestKey === key ? 'underline' : '' )} > - {val == null || Number.isNaN(val) - ? '–' - : config.format(val)} + {val == null || Number.isNaN(val) ? ( + '–' + ) : Component ? ( + + ) : config.format ? ( + config.format(val) + ) : ( + val + )} ) @@ -345,7 +352,13 @@ export function ChartXYGraph({ weight="bold" className="pt-2 text-end" > - {config.format(total)} + {Component ? ( + + ) : config.format ? ( + config.format(total) + ) : ( + total + )} )} diff --git a/libs/design-system/src/components/ChartXY/types.ts b/libs/design-system/src/components/ChartXY/types.ts index dc123743c..2e73a9358 100644 --- a/libs/design-system/src/components/ChartXY/types.ts +++ b/libs/design-system/src/components/ChartXY/types.ts @@ -13,7 +13,8 @@ export type ChartConfig = { toOpacity?: number } > - format: (v: number) => string + format?: (v: number) => string + formatComponent?: React.FC<{ value: number }> formatTimestamp?: (v: number) => string disableAnimations?: boolean } diff --git a/libs/design-system/src/components/DatumCard.tsx b/libs/design-system/src/components/DatumCard.tsx index 53566264f..2b873f55d 100644 --- a/libs/design-system/src/components/DatumCard.tsx +++ b/libs/design-system/src/components/DatumCard.tsx @@ -1,6 +1,5 @@ import { Text } from '../core/Text' import { ValueSf } from '../components/ValueSf' -import { ValueSc } from '../components/ValueSc' import { ValueCopyable } from '../components/ValueCopyable' import { Tooltip } from '../core/Tooltip' import BigNumber from 'bignumber.js' @@ -9,6 +8,7 @@ import { Panel } from '../core/Panel' import { EntityType, getEntityTypeLabel } from '../lib/entityTypes' import { cx } from 'class-variance-authority' import { Skeleton } from '../core/Skeleton' +import { ValueScFiat } from './ValueScFiat' // entityType&entityValue | value | values | sc | sf type Props = { @@ -72,7 +72,7 @@ export function DatumCard({ {!isLoading ? ( <> {sc !== undefined && ( - {title || truncHashEl}
- {!!sc && } + {!!sc && } {!!sf && }
diff --git a/libs/design-system/src/components/ValueFiat.tsx b/libs/design-system/src/components/ValueFiat.tsx new file mode 100644 index 000000000..640a5ae1d --- /dev/null +++ b/libs/design-system/src/components/ValueFiat.tsx @@ -0,0 +1,120 @@ +'use client' + +import { useSiaCentralExchangeRates } from '@siafoundation/react-sia-central' +import { Text } from '../core/Text' +import { Tooltip } from '../core/Tooltip' +import BigNumber from 'bignumber.js' +import { useAppSettings } from '@siafoundation/react-core' + +type Props = { + size?: React.ComponentProps['size'] + scaleSize?: React.ComponentProps['scaleSize'] + sc: BigNumber // hastings + color?: React.ComponentProps['color'] + variant?: 'change' | 'value' + tooltip?: string + fixed?: number + fixedTip?: number + dynamicUnits?: boolean + hastingUnits?: boolean + extendedSuffix?: string + showTooltip?: boolean +} + +export function ValueFiat({ + sc, + size, + scaleSize, + color: customColor, + tooltip = '', + variant = 'change', + fixed, + fixedTip = 20, + showTooltip = true, +}: Props) { + const exchangeRates = useSiaCentralExchangeRates() + const { + settings: { currency }, + } = useAppSettings() + const sign = sc.isZero() + ? '' + : sc.isGreaterThan(0) && variant === 'change' + ? '+' + : sc.isLessThan(0) + ? '-' + : '' + const color = + customColor || + (variant === 'change' + ? sc.isGreaterThan(0) + ? 'green' + : sc.isLessThan(0) + ? 'red' + : 'subtle' + : 'contrast') + + if (!exchangeRates.data) { + return null + } + const fiat = new BigNumber(exchangeRates.data.rates.sc[currency.id] || 1) + .times(sc) + .div(1e24) + + const digits = fixed !== undefined ? fixed : currency.fixed + + const el = ( + + {`${sign}${currency.prefix}${formatBigNumberLocale( + fiat.absoluteValue(), + digits + )}`} + + ) + + if (showTooltip) { + return ( + + {el} + + ) + } + + return el +} + +function formatBigNumberLocale(bigNumber: BigNumber, fixed: number): string { + // Convert BigNumber to a string and split into integer and decimal parts + const [integerPart, decimalPart] = bigNumber.toFixed(fixed).split('.') + + // Format the integer part using the locale + const formattedIntegerPart = new Intl.NumberFormat().format( + parseInt(integerPart) + ) + + // Return the formatted number with the original decimal part + return decimalPart + ? `${formattedIntegerPart}${getDecimalSeparator()}${decimalPart}` + : formattedIntegerPart +} + +function getDecimalSeparator(): string { + const numberWithDecimalSeparator = 1.1 + return Intl.NumberFormat().format(numberWithDecimalSeparator).charAt(1) +} diff --git a/libs/design-system/src/components/ValueSc.tsx b/libs/design-system/src/components/ValueSc.tsx index 54e20093f..df7841a9c 100644 --- a/libs/design-system/src/components/ValueSc.tsx +++ b/libs/design-system/src/components/ValueSc.tsx @@ -12,6 +12,7 @@ type Props = { variant?: 'change' | 'value' tooltip?: string fixed?: number + color?: React.ComponentProps['color'] dynamicUnits?: boolean hastingUnits?: boolean extendedSuffix?: string @@ -24,6 +25,7 @@ export function ValueSc({ scaleSize, tooltip = '', variant = 'change', + color: customColor, fixed = 3, dynamicUnits = true, hastingUnits = true, @@ -32,13 +34,14 @@ export function ValueSc({ }: Props) { const sign = value.isGreaterThan(0) ? '+' : value.isLessThan(0) ? '-' : '' const color = - variant === 'change' + customColor || + (variant === 'change' ? value.isGreaterThan(0) ? 'green' : value.isLessThan(0) ? 'red' : 'subtle' - : 'contrast' + : 'contrast') const el = ( ['size'] + scaleSize?: React.ComponentProps['scaleSize'] + value: BigNumber // hastings + variant?: 'change' | 'value' + tooltip?: string + fixed?: number + fixedFiat?: number + fixedTipFiat?: number + dynamicUnits?: boolean + hastingUnits?: boolean + extendedSuffix?: string + showTooltip?: boolean + displayBoth?: boolean + displayBothDirection?: 'row' | 'column' +} + +export function ValueScFiat({ + value, + size, + scaleSize, + tooltip = '', + variant = 'change', + fixed = 3, + fixedFiat, + fixedTipFiat, + dynamicUnits = true, + hastingUnits = true, + extendedSuffix, + showTooltip = true, + displayBoth = false, + displayBothDirection = 'column', +}: Props) { + const { + settings: { currencyDisplay }, + } = useAppSettings() + const exchangeRates = useSiaCentralExchangeRates() + + const scEl = useMemo( + () => ( + + ), + [ + value, + size, + scaleSize, + tooltip, + variant, + fixed, + dynamicUnits, + hastingUnits, + extendedSuffix, + showTooltip, + currencyDisplay, + displayBoth, + displayBothDirection, + ] + ) + + const fiatEl = useMemo( + () => ( + + ), + [ + value, + size, + scaleSize, + tooltip, + variant, + fixedFiat, + fixedTipFiat, + dynamicUnits, + hastingUnits, + extendedSuffix, + showTooltip, + currencyDisplay, + displayBoth, + displayBothDirection, + ] + ) + + if (currencyDisplay === 'sc') { + return scEl + } + + if (!exchangeRates.data) { + return scEl + } + + if (currencyDisplay === 'fiat') { + return fiatEl + } + + if (currencyDisplay === 'bothPreferSc') { + if (displayBoth) { + return ( +
+ {scEl} + {fiatEl} +
+ ) + } else { + return scEl + } + } + + if (currencyDisplay === 'bothPreferFiat') { + if (displayBoth) { + return ( +
+ {fiatEl} + {scEl} +
+ ) + } else { + return fiatEl + } + } + + return scEl +} diff --git a/libs/design-system/src/index.ts b/libs/design-system/src/index.ts index abd5ffc5c..ab7b9ac7c 100644 --- a/libs/design-system/src/index.ts +++ b/libs/design-system/src/index.ts @@ -58,6 +58,7 @@ export * from './components/ValueCopyable' export * from './components/ValueMenu' export * from './components/ValueSf' export * from './components/ValueSc' +export * from './components/ValueScFiat' export * from './components/ValueNum' export * from './components/EntityList' export * from './components/EntityListItem' @@ -88,7 +89,7 @@ export * from './app/WalletAddAddressDialog' export * from './app/WalletSingleAddressDetailsDialog' export * from './app/WalletSyncWarning' export * from './app/SettingsDialog' -export * from './app/CurrencySelector' +export * from './app/CurrencyFiatSelector' export * from './app/WalletAddressCode' export * from './app/WalletLayoutActions' export * from './app/WalletBalanceEvolution' diff --git a/libs/design-system/src/lib/numbers.ts b/libs/design-system/src/lib/numbers.ts index fdf4747d1..6a8877951 100644 --- a/libs/design-system/src/lib/numbers.ts +++ b/libs/design-system/src/lib/numbers.ts @@ -11,7 +11,9 @@ export function toFixedOrPrecision( digits, dynamicFixed = true, }: { + // number of digits to round to digits: number + // if true, will use toFixed if decimalPlaces > digits dynamicFixed?: boolean } ) { diff --git a/libs/react-core/src/useAppSettings/index.tsx b/libs/react-core/src/useAppSettings/index.tsx index ffc7da7f8..f1ffd2771 100644 --- a/libs/react-core/src/useAppSettings/index.tsx +++ b/libs/react-core/src/useAppSettings/index.tsx @@ -9,6 +9,8 @@ import { clearAllSwrKeys } from '../utils' import { useWorkflows } from '../workflows' import { CurrencyId, CurrencyOption, currencyOptions } from './currency' +export type CurrencyDisplay = 'sc' | 'fiat' | 'bothPreferSc' | 'bothPreferFiat' + export type AppSettings = { api: string allowCustomApi: boolean @@ -20,6 +22,7 @@ export type AppSettings = { lastUsed: number } } + currencyDisplay: CurrencyDisplay autoLock?: boolean autoLockTimeout?: number } @@ -30,6 +33,7 @@ const defaultSettings: AppSettings = { siaCentral: true, password: undefined, currency: currencyOptions[0], + currencyDisplay: 'bothPreferSc', recentApis: {}, autoLock: false, autoLockTimeout: 1000 * 60 * 10, // 10 minutes diff --git a/libs/react-walletd/src/siaTypes.ts b/libs/react-walletd/src/siaTypes.ts index 574fa5020..6977eb60e 100644 --- a/libs/react-walletd/src/siaTypes.ts +++ b/libs/react-walletd/src/siaTypes.ts @@ -142,130 +142,130 @@ export type PoolTransaction = { // events type BaseEvent = { - Timestamp: string - Index: ChainIndex + timestamp: string + index: ChainIndex } // EventBlockReward represents a block reward. type EventBlockReward = BaseEvent & { - Type: 'block reward' - Val: { - OutputID: string - Output: SiacoinOutput - MaturityHeight: number + type: 'block reward' + val: { + outputID: string + output: SiacoinOutput + maturityHeight: number } } // EventFoundationSubsidy represents a Foundation subsidy. type EventFoundationSubsidy = BaseEvent & { - Type: 'foundation subsidy' - Val: { - OutputID: string - Output: SiacoinOutput - MaturityHeight: number + type: 'foundation subsidy' + val: { + outputID: string + output: SiacoinOutput + maturityHeight: number } } // EventSiacoinMaturation represents the maturation of a siacoin output. type EventSiacoinMaturation = BaseEvent & { - Type: 'siacoin maturation' - Val: { - OutputID: string - Output: SiacoinOutput - Source: number + type: 'siacoin maturation' + val: { + outputID: string + output: SiacoinOutput + source: number } } // EventSiacoinTransfer represents the transfer of siacoins within a // transaction. type EventSiacoinTransfer = BaseEvent & { - Type: 'siacoin transfer' - Val: { - TransactionID: string - Inputs: SiacoinElement[] - Outputs: SiacoinElement[] - Fee: Currency + type: 'siacoin transfer' + val: { + transactionID: string + inputs: SiacoinElement[] + outputs: SiacoinElement[] + fee: Currency } } // EventSiafundTransfer represents the transfer of siafunds within a // transaction. type EventSiafundTransfer = BaseEvent & { - Type: 'siafund transfer' - Val: { - TransactionID: string - Inputs: SiafundElement[] - Outputs: SiafundElement[] - ClaimOutputID: string - ClaimOutput: SiacoinOutput + type: 'siafund transfer' + val: { + transactionID: string + inputs: SiafundElement[] + outputs: SiafundElement[] + claimOutputID: string + claimOutput: SiacoinOutput } } // EventFileContractFormation represents the formation of a file contract within // a transaction. type EventFileContractFormation = BaseEvent & { - Type: 'file contract formation' - Val: { - TransactionID: string - ContractID: string - Contract: FileContract + type: 'file contract formation' + val: { + transactionID: string + contractID: string + contract: FileContract } } // EventFileContractRevision represents the revision of a file contract within // a transaction. type EventFileContractRevision = BaseEvent & { - Type: 'file contract revision' - Val: { - TransactionID: string - ContractID: string - OldContract: FileContract - NewContract: FileContract + type: 'file contract revision' + val: { + transactionID: string + contractID: string + oldContract: FileContract + newContract: FileContract } } // EventFileContractResolutionValid represents the valid resolution of a file // contract within a transaction. type EventFileContractResolutionValid = BaseEvent & { - Type: 'file contract resolution (valid)' - Val: { - TransactionID: string - ContractID: string - Contract: FileContract - OutputID: string - Output: SiacoinOutput - MaturityHeight: number + type: 'file contract resolution (valid)' + val: { + transactionID: string + contractID: string + contract: FileContract + outputID: string + output: SiacoinOutput + maturityHeight: number } } // EventFileContractResolutionMissed represents the expiration of a file // contract. type EventFileContractResolutionMissed = BaseEvent & { - Type: 'file contract resolution (missed)' - Val: { - Contract: FileContract - OutputID: string - Output: SiacoinOutput - MaturityHeight: number + type: 'file contract resolution (missed)' + val: { + contract: FileContract + outputID: string + output: SiacoinOutput + maturityHeight: number } } // EventHostAnnouncement represents a host announcement within a transaction. type EventHostAnnouncement = BaseEvent & { - Type: 'host annoucement' - Val: { - TransactionID: string - PublicKey: string - NetAddress: string + type: 'host annoucement' + val: { + transactionID: string + publicKey: string + netAddress: string } } // EventTransaction represents a generic transaction. type EventTransaction = BaseEvent & { - Type: 'transaction' - Val: { - TransactionID: string - Transaction: Transaction + type: 'transaction' + val: { + transactionID: string + transaction: Transaction } }