Merge branch 'master' into natsoni/acc-timeline-polish

This commit is contained in:
wiz
2024-07-08 21:58:34 +09:00
committed by GitHub
48 changed files with 9111 additions and 5739 deletions

View File

@@ -1,12 +1,24 @@
<div class="box card w-100" style="background: var(--box-bg)" id=acceleratePreviewAnchor>
@if (error) {
<div class="row mt-2">
<div class="col">
<app-mempool-error [error]="error" [alertClass]="error === 'waitlisted' ? 'alert-mempool' : 'alert-danger'"></app-mempool-error>
@if (accelerateError) {
<div class="row mb-1 text-center">
<div class="col-sm">
<h1 style="font-size: larger;">Sorry, something went wrong!</h1>
</div>
</div>
}
@else if (step === 'quote') {
<div class="row text-center mt-1">
<div class="col-sm">
<div class="d-flex flex-row justify-content-center align-items-center">
<span i18n="accelerator.error-failed-to-accelerate">We were not able to accelerate this transaction. Please try again later.</span>
</div>
</div>
</div>
<hr>
<div class="row mt-2 mb-2 text-center">
<div class="col-sm d-flex flex-column">
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="closeModal()" i18n="close">Close</button>
</div>
</div>
} @else if (step === 'quote') {
<div class="accelerate-cols">
<ng-container *ngIf="!isMobile">
<app-accelerate-fee-graph
@@ -20,7 +32,7 @@
</ng-container>
<ng-container *ngIf="estimate else loadingEstimate">
<div [class.disabled]="error || showSuccess">
<div>
@if (showDetails) {
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
<div class="row">
@@ -264,7 +276,7 @@
}
@if (!advancedEnabled) {
<form [class.disabled]="error || showSuccess">
<form>
<div class="row">
<div class="col-md">
<div class="form-group form-check mb-2">
@@ -295,7 +307,7 @@
</div>
</form>
} @else {
<div [class.disabled]="error || showSuccess">
<div>
<div class="row summary-row">
<div>
<div class="mb-2">
@@ -342,18 +354,18 @@
</div>
<div class="col-md pie d-none d-md-flex" *ngIf="!forceMobile">
<small class="form-text checkout-text mb-2" *ngIf="(etaInfo$ | async) as etaInfo"><ng-container *ngTemplateOutlet="prioritizedBy; context: {$implicit:etaInfo.hashratePercentage}"></ng-container></small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true" class="ml-2"></app-active-acceleration-box>
</div>
</div>
<div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess" style="font-size: 14px;">
<div class="payment-area mt-2 p-2" style="font-size: 14px;">
<div class="row text-center justify-content-center mx-2">
<p i18n="accelerator.payment-to-mempool-space">Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank">{{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></p>
</div>
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) {
<div class="row">
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p>Your account will be debited no more than <span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p>
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || error || showSuccess">
<p><ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than</ng-container>&nbsp;<small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></p>
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || quoteError || accelerateError || showSuccess">
<ng-container *ngTemplateOutlet="accountPayButton"></ng-container>
</div>
</div>
@@ -365,6 +377,11 @@
@if (invoice) {
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<span><small style="font-family: monospace;">{{ ((invoice.btcDue * 100_000_000) || cost) | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p>
<app-bitcoin-invoice style="width: 100%;" [invoice]="invoice" [minimal]="true" (completed)="bitcoinPaymentCompleted()"></app-bitcoin-invoice>
} @else if (btcpayInvoiceFailed) {
<p i18n="accelerator.failed-to-load-invoice">Failed to load invoice</p>
<div class="d-flex flex-column align-items-center justify-content-center" style="width: 100%; height: 292px;">
<fa-icon style="font-size: 24px; color: var(--red)" [icon]="['fas', 'circle-xmark']"></fa-icon>
</div>
} @else {
<p i18n="accelerator.loading-invoice">Loading invoice...</p>
<div class="d-flex align-items-center justify-content-center" style="width: 100%; height: 292px;">
@@ -486,9 +503,12 @@
<div class="row text-center mt-1">
<div class="col-sm">
<div class="d-flex flex-row justify-content-center align-items-center">
<div class="d-flex flex-row flex-column justify-content-center align-items-center">
<span i18n="accelerator.confirming-acceleration-with-miners">Confirming your acceleration with our mining pool partners...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
@if (timeSincePaid > 20000) {
<span i18n="accelerator.confirming-acceleration-with-miners">...sorry, this is taking longer than expected...</span>
}
<div class="m-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div>
</div>
</div>
@@ -532,16 +552,21 @@
<ng-template #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template>
<ng-template #accelerateButton>
@if (canPayWithBalance || canPayWithBitcoin || canPayWithCashapp) {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
</button>
} @else {
@if (!couldPay && !quoteError && !(estimate?.availablePaymentMethods.bitcoin || estimate?.availablePaymentMethods.balance)) {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Coming soon</span>
</button>
} @else {
<div class="position-relative">
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || quoteError || cantPayReason || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
</button>
@if (quoteError || cantPayReason) {
<div class="btn-error-wrapper"><span class="btn-error"><app-mempool-error [error]="quoteError || cantPayReason" [textOnly]="true" alertClass=""></app-mempool-error></span></div>
}
</div>
}
</ng-template>

View File

@@ -189,3 +189,17 @@
flex-direction: column;
}
}
.btn-error {
position: absolute;
right: 0;
font-size: 12px;
color: var(--red);
text-align: center;
width: 200px;
white-space: normal;
}
.btn-error-wrapper {
height: 26px;
}

View File

@@ -23,6 +23,10 @@ export type AccelerationEstimate = {
vsizeFee: number;
pools: number[];
availablePaymentMethods: {[method: string]: {min: number, max: number}};
unavailable?: boolean;
options: { // recommended bid options
fee: number; // recommended userBid in sats
}[];
}
export type TxSummary = {
txid: string; // txid of the current transaction
@@ -59,19 +63,25 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() forceMobile: boolean = false;
@Input() showDetails: boolean = false;
@Input() noCTA: boolean = false;
@Output() unavailable = new EventEmitter<boolean>();
@Output() completed = new EventEmitter<boolean>();
@Output() hasDetails = new EventEmitter<boolean>();
@Output() changeMode = new EventEmitter<boolean>();
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;
private _step: CheckoutStep = 'summary';
simpleMode: boolean = true;
paymentMethod: 'cashapp' | 'btcpay';
timeoutTimer: any;
authSubscription$: Subscription;
auth: IAuth | null = null;
@@ -98,6 +108,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
// square
loadingCashapp = false;
cashappError = false;
cashappSubmit: any;
payments: any;
cashAppPay: any;
@@ -125,7 +136,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;
@@ -178,10 +192,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
moveToStep(step: CheckoutStep) {
this._step = step;
if (this.timeoutTimer) {
clearTimeout(this.timeoutTimer);
}
if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) {
this.fetchEstimate();
}
if (this._step === 'checkout' && this.canPayWithBitcoin) {
this.btcpayInvoiceFailed = false;
this.loadingBtcpayInvoice = true;
this.invoice = null;
this.requestBTCPayInvoice();
@@ -189,6 +207,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.loadingCashapp = true;
this.insertSquare();
this.setupSquare();
} else if (this._step === 'paid') {
this.timePaid = Date.now();
this.timeoutTimer = setTimeout(() => {
if (this.step === 'paid') {
this.accelerateError = 'internal_server_error';
}
}, 120000)
}
this.hasDetails.emit(this._step === 'quote');
}
@@ -226,47 +251,47 @@ 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`;
}
}
if (this.estimate.unavailable) {
this.quoteError = `temporarily_unavailable`;
}
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats);
// Make min extra fee at least 50% of the current tx fee
this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
this.maxRateOptions = [1, 2, 4].map((multiplier, index) => {
return {
fee: this.minExtraCost * multiplier,
rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize,
index,
};
});
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
this.maxRateOptions = this.estimate.options.map((option, index) => ({
fee: option.fee,
rate: (this.estimate.txSummary.effectiveFee + option.fee) / this.estimate.txSummary.effectiveVsize,
index
}));
this.defaultBid = this.maxRateOptions[1].fee;
this.userBid = this.defaultBid;
if (this.userBid < this.minBidAllowed) {
this.userBid = this.minBidAllowed;
} else if (this.userBid > this.maxBidAllowed) {
this.userBid = this.maxBidAllowed;
}
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 +304,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 +363,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 +411,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 +458,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 +481,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 +507,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}),
catchError(error => {
console.log(error);
this.btcpayInvoiceFailed = true;
return of(null);
})
).subscribe((invoice) => {
@@ -497,6 +537,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 +589,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() {
@@ -534,6 +600,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return this.isLoggedIn() && this.estimate?.hasAccess;
}
get timeSincePaid(): number {
return Date.now() - this.timePaid;
}
@HostListener('window:resize', ['$event'])
onResize(): void {
this.isMobile = window.innerWidth <= 767.98;

View File

@@ -80,7 +80,7 @@ export class ActiveAccelerationBox implements OnChanges {
data.push(getDataItem(
(this.miningStats.lastEstimatedHashrate - totalAcceleratedHashrate),
'rgba(127, 127, 127, 0.3)',
`not accelerating (${notAcceleratedByPercentage})`,
$localize`not accelerating` + ` (${notAcceleratedByPercentage})`,
false,
) as PieSeriesOption);

View File

@@ -78,7 +78,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
}
calculate() {
if (!this.time) {
if (this.time == null) {
return;
}

View File

@@ -132,6 +132,7 @@
[miningStats]="miningStats"
[eta]="eta"
[scrollEvent]="scrollIntoAccelPreview"
(unavailable)="eligibleForAcceleration = false"
class="h-100 w-100"
></app-accelerate-checkout>
</ng-container>

View File

@@ -128,7 +128,7 @@
<div class="title float-left mb-1">
<h2><a [href]="[ isMempoolSpaceBuild ? '/accelerator' : 'https://mempool.space/accelerator']" [target]="isMempoolSpaceBuild ? '' : 'blank'"><app-svg-images name="accelerator" [height]="isMobile ? '35px' : '45px'"></app-svg-images></a></h2>
</div>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="closeAccelerator()" i18n="accelerator.hide">Hide accelerator</button>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" [class.hide-on-mobile]="hasAccelerationDetails" (click)="closeAccelerator()" i18n="accelerator.hide">Hide accelerator</button>
<button *ngIf="hasAccelerationDetails" class="btn btn-sm btn-outline-info details-button float-right ml-2" (click)="showAccelerationDetails = !showAccelerationDetails" i18n="transaction.details|Transaction Details">Details</button>
<div class="clearfix"></div>
@@ -146,6 +146,7 @@
[noCTA]="true"
(hasDetails)="setHasAccelerationDetails($event)"
(completed)="onAccelerationCompleted()"
(unavailable)="eligibleForAcceleration = false"
class="h-100 w-100"
></app-accelerate-checkout>
</ng-container>

View File

@@ -167,6 +167,12 @@
}
}
@media (max-width: 767px){
.hide-on-mobile {
display: none;
}
}
.effective-fee-rating {
@media (max-width: 767px){
margin-right: 0px !important;