diff --git a/src/components/jar_details/JarDetailsOverlay.module.css b/src/components/jar_details/JarDetailsOverlay.module.css index 1700f4433..debd01258 100644 --- a/src/components/jar_details/JarDetailsOverlay.module.css +++ b/src/components/jar_details/JarDetailsOverlay.module.css @@ -1,38 +1,3 @@ -.utxoDetailModalBackdrop { - z-index: 1300 !important; -} - -.utxoDetailModal { - z-index: 1400 !important; -} - -.modalHeader { - display: flex !important; - justify-content: flex-start !important; - background-color: transparent !important; - padding: 1.25rem !important; -} - -.modalTitle { - width: 100%; - font-size: 1rem !important; - font-weight: 400 !important; - color: var(--bs-body-color) !important; -} - -.modalTitle > div:first-child { - display: flex; - justify-content: space-between; - align-items: center; -} - -.modalTitle .cancelButton { - padding: 0 0 0 1rem; - color: var(--bs-body-color); - background-color: transparent !important; - border: none; -} - .overlayContainer .accountStepperTitle { display: inline-flex; justify-content: center; @@ -76,18 +41,19 @@ .overlayContainer .tabContainer { display: flex; flex-direction: column; - gap: 2.5rem; + gap: 0.5rem; background-color: var(--bs-body-bg); } @media only screen and (min-width: 992px) { .overlayContainer .tabContainer { + gap: 1.5rem; padding: 2rem; border-radius: 0.5rem; } } -.overlayContainer .tabContainer > .utxoListTitleBar { +.overlayContainer .tabContainer .utxoListTitleBar { min-height: 3.6rem; display: flex; justify-content: space-between; @@ -99,25 +65,43 @@ } @media only screen and (min-width: 992px) { - .overlayContainer .tabContainer > .utxoListTitleBar { - flex-direction: row; - align-items: center; + .overlayContainer .tabContainer .utxoListTitleBar { padding: 0.8rem 1rem; border-radius: 0.6rem; } } -:root[data-theme='dark'] .overlayContainer .tabContainer > .utxoListTitleBar { +:root[data-theme='dark'] .overlayContainer .tabContainer .utxoListTitleBar { background-color: var(--bs-gray-800); } -.overlayContainer .tabContainer > .utxoListTitleBar .freezeUnfreezeButtonsContainer { +.overlayContainer .tabContainer .utxoListTitleBar .operationsContainer { + display: flex; + width: 100%; + justify-content: space-between; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; +} + +.selectedUtxosSumContainer { + align-self: end; +} + +@media only screen and (min-width: 576px) { + .overlayContainer .tabContainer .utxoListTitleBar .operationsContainer { + flex-direction: row; + align-items: center; + } +} + +.overlayContainer .tabContainer .utxoListTitleBar .freezeUnfreezeButtonsContainer { display: flex; flex-direction: row; gap: 0.5rem; } -.overlayContainer .tabContainer > .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { +.overlayContainer .tabContainer .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { width: 8rem; font-size: 0.6rem; padding: 0.3rem 0.5rem; @@ -125,25 +109,25 @@ justify-content: center; align-items: center; gap: 0.3rem; - background-color: white; + background-color: var(--bs-white); border: 1px solid var(--bs-gray-200); color: var(--bs-body-color); border-radius: 0.35rem; } -.overlayContainer .tabContainer > .utxoListTitleBar .freezeUnfreezeButtonsContainer > button:hover { +.overlayContainer .tabContainer .utxoListTitleBar .freezeUnfreezeButtonsContainer > button:hover { background-color: var(--bs-btn-hover-bg); border-color: var(--bs-btn-hover-border-color); } @media only screen and (min-width: 768px) { - .overlayContainer .tabContainer > .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { + .overlayContainer .tabContainer .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { width: 10rem; font-size: 0.8rem; } } -:root[data-theme='dark'] .overlayContainer .tabContainer > .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { +:root[data-theme='dark'] .overlayContainer .tabContainer .utxoListTitleBar .freezeUnfreezeButtonsContainer > button { background-color: var(--bs-gray-600) !important; border-color: var(--bs-gray-600); } @@ -151,14 +135,14 @@ :root[data-theme='dark'] .overlayContainer .tabContainer - > .utxoListTitleBar + .utxoListTitleBar .freezeUnfreezeButtonsContainer > button:hover { background-color: var(--bs-gray-700) !important; border-color: var(--bs-gray-700); } -.overlayContainer .tabContainer > .utxoListTitleBar .refreshButton { +.overlayContainer .tabContainer .utxoListTitleBar .refreshButton { display: flex; justify-content: center; align-items: center; diff --git a/src/components/jar_details/JarDetailsOverlay.tsx b/src/components/jar_details/JarDetailsOverlay.tsx index e1beaa53a..e0d44fe3f 100644 --- a/src/components/jar_details/JarDetailsOverlay.tsx +++ b/src/components/jar_details/JarDetailsOverlay.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo, useCallback } from 'react' import * as rb from 'react-bootstrap' -import { useTranslation } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import * as Api from '../../libs/JmWalletApi' import { useSettings } from '../../context/SettingsContext' import { Account, Utxo, WalletInfo, CurrentWallet, useReloadCurrentWalletInfo } from '../../context/WalletContext' @@ -10,10 +10,11 @@ import Alert, { SimpleMessageAlertProps } from '../Alert' import Balance from '../Balance' import Sprite from '../Sprite' import SegmentedTabs from '../SegmentedTabs' +import UtxoDetailModal from './UtxoDetailModule' import { UtxoList } from './UtxoList' import { DisplayBranchHeader, DisplayBranchBody } from './DisplayBranch' -import styles from './JarDetailsOverlay.module.css' import { jarInitial } from '../jars/Jar' +import styles from './JarDetailsOverlay.module.css' const TABS = { UTXOS: 'UTXOS', @@ -29,13 +30,6 @@ interface HeaderProps { initialTab: string } -interface UtxoDetailModalProps { - utxo: Utxo - status: string | null - isShown: boolean - close: () => void -} - interface JarDetailsOverlayProps { accounts: Account[] initialAccountIndex: number @@ -91,99 +85,6 @@ const Header = ({ account, nextAccount, previousAccount, setTab, onHide, initial ) } -const UtxoDetailModal = ({ utxo, status, isShown, close }: UtxoDetailModalProps) => { - const { t } = useTranslation() - const settings = useSettings() - - return ( - - - -
-
- -
- - - -
-
-
- -
-
- {t('jar_details.utxo_list.utxo_detail_label_id')}: {utxo.utxo} -
- {t('jar_details.utxo_list.utxo_detail_label_address')}: {utxo.address} -
-
- {t('jar_details.utxo_list.utxo_detail_label_path')}: {utxo.path} -
- {utxo.label && ( -
- {t('jar_details.utxo_list.utxo_detail_label_label')}: {utxo.label} -
- )} -
- {t('jar_details.utxo_list.utxo_detail_label_value')}:{' '} - -
-
- {t('jar_details.utxo_list.utxo_detail_label_tries')}: {utxo.tries} -
-
- {t('jar_details.utxo_list.utxo_detail_label_tries_remaining')}: {utxo.tries_remaining} -
-
- {t('jar_details.utxo_list.utxo_detail_label_is_external')}:{' '} - {utxo.external ? 'Yes' : 'No'} -
-
- {t('jar_details.utxo_list.utxo_detail_label_jar')}: {utxo.mixdepth} -
-
- {t('jar_details.utxo_list.utxo_detail_label_confirmations')}: {utxo.confirmations} -
-
- {t('jar_details.utxo_list.utxo_detail_label_is_frozen')}: {utxo.frozen ? 'Yes' : 'No'} -
- {utxo.locktime && ( -
- {t('jar_details.utxo_list.utxo_detail_label_locktime')}: {utxo.locktime} -
- )} -
- {t('jar_details.utxo_list.utxo_detail_label_status')}: {status} -
-
-
-
- - - {t('global.close')} - - -
- ) -} - const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { const { t } = useTranslation() const settings = useSettings() @@ -200,6 +101,14 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { const [detailUtxo, setDetailUtxo] = useState(null) const account = useMemo(() => props.accounts[accountIndex], [props.accounts, accountIndex]) + const utxos = useMemo(() => props.utxosByAccount[accountIndex] || [], [props.utxosByAccount, accountIndex]) + const selectedUtxos = useMemo( + () => utxos.filter((utxo: Utxo) => selectedUtxoIds.includes(utxo.utxo)), + [utxos, selectedUtxoIds] + ) + const selectedUtxosBalance: Api.AmountSats = useMemo(() => { + return selectedUtxos.map((it) => it.value).reduce((acc, curr) => acc + curr, 0) + }, [selectedUtxos]) const nextAccount = useCallback( () => setAccountIndex((current) => (current + 1 >= props.accounts.length ? 0 : current + 1)), @@ -211,6 +120,10 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { ) useEffect(() => setAccountIndex(props.initialAccountIndex), [props.initialAccountIndex]) + useEffect(() => { + // reset selected utxos when switching jars + setSelectedUtxoIds([]) + }, [accountIndex]) const onKeyDown = useCallback( (e: KeyboardEvent) => { @@ -227,7 +140,7 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { return () => document.removeEventListener('keydown', onKeyDown) }, [props.isShown, onKeyDown]) - const isTakerOrMakerRunning = useCallback( + const isTakerOrMakerRunning = useMemo( () => serviceInfo && (serviceInfo.makerRunning || serviceInfo.coinjoinInProgress), [serviceInfo] ) @@ -257,13 +170,9 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { } const changeSelectedUtxoFreeze = async (freeze: boolean) => { - if (isLoadingFreeze || isLoadingUnfreeze || isLoadingRefresh || isTakerOrMakerRunning()) return + if (isLoadingFreeze || isLoadingUnfreeze || isLoadingRefresh || isTakerOrMakerRunning) return - if (selectedUtxoIds.length <= 0) return - - const selectedUtxos = (props.utxosByAccount[accountIndex] || []).filter((utxo: Utxo) => - selectedUtxoIds.includes(utxo.utxo) - ) + if (selectedUtxos.length <= 0) return setAlert(null) freeze ? setIsLoadingFreeze(true) : setIsLoadingUnfreeze(true) @@ -294,7 +203,6 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { } const utxoListTitle = () => { - const utxos = props.utxosByAccount[accountIndex] || [] return t('jar_details.utxo_list.title', { count: utxos.length, jar: jarInitial(accountIndex) }) } @@ -302,7 +210,7 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { return ( { refreshUtxos() @@ -319,10 +227,12 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { const freezeUnfreezeButton = ({ freeze }: { freeze: boolean }) => { const isLoading = freeze ? isLoadingFreeze : isLoadingUnfreeze + const isDisabled = + isLoadingRefresh || isLoadingFreeze || isLoadingUnfreeze || isTakerOrMakerRunning || selectedUtxos.length <= 0 return ( { changeSelectedUtxoFreeze(freeze) @@ -351,9 +261,7 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { { - props.onHide() - }} + onHide={props.onHide} keyboard={false} placement="bottom" > @@ -370,7 +278,7 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { - + {alert && ( @@ -401,18 +309,32 @@ const JarDetailsOverlay = (props: JarDetailsOverlayProps) => { {refreshButton()} {utxoListTitle()} - {(props.utxosByAccount[accountIndex] || []).length > 0 && ( -
- {freezeUnfreezeButton({ freeze: true })} - {freezeUnfreezeButton({ freeze: false })} -
- )} +
+ {utxos.length > 0 && ( +
+ {freezeUnfreezeButton({ freeze: true })} + {freezeUnfreezeButton({ freeze: false })} +
+ )} + {selectedUtxosBalance > 0 && ( +
+ + + +
+ )} +
- {(props.utxosByAccount[accountIndex] || []).length > 0 && ( + {utxos.length > 0 && (
diff --git a/src/components/jar_details/UtxoDetailModule.module.css b/src/components/jar_details/UtxoDetailModule.module.css new file mode 100644 index 000000000..ea7c2f22b --- /dev/null +++ b/src/components/jar_details/UtxoDetailModule.module.css @@ -0,0 +1,34 @@ +.utxoDetailModalBackdrop { + z-index: 1300 !important; +} + +.utxoDetailModal { + z-index: 1400 !important; +} + +.modalHeader { + display: flex !important; + justify-content: flex-start !important; + background-color: transparent !important; + padding: 1.25rem !important; +} + +.modalTitle { + width: 100%; + font-size: 1rem !important; + font-weight: 400 !important; + color: var(--bs-body-color) !important; +} + +.modalTitle > div:first-child { + display: flex; + justify-content: space-between; + align-items: center; +} + +.modalTitle .cancelButton { + padding: 0 0 0 1rem; + color: var(--bs-body-color); + background-color: transparent !important; + border: none; +} diff --git a/src/components/jar_details/UtxoDetailModule.tsx b/src/components/jar_details/UtxoDetailModule.tsx new file mode 100644 index 000000000..785e40f12 --- /dev/null +++ b/src/components/jar_details/UtxoDetailModule.tsx @@ -0,0 +1,109 @@ +import * as rb from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import { useSettings } from '../../context/SettingsContext' +import { Utxo } from '../../context/WalletContext' +import Balance from '../Balance' +import Sprite from '../Sprite' +import styles from './UtxoDetailModule.module.css' + +interface UtxoDetailModalProps { + utxo: Utxo + status: string | null + isShown: boolean + close: () => void +} + +const UtxoDetailModal = ({ utxo, status, isShown, close }: UtxoDetailModalProps) => { + const { t } = useTranslation() + const settings = useSettings() + + return ( + + + +
+
+ +
+ + + +
+
+
+ +
+
+ {t('jar_details.utxo_list.utxo_detail_label_id')}: {utxo.utxo} +
+ {t('jar_details.utxo_list.utxo_detail_label_address')}: {utxo.address} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_path')}: {utxo.path} +
+ {utxo.label && ( +
+ {t('jar_details.utxo_list.utxo_detail_label_label')}: {utxo.label} +
+ )} +
+ {t('jar_details.utxo_list.utxo_detail_label_value')}:{' '} + +
+
+ {t('jar_details.utxo_list.utxo_detail_label_tries')}: {utxo.tries} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_tries_remaining')}: {utxo.tries_remaining} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_is_external')}:{' '} + {utxo.external ? 'Yes' : 'No'} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_jar')}: {utxo.mixdepth} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_confirmations')}: {utxo.confirmations} +
+
+ {t('jar_details.utxo_list.utxo_detail_label_is_frozen')}: {utxo.frozen ? 'Yes' : 'No'} +
+ {utxo.locktime && ( +
+ {t('jar_details.utxo_list.utxo_detail_label_locktime')}: {utxo.locktime} +
+ )} +
+ {t('jar_details.utxo_list.utxo_detail_label_status')}: {status} +
+
+
+
+ + + {t('global.close')} + + +
+ ) +} + +export default UtxoDetailModal diff --git a/src/components/jar_details/UtxoList.tsx b/src/components/jar_details/UtxoList.tsx index 9e8b0435a..3a586ff6a 100644 --- a/src/components/jar_details/UtxoList.tsx +++ b/src/components/jar_details/UtxoList.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import { useMemo } from 'react' import * as rb from 'react-bootstrap' import classnames from 'classnames' import { useTranslation } from 'react-i18next' @@ -7,6 +7,7 @@ import { usePagination } from '@table-library/react-table-library/pagination' import { useRowSelect, HeaderCellSelect, CellSelect, SelectTypes } from '@table-library/react-table-library/select' import { useSort, HeaderCellSort, SortToggleType } from '@table-library/react-table-library/sort' import * as TableTypes from '@table-library/react-table-library/types/table' +import { State } from '@table-library/react-table-library/types/common' import { useTheme } from '@table-library/react-table-library/theme' import { useSettings } from '../../context/SettingsContext' import { Utxo, WalletInfo } from '../../context/WalletContext' @@ -73,11 +74,12 @@ const TABLE_THEME = { interface UtxoListProps { utxos: Array walletInfo: WalletInfo + selectState: State setSelectedUtxoIds: (selectedUtxoIds: Array) => void setDetailUtxo: (utxo: Utxo) => void } -const UtxoList = ({ utxos, walletInfo, setSelectedUtxoIds, setDetailUtxo }: UtxoListProps) => { +const UtxoList = ({ utxos, walletInfo, selectState, setSelectedUtxoIds, setDetailUtxo }: UtxoListProps) => { const { t } = useTranslation() const settings = useSettings() @@ -131,13 +133,14 @@ const UtxoList = ({ utxos, walletInfo, setSelectedUtxoIds, setDetailUtxo }: Utxo }, }) - const onTableSelectChange = (action: any, state: any) => { + const onTableSelectChange = (action: any, state: State) => { setSelectedUtxoIds(state.ids) } const tableSelect = useRowSelect( tableData, { + state: selectState, onChange: onTableSelectChange, }, { @@ -284,7 +287,7 @@ const UtxoList = ({ utxos, walletInfo, setSelectedUtxoIds, setDetailUtxo }: Utxo )} -
+
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0f9a84de1..5422c46b9 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -482,13 +482,14 @@ }, "jar_details": { "title_tab_utxos": "UTXOs", - "title_tab_account": "Jar Details", + "title_tab_account": "Details", "utxo_list": { "title_zero": "No UTXOs in Jar {{ jar }}", "title_one": "{{ count }} UTXO in Jar {{ jar }}", "title_other": "{{ count }} UTXOs in Jar {{ jar }}", - "button_freeze": "Freeze selected", - "button_unfreeze": "Unfreeze selected", + "text_balance_sum_selected": "<0> selected", + "button_freeze": "Freeze", + "button_unfreeze": "Unfreeze", "button_freeze_loading": "Freezing...", "button_unfreeze_loading": "Unfreezing...", "utxo_tag_frozen": "frozen",