From f33f7b8138ca38285f570ea57705dbfc70dd2c12 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Tue, 28 Jul 2026 18:14:59 +0200 Subject: [PATCH 1/9] chore(send): remove dev/prod distinction for minimum collaborators this is already dynamically done by fetching the backend config value --- src/components/send/SendForm.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/send/SendForm.tsx b/src/components/send/SendForm.tsx index 78dce756a..b7783fc85 100644 --- a/src/components/send/SendForm.tsx +++ b/src/components/send/SendForm.tsx @@ -9,7 +9,6 @@ import type { Resolver, SubmitHandler } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import QrScannerDialog from '@/components/ui/QrScannerDialog' -import { isDevMode } from '@/constants/debugFeatures' import { MAX_NUM_COLLABORATORS } from '@/constants/jam' import { JM_MINIMUM_MAKERS_DEFAULT, JM_TAKER_UTXO_AGE } from '@/constants/jm' import { useDetectNetwork, type AddressSummary, type Jar } from '@/context/JamWalletInfoContext' @@ -94,12 +93,6 @@ const AddressFromJarSelectorDialog = ({ ) } -// set the default to one collaborator in dev mode -const DEV_INITIAL_NUM_COLLABORATORS_INPUT = 1 - -// TODO: this value should be dynamic via jm backend settings -const MIN_NUM_COLLABORATORS = isDevMode() ? DEV_INITIAL_NUM_COLLABORATORS_INPUT : JM_MINIMUM_MAKERS_DEFAULT - const FieldPrefixSatSymbol = ( Date: Tue, 28 Jul 2026 22:26:03 +0200 Subject: [PATCH 2/9] chore(send): jars with frozen funds are selectable --- src/components/send/SendForm.schema.ts | 27 +++++++-- src/components/send/SendForm.tsx | 83 ++++++++++++++++---------- src/components/send/SendPage.test.tsx | 5 +- src/components/send/SendPage.tsx | 39 +++++++----- src/hooks/useUtxoSelectionDialog.ts | 8 +-- src/i18n/locales/en/translation.json | 2 + src/lib/balanceSummary.test.ts | 3 + src/lib/balanceSummary.ts | 13 +++- 8 files changed, 121 insertions(+), 59 deletions(-) diff --git a/src/components/send/SendForm.schema.ts b/src/components/send/SendForm.schema.ts index 9b353799f..73589821f 100644 --- a/src/components/send/SendForm.schema.ts +++ b/src/components/send/SendForm.schema.ts @@ -64,11 +64,28 @@ export const createSendFormSchema = ( .object({ source: yup .object({ - fromJar: sourceJarField( - t('send.feedback_invalid_source_jar'), - (jarIndex) => - (jars.find((it) => it.jarIndex === jarIndex)?.balanceSummary.calculatedAvailableBalanceInSats || 0) > 0, - ), + fromJar: sourceJarField(t('send.feedback_invalid_source_jar'), (jarIndex) => + jars.some((it) => it.jarIndex === jarIndex), + ) + .test( + 'valid-source-jar-must-unfreeze-utxos-test', + t('send.feedback_invalid_source_jar_must_unfreeze_utxos'), + (jarIndex) => { + const jar = jars.find((it) => it.jarIndex === jarIndex) + if (!jar) return true + if (jar.balanceSummary.calculatedAvailableBalanceInSats > 0) return true + return jar.balanceSummary.calculatedAvailableFrozenBalanceInSats <= 0 + }, + ) + .test( + 'valid-source-jar-has-funds-test', + t('send.feedback_invalid_source_jar_no_available_utxos'), + (jarIndex) => { + const jar = jars.find((it) => it.jarIndex === jarIndex) + if (!jar) return true + return jar.balanceSummary.calculatedAvailableBalanceInSats > 0 + }, + ), }) .required(), destination: yup diff --git a/src/components/send/SendForm.tsx b/src/components/send/SendForm.tsx index b7783fc85..a5112b463 100644 --- a/src/components/send/SendForm.tsx +++ b/src/components/send/SendForm.tsx @@ -5,7 +5,7 @@ import { getAddressInfo, Network } from 'bitcoin-address-validation' import type { AddressInfo } from 'bitcoin-address-validation' import { AlertTriangleIcon, BrushCleaningIcon, MilkIcon, ScanQrCodeIcon, XIcon } from 'lucide-react' import { FormProvider, useForm, useWatch } from 'react-hook-form' -import type { Resolver, SubmitHandler } from 'react-hook-form' +import type { FieldErrors, Resolver, SubmitHandler } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import QrScannerDialog from '@/components/ui/QrScannerDialog' @@ -107,7 +107,7 @@ interface SendFormProps { className?: string onSubmit: SubmitHandler onSourceJarChange?: (jarIndex: JarIndex | undefined) => void - sourceJarLabelButton?: React.ReactElement + sourceJarLabelButton?: (errors?: FieldErrors<{ fromJar: number }>) => React.ReactElement minNumberOfCollaborators?: number feeConfigValues: JamFeeConfigValues walletFileName: WalletFileName @@ -197,6 +197,15 @@ export function SendForm({ onSourceJarChange?.(sourceJarIndex) }, [onSourceJarChange, sourceJarIndex]) + useEffect(() => { + if (formState.dirtyFields.source?.fromJar) { + console.debug( + 'Validating `source.fromJar` as wallet balance summary changed (e.g. frozen/unfrozen utxos, new transaction, etc.)', + ) + void trigger('source.fromJar') + } + }, [walletBalanceSummary, trigger, formState.dirtyFields.source?.fromJar]) + const destinationJar = useMemo(() => { if (destinationJarIndex === undefined) return return jars.find((it) => it.jarIndex === destinationJarIndex) @@ -294,40 +303,50 @@ export function SendForm({ >
void doOnSubmit(event)} className={cn('flex flex-col gap-4', className)} noValidate>
- +
{t('send.label_source_jar')} - {sourceJarLabelButton && <>{sourceJarLabelButton}} + {sourceJarLabelButton?.(errors.source?.fromJar)}
- {jars.map((jar, index) => ( - { - setValue('source.fromJar', jar.jarIndex, { shouldValidate: true }) - - if (isSweep === true) { - setValue('amount.isSweep', false, { shouldValidate: true }) - setValue('amount.sweepAmount', undefined, { shouldValidate: true }) - setValue('amount.amount', undefined, { shouldValidate: true }) - } - if (destinationJarIndex === jar.jarIndex) { - setValue('destination.address', '', { shouldValidate: true }) - setValue('destination.fromJar', undefined, { shouldValidate: true }) - } else if (destinationAddress !== undefined) { - void trigger('destination.address') - } - }} - disabled={disabled || jar.balanceSummary.calculatedAvailableBalanceInSats <= 0} - /> - ))} + {jars.map((jar, index) => { + const noFunds = + jar.balanceSummary.calculatedAvailableBalanceInSats <= 0 && + jar.balanceSummary.calculatedAvailableFrozenBalanceInSats <= 0 + + return ( + { + setValue('source.fromJar', jar.jarIndex, { + shouldValidate: true, + shouldDirty: true, + shouldTouch: true, + }) + + if (isSweep === true) { + setValue('amount.isSweep', false, { shouldValidate: true }) + setValue('amount.sweepAmount', undefined, { shouldValidate: true }) + setValue('amount.amount', undefined, { shouldValidate: true }) + } + if (destinationJarIndex === jar.jarIndex) { + setValue('destination.address', '', { shouldValidate: true }) + setValue('destination.fromJar', undefined, { shouldValidate: true }) + } else if (destinationAddress !== undefined) { + void trigger('destination.address') + } + }} + disabled={disabled || noFunds} + /> + ) + })}
{errors.source?.fromJar?.message ? {errors.source?.fromJar.message} : null}
diff --git a/src/components/send/SendPage.test.tsx b/src/components/send/SendPage.test.tsx index 5aa3bd87c..d7ec0b856 100644 --- a/src/components/send/SendPage.test.tsx +++ b/src/components/send/SendPage.test.tsx @@ -209,11 +209,11 @@ vi.mock('./SendForm', () => ({ disabled?: boolean onSourceJarChange?: (jarIndex: number | undefined) => void onSubmit: (values: SendFormValues) => void - sourceJarLabelButton?: React.ReactElement + sourceJarLabelButton?: () => React.ReactElement }) => (
send-form:{String(disabled)} -
{sourceJarLabelButton}
+
{sourceJarLabelButton?.()}
- } + sourceJarLabelButton={(errors) => { + const errorSolvedByUtxoSelection = errors?.fromJar?.type === 'valid-source-jar-must-unfreeze-utxos' + const animate = utxoSelectionDialog.dialogProps.open === false && errorSolvedByUtxoSelection + return ( + + ) + }} /> diff --git a/src/hooks/useUtxoSelectionDialog.ts b/src/hooks/useUtxoSelectionDialog.ts index 1682b2896..e647c249b 100644 --- a/src/hooks/useUtxoSelectionDialog.ts +++ b/src/hooks/useUtxoSelectionDialog.ts @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { yupResolver } from '@hookform/resolvers/yup' import { freezeMutation } from '@joinmarket-webui/joinmarket-ng-api-ts/@tanstack/react-query' import { useMutation } from '@tanstack/react-query' @@ -116,7 +116,7 @@ export const useUtxoSelectionDialog = ({ walletFileName, sourceJar, addressSumma }, }) - const onOpenUtxoSelector = () => { + const onOpenUtxoSelector = useCallback(() => { if (!sourceJar) return toast.dismiss(SEND_AUTO_SELECTION_TOAST_ID) reset({ @@ -124,7 +124,7 @@ export const useUtxoSelectionDialog = ({ walletFileName, sourceJar, addressSumma rowSelection: initialRowSelection, }) setOpen(true) - } + }, [sourceJar, reset, initialRowSelection]) const onSubmit = handleSubmit(async ({ rowSelection: submittedRowSelection }) => { if (!sourceJar) return @@ -239,7 +239,7 @@ export const useUtxoSelectionDialog = ({ walletFileName, sourceJar, addressSumma shouldValidate: true, }) }, - onSubmit: async () => onSubmit(), + onSubmit, } return { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0b9c7c7ba..197059e59 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -440,6 +440,8 @@ "qr_scan_file_no_qr": "No QR code found in the selected image.", "qr_scan_bip21_applied": "Payment request applied.", "feedback_invalid_source_jar": "Please select a jar to send from.", + "feedback_invalid_source_jar_must_unfreeze_utxos": "Please select UTXOs from the selected jar.", + "feedback_invalid_source_jar_no_available_utxos": "Please select a jar with spendable balance to send from.", "feedback_invalid_destination_address": "Please enter a valid destination address.", "feedback_destination_network_mismatch": "This address does not match your wallet's network.", "feedback_reused_address": "This address is already used. To preserve your privacy please choose another one.", diff --git a/src/lib/balanceSummary.test.ts b/src/lib/balanceSummary.test.ts index 3c2449c1d..6be77776e 100644 --- a/src/lib/balanceSummary.test.ts +++ b/src/lib/balanceSummary.test.ts @@ -44,6 +44,7 @@ describe('BalanceSummary', () => { calculatedTotalBalanceInSats: 11, calculatedAvailableBalanceInSats: 6, calculatedConfirmedAvailableBalanceInSats: 1, + calculatedAvailableFrozenBalanceInSats: 3, calculatedFrozenOrLockedBalanceInSats: 5, }) }) @@ -86,6 +87,7 @@ describe('BalanceSummary', () => { calculatedTotalBalanceInSats: 677777777, calculatedAvailableBalanceInSats: 333333333, calculatedConfirmedAvailableBalanceInSats: 111111111, + calculatedAvailableFrozenBalanceInSats: 344444444, calculatedFrozenOrLockedBalanceInSats: 344444444, }) }) @@ -136,6 +138,7 @@ describe('BalanceSummary', () => { calculatedTotalBalanceInSats: 11, calculatedAvailableBalanceInSats: 5, calculatedConfirmedAvailableBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 4, calculatedFrozenOrLockedBalanceInSats: 6, }) }) diff --git a/src/lib/balanceSummary.ts b/src/lib/balanceSummary.ts index b0e651117..e261c4960 100644 --- a/src/lib/balanceSummary.ts +++ b/src/lib/balanceSummary.ts @@ -16,6 +16,10 @@ export type BalanceSummary = { * @description Manually calculated confirmed available balance in sats. */ calculatedConfirmedAvailableBalanceInSats: AmountSats + /** + * @description Manually calculated frozen balance in sats, that can be unfrozen. + */ + calculatedAvailableFrozenBalanceInSats: AmountSats /** * @description Manually calculated frozen or locked balance in sats. */ @@ -26,6 +30,7 @@ export const BALANCE_SUMMARY_EMPTY: BalanceSummary = { calculatedTotalBalanceInSats: 0, calculatedAvailableBalanceInSats: 0, calculatedConfirmedAvailableBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 0, calculatedFrozenOrLockedBalanceInSats: 0, } @@ -34,14 +39,17 @@ export const toBalanceSummary = (utxos: Utxo[], now?: Milliseconds): BalanceSumm return utxos .map((utxo) => { const isFidelityBond = fb.utxo.isFidelityBond(utxo) - const frozenOrLocked = utxo.frozen || (isFidelityBond && fb.utxo.isLocked(utxo, refTime)) + const isLocked = isFidelityBond && fb.utxo.isLocked(utxo, refTime) + const frozen = utxo.frozen && !isLocked + const frozenOrLocked = frozen || isLocked const available = !frozenOrLocked return { total: utxo.value, available: available ? utxo.value : 0, confirmedAvailable: available && utxo.confirmations > 0 ? utxo.value : 0, frozenOrLocked: frozenOrLocked ? utxo.value : 0, - bond: fb.utxo.isFidelityBond(utxo) ? utxo.value : 0, + frozen: frozen ? utxo.value : 0, + bond: isFidelityBond ? utxo.value : 0, } }) .reduce( @@ -50,6 +58,7 @@ export const toBalanceSummary = (utxos: Utxo[], now?: Milliseconds): BalanceSumm calculatedAvailableBalanceInSats: acc.calculatedAvailableBalanceInSats + info.available, calculatedConfirmedAvailableBalanceInSats: acc.calculatedConfirmedAvailableBalanceInSats + info.confirmedAvailable, + calculatedAvailableFrozenBalanceInSats: acc.calculatedAvailableFrozenBalanceInSats + info.frozen, calculatedFrozenOrLockedBalanceInSats: acc.calculatedFrozenOrLockedBalanceInSats + info.frozenOrLocked, }), BALANCE_SUMMARY_EMPTY, From 1131f5086fd10211af241c11a0bd5fffc1afd47f Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Tue, 28 Jul 2026 22:45:20 +0200 Subject: [PATCH 3/9] ui(send): justify jars and increase space to precondition alert --- src/components/send/SendForm.tsx | 4 ++-- src/lib/balanceSummary.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/send/SendForm.tsx b/src/components/send/SendForm.tsx index a5112b463..714303fe4 100644 --- a/src/components/send/SendForm.tsx +++ b/src/components/send/SendForm.tsx @@ -302,13 +302,13 @@ export function SendForm({ {...sendFormMethods} > void doOnSubmit(event)} className={cn('flex flex-col gap-4', className)} noValidate> -
+
{t('send.label_source_jar')} {sourceJarLabelButton?.(errors.source?.fromJar)}
-
+
{jars.map((jar, index) => { const noFunds = jar.balanceSummary.calculatedAvailableBalanceInSats <= 0 && diff --git a/src/lib/balanceSummary.test.ts b/src/lib/balanceSummary.test.ts index 6be77776e..b12ce76e4 100644 --- a/src/lib/balanceSummary.test.ts +++ b/src/lib/balanceSummary.test.ts @@ -138,7 +138,7 @@ describe('BalanceSummary', () => { calculatedTotalBalanceInSats: 11, calculatedAvailableBalanceInSats: 5, calculatedConfirmedAvailableBalanceInSats: 0, - calculatedAvailableFrozenBalanceInSats: 4, + calculatedAvailableFrozenBalanceInSats: 3, calculatedFrozenOrLockedBalanceInSats: 6, }) }) From 2b92f2527c5c0abc1a78f8f48d357f433abb2a97 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 29 Jul 2026 09:45:06 +0200 Subject: [PATCH 4/9] build: fix errors in test setups --- src/components/MainWalletPage.test.tsx | 1 + src/components/send/PaymentConfirmDialog.test.tsx | 1 + src/components/sweep/SweepPage.test.tsx | 3 +++ src/components/wallet/WalletJarsDetailsContent.test.tsx | 3 +++ 4 files changed, 8 insertions(+) diff --git a/src/components/MainWalletPage.test.tsx b/src/components/MainWalletPage.test.tsx index e4661cee8..069fe495c 100644 --- a/src/components/MainWalletPage.test.tsx +++ b/src/components/MainWalletPage.test.tsx @@ -74,6 +74,7 @@ const makeJar = (jarIndex: number, name: string): Jar => calculatedTotalBalanceInSats: 100, calculatedAvailableBalanceInSats: 100, calculatedFrozenOrLockedBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 0, }, }) as unknown as Jar diff --git a/src/components/send/PaymentConfirmDialog.test.tsx b/src/components/send/PaymentConfirmDialog.test.tsx index f3b2962f7..d8b427bfe 100644 --- a/src/components/send/PaymentConfirmDialog.test.tsx +++ b/src/components/send/PaymentConfirmDialog.test.tsx @@ -50,6 +50,7 @@ const sourceJar: Jar = { calculatedAvailableBalanceInSats: 50_000, calculatedTotalBalanceInSats: 50_000, calculatedConfirmedAvailableBalanceInSats: 50_000, + calculatedAvailableFrozenBalanceInSats: 0, calculatedFrozenOrLockedBalanceInSats: 0, }, color: '#e2b86a', diff --git a/src/components/sweep/SweepPage.test.tsx b/src/components/sweep/SweepPage.test.tsx index b290d6eaa..c46ef3e3e 100644 --- a/src/components/sweep/SweepPage.test.tsx +++ b/src/components/sweep/SweepPage.test.tsx @@ -303,6 +303,7 @@ const makeWalletInfo = (overrides: Partial = {}): WalletInfo => { calculatedAvailableBalanceInSats: 100_000, calculatedTotalBalanceInSats: 100_000, calculatedConfirmedAvailableBalanceInSats: 100_000, + calculatedAvailableFrozenBalanceInSats: 0, calculatedFrozenOrLockedBalanceInSats: 0, }, color: '#e2b86a', @@ -315,6 +316,7 @@ const makeWalletInfo = (overrides: Partial = {}): WalletInfo => { calculatedAvailableBalanceInSats: 0, calculatedTotalBalanceInSats: 0, calculatedConfirmedAvailableBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 0, calculatedFrozenOrLockedBalanceInSats: 0, }, color: '#ccc', @@ -341,6 +343,7 @@ const makeWalletInfo = (overrides: Partial = {}): WalletInfo => { calculatedAvailableBalanceInSats: 100_000, calculatedTotalBalanceInSats: 100_000, calculatedConfirmedAvailableBalanceInSats: 100_000, + calculatedAvailableFrozenBalanceInSats: 0, calculatedFrozenOrLockedBalanceInSats: 0, }, walletName: 'wallet.jmdat', diff --git a/src/components/wallet/WalletJarsDetailsContent.test.tsx b/src/components/wallet/WalletJarsDetailsContent.test.tsx index 0059db974..fa0490251 100644 --- a/src/components/wallet/WalletJarsDetailsContent.test.tsx +++ b/src/components/wallet/WalletJarsDetailsContent.test.tsx @@ -82,6 +82,7 @@ const jars: Jar[] = [ calculatedAvailableBalanceInSats: 12_000, calculatedConfirmedAvailableBalanceInSats: 12_000, calculatedFrozenOrLockedBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 0, calculatedTotalBalanceInSats: 12_000, }, color: '#e2b86a', @@ -94,6 +95,7 @@ const jars: Jar[] = [ calculatedAvailableBalanceInSats: 5_000, calculatedConfirmedAvailableBalanceInSats: 5_000, calculatedFrozenOrLockedBalanceInSats: 0, + calculatedAvailableFrozenBalanceInSats: 0, calculatedTotalBalanceInSats: 5_000, }, color: '#3b5ba9', @@ -106,6 +108,7 @@ const jars: Jar[] = [ calculatedAvailableBalanceInSats: 8_000, calculatedConfirmedAvailableBalanceInSats: 8_000, calculatedFrozenOrLockedBalanceInSats: 8_000, + calculatedAvailableFrozenBalanceInSats: 8_000, calculatedTotalBalanceInSats: 8_000, }, color: '#5ba93b', From 654ee026dcdedaab51a4fbd837fa0ebe96be6dbe Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 29 Jul 2026 12:11:47 +0200 Subject: [PATCH 5/9] feat(send): open utxo selector on click on selected jar --- .../CreateFidelityBondDialogSteps.test.tsx | 6 +++--- src/components/earn/MoveToJarDialog.test.tsx | 6 +++--- .../earn/fidelity-bond/FidelityBondJarSelector.tsx | 2 +- src/components/receive/ReceiveForm.test.tsx | 4 ++-- src/components/receive/ReceiveForm.tsx | 2 +- src/components/send/JarSelectorDialog.test.tsx | 6 +++--- src/components/send/JarSelectorDialog.tsx | 2 +- src/components/send/SendForm.test.tsx | 4 ++-- src/components/send/SendForm.tsx | 13 +++++++++++-- src/components/send/SendPage.tsx | 3 ++- src/components/ui/jam/SelectableJar.tsx | 11 ++++++----- src/stories/jam/SelectableJar.stories.tsx | 9 ++++++++- 12 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialogSteps.test.tsx b/src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialogSteps.test.tsx index 4b5b62a04..fd0afdd33 100644 --- a/src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialogSteps.test.tsx +++ b/src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialogSteps.test.tsx @@ -53,16 +53,16 @@ vi.mock('@/context/JamWalletInfoContext', () => ({ vi.mock('@/components/ui/jam/SelectableJar', () => ({ SelectableJar: ({ name, - onClick, + onSelect, disabled, isSelected, }: { name?: string - onClick?: () => void + onSelect?: () => void disabled?: boolean isSelected?: boolean }) => ( - ), diff --git a/src/components/earn/MoveToJarDialog.test.tsx b/src/components/earn/MoveToJarDialog.test.tsx index 9215d1923..98210f2ad 100644 --- a/src/components/earn/MoveToJarDialog.test.tsx +++ b/src/components/earn/MoveToJarDialog.test.tsx @@ -72,16 +72,16 @@ vi.mock('@/context/JamWalletInfoContext', () => ({ vi.mock('@/components/ui/jam/SelectableJar', () => ({ SelectableJar: ({ name, - onClick, + onSelect, disabled, isSelected, }: { name?: string - onClick?: () => void + onSelect?: () => void disabled?: boolean isSelected?: boolean }) => ( - ), diff --git a/src/components/earn/fidelity-bond/FidelityBondJarSelector.tsx b/src/components/earn/fidelity-bond/FidelityBondJarSelector.tsx index 68f6a4e46..db4db5f7a 100644 --- a/src/components/earn/fidelity-bond/FidelityBondJarSelector.tsx +++ b/src/components/earn/fidelity-bond/FidelityBondJarSelector.tsx @@ -24,7 +24,7 @@ export function FidelityBondJarSelector({ selectedJarIndex, onSelect, isJarDisab frozenOrLockedBalance={jar.balanceSummary.calculatedFrozenOrLockedBalanceInSats} totalWalletBalance={walletBalanceSummary.calculatedTotalBalanceInSats} isSelected={selectedJarIndex === jar.jarIndex} - onClick={() => onSelect(jar.jarIndex)} + onSelect={() => onSelect(jar.jarIndex)} disabled={isJarDisabled(jar)} /> ))} diff --git a/src/components/receive/ReceiveForm.test.tsx b/src/components/receive/ReceiveForm.test.tsx index 5eba191df..9e04c2192 100644 --- a/src/components/receive/ReceiveForm.test.tsx +++ b/src/components/receive/ReceiveForm.test.tsx @@ -19,8 +19,8 @@ vi.mock('@/context/JamWalletInfoContext', () => ({ })) vi.mock('@/components/ui/jam/SelectableJar', () => ({ - SelectableJar: ({ name, isSelected, onClick }: { name: string; isSelected: boolean; onClick: () => void }) => ( - ), diff --git a/src/components/receive/ReceiveForm.tsx b/src/components/receive/ReceiveForm.tsx index f3be268f9..241e9fde7 100644 --- a/src/components/receive/ReceiveForm.tsx +++ b/src/components/receive/ReceiveForm.tsx @@ -102,7 +102,7 @@ export const ReceiveForm = ({ className, defaultValues, onSubmit, jars, disabled frozenOrLockedBalance={jar.balanceSummary.calculatedFrozenOrLockedBalanceInSats} totalWalletBalance={walletBalanceSummary.calculatedTotalBalanceInSats} isSelected={values.source?.fromJar === jar.jarIndex} - onClick={() => { + onSelect={() => { setValue('source.fromJar', jar.jarIndex, { shouldValidate: true }) }} disabled={disabled || isSubmitting} diff --git a/src/components/send/JarSelectorDialog.test.tsx b/src/components/send/JarSelectorDialog.test.tsx index ca199a44e..6d86673af 100644 --- a/src/components/send/JarSelectorDialog.test.tsx +++ b/src/components/send/JarSelectorDialog.test.tsx @@ -11,16 +11,16 @@ vi.mock('react-i18next', () => ({ vi.mock('../ui/jam/SelectableJar', () => ({ SelectableJar: ({ name, - onClick, + onSelect, disabled, isSelected, }: { name?: string - onClick?: () => void + onSelect?: () => void disabled?: boolean isSelected?: boolean }) => ( - ), diff --git a/src/components/send/JarSelectorDialog.tsx b/src/components/send/JarSelectorDialog.tsx index 27b15d64f..fd8e82063 100644 --- a/src/components/send/JarSelectorDialog.tsx +++ b/src/components/send/JarSelectorDialog.tsx @@ -90,7 +90,7 @@ export default function JarSelectorDialog({ frozenOrLockedBalance={jar.balanceSummary.calculatedFrozenOrLockedBalanceInSats} totalWalletBalance={walletBalanceSummary.calculatedTotalBalanceInSats} isSelected={selectedJarIndex === jar.jarIndex} - onClick={() => + onSelect={() => setValue('jarIndex', jar.jarIndex, { shouldDirty: true, shouldTouch: true, diff --git a/src/components/send/SendForm.test.tsx b/src/components/send/SendForm.test.tsx index 78597dd9f..d08ea7850 100644 --- a/src/components/send/SendForm.test.tsx +++ b/src/components/send/SendForm.test.tsx @@ -60,8 +60,8 @@ vi.mock('../ui/jam/Balance', () => ({ })) vi.mock('../ui/jam/SelectableJar', () => ({ - SelectableJar: ({ name, onClick, disabled }: { name?: string; onClick?: () => void; disabled?: boolean }) => ( - ), diff --git a/src/components/send/SendForm.tsx b/src/components/send/SendForm.tsx index 714303fe4..0140b17f1 100644 --- a/src/components/send/SendForm.tsx +++ b/src/components/send/SendForm.tsx @@ -107,6 +107,7 @@ interface SendFormProps { className?: string onSubmit: SubmitHandler onSourceJarChange?: (jarIndex: JarIndex | undefined) => void + onSourceJarClicked?: (jarIndex: JarIndex | undefined) => void sourceJarLabelButton?: (errors?: FieldErrors<{ fromJar: number }>) => React.ReactElement minNumberOfCollaborators?: number feeConfigValues: JamFeeConfigValues @@ -122,6 +123,7 @@ export function SendForm({ className, onSubmit, onSourceJarChange, + onSourceJarClicked, sourceJarLabelButton, disabled, feeConfigValues, @@ -306,7 +308,7 @@ export function SendForm({
{t('send.label_source_jar')} - {sourceJarLabelButton?.(errors.source?.fromJar)} + {sourceJarLabelButton?.(errors.source)}
{jars.map((jar, index) => { @@ -314,6 +316,8 @@ export function SendForm({ jar.balanceSummary.calculatedAvailableBalanceInSats <= 0 && jar.balanceSummary.calculatedAvailableFrozenBalanceInSats <= 0 + const isSelected = sourceJarIndex === jar.jarIndex + return ( { + if (isSelected) { + onSourceJarClicked?.(sourceJarIndex) + } + }} + onSelect={() => { setValue('source.fromJar', jar.jarIndex, { shouldValidate: true, shouldDirty: true, diff --git a/src/components/send/SendPage.tsx b/src/components/send/SendPage.tsx index 6400d40d8..21c8096a0 100644 --- a/src/components/send/SendPage.tsx +++ b/src/components/send/SendPage.tsx @@ -610,8 +610,9 @@ export const SendPage = ({ walletFileName }: SendPageProps) => { } debug={isDeveloperMode} onSourceJarChange={setSourceJarIndex} + onSourceJarClicked={onOpenUtxoSelector} sourceJarLabelButton={(errors) => { - const errorSolvedByUtxoSelection = errors?.fromJar?.type === 'valid-source-jar-must-unfreeze-utxos' + const errorSolvedByUtxoSelection = errors?.fromJar?.type === 'valid-source-jar-must-unfreeze-utxos-test' const animate = utxoSelectionDialog.dialogProps.open === false && errorSolvedByUtxoSelection return (
@@ -235,6 +241,7 @@ export const Interactive: Story = { frozenOrLockedBalance: 100_000, totalWalletBalance: 500_000, isSelected: false, + onSelect: () => alert('Selected'), onClick: () => alert('Interactive jar clicked'), }, parameters: { From e911647e12febf0554c23b65abead250cda4bffc Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 29 Jul 2026 12:49:05 +0200 Subject: [PATCH 6/9] test: add tests for Jar and SelectableJar --- src/components/MainWalletPage.test.tsx | 2 +- src/components/earn/FidelityBondCard.test.tsx | 2 +- src/components/earn/FidelityBondCard.tsx | 2 +- src/components/earn/OfferCard.test.tsx | 2 +- src/components/layout/AppNavbar.tsx | 2 +- src/components/orderbook/OrderbookContent.tsx | 2 +- .../send/PaymentConfirmDialog.test.tsx | 2 +- src/components/send/PaymentConfirmDialog.tsx | 2 +- src/components/send/SendForm.test.tsx | 2 +- src/components/send/SendForm.tsx | 2 +- src/components/ui/jam/Balance.test.tsx | 2 +- src/components/ui/jam/Jar.test.tsx | 70 ++++++++++ src/components/ui/jam/Jar.tsx | 2 +- src/components/ui/jam/SelectableJar.test.tsx | 131 ++++++++++++++++++ .../wallet/BranchEntryTable.test.tsx | 2 +- src/components/wallet/BranchEntryTable.tsx | 2 +- src/components/wallet/JarUtxosTable.test.tsx | 2 +- src/components/wallet/JarUtxosTable.tsx | 2 +- .../wallet/WalletJarsDetailsContent.test.tsx | 2 +- .../wallet/WalletJarsDetailsContent.tsx | 2 +- 20 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 src/components/ui/jam/Jar.test.tsx create mode 100644 src/components/ui/jam/SelectableJar.test.tsx diff --git a/src/components/MainWalletPage.test.tsx b/src/components/MainWalletPage.test.tsx index 069fe495c..5d456415b 100644 --- a/src/components/MainWalletPage.test.tsx +++ b/src/components/MainWalletPage.test.tsx @@ -53,7 +53,7 @@ vi.mock('./wallet/WalletJarsDetailsOverlay', () => ({ ), })) -vi.mock('./ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString, onClick }: { valueString: string; onClick?: () => void }) => ( {valueString} ), diff --git a/src/components/earn/FidelityBondCard.test.tsx b/src/components/earn/FidelityBondCard.test.tsx index 60b17a504..b7cffbfc8 100644 --- a/src/components/earn/FidelityBondCard.test.tsx +++ b/src/components/earn/FidelityBondCard.test.tsx @@ -34,7 +34,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: ({ value }: { value?: string }) => {value}, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString?: string }) => {valueString}, })) diff --git a/src/components/earn/FidelityBondCard.tsx b/src/components/earn/FidelityBondCard.tsx index 7cb649bf7..7b6e10f00 100644 --- a/src/components/earn/FidelityBondCard.tsx +++ b/src/components/earn/FidelityBondCard.tsx @@ -2,12 +2,12 @@ import { useMemo, type PropsWithChildren } from 'react' import { ClockIcon, CoinsIcon, CopyIcon } from 'lucide-react' import { Trans, useTranslation } from 'react-i18next' import { Card, CardContent, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' +import { Balance } from '@/components/ui/jam/Balance' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import type { FidelityBondUtxo } from '@/hooks/useQueryUtxos' import * as fb from '@/lib/fidelityBondUtils' import { time } from '@/lib/utils' import { Address } from '../ui/jam/Address' -import { Balance } from '../ui/jam/Balance' interface FidelityBondCardProps { value: FidelityBondUtxo diff --git a/src/components/earn/OfferCard.test.tsx b/src/components/earn/OfferCard.test.tsx index 628214672..2f8e48f27 100644 --- a/src/components/earn/OfferCard.test.tsx +++ b/src/components/earn/OfferCard.test.tsx @@ -18,7 +18,7 @@ vi.mock('@/lib/utils', () => ({ isRelativeOffer: (type: string) => type === 'sw0relo', })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString?: string }) => {valueString}, })) diff --git a/src/components/layout/AppNavbar.tsx b/src/components/layout/AppNavbar.tsx index c4a883ffb..4255c19a3 100644 --- a/src/components/layout/AppNavbar.tsx +++ b/src/components/layout/AppNavbar.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next' import { Link, useNavigate, type NavigateFunction } from 'react-router-dom' import { DevBadge } from '@/components/dev/DevBadge' import { Button } from '@/components/ui/button' +import { Balance } from '@/components/ui/jam/Balance' import { ThemeToggleButton } from '@/components/ui/jam/ThemeToggleButton' import { Skeleton } from '@/components/ui/skeleton' import type { SidebarContextProps } from '@/components/ui/use-sidebar' @@ -15,7 +16,6 @@ import type { RescanInfo } from '@/context/JamSessionInfoContext' import { cn, shortenStringMiddle } from '@/lib/utils' import type { AmountSats } from '@/types/global' import { WithActivityIndicator } from '../ui/jam/ActivityIndicator' -import { Balance } from '../ui/jam/Balance' import { Spinner } from '../ui/spinner' import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip' diff --git a/src/components/orderbook/OrderbookContent.tsx b/src/components/orderbook/OrderbookContent.tsx index e6c9893b0..ae616ade6 100644 --- a/src/components/orderbook/OrderbookContent.tsx +++ b/src/components/orderbook/OrderbookContent.tsx @@ -18,6 +18,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' +import { Balance } from '@/components/ui/jam/Balance' import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' @@ -36,7 +37,6 @@ import { } from '@/lib/utils' import { useDeveloperMode } from '@/store/jamSettingsStore' import { jmSessionStore } from '@/store/jmSessionStore' -import { Balance } from '../ui/jam/Balance' import { Spinner } from '../ui/spinner' import { OrderbookChart } from './OrderbookChart' import { OrderbookTable, type OrderTableEntry } from './OrderbookTable' diff --git a/src/components/send/PaymentConfirmDialog.test.tsx b/src/components/send/PaymentConfirmDialog.test.tsx index d8b427bfe..cf6c04cee 100644 --- a/src/components/send/PaymentConfirmDialog.test.tsx +++ b/src/components/send/PaymentConfirmDialog.test.tsx @@ -27,7 +27,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: ({ value }: { value: string }) => address:{value}, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString: string }) => balance:{valueString}, })) diff --git a/src/components/send/PaymentConfirmDialog.tsx b/src/components/send/PaymentConfirmDialog.tsx index 5e455a8d4..462beabac 100644 --- a/src/components/send/PaymentConfirmDialog.tsx +++ b/src/components/send/PaymentConfirmDialog.tsx @@ -3,6 +3,7 @@ import { InfoIcon } from 'lucide-react' import { Trans, useTranslation } from 'react-i18next' import { Badge } from '@/components/ui/badge' import { jarBadgeVariant } from '@/components/ui/badge-variants' +import { Balance } from '@/components/ui/jam/Balance' import type { Jar } from '@/context/JamWalletInfoContext' import type { Utxo } from '@/hooks/useQueryUtxos' import type { JamFeeConfigValues } from '@/lib/feeConfig' @@ -13,7 +14,6 @@ import { Button } from '../ui/button' import { Card, CardContent, CardHeader } from '../ui/card' import { Dialog, DialogTitle, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '../ui/dialog' import { Address } from '../ui/jam/Address' -import { Balance } from '../ui/jam/Balance' import { Spinner } from '../ui/spinner' import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip' import { estimateMaxCollaboratorFee, useMiningFeeText, type EstimateMaxCollaboratorFeeResult } from './feeEstimate' diff --git a/src/components/send/SendForm.test.tsx b/src/components/send/SendForm.test.tsx index d08ea7850..10f4a0a3c 100644 --- a/src/components/send/SendForm.test.tsx +++ b/src/components/send/SendForm.test.tsx @@ -55,7 +55,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: () =>
, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString?: string }) =>
{valueString}
, })) diff --git a/src/components/send/SendForm.tsx b/src/components/send/SendForm.tsx index 0140b17f1..bad1016f4 100644 --- a/src/components/send/SendForm.tsx +++ b/src/components/send/SendForm.tsx @@ -9,6 +9,7 @@ import type { FieldErrors, Resolver, SubmitHandler } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import QrScannerDialog from '@/components/ui/QrScannerDialog' +import { Balance } from '@/components/ui/jam/Balance' import { MAX_NUM_COLLABORATORS } from '@/constants/jam' import { JM_MINIMUM_MAKERS_DEFAULT, JM_TAKER_UTXO_AGE } from '@/constants/jm' import { useDetectNetwork, type AddressSummary, type Jar } from '@/context/JamWalletInfoContext' @@ -30,7 +31,6 @@ import { Field, FieldDescription, FieldError, FieldLabel } from '../ui/field' import { Input } from '../ui/input' import { inputVariants } from '../ui/input-variants' import { Address } from '../ui/jam/Address' -import { Balance } from '../ui/jam/Balance' import { SatSymbol } from '../ui/jam/CurrencySymbol' import { SelectableJar } from '../ui/jam/SelectableJar' import { Slider } from '../ui/slider' diff --git a/src/components/ui/jam/Balance.test.tsx b/src/components/ui/jam/Balance.test.tsx index 25489f7eb..b5c8ce406 100644 --- a/src/components/ui/jam/Balance.test.tsx +++ b/src/components/ui/jam/Balance.test.tsx @@ -3,8 +3,8 @@ import '@testing-library/jest-dom/vitest' import { render as reactRender, screen, type RenderOptions } from '@testing-library/react' import user from '@testing-library/user-event' import { describe, it, expect, vi } from 'vitest' +import { Balance } from '@/components/ui/jam/Balance' import { JamDisplayContextProvider } from '@/context/JamDisplayContextProvider' -import { Balance } from './Balance' const render = (ui: React.ReactNode, options?: Omit) => { const providers = ({ children }: { children: React.ReactNode }) => { diff --git a/src/components/ui/jam/Jar.test.tsx b/src/components/ui/jam/Jar.test.tsx new file mode 100644 index 000000000..af507cf5f --- /dev/null +++ b/src/components/ui/jam/Jar.test.tsx @@ -0,0 +1,70 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import { type Jar as JarType } from '@/context/JamWalletInfoContext' +import type { AmountSats } from '@/types/global' +import { Jar } from './Jar' + +vi.mock('@/components/ui/jam/Balance', () => ({ + Balance: ({ valueString, onClick }: { valueString: string; onClick?: () => void }) => ( + balance:{valueString} + ), +})) + +const DUMMY_JAR: JarType = { + balanceSummary: { + calculatedTotalBalanceInSats: 5, + calculatedAvailableBalanceInSats: 4, + calculatedConfirmedAvailableBalanceInSats: 3, + calculatedAvailableFrozenBalanceInSats: 2, + calculatedFrozenOrLockedBalanceInSats: 1, + }, + color: '#666', + jarIndex: 0, + name: 'Test jar', + utxos: [], +} + +describe('Jar', () => { + it('renders correctly', () => { + render( + , + ) + + expect(screen.getByText(DUMMY_JAR.name)).toBeInTheDocument() + expect(screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedAvailableBalanceInSats)).toBeInTheDocument() + expect( + screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedFrozenOrLockedBalanceInSats), + ).toBeInTheDocument() + expect( + screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedFrozenOrLockedBalanceInSats).parentNode, + ).not.toHaveClass('hidden') + }) + it('does not render zero frozen balance', () => { + const frozenBalance: AmountSats = 0 + + render( + , + ) + + expect(screen.getByText(DUMMY_JAR.name)).toBeInTheDocument() + expect(screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedAvailableBalanceInSats)).toBeInTheDocument() + expect(screen.getByText('balance:' + frozenBalance)).toBeInTheDocument() + expect(screen.getByText('balance:' + frozenBalance).parentNode).toHaveClass('hidden') + }) +}) diff --git a/src/components/ui/jam/Jar.tsx b/src/components/ui/jam/Jar.tsx index 451ea5108..c8192366b 100644 --- a/src/components/ui/jam/Jar.tsx +++ b/src/components/ui/jam/Jar.tsx @@ -1,7 +1,7 @@ +import { Balance } from '@/components/ui/jam/Balance' import type { JarColor } from '@/context/JamWalletInfoContext' import { cn } from '@/lib/utils' import type { AmountSats } from '@/types/global' -import { Balance } from './Balance' import { JarIcon } from './JarIcon' interface JarProps { diff --git a/src/components/ui/jam/SelectableJar.test.tsx b/src/components/ui/jam/SelectableJar.test.tsx new file mode 100644 index 000000000..cb7c7a6ea --- /dev/null +++ b/src/components/ui/jam/SelectableJar.test.tsx @@ -0,0 +1,131 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { describe, expect, it, vi } from 'vitest' +import { beforeEach } from 'vitest' +import type { Jar } from '@/context/JamWalletInfoContext' +import { SelectableJar } from './SelectableJar' + +const mocks = vi.hoisted(() => ({ + onClick: vi.fn(), + onSelect: vi.fn(), +})) + +vi.mock('@/components/ui/jam/Balance', () => ({ + Balance: ({ valueString, onClick }: { valueString: string; onClick?: () => void }) => ( + balance:{valueString} + ), +})) + +const DUMMY_JAR: Jar = { + balanceSummary: { + calculatedTotalBalanceInSats: 5, + calculatedAvailableBalanceInSats: 4, + calculatedConfirmedAvailableBalanceInSats: 3, + calculatedAvailableFrozenBalanceInSats: 2, + calculatedFrozenOrLockedBalanceInSats: 1, + }, + color: '#666', + jarIndex: 0, + name: 'Test jar', + utxos: [], +} + +describe('SelectableJar', () => { + beforeEach(() => { + mocks.onClick.mockReset() + mocks.onSelect.mockReset() + }) + + it('renders correctly', () => { + render( + , + ) + + expect(screen.getByText(DUMMY_JAR.name)).toBeInTheDocument() + expect(screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedAvailableBalanceInSats)).toBeInTheDocument() + expect( + screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedFrozenOrLockedBalanceInSats), + ).toBeInTheDocument() + }) + + it('can be selected', async () => { + const user = userEvent.setup() + + render( + , + ) + + await user.click(screen.getByRole('button')) + + expect(mocks.onClick).toHaveBeenCalled() + expect(mocks.onSelect).toHaveBeenCalled() + }) + + it('can not be selected if disabled', async () => { + const user = userEvent.setup() + + render( + , + ) + + await user.click(screen.getByRole('button')) + + expect(mocks.onClick).not.toHaveBeenCalled() + expect(mocks.onSelect).not.toHaveBeenCalled() + }) + + it('does not call onSelect if already selected', async () => { + const user = userEvent.setup() + + render( + , + ) + + await user.click(screen.getByRole('button')) + + expect(mocks.onClick).toHaveBeenCalled() + expect(mocks.onSelect).not.toHaveBeenCalled() + }) +}) diff --git a/src/components/wallet/BranchEntryTable.test.tsx b/src/components/wallet/BranchEntryTable.test.tsx index 9c5253f51..ab0af3bb6 100644 --- a/src/components/wallet/BranchEntryTable.test.tsx +++ b/src/components/wallet/BranchEntryTable.test.tsx @@ -14,7 +14,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: ({ value }: { value: string }) => {value}, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString: string }) => {valueString}, })) diff --git a/src/components/wallet/BranchEntryTable.tsx b/src/components/wallet/BranchEntryTable.tsx index 9504f6d2b..f3cb314d2 100644 --- a/src/components/wallet/BranchEntryTable.tsx +++ b/src/components/wallet/BranchEntryTable.tsx @@ -19,6 +19,7 @@ import { type Table as TableType, } from '@tanstack/react-table' import { useTranslation } from 'react-i18next' +import { Balance } from '@/components/ui/jam/Balance' import { TablePagination } from '@/components/ui/jam/TablePagination' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import type { AccountBranch } from '@/context/JamWalletInfoContext' @@ -26,7 +27,6 @@ import type { UtxoTag } from '@/lib/tags' import { cn } from '@/lib/utils' import type { AmountSats, BitcoinAddress, HdPath } from '@/types/global' import { Address } from '../ui/jam/Address' -import { Balance } from '../ui/jam/Balance' import { SortIcon } from '../ui/jam/SortIcon' import { StatusBadge } from '../ui/jam/StatusBadge' diff --git a/src/components/wallet/JarUtxosTable.test.tsx b/src/components/wallet/JarUtxosTable.test.tsx index bcb81cb6d..213a09140 100644 --- a/src/components/wallet/JarUtxosTable.test.tsx +++ b/src/components/wallet/JarUtxosTable.test.tsx @@ -26,7 +26,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: ({ value }: { value: string }) => {value}, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString: string }) => {valueString}, })) diff --git a/src/components/wallet/JarUtxosTable.tsx b/src/components/wallet/JarUtxosTable.tsx index 3f2bed14d..f6ac6e5ad 100644 --- a/src/components/wallet/JarUtxosTable.tsx +++ b/src/components/wallet/JarUtxosTable.tsx @@ -28,6 +28,7 @@ import type { TFunction } from 'i18next' import { ChevronDownIcon, SnowflakeIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' +import { Balance } from '@/components/ui/jam/Balance' import { TablePagination } from '@/components/ui/jam/TablePagination' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import type { Utxo } from '@/hooks/useQueryUtxos' @@ -38,7 +39,6 @@ import { Button } from '../ui/button' import { Card, CardContent } from '../ui/card' import { Checkbox } from '../ui/checkbox' import { Address } from '../ui/jam/Address' -import { Balance } from '../ui/jam/Balance' import { SortIcon } from '../ui/jam/SortIcon' import { StatusBadge } from '../ui/jam/StatusBadge' diff --git a/src/components/wallet/WalletJarsDetailsContent.test.tsx b/src/components/wallet/WalletJarsDetailsContent.test.tsx index fa0490251..fc289e62e 100644 --- a/src/components/wallet/WalletJarsDetailsContent.test.tsx +++ b/src/components/wallet/WalletJarsDetailsContent.test.tsx @@ -51,7 +51,7 @@ vi.mock('../ui/jam/Address', () => ({ Address: ({ value }: { value: string }) => {value}, })) -vi.mock('../ui/jam/Balance', () => ({ +vi.mock('@/components/ui/jam/Balance', () => ({ Balance: ({ valueString }: { valueString: string }) => {valueString}, })) diff --git a/src/components/wallet/WalletJarsDetailsContent.tsx b/src/components/wallet/WalletJarsDetailsContent.tsx index e145599bc..5df718453 100644 --- a/src/components/wallet/WalletJarsDetailsContent.tsx +++ b/src/components/wallet/WalletJarsDetailsContent.tsx @@ -7,6 +7,7 @@ import { AlertTriangleIcon, RefreshCwIcon, ThermometerSnowflakeIcon, Thermometer import { Trans, useTranslation } from 'react-i18next' import { toast } from 'sonner' import { useJamSessionInfoContext } from '@/context/JamSessionInfoContext' +import { Balance } from '@/components/ui/jam/Balance' import { useAccountSummary, useAddressSummary, @@ -26,7 +27,6 @@ import { Alert, AlertDescription, AlertTitle } from '../ui/alert' import { Button } from '../ui/button' import { ButtonGroup } from '../ui/button-group' import { Input } from '../ui/input' -import { Balance } from '../ui/jam/Balance' import { Spinner } from '../ui/spinner' import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs' import { AccountDetailsTabContent } from './AccountDetailsTabContent' From 76d420a64a78a20cb2e2870136e735f704b0cc59 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Wed, 29 Jul 2026 12:53:53 +0200 Subject: [PATCH 7/9] chore(ui): frozen balance with class 'invisible' instead of 'hidden' --- src/components/ui/jam/Jar.test.tsx | 4 ++-- src/components/ui/jam/Jar.tsx | 2 +- src/components/ui/jam/SelectableJar.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ui/jam/Jar.test.tsx b/src/components/ui/jam/Jar.test.tsx index af507cf5f..d9c82b421 100644 --- a/src/components/ui/jam/Jar.test.tsx +++ b/src/components/ui/jam/Jar.test.tsx @@ -45,7 +45,7 @@ describe('Jar', () => { ).toBeInTheDocument() expect( screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedFrozenOrLockedBalanceInSats).parentNode, - ).not.toHaveClass('hidden') + ).not.toHaveClass('invisible') }) it('does not render zero frozen balance', () => { const frozenBalance: AmountSats = 0 @@ -65,6 +65,6 @@ describe('Jar', () => { expect(screen.getByText(DUMMY_JAR.name)).toBeInTheDocument() expect(screen.getByText('balance:' + DUMMY_JAR.balanceSummary.calculatedAvailableBalanceInSats)).toBeInTheDocument() expect(screen.getByText('balance:' + frozenBalance)).toBeInTheDocument() - expect(screen.getByText('balance:' + frozenBalance).parentNode).toHaveClass('hidden') + expect(screen.getByText('balance:' + frozenBalance).parentNode).toHaveClass('invisible') }) }) diff --git a/src/components/ui/jam/Jar.tsx b/src/components/ui/jam/Jar.tsx index c8192366b..8cb6250f5 100644 --- a/src/components/ui/jam/Jar.tsx +++ b/src/components/ui/jam/Jar.tsx @@ -53,7 +53,7 @@ export function Jar({
diff --git a/src/components/ui/jam/SelectableJar.tsx b/src/components/ui/jam/SelectableJar.tsx index fedac0b00..a526b7cf8 100644 --- a/src/components/ui/jam/SelectableJar.tsx +++ b/src/components/ui/jam/SelectableJar.tsx @@ -24,7 +24,7 @@ export const SelectableJar = ({ return (