Files
xrpl-dev-portal/resources/dev-tools/components/TransactionButton.tsx
Alexander Mahan 88c8dd3f50 fix loading bar
2026-08-28 11:46:55 -07:00

175 lines
7.0 KiB
TypeScript

import * as React from 'react';
import { useState } from 'react'
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { clsx } from 'clsx'
import { type Transaction, type Wallet } from 'xrpl'
import { SubmitConstData, submitAndUpdateUI, canSendTransaction } from '../utils';
export interface TransactionButtonProps {
submitConstData: SubmitConstData,
connectionReady: boolean,
transaction: Transaction,
sendingWallet: Wallet | undefined
id: string, // Used to set all ids within component
content: {
buttonText: string,
units: string, // Displays after the input number
longerDescription: React.JSX.Element // JSX allows for embedding links within the longer description
buttonTitle?: string // Only used while loading bar is activated
},
inputSettings?: {
defaultValue: number, // Should NOT be a dynamic number
setInputValue: React.Dispatch<React.SetStateAction<number>>,
min: number,
max: number,
},
loadingBar?: {
id: string,
widthPercent: number,
description: string,
defaultOn: boolean,
},
checkBox?: {
setCheckValue: React.Dispatch<React.SetStateAction<boolean>>,
defaultValue: boolean,
description: string,
}
customOnClick?: Function
}
function shouldDisableButton(
connectionReady: boolean,
sendingWallet: Wallet | undefined,
waitingForTransaction: boolean,
loadingBar?: {
widthPercent: number
defaultOn: boolean
}
): boolean {
const loadingBarInProgress = !!loadingBar && loadingBar.widthPercent < 100 &&
(loadingBar.defaultOn || loadingBar.widthPercent > 0)
return !canSendTransaction(connectionReady, sendingWallet?.address)
|| waitingForTransaction
|| loadingBarInProgress
}
export function TransactionButton({
id,
submitConstData,
connectionReady,
transaction,
sendingWallet,
content,
inputSettings,
loadingBar,
checkBox,
customOnClick
}: TransactionButtonProps ) {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
const [waitingForTransaction, setWaitingForTransaction] = useState(false)
const loadingBarComplete = (loadingBar?.widthPercent ?? 0) >= 100
const loadingBarInProgress = !!loadingBar &&
loadingBar.widthPercent > 0 && loadingBar.widthPercent < 100
const showLoadingStatus = !!loadingBar && !loadingBarComplete &&
(loadingBar.defaultOn || loadingBar.widthPercent > 0)
return (
<div>
<div className="form-group" id={id}>
{/* Status + optional progress: Partial Payments setup and EscrowFinish wait */}
{showLoadingStatus && loadingBar && <div className="mb-1" id={loadingBar.id}>
{loadingBarInProgress ? (
<div className="progress tx-sender-progress">
<div
className="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
style={{ width: `${Math.max(loadingBar.widthPercent, 5)}%` }}
aria-valuenow={loadingBar.widthPercent}
aria-valuemin={0}
aria-valuemax={100}
/>
<small className="tx-sender-progress-label">
{translate(loadingBar.description)}
</small>
</div>
) : (
<small className="tx-sender-progress-label">
{translate(loadingBar.description)}
</small>
)}
</div>}
<div className="input-group mb-3">
{/* Loading icon for when transaction is being submitted */}
<div className="input-group-prepend">
<span className="input-group-text loader" style={{display: waitingForTransaction ? '' : 'none'}}>
<img className="throbber" src="/img/xrp-loader-96.png" alt={translate("(loading)")} />
</span>
</div>
<button className={clsx("btn btn-primary form-control needs-connection",
(shouldDisableButton(connectionReady, sendingWallet, waitingForTransaction, loadingBar) && "disabled"))}
type="button" id={id + "_btn"}
disabled={shouldDisableButton(connectionReady, sendingWallet, waitingForTransaction, loadingBar)}
onClick={async () => {
setWaitingForTransaction(true)
customOnClick ? await customOnClick() : await submitAndUpdateUI(submitConstData, sendingWallet!, transaction)
setWaitingForTransaction(false)
}}
title={(loadingBar && (loadingBar.widthPercent > 0 && loadingBar.widthPercent < 100)) ? translate(content.buttonTitle) : ""}
>
{translate(content.buttonText)}
</button>
{inputSettings &&
<input id={id + "_amount"} className="form-control" type="number"
aria-describedby={id + "amount_help"}
defaultValue={inputSettings?.defaultValue}
min={inputSettings?.min}
max={inputSettings?.max}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
// Enforce min / max values
let { value, min, max } = event.target;
const newValue = Math.max(Number(min), Math.min(Number(max), Number(value)));
// Share the value so other logic can update based on it
inputSettings?.setInputValue(newValue)
}
} />}
{inputSettings && <div className="input-group-append">
<span className="input-group-text" id={id + "_help"}>
{translate(content.units)}
</span>
</div>
}
{/* Used for Escrow */}
{checkBox && <span className="input-group-text">
(
<input type="checkbox"
id={id + "_checkbox"}
defaultValue={checkBox.defaultValue ? 1 : 0}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => checkBox.setCheckValue(event.target.checked)} />
<label className="form-check-label" htmlFor={id + "_checkbox"}>
{translate(checkBox.description)}
</label>)
</span>}
</div>
<small className="form-text text-muted">
{content.longerDescription}
</small>
</div>
<hr />
</div>)
}