mirror of
https://github.com/joinmarket-webui/jam.git
synced 2026-08-20 13:28:21 +02:00
feat: abort collaborative transaction (#497)
* feat(taker): ability to abort single collaborative transaction * feat(taker): show confirm modal before aborting collaborative transaction * fix: disable abort button if abort confirm modal is already shown * fix: let normal init process reload wallet and service info * doc(send): fix typo in comment Co-authored-by: Gigi <109058+dergigi@users.noreply.github.com>
This commit is contained in:
parent
db292356f5
commit
80e40ff510
4 changed files with 74 additions and 21 deletions
|
|
@ -174,7 +174,7 @@ export default function Jam() {
|
|||
setIsLoading(true)
|
||||
|
||||
const abortCtrl = new AbortController()
|
||||
return Api.getSchedulerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token })
|
||||
return Api.getTakerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token })
|
||||
.then((res) => (res.ok ? true : Api.Helper.throwError(res, t('scheduler.error_stopping_schedule_failed'))))
|
||||
.then((_) => setCollaborativeOperationRunning(false))
|
||||
.then((_) =>
|
||||
|
|
|
|||
|
|
@ -252,7 +252,8 @@ export default function Send() {
|
|||
[isInitializing, waitForUtxosToBeSpent]
|
||||
)
|
||||
|
||||
const [showConfirmInputsModal, setShowConfirmInputsModal] = useState(false)
|
||||
const [showConfirmAbortModal, setShowConfirmAbortModal] = useState(false)
|
||||
const [showConfirmSendModal, setShowConfirmSendModal] = useState(false)
|
||||
const submitButtonRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -498,6 +499,34 @@ export default function Send() {
|
|||
return success
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// hide the abort modal, if a user wants to abort a running transaction,
|
||||
// but the transaction failed or succeeded in the meantime
|
||||
if (showConfirmAbortModal && !isCoinjoinInProgress) {
|
||||
setShowConfirmAbortModal(false)
|
||||
}
|
||||
}, [isCoinjoinInProgress, showConfirmAbortModal])
|
||||
|
||||
const abortCoinjoin = async () => {
|
||||
if (!isCoinjoinInProgress) {
|
||||
setShowConfirmAbortModal(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (!showConfirmAbortModal) {
|
||||
setShowConfirmAbortModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
setShowConfirmAbortModal(false)
|
||||
setAlert(null)
|
||||
|
||||
const abortCtrl = new AbortController()
|
||||
return Api.getTakerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token }).catch((err) => {
|
||||
setAlert({ variant: 'danger', message: err.message })
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
|
|
@ -509,12 +538,12 @@ export default function Send() {
|
|||
const isValid = formIsValid
|
||||
|
||||
if (isValid) {
|
||||
if (!showConfirmInputsModal) {
|
||||
setShowConfirmInputsModal(true)
|
||||
if (!showConfirmSendModal) {
|
||||
setShowConfirmSendModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
setShowConfirmInputsModal(false)
|
||||
setShowConfirmSendModal(false)
|
||||
|
||||
const counterparties = parseInt(numCollaborators, 10)
|
||||
|
||||
|
|
@ -652,8 +681,17 @@ export default function Send() {
|
|||
</Link>
|
||||
)}
|
||||
{isCoinjoinInProgress && (
|
||||
<rb.Alert variant="info" className="mb-4">
|
||||
<rb.Alert variant="info" className="mb-4 d-flex align-items-center">
|
||||
{t('send.text_coinjoin_already_running')}
|
||||
|
||||
<rb.Button
|
||||
variant={'outline-light'}
|
||||
className="ms-auto"
|
||||
disabled={showConfirmAbortModal}
|
||||
onClick={() => abortCoinjoin()}
|
||||
>
|
||||
{t('global.abort')}
|
||||
</rb.Button>
|
||||
</rb.Alert>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -893,10 +931,20 @@ export default function Send() {
|
|||
t('send.button_send_without_improved_privacy')
|
||||
)}
|
||||
</rb.Button>
|
||||
|
||||
<ConfirmModal
|
||||
isShown={showConfirmInputsModal}
|
||||
title={t('send.confirm_modal.title')}
|
||||
onCancel={() => setShowConfirmInputsModal(false)}
|
||||
isShown={showConfirmAbortModal}
|
||||
title={t('send.confirm_abort_modal.title')}
|
||||
onCancel={() => setShowConfirmAbortModal(false)}
|
||||
onConfirm={() => abortCoinjoin()}
|
||||
>
|
||||
{t('send.confirm_abort_modal.text_body')}
|
||||
</ConfirmModal>
|
||||
|
||||
<ConfirmModal
|
||||
isShown={showConfirmSendModal}
|
||||
title={t('send.confirm_send_modal.title')}
|
||||
onCancel={() => setShowConfirmSendModal(false)}
|
||||
onConfirm={() => {
|
||||
submitButtonRef.current?.click()
|
||||
}}
|
||||
|
|
@ -905,23 +953,23 @@ export default function Send() {
|
|||
<rb.Row className="mt-2 mb-3">
|
||||
<rb.Col xs={12} className="text-center">
|
||||
{isCoinjoin ? (
|
||||
<strong className="text-success">{t('send.confirm_modal.text_collaborative_tx_enabled')}</strong>
|
||||
<strong className="text-success">{t('send.confirm_send_modal.text_collaborative_tx_enabled')}</strong>
|
||||
) : (
|
||||
<strong className="text-danger">{t('send.confirm_modal.text_collaborative_tx_disabled')}</strong>
|
||||
<strong className="text-danger">{t('send.confirm_send_modal.text_collaborative_tx_disabled')}</strong>
|
||||
)}
|
||||
</rb.Col>
|
||||
</rb.Row>
|
||||
<rb.Row>
|
||||
<rb.Col xs={3} className="text-end">
|
||||
<strong>{t('send.confirm_modal.label_source_jar')}</strong>
|
||||
<strong>{t('send.confirm_send_modal.label_source_jar')}</strong>
|
||||
</rb.Col>
|
||||
<rb.Col xs={9} className="text-start">
|
||||
{t('send.confirm_modal.text_source_jar', { jarId: jarInitial(account) })}
|
||||
{t('send.confirm_send_modal.text_source_jar', { jarId: jarInitial(account) })}
|
||||
</rb.Col>
|
||||
</rb.Row>
|
||||
<rb.Row>
|
||||
<rb.Col xs={3} className="text-end">
|
||||
<strong>{t('send.confirm_modal.label_recipient')}</strong>
|
||||
<strong>{t('send.confirm_send_modal.label_recipient')}</strong>
|
||||
</rb.Col>
|
||||
<rb.Col xs={9} className="text-start text-break slashed-zeroes">
|
||||
{destination}
|
||||
|
|
@ -929,12 +977,12 @@ export default function Send() {
|
|||
</rb.Row>
|
||||
<rb.Row>
|
||||
<rb.Col xs={3} className="text-end">
|
||||
<strong>{t('send.confirm_modal.label_amount')}</strong>
|
||||
<strong>{t('send.confirm_send_modal.label_amount')}</strong>
|
||||
</rb.Col>
|
||||
<rb.Col xs={9} className="text-start">
|
||||
{isSweep ? (
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Trans i18nKey="send.confirm_modal.text_sweep_balance">
|
||||
<Trans i18nKey="send.confirm_send_modal.text_sweep_balance">
|
||||
Sweep
|
||||
<Balance
|
||||
valueString={amountFieldValue().toString()}
|
||||
|
|
@ -946,7 +994,7 @@ export default function Send() {
|
|||
placement="right"
|
||||
overlay={
|
||||
<rb.Popover>
|
||||
<rb.Popover.Body>{t('send.confirm_modal.text_sweep_info_popover')}</rb.Popover.Body>
|
||||
<rb.Popover.Body>{t('send.confirm_send_modal.text_sweep_info_popover')}</rb.Popover.Body>
|
||||
</rb.Popover>
|
||||
}
|
||||
>
|
||||
|
|
@ -967,7 +1015,7 @@ export default function Send() {
|
|||
{isCoinjoin && (
|
||||
<rb.Row>
|
||||
<rb.Col xs={3} className="text-end">
|
||||
<strong>{t('send.confirm_modal.label_num_collaborators')}</strong>
|
||||
<strong>{t('send.confirm_send_modal.label_num_collaborators')}</strong>
|
||||
</rb.Col>
|
||||
<rb.Col xs={9} className="text-start">
|
||||
{numCollaborators}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"next": "Next",
|
||||
"back": "Back",
|
||||
"close": "Close",
|
||||
"abort": "Abort",
|
||||
"table": {
|
||||
"pagination": {
|
||||
"items_per_page": {
|
||||
|
|
@ -208,7 +209,7 @@
|
|||
"sweep_amount_breakdown_frozen_balance": "Frozen or locked balance",
|
||||
"sweep_amount_breakdown_estimated_amount": "Estimated amount to be sent",
|
||||
"sweep_amount_breakdown_explanation": "A sweep transaction will consume all UTXOs of a mixdepth leaving no coins behind except those that have been <1>frozen</1> or <3>time-locked</3>. Onchain transaction fees and market maker fees will be deducted from the amount so as to leave zero change. The exact transaction amount can only be calculated by JoinMarket at the point when the transaction is made. Therefore the estimated amount shown might deviate from the actually sent amount. Refer to the <5>JoinMarket documentation</5> for more details.",
|
||||
"confirm_modal": {
|
||||
"confirm_send_modal": {
|
||||
"title": "Confirm payment",
|
||||
"label_amount": "Amount",
|
||||
"text_sweep_balance": "Sweep whole jar (<1></1>)",
|
||||
|
|
@ -220,6 +221,10 @@
|
|||
"text_collaborative_tx_enabled": "Payment with privacy improvement",
|
||||
"text_collaborative_tx_disabled": "Payment without privacy improvement"
|
||||
},
|
||||
"confirm_abort_modal": {
|
||||
"title": "Abort payment",
|
||||
"text_body": "Are you sure you want to abort the collaborative transaction?"
|
||||
},
|
||||
"coinjoin_precondition": {
|
||||
"hint_missing_utxos": "To execute a collaborative transaction you need UTXOs with {{ minConfirmations }} or more confirmations in the source jar. $t(send.coinjoin_precondition.nested_hint_fund_jar, {\"count\": {{ minConfirmations }} })",
|
||||
"hint_missing_confirmations": "A collaborative transaction requires your UTXOs to have {{ minConfirmations }} or more confirmations. $t(send.coinjoin_precondition.nested_hint_wait_for_block, {\"count\": {{ amountOfMissingConfirmations }} })",
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ const postSchedulerStart = async ({ token, signal, walletName }: WalletRequestCo
|
|||
})
|
||||
}
|
||||
|
||||
const getSchedulerStop = async ({ token, signal, walletName }: WalletRequestContext) => {
|
||||
const getTakerStop = async ({ token, signal, walletName }: WalletRequestContext) => {
|
||||
return await fetch(`${basePath()}/v1/wallet/${encodeURIComponent(walletName)}/taker/stop`, {
|
||||
headers: { ...Helper.buildAuthHeader(token) },
|
||||
signal,
|
||||
|
|
@ -414,7 +414,7 @@ export {
|
|||
postConfigGet,
|
||||
getWalletSeed,
|
||||
postSchedulerStart,
|
||||
getSchedulerStop,
|
||||
getTakerStop,
|
||||
getSchedule,
|
||||
Helper,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue