diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index 88baab836..ed381d7c3 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,12 +1,24 @@
- @if (error) { -
-
- + @if (accelerateError) { +
+
+

Sorry, something went wrong!

- } - @else if (step === 'quote') { +
+
+
+ We were not able to accelerate this transaction. Please try again later. +
+
+
+
+
+
+ +
+
+ } @else if (step === 'quote') {
-
+
@if (showDetails) {
Your transaction
@@ -264,7 +276,7 @@ } @if (!advancedEnabled) { -
+
@@ -295,7 +307,7 @@
} @else { -
+
@@ -345,7 +357,7 @@
-
+

Payment to mempool.space for acceleration of txid {{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}

@@ -353,7 +365,7 @@

Your account will be debited no more than {{ cost | number }} sats

-
+
@@ -365,6 +377,11 @@ @if (invoice) {

Pay {{ ((invoice.btcDue * 100_000_000) || cost) | number }} sats

+ } @else if (btcpayInvoiceFailed) { +

Failed to load invoice

+
+ +
} @else {

Loading invoice...

@@ -532,16 +549,21 @@ Accelerate to ~{{ x | number : '1.0-0' }} sat/vB - @if (canPayWithBalance || canPayWithBitcoin || canPayWithCashapp) { - - } @else { + @if (!couldPay && !quoteError && !(estimate?.availablePaymentMethods.bitcoin || estimate?.availablePaymentMethods.balance)) { + } @else { +
+ + @if (quoteError || cantPayReason) { + + } +
}
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss index 5b302b9ad..c7c0fdb6d 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss @@ -189,3 +189,10 @@ flex-direction: column; } } + +.btn-error { + position: absolute; + right: 0; + font-size: 12px; + color: var(--red); +} \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 9805b5e96..68ed74ad6 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -59,13 +59,18 @@ export class AccelerateCheckout implements OnInit, OnDestroy { @Input() forceMobile: boolean = false; @Input() showDetails: boolean = false; @Input() noCTA: boolean = false; + @Output() unavailable = new EventEmitter(); @Output() completed = new EventEmitter(); @Output() hasDetails = new EventEmitter(); @Output() changeMode = new EventEmitter(); calculating = true; selectedOption: 'wait' | 'accel'; - error = ''; + cantPayReason = ''; + quoteError = ''; // error fetching estimate or initial data + accelerateError = ''; // error executing acceleration + btcpayInvoiceFailed = false; + timePaid: number = 0; // time acceleration requested math = Math; isMobile: boolean = window.innerWidth <= 767.98; @@ -98,6 +103,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { // square loadingCashapp = false; + cashappError = false; cashappSubmit: any; payments: any; cashAppPay: any; @@ -125,7 +131,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { if (this.auth?.user?.userId !== auth?.user?.userId) { this.auth = auth; this.estimate = null; - this.error = null; + this.quoteError = null; + this.accelerateError = null; + this.timePaid = 0; + this.btcpayInvoiceFailed = false; this.moveToStep('summary'); } else { this.auth = auth; @@ -182,6 +191,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.fetchEstimate(); } if (this._step === 'checkout' && this.canPayWithBitcoin) { + this.btcpayInvoiceFailed = false; this.loadingBtcpayInvoice = true; this.invoice = null; this.requestBTCPayInvoice(); @@ -226,19 +236,27 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.estimateSubscription.unsubscribe(); } this.calculating = true; + this.quoteError = null; + this.accelerateError = null; this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( tap((response) => { if (response.status === 204) { - this.error = `cannot_accelerate_tx`; + this.quoteError = `cannot_accelerate_tx`; + if (this.step === 'summary') { + this.unavailable.emit(true); + } } else { this.estimate = response.body; if (!this.estimate) { - this.error = `cannot_accelerate_tx`; + this.quoteError = `cannot_accelerate_tx`; + if (this.step === 'summary') { + this.unavailable.emit(true); + } return; } if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { if (this.isLoggedIn()) { - this.error = `not_enough_balance`; + this.quoteError = `not_enough_balance`; } } this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; @@ -267,6 +285,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; + this.validateChoice(); + if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) { this.loadingBtcpayInvoice = true; this.requestBTCPayInvoice(); @@ -279,13 +299,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy { catchError((response) => { this.estimate = undefined; - this.error = `cannot_accelerate_tx`; + this.quoteError = `cannot_accelerate_tx`; this.estimateSubscription.unsubscribe(); + if (this.step === 'summary') { + this.unavailable.emit(true); + } else { + this.accelerateError = 'cannot_accelerate_tx'; + } return of(null); }) ).subscribe(); } + validateChoice(): void { + if (!this.canPay) { + if (this.estimate?.availablePaymentMethods?.balance) { + if (this.cost >= this.estimate?.userBalance) { + this.cantPayReason = 'not_enough_balance'; + } + } else { + this.cantPayReason = 'cannot_accelerate_tx'; + } + } else { + this.cantPayReason = ''; + } + } + /** * User changed his bid */ @@ -319,11 +358,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.moveToStep('paid') }, error: (response) => { - if (response.status === 403 && response.error === 'not_available') { - this.error = 'waitlisted'; - } else { - this.error = response.error; - } + this.accelerateError = response.error; } }); } @@ -371,6 +406,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { await this.requestCashAppPayment(); } catch (e) { console.debug('Error loading Square Payments', e); + this.cashappError = true; return; } } @@ -417,7 +453,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.cashAppPay.addEventListener('ontokenization', function (event) { const { tokenResult, error } = event.detail; if (error) { - this.error = error; + this.accelerateError = error; } else if (tokenResult.status === 'OK') { that.servicesApiService.accelerateWithCashApp$( that.tx.txid, @@ -440,10 +476,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { }, 1000); }, error: (response) => { - if (response.status === 403 && response.error === 'not_available') { - that.error = 'waitlisted'; - } else { - that.error = response.error; + that.accelerateError = response.error; + if (!(response.status === 403 && response.error === 'not_available')) { setTimeout(() => { // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); @@ -468,6 +502,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { }), catchError(error => { console.log(error); + this.btcpayInvoiceFailed = true; return of(null); }) ).subscribe((invoice) => { @@ -497,6 +532,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return this._step; } + get paymentMethods() { + return Object.keys(this.estimate?.availablePaymentMethods || {}); + } + + get couldPayWithBitcoin() { + return !!this.estimate?.availablePaymentMethods?.bitcoin; + } + + get couldPayWithCashapp() { + if (!this.cashappEnabled || this.stateService.referrer !== 'https://cash.app/') { + return false; + } + return !!this.estimate?.availablePaymentMethods?.cashapp; + } + + get couldPayWithBalance() { + if (!this.hasAccessToBalanceMode) { + return false; + } + return !!this.estimate?.availablePaymentMethods?.balance; + } + + get couldPay() { + return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp; + } + get canPayWithBitcoin() { const paymentMethod = this.estimate?.availablePaymentMethods?.bitcoin; return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max; @@ -523,7 +584,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return false; } const paymentMethod = this.estimate?.availablePaymentMethods?.balance; - return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max; + return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max && this.cost <= this.estimate?.userBalance; } get canPay() { diff --git a/frontend/src/app/components/tracker/tracker.component.html b/frontend/src/app/components/tracker/tracker.component.html index 6071a55e4..1efce182c 100644 --- a/frontend/src/app/components/tracker/tracker.component.html +++ b/frontend/src/app/components/tracker/tracker.component.html @@ -132,6 +132,7 @@ [miningStats]="miningStats" [eta]="eta" [scrollEvent]="scrollIntoAccelPreview" + (unavailable)="eligibleForAcceleration = false" class="h-100 w-100" > diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index e898549cb..4bbb194fb 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -146,6 +146,7 @@ [noCTA]="true" (hasDetails)="setHasAccelerationDetails($event)" (completed)="onAccelerationCompleted()" + (unavailable)="eligibleForAcceleration = false" class="h-100 w-100" > diff --git a/frontend/src/app/shared/components/mempool-error/mempool-error.component.html b/frontend/src/app/shared/components/mempool-error/mempool-error.component.html index b8179e4c3..d461530b0 100644 --- a/frontend/src/app/shared/components/mempool-error/mempool-error.component.html +++ b/frontend/src/app/shared/components/mempool-error/mempool-error.component.html @@ -1,2 +1,7 @@ -
-
+@if (!textOnly) { +
+
+} @else { + + +} diff --git a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts index d33b3c37c..889177dd7 100644 --- a/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts +++ b/frontend/src/app/shared/components/mempool-error/mempool-error.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; -const MempoolErrors = { +export const MempoolErrors = { 'bad_request': `Your request was not valid. Please try again.`, 'internal_server_error': `Something went wrong, please try again later`, 'acceleration_duplicated': `This transaction has already been accelerated.`, @@ -44,6 +44,7 @@ export function isMempoolError(error: string) { export class MempoolErrorComponent implements OnInit { @Input() error: string; @Input() alertClass = 'alert-danger'; + @Input() textOnly = false; errorContent: SafeHtml; constructor(private sanitizer: DomSanitizer) { } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 868cb1bd9..313a43e1f 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline } from '@fortawesome/free-solid-svg-icons'; + faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark} from '@fortawesome/free-solid-svg-icons'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MenuComponent } from '../components/menu/menu.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; @@ -433,5 +433,6 @@ export class SharedModule { library.addIcons(faWandMagicSparkles); library.addIcons(faFaucetDrip); library.addIcons(faTimeline); + library.addIcons(faCircleXmark); } }