From 4cfe38599120137940b18191179191a358583302 Mon Sep 17 00:00:00 2001 From: Thebora Kompanioni Date: Tue, 20 Aug 2024 10:24:21 +0200 Subject: [PATCH] refactor: align utxo list and modal components (#815) * refactor: externalize UtxoIcon component and utxoTags function * refactor: externalize UtxoConfirmations component * refactor: reuse utxo icons in Jar details and UTXO list * refactor(ui): simpler checkbox in utxo list * refactor(send): vertically align balance * refactor(send): tooltip for shortened addresses * refactor(ui): externalize UtxoTags component --- src/components/Send/ShowUtxos.module.css | 90 +++--------- src/components/Send/ShowUtxos.tsx | 136 ++++++------------ .../jar_details/UtxoList.module.css | 63 -------- src/components/jar_details/UtxoList.tsx | 92 ++---------- src/components/utxo/Confirmations.module.css | 10 ++ src/components/utxo/Confirmations.tsx | 51 +++++++ src/components/utxo/UtxoIcon.module.css | 5 + src/components/utxo/UtxoIcon.tsx | 59 ++++++++ src/components/utxo/UtxoTags.module.css | 37 +++++ src/components/utxo/UtxoTags.tsx | 19 +++ src/components/utxo/utils.ts | 44 ++++++ src/context/WalletContext.tsx | 2 - 12 files changed, 299 insertions(+), 309 deletions(-) create mode 100644 src/components/utxo/Confirmations.module.css create mode 100644 src/components/utxo/Confirmations.tsx create mode 100644 src/components/utxo/UtxoIcon.module.css create mode 100644 src/components/utxo/UtxoIcon.tsx create mode 100644 src/components/utxo/UtxoTags.module.css create mode 100644 src/components/utxo/UtxoTags.tsx create mode 100644 src/components/utxo/utils.ts diff --git a/src/components/Send/ShowUtxos.module.css b/src/components/Send/ShowUtxos.module.css index 97d512a17..50bea0e8c 100644 --- a/src/components/Send/ShowUtxos.module.css +++ b/src/components/Send/ShowUtxos.module.css @@ -1,84 +1,36 @@ -.joinedUtxoAndCjout { - background-color: #27ae600d !important; - color: #27ae60 !important; -} - -.frozenUtxo { - background-color: #2d9cdb0d !important; - color: #2d9cdb !important; -} - -.depositUtxo { - background-color: var(--bs-body-bg) !important; - color: var(--bs-modal-color) !important; -} - -.changeAndReuseUtxo { - background-color: #eb57570d !important; - color: #eb5757 !important; -} - -.utxoTagDeposit { - color: #999999; - border: 1px solid #bbbbbb; - background-color: #dedede !important; - border-radius: 0.35rem; - padding: 0rem 0.25rem; -} - -.utxoTagJoinedAndCjout { - border: 1px solid #27ae60; - background-color: #c6eed7 !important; - border-radius: 0.35rem; - padding: 0rem 0.25rem; -} - -.utxoTagFreeze { - border: 1px solid #2d9cdb; - background-color: #bce7ff !important; - border-radius: 0.35rem; - padding: 0rem 0.25rem; +.utxoListDisplayHeight { + max-height: 17.6rem; } -.utxoTagChangeAndReuse { - border: 1px solid #eb5757; - background-color: #fac7c7 !important; - border-radius: 0.35rem; - padding: 0rem 0.25rem; +.row.row-normal { + background-color: transparent !important; } -.squareToggleButton { - appearance: none; - width: 22px; - height: 22px; - border-radius: 3px; - border: 1px solid var(--bs-body-color); - margin-top: 0.45rem; +.row.row-success { + background-color: #27ae600d !important; + color: #27ae60 !important; } -.selected { - visibility: visible !important; - background-color: var(--bs-body-color); +.row.row-warning { + background-color: #bb97200d !important; + color: #ebc957 !important; } -.squareFrozenToggleButton { - appearance: none; - width: 22px; - height: 22px; - border-radius: 3px; - border: 1px solid #2d9cdb; - margin-top: 0.45rem; +.row.row-danger { + background-color: #eb57570d !important; + color: #eb5757 !important; } -.utxoListDisplayHeight { - max-height: 17.6rem; +.row.row-frozen { + background-color: #2d9cdb0d !important; + color: #2d9cdb !important; } -.customHeaderClass { - background-color: var(--bs-gray-800) !important; - padding: var(--bs-modal-header-padding) !important; +.checkbox { + width: 24px; + height: 24px; } -.customTitleClass { - color: var(--bs-heading-color) !important; +.checkbox:checked { + accent-color: var(--bs-black); } diff --git a/src/components/Send/ShowUtxos.tsx b/src/components/Send/ShowUtxos.tsx index 8714de224..b7100ab49 100755 --- a/src/components/Send/ShowUtxos.tsx +++ b/src/components/Send/ShowUtxos.tsx @@ -13,7 +13,10 @@ import Balance from '../Balance' import Divider from '../Divider' import { BaseModal } from '../Modal' import Sprite from '../Sprite' -import { utxoTags } from '../jar_details/UtxoList' +import { utxoTags } from '../utxo/utils' +import { UtxoConfirmations } from '../utxo/Confirmations' +import UtxoIcon from '../utxo/UtxoIcon' +import UtxoTags from '../utxo/UtxoTags' import { shortenStringMiddle } from '../../utils' import styles from './ShowUtxos.module.css' @@ -42,81 +45,15 @@ interface UtxoListDisplayProps { showBackgroundColor: boolean } -// Utility function to Identifies Icons -const utxoIcon = (tag: string, isFrozen: boolean) => { - if (isFrozen && tag === 'bond') return 'timelock' - if (isFrozen) return 'snowflake' - if (tag === 'bond') return 'timelock' - if (tag === 'cj-out') return 'mixed' - if (tag === 'deposit' || tag === 'non-cj-change' || tag === 'reused') return 'unmixed' - return 'unmixed' // fallback -} - -// Utility function to allot classes -const allotClasses = (tag: string, isFrozen: boolean) => { - if (isFrozen) return { row: styles.frozenUtxo, tag: styles.utxoTagFreeze } - if (tag === 'deposit') return { row: styles.depositUtxo, tag: styles.utxoTagDeposit } - if (tag === 'joined' || tag === 'cj-out') return { row: styles.joinedUtxoAndCjout, tag: styles.utxoTagJoinedAndCjout } - if (tag === 'non-cj-change' || tag === 'reused') - return { row: styles.changeAndReuseUtxo, tag: styles.utxoTagChangeAndReuse } - return { row: styles.depositUtxo, tag: styles.utxoTagDeposit } -} - -interface ConfirmationFormat { - symbol: string - display: string - confirmations: number -} - -const formatConfirmations = (confirmations: number): ConfirmationFormat => ({ - symbol: `confs-${confirmations >= 6 ? 'full' : confirmations}`, - display: confirmations > 9999 ? `${Number(9999).toLocaleString()}+` : confirmations.toLocaleString(), - confirmations, -}) - -const Confirmations = ({ value }: { value: ConfirmationFormat }) => - value.confirmations > 9999 ? ( - {value.confirmations.toLocaleString()}} - > -
- - {value.display} -
-
- ) : ( -
- - {value.display} -
- ) - const UtxoRow = ({ utxo, onToggle, showBackgroundColor, settings, walletInfo, t }: UtxoRowProps) => { - const address = useMemo(() => shortenStringMiddle(utxo.address, 16), [utxo.address]) - const confFormat = useMemo(() => formatConfirmations(utxo.confirmations), [utxo.confirmations]) - const tag = useMemo(() => utxoTags(utxo, walletInfo, t), [utxo, walletInfo, t]) - - const { icon, rowAndTagClass } = useMemo(() => { - if (tag.length === 0) { - return { icon: 'unmixed', rowAndTagClass: { row: styles.depositUtxo, tag: styles.utxoTagDeposit } } - } - return { icon: utxoIcon(tag[0].tag, utxo.frozen), rowAndTagClass: allotClasses(tag[0].tag, utxo.frozen) } - }, [tag, utxo.frozen]) + const displayAddress = useMemo(() => shortenStringMiddle(utxo.address, 24), [utxo.address]) + const tags = useMemo(() => utxoTags(utxo, walletInfo, t), [utxo, walletInfo, t]) return ( {utxo.selectable && ( - utxo.selectable && onToggle(utxo)} - className={classNames(utxo.frozen ? styles.squareFrozenToggleButton : styles.squareToggleButton, { - [styles.selected]: utxo.checked, - })} - /> +
+ utxo.selectable && onToggle(utxo)} + className={styles.checkbox} + /> +
)}
- + + + + ( + + {utxo.address} + + )} + > + {displayAddress} + - {address} - + -
{tag.length ? tag[0].tag : ''}
+
) } -type SelectableUtxoTableRowData = SelectableUtxo & { - // TODO: add "tags" here and remove from "Utxo" type - // tags?: { tag: string; color: string }[] -} & Pick +type SelectableUtxoTableRowData = SelectableUtxo & Pick const UtxoListDisplay = ({ utxos, onToggle, settings, showBackgroundColor = true }: UtxoListDisplayProps) => { const { t } = useTranslation() @@ -172,14 +116,16 @@ const UtxoListDisplay = ({ utxos, onToggle, settings, showBackgroundColor = true const TABLE_THEME = { Table: ` - --data-table-library_grid-template-columns: 3.5rem 2.5rem 12rem 2fr 3fr 10rem; - @media only screen and (min-width: 768px) { - --data-table-library_grid-template-columns: 3.5rem 2.5rem 14rem 5fr 3fr 10rem}; - } + --data-table-library_grid-template-columns: 2.5rem 2.5rem 17rem 3rem 12rem 1fr}; `, BaseCell: ` padding: 0.35rem 0.25rem !important; margin: 0.15rem 0px !important; + `, + Cell: ` + &:nth-of-type(5) { + text-align: right; + } `, } const tableTheme = useTheme(TABLE_THEME) @@ -265,8 +211,8 @@ const ShowUtxos = ({ isOpen, onCancel, onConfirm, isLoading, utxos, alert }: Sho backdrop={true} title={t('show_utxos.show_utxo_title')} closeButton - headerClassName={styles.customHeaderClass} - titleClassName={styles.customTitleClass} + headerClassName="" + titleClassName="" > {isLoading ? ( diff --git a/src/components/jar_details/UtxoList.module.css b/src/components/jar_details/UtxoList.module.css index 81a0d1648..79a0f6f05 100644 --- a/src/components/jar_details/UtxoList.module.css +++ b/src/components/jar_details/UtxoList.module.css @@ -7,10 +7,6 @@ margin-bottom: 1px; } -.utxoList .utxoIcon > .iconFrozen { - margin-bottom: 1px; -} - .utxoList tr.frozen td { --bs-code-color: var(--bs-blue); color: var(--bs-blue); @@ -36,65 +32,6 @@ display: none !important; } -.utxoList .utxoIcon > .iconLocked { - margin-bottom: 3px; -} - -.utxoList .utxoTagList { - display: flex; - gap: 0.5rem; - color: var(--bs-body-color); -} - -.utxoList .utxoConfirmations { - width: 2rem; - display: flex; - gap: 0.1rem; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 0.1rem 0.2rem; - border-radius: 0.2rem; - color: var(--bs-success); - font-size: 0.6rem; -} - -.utxoList .utxoConfirmations-0 { - color: var(--bs-info); -} - -.utxoList .utxoTagList > .utxoTag { - display: flex; - justify-content: center; - align-items: center; - gap: 0.3rem; - font-size: 0.7rem; - border: 1px solid var(--bs-body-color); - border-radius: 0.2rem; - padding: 0.1rem 0.3rem; -} - -.utxoList .utxoTagList > .utxoTag.utxoTag-success { - color: var(--bs-success); - background-color: rgba(var(--bs-success-rgb), 0.1); - border-color: var(--bs-success); -} -.utxoList .utxoTagList > .utxoTag.utxoTag-warning { - color: var(--bs-warning); - background-color: rgba(var(--bs-warning-rgb), 0.1); - border-color: var(--bs-warning); -} -.utxoList .utxoTagList > .utxoTag.utxoTag-danger { - color: var(--bs-danger); - background-color: rgba(var(--bs-danger-rgb), 0.1); - border-color: var(--bs-danger); -} -.utxoList .utxoTagList > .utxoTag.utxoTag-dark { - color: var(--bs-white); - background-color: var(--bs-dark); - border-color: var(--bs-dark); -} - .utxoList .utxoListButtonDetails { font-size: 0.9rem; } diff --git a/src/components/jar_details/UtxoList.tsx b/src/components/jar_details/UtxoList.tsx index e782f0800..891ab4330 100644 --- a/src/components/jar_details/UtxoList.tsx +++ b/src/components/jar_details/UtxoList.tsx @@ -2,7 +2,6 @@ import { useMemo } from 'react' import * as rb from 'react-bootstrap' import classNames from 'classnames' import { useTranslation } from 'react-i18next' -import { TFunction } from 'i18next' import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table' import { usePagination } from '@table-library/react-table-library/pagination' import { useRowSelect, HeaderCellSelect, CellSelect, SelectTypes } from '@table-library/react-table-library/select' @@ -13,11 +12,15 @@ import { useTheme } from '@table-library/react-table-library/theme' import { useSettings } from '../../context/SettingsContext' import { Utxo, WalletInfo } from '../../context/WalletContext' import * as fb from '../fb/utils' +import { utxoTags, UtxoTag } from '../utxo/utils' import { shortenStringMiddle } from '../../utils' import Sprite from '../Sprite' import Balance from '../Balance' import TablePagination from '../TablePagination' import styles from './UtxoList.module.css' +import UtxoIcon from '../utxo/UtxoIcon' +import { UtxoConfirmations } from '../utxo/Confirmations' +import UtxoTags from '../utxo/UtxoTags' const withTooltip = ({ node, tooltip }: { node: React.ReactElement; tooltip: React.ReactElement }) => { return ( @@ -25,74 +28,6 @@ const withTooltip = ({ node, tooltip }: { node: React.ReactElement; tooltip: Rea ) } -const ADDRESS_STATUS_COLORS: { [key: string]: string } = { - new: 'normal', - used: 'normal', - reused: 'danger', - 'cj-out': 'success', - 'change-out': 'warning', - 'non-cj-change': 'normal', - deposit: 'normal', -} - -type Tag = { tag: string; color: string } - -export const utxoTags = (utxo: Utxo, walletInfo: WalletInfo, t: TFunction): Tag[] => { - const rawStatus = walletInfo.addressSummary[utxo.address]?.status - - let status: string | null = null - - // If a UTXO is locked, it's `status` will be the locktime, with other states - // appended in brackets, e.g. `2099-12-01 [LOCKED] [FROZEN]` - if (rawStatus && !utxo.locktime) { - const indexOfOtherTag = rawStatus.indexOf('[') - - if (indexOfOtherTag !== -1) { - status = rawStatus.substring(0, indexOfOtherTag).trim() - } else { - status = rawStatus - } - } - - const tags: Tag[] = [] - - if (utxo.label) tags.push({ tag: utxo.label, color: 'normal' }) - if (status) tags.push({ tag: status, color: ADDRESS_STATUS_COLORS[status] || 'normal' }) - if (fb.utxo.isFidelityBond(utxo)) tags.push({ tag: t('jar_details.utxo_list.utxo_tag_fb'), color: 'dark' }) - return tags -} - -const utxoIcon = (utxo: Utxo, t: TFunction) => { - if (fb.utxo.isFidelityBond(utxo)) { - return withTooltip({ - node: ( -
- -
- ), - tooltip:
{t('jar_details.utxo_list.utxo_tooltip_locktime', { locktime: utxo.locktime })}
, - }) - } else if (utxo.frozen) { - return ( -
- -
- ) - } - return <> -} - -const utxoConfirmations = (utxo: Utxo) => { - const symbol = `confs-${utxo.confirmations >= 6 ? 'full' : utxo.confirmations}` - - return ( -
- -
{utxo.confirmations}
-
- ) -} - const SORT_KEYS = { frozenOrLocked: 'FROZEN_OR_LOCKED', value: 'VALUE', @@ -158,7 +93,7 @@ const toUtxo = (tableNode: TableTypes.TableNode): Utxo => { interface UtxoTableRow extends Utxo, TableTypes.TableNode { _icon: JSX.Element - _tags: Tag[] + _tags: UtxoTag[] _confs: JSX.Element } @@ -187,9 +122,9 @@ const UtxoList = ({ nodes: utxos.map((utxo: Utxo) => ({ ...utxo, id: utxo.utxo, - _icon: utxoIcon(utxo, t), + _icon: , _tags: utxoTags(utxo, walletInfo, t), - _confs: utxoConfirmations(utxo), + _confs: , })), }), [utxos, walletInfo, t], @@ -254,7 +189,9 @@ const UtxoList = ({ [SORT_KEYS.value]: (array) => array.sort((a, b) => a.value - b.value), [SORT_KEYS.confirmations]: (array) => array.sort((a, b) => a.confirmations - b.confirmations), [SORT_KEYS.tags]: (array) => - array.sort((a, b) => (String(a._tags[0]?.tag) || 'z').localeCompare(String(b._tags[0]?.tag) || 'z')), + array.sort((a, b) => + (String(a._tags[0]?.displayValue) || 'z').localeCompare(String(b._tags[0]?.displayValue) || 'z'), + ), }, }, ) @@ -327,6 +264,7 @@ const UtxoList = ({ convertToUnit={settings.unit} showBalance={settings.showBalance} frozen={item.frozen} + colored={!item.locktime} /> @@ -348,13 +286,7 @@ const UtxoList = ({ {item._confs} -
- {item._tags.map((tag: Tag, index: number) => ( -
- {tag.tag} -
- ))} -
+
({ + symbol: `confs-${confirmations >= 6 ? 'full' : confirmations}`, + display: confirmations > 9999 ? `${Number(9999).toLocaleString()}+` : confirmations.toLocaleString(), + tooltip: confirmations > 9999 ? confirmations.toLocaleString() : undefined, + confirmations, +}) + +export function Confirmations({ className, value }: { className?: string; value: ConfirmationFormat }) { + return value.tooltip !== undefined ? ( + {value.tooltip}} + > +
+ +
{value.display}
+
+
+ ) : ( +
+ +
{value.display}
+
+ ) +} + +export function UtxoConfirmations({ className, value }: { className?: string; value: Utxo }) { + return +} diff --git a/src/components/utxo/UtxoIcon.module.css b/src/components/utxo/UtxoIcon.module.css new file mode 100644 index 000000000..23ad93d30 --- /dev/null +++ b/src/components/utxo/UtxoIcon.module.css @@ -0,0 +1,5 @@ +.utxoIcon { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/utxo/UtxoIcon.tsx b/src/components/utxo/UtxoIcon.tsx new file mode 100644 index 000000000..af3458668 --- /dev/null +++ b/src/components/utxo/UtxoIcon.tsx @@ -0,0 +1,59 @@ +import { PropsWithChildren, useMemo } from 'react' +import * as rb from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import { Utxo } from '../../context/WalletContext' +import * as fb from '../fb/utils' +import { UtxoTag, UtxoStatus } from './utils' +import Sprite from '../Sprite' +import styles from './UtxoIcon.module.css' + +const toIcon = (utxo: Utxo, tags?: UtxoStatus[]) => { + if (fb.utxo.isFidelityBond(utxo)) return 'timelock' + if (utxo.frozen) return 'snowflake' + if (tags?.includes('cj-out')) return 'mixed' + if (tags) return 'unmixed' + return undefined +} + +function LockedIconTooltip({ utxo, children }: PropsWithChildren<{ utxo: Utxo }>) { + const { t } = useTranslation() + + return ( + ( + +
{t('jar_details.utxo_list.utxo_tooltip_locktime', { locktime: utxo.locktime })}
+
+ )} + > +
{children}
+
+ ) +} + +interface UtxoIconProps { + value: Utxo + tags?: UtxoTag[] + size?: 20 | 24 +} + +export default function UtxoIcon({ value, tags, size = 24 }: UtxoIconProps) { + const symbol = useMemo( + () => + toIcon( + value, + tags?.map((it) => it.value), + ), + [value, tags], + ) + + const element = ( +
{symbol && }
+ ) + + if (fb.utxo.isFidelityBond(value)) { + return {element} + } + + return element +} diff --git a/src/components/utxo/UtxoTags.module.css b/src/components/utxo/UtxoTags.module.css new file mode 100644 index 000000000..6bffdd1fb --- /dev/null +++ b/src/components/utxo/UtxoTags.module.css @@ -0,0 +1,37 @@ +.utxoTagList { + display: flex; + gap: 0.5rem; + color: var(--bs-body-color); +} + +.utxoTagList > .utxoTag { + display: flex; + justify-content: center; + align-items: center; + gap: 0.3rem; + font-size: 0.7rem; + border: 1px solid var(--bs-body-color); + border-radius: 0.2rem; + padding: 0.1rem 0.3rem; +} + +.utxoTagList > .utxoTag.utxoTag-success { + color: var(--bs-success); + background-color: rgba(var(--bs-success-rgb), 0.1); + border-color: var(--bs-success); +} +.utxoTagList > .utxoTag.utxoTag-warning { + color: var(--bs-warning); + background-color: rgba(var(--bs-warning-rgb), 0.1); + border-color: var(--bs-warning); +} +.utxoTagList > .utxoTag.utxoTag-danger { + color: var(--bs-danger); + background-color: rgba(var(--bs-danger-rgb), 0.1); + border-color: var(--bs-danger); +} +.utxoTagList > .utxoTag.utxoTag-dark { + color: var(--bs-white); + background-color: var(--bs-dark); + border-color: var(--bs-dark); +} diff --git a/src/components/utxo/UtxoTags.tsx b/src/components/utxo/UtxoTags.tsx new file mode 100644 index 000000000..a0ad39864 --- /dev/null +++ b/src/components/utxo/UtxoTags.tsx @@ -0,0 +1,19 @@ +import classNames from 'classnames' +import { UtxoTag } from '../utxo/utils' +import styles from './UtxoTags.module.css' + +interface UtxoTagsProps { + value: UtxoTag[] +} + +export default function UtxoTags({ value }: UtxoTagsProps) { + return ( +
+ {value.map((tag: UtxoTag, index: number) => ( +
+ {tag.displayValue} +
+ ))} +
+ ) +} diff --git a/src/components/utxo/utils.ts b/src/components/utxo/utils.ts new file mode 100644 index 000000000..32b46c075 --- /dev/null +++ b/src/components/utxo/utils.ts @@ -0,0 +1,44 @@ +import { TFunction } from 'i18next' +import { Utxo, WalletInfo } from '../../context/WalletContext' +import * as fb from '../fb/utils' + +export type UtxoStatus = 'new' | 'used' | 'reused' | 'cj-out' | 'non-cj-change' | 'change-out' | 'deposit' | string + +const UTXO_STATUS_COLORS: { [key in UtxoStatus]: string } = { + new: 'normal', + used: 'normal', + reused: 'danger', + 'cj-out': 'success', + 'change-out': 'warning', + 'non-cj-change': 'warning', + deposit: 'normal', +} + +export type UtxoTag = { value: UtxoStatus; displayValue: string; color: string } + +export const utxoTags = (utxo: Utxo, walletInfo: WalletInfo, t: TFunction): UtxoTag[] => { + const rawStatus = walletInfo.addressSummary[utxo.address]?.status + + let status: string | null = null + + // If a UTXO is locked, it's `status` will be the locktime, with other states + // appended in brackets, e.g. `2099-12-01 [LOCKED] [FROZEN]` + // other possible values include `cj-out`, `reused [FROZEN]`, etc. + if (rawStatus && !utxo.locktime) { + const indexOfOtherTag = rawStatus.indexOf('[') + + if (indexOfOtherTag !== -1) { + status = rawStatus.substring(0, indexOfOtherTag).trim() + } else { + status = rawStatus + } + } + + const tags: UtxoTag[] = [] + + if (fb.utxo.isFidelityBond(utxo)) + tags.push({ value: 'bond', displayValue: t('jar_details.utxo_list.utxo_tag_fb'), color: 'dark' }) + if (status) tags.push({ value: status, displayValue: status, color: UTXO_STATUS_COLORS[status] || 'normal' }) + if (utxo.label) tags.push({ value: utxo.label, displayValue: utxo.label, color: 'normal' }) + return tags +} diff --git a/src/context/WalletContext.tsx b/src/context/WalletContext.tsx index 080df1ae4..5bfce6abc 100644 --- a/src/context/WalletContext.tsx +++ b/src/context/WalletContext.tsx @@ -53,8 +53,6 @@ export type Utxo = { // `locktime` in format "yyyy-MM-dd 00:00:00" // NOTE: it is unparsable with safari Date constructor locktime?: string - // TODO: remove 'tags' prop - tags?: { tag: string; color: string }[] } export type Utxos = Utxo[]