Merge pull request #4975 from mempool/nymkappa/mini-branch

[accelerator] polish pizza accel
This commit is contained in:
wiz
2024-04-16 17:11:55 +09:00
committed by GitHub
10 changed files with 185 additions and 163 deletions

View File

@@ -1,147 +1,133 @@
<div class="container card" style="padding: 20px; background: var(--bg)">
<div class="container-md card w-100" style="padding: 1em; background: var(--box-bg)" id=acceleratePreviewAnchor>
@if (error) {
<app-mempool-error [error]="error"></app-mempool-error>
} @else {
<div class="mt-2">
<app-mempool-error [error]="error"></app-mempool-error>
</div>
}
@if (step === 'completed') {
<div class="row text-center mt-3">
<div class="col-sm">
<div class="form-group w-100">
<div display="d-flex flex-row justify-content-center">
<div class="alert alert-success">Transaction is now being accelerated!</div>
</div>
</div>
</div>
@else if (step === 'cta') {
<!-- Show A/B CTAs -->
<div class="row mb-1">
<div class="col-sm">
<h1 style="font-size: larger;">Accelerate your Bitcoin transaction?</h1>
</div>
}
</div>
@else if (step === 'cta') {
<!-- Show A/B CTAs -->
<div class="row mb-3">
<form>
<div class="row">
<div class="col-sm">
<h1 style="font-size: larger;">Accelerate your Bitcoin transaction?</h1>
</div>
</div>
<form>
<div class="row">
<div class="col-sm">
<div class="form-group form-check">
<input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accelerate">
<span class="font-weight-bold">Accelerate</span>
<span style="color: rgb(186, 186, 186)">Settlement expected in ~1 hour or less<br>
@if (!calculating) {
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats|sats">sats</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm">
<div class="form-group form-check">
<input type="radio" class="form-check-input" id="wait" name="accelerate" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="wait">
<span class="font-weight-bold">Wait</span>
<span style="color: rgb(186, 186, 186)">Settlement expected to occur <app-time kind="within" [time]="eta" [fastRender]="false" [fixedRender]="true"></app-time></span>
</label>
</div>
</div>
</div>
<div class="row mt-2" [style]="choosenOption === 'wait' ? 'opacity: 0.25; pointer-events: none' : ''">
<div class="col-sm d-flex flex-row justify-content-center">
<button type="button" class="mt-1 btn btn-light rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" style="width: 200px" (click)="enableCheckoutPage()">
<img src="/resources/mempool-accelerator-sparkles-compressed.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
</div>
</form>
}
@else if (step === 'checkout') {
<!-- Show checkout page -->
<div class="row mb-3 text-center">
<div class="col-sm">
<h1 style="font-size: larger;">Confirm your payment</h1>
</div>
</div>
<div class="row text-center">
<div class="col-sm">
<div class="form-group w-100">
Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + txid" target="_blank">{{ txid.substr(0, 10) }}..{{ txid.substr(-10) }}</a>
</div>
</div>
</div>
@if (!loadingCashapp) {
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
<span><u><strong>Total additional cost</strong></u><br>
<span style="font-size: 16px" class="d-block mt-2">
Pay
<strong><app-fiat [value]="cost"></app-fiat></strong>
with
</span>
<div class="form-group form-check mb-2">
<input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accelerate">
<span class="font-weight-bold">Accelerate</span>
<span style="color: rgb(186, 186, 186); font-size: 14px;">Settlement expected in ~1 hour or less<br>
@if (!calculating) {
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats|sats">sats</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
</span>
</div>
</div>
</div>
}
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
@if (loadingCashapp) {
<div display="d-flex flex-row justify-content-center">
<span>Loading payment method...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div>
}
</label>
</div>
</div>
</div>
<hr>
<div class="row mt-2 text-center">
<div class="col-sm d-flex flex-column">
<small>Changed your mind?</small>
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="restart(); closeModal()">Close</button>
</div>
</div>
}
@else if (step === 'processing') {
<div class="row mb-3 text-center">
<div class="row">
<div class="col-sm">
<h1 style="font-size: larger;">Confirm your payment</h1>
<div class="form-group form-check mb-2">
<input type="radio" class="form-check-input" id="wait" name="accelerate" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="wait">
<span class="font-weight-bold">Wait</span>
<span style="color: rgb(186, 186, 186); font-size: 14px;">Settlement expected to occur <app-time kind="within" [time]="eta" [fastRender]="false" [fixedRender]="true"></app-time></span>
</label>
</div>
</div>
</div>
<div class="row mt-2 mb-2" [style]="(choosenOption === 'wait' || calculating) ? 'opacity: 0.25; pointer-events: none' : ''">
<div class="col-sm d-flex flex-row justify-content-center">
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" style="width: 200px" (click)="enableCheckoutPage()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
</div>
</form>
}
@else if (step === 'checkout') {
<!-- Show checkout page -->
<div class="row mb-md-1 text-center">
<div class="col-sm">
<h1 style="font-size: larger;">Confirm your payment</h1>
</div>
</div>
<!-- Processing payment -->
<div id="cash-app-pay" class="d-inline-block" [style]="'opacity: 0; width: 0px; height: 0px; pointer-events: none;'"></div>
<div class="row text-center">
<div class="col-sm">
<div class="form-group w-100" style="font-size: 14px">
Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + txid" target="_blank">{{ txid.substr(0, 10) }}..{{ txid.substr(-10) }}</a>
</div>
</div>
</div>
@if (!loadingCashapp) {
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
<div display="d-flex flex-row justify-content-center">
<span>We are processing your payment...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div>
<span><u><strong>Total additional cost</strong></u><br>
<span style="font-size: 16px" class="d-block mt-2">
Pay
<strong><app-fiat [value]="cost"></app-fiat></strong>
with
</span>
</span>
</div>
</div>
</div>
}
<span class="close-button" (click)="closeModal()"></span>
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
@if (loadingCashapp) {
<div display="d-flex flex-row justify-content-center">
<span>Loading payment method...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div>
}
</div>
</div>
</div>
<hr>
<div class="row mt-2 mb-2 text-center">
<div class="col-sm d-flex flex-column">
<small>Changed your mind?</small>
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="step = 'cta'">Go Back</button>
</div>
</div>
}
@else if (step === 'processing') {
<div class="row mb-1 text-center">
<div class="col-sm">
<h1 style="font-size: larger;">Confirm your payment</h1>
</div>
</div>
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
<!-- Processing payment -->
<div id="cash-app-pay" class="d-inline-block" [style]="'opacity: 0; width: 0px; height: 0px; pointer-events: none;'"></div>
<div display="d-flex flex-row justify-content-center">
<span>We are processing your payment...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div>
</div>
</div>
</div>
}
</div>

View File

@@ -1,6 +1,5 @@
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef } from '@angular/core';
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core';
import { Subscription, tap, of, catchError } from 'rxjs';
import { WebsocketService } from '../../services/websocket.service';
import { ServicesApiServices } from '../../services/services-api.service';
import { nextRoundNumber } from '../../shared/common.utils';
import { StateService } from '../../services/state.service';
@@ -14,6 +13,7 @@ import { AudioService } from '../../services/audio.service';
export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() eta: number = Date.now() + 123456789;
@Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad';
@Input() scrollEvent: boolean;
@Output() close = new EventEmitter<null>();
calculating = true;
@@ -34,10 +34,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
cashAppPay: any;
cashAppSubscription: Subscription;
conversionsSubscription: Subscription;
step: 'cta' | 'checkout' | 'processing' | 'completed' = 'cta';
step: 'cta' | 'checkout' | 'processing' = 'cta';
constructor(
private websocketService: WebsocketService,
private servicesApiService: ServicesApiServices,
private stateService: StateService,
private audioService: AudioService,
@@ -63,7 +62,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.estimate();
}
});
}
ngOnDestroy() {
@@ -72,6 +70,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.scrollEvent) {
this.scrollToPreview('acceleratePreviewAnchor', 'start');
}
}
/**
* Scroll to element id with or without setTimeout
*/
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
setTimeout(() => {
this.scrollToPreview(id, position);
}, 1000);
}
scrollToPreview(id: string, position: ScrollLogicalPosition) {
const acceleratePreviewAnchor = document.getElementById(id);
if (acceleratePreviewAnchor) {
this.cd.markForCheck();
acceleratePreviewAnchor.scrollIntoView({
behavior: 'smooth',
inline: position,
block: position,
});
}
}
/**
* Accelerator
*/
@@ -148,8 +172,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.payments = window.Square.payments(this.square.appId, this.square.locationId)
await this.requestCashAppPayment();
} catch (e) {
console.error(e);
this.error = 'Error loading Square Payments';
console.debug('Error loading Square Payments', e);
return;
}
}
@@ -164,7 +187,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.conversionsSubscription = this.stateService.conversions$.subscribe(
async (conversions) => {
if (this.cashAppPay) {
await this.cashAppPay.destroy();
this.cashAppPay.destroy();
}
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
@@ -206,16 +229,27 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
).subscribe({
next: () => {
that.audioService.playSound('ascend-chime-cartoon');
that.step = 'completed';
if (that.cashAppPay) {
that.cashAppPay.destroy();
}
setTimeout(() => {
that.closeModal();
}, 10000);
if (window.history.replaceState) {
const urlParams = new URLSearchParams(window.location.search);
window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ''));
}
}, 1000);
},
error: (response) => {
if (response.status === 403 && response.error === 'not_available') {
that.error = 'waitlisted';
} else {
that.error = response.error;
setTimeout(() => {
// Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams(window.location.search);
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
}, 3000);
}
}
});
@@ -236,19 +270,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
selectedOptionChanged(event) {
this.choosenOption = event.target.id;
if (this.choosenOption === 'wait') {
this.restart();
this.closeModal();
}
}
restart() {
this.step = 'cta';
this.choosenOption = 'wait';
}
closeModal(): void {
if (this.cashAppPay) {
this.cashAppPay.destroy();
}
this.close.emit();
}
}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core';
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
import { Subscription, catchError, of, tap } from 'rxjs';
import { StorageService } from '../../services/storage.service';
import { Transaction } from '../../interfaces/electrs.interface';

View File

@@ -29,7 +29,7 @@
</div>
<div class="data">
@if (tx && !tx.status?.confirmed && mempoolPosition?.block != null) {
<div class="field">
<div class="field narrower mt-2">
<div class="label" i18n="transaction.first-seen|Transaction first seen">First seen</div>
<div class="value">
@if (transactionTime > 0) {
@@ -39,7 +39,7 @@
}
</div>
</div>
<div class="field">
<div class="field narrower">
<div class="label" i18n="transaction.eta|Transaction ETA">ETA</div>
<div class="value">
<span class="justify-content-end d-flex align-items-center">
@@ -48,14 +48,14 @@
} @else {
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.adjustedTimeAvg * (mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
}
@if (isMobile && paymentType === 'cashapp' && accelerationEligible && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
@if (!showAccelerationSummary && isMobile && paymentType === 'cashapp' && accelerationEligible && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
<a class="btn btn-sm accelerate btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
}
</span>
</div>
</div>
} @else if (tx && tx.status?.confirmed) {
<div class="field">
<div class="field narrower mt-2">
<div class="label" i18n="transaction.confirmed-at">Confirmed at</div>
<div class="value">
&lrm;{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
@@ -64,7 +64,7 @@
</div>
</div>
</div>
<div class="field">
<div class="field narrower">
<div class="label" i18n="transaction.block-height">Block height</div>
<div class="value">
<span class="justify-content-end d-flex align-items-center">
@@ -84,8 +84,8 @@
</div>
<div class="bottom-panel">
@if (showAccelerationSummary) {
<app-accelerate-checkout *ngIf="(da$ | async) as da;" [txid]="tx.txid" [eta]="mempoolPosition?.block >= 7 ? null : da.adjustedTimeAvg * (mempoolPosition.block + 1) + now + da.timeOffset" (close)="showAccelerationSummary = false"></app-accelerate-checkout>
@if (showAccelerationSummary && !accelerationFlowCompleted) {
<app-accelerate-checkout *ngIf="(da$ | async) as da;" [txid]="tx.txid" [eta]="mempoolPosition?.block >= 7 ? null : da.adjustedTimeAvg * (mempoolPosition.block + 1) + now + da.timeOffset" (close)="accelerationFlowCompleted = true" [scrollEvent]="scrollIntoAccelPreview" class="h-100 w-100"></app-accelerate-checkout>
} @else {
@if (tx?.acceleration && !tx.status?.confirmed) {
<div class="progress-icon">

View File

@@ -92,6 +92,10 @@
white-space: nowrap;
text-align: end;
}
&.narrower {
padding-top: 0.75em !important;
padding-bottom: 0.75em !important;
}
}
.tracker-bar {

View File

@@ -19,11 +19,9 @@ import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from '../../services/audio.service';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { StorageService } from '../../services/storage.service';
import { seoDescriptionNetwork } from '../../shared/common.utils';
import { Filter } from '../../shared/filters.utils';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration } from '../../interfaces/node-api.interface';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { PriceService } from '../../services/price.service';
import { ServicesApiServices } from '../../services/services-api.service';
import { EnterpriseService } from '../../services/enterprise.service';
@@ -111,6 +109,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
accelerationEligible: boolean = false;
showAccelerationSummary = false;
accelerationFlowCompleted = false;
scrollIntoAccelPreview = false;
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
@@ -143,7 +142,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') {
if (this.acceleratorAvailable && this.stateService.referrer === 'https://cash.app/') {
this.paymentType = 'cashapp';
}
const urlParams = new URLSearchParams(window.location.search);
@@ -351,6 +350,9 @@ export class TrackerComponent implements OnInit, OnDestroy {
}
if (txPosition.position?.block > 0 && this.tx.weight < 4000) {
this.accelerationEligible = true;
if (this.acceleratorAvailable && this.paymentType === 'cashapp') {
this.showAccelerationSummary = true;
}
}
}
} else {

View File

@@ -747,11 +747,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
resetTransaction() {
if (!this.firstLoad) {
this.stateService.ref = '';
} else {
this.firstLoad = false;
}
this.firstLoad = false;
this.error = undefined;
this.tx = null;
this.setFeatures();
@@ -847,7 +843,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngOnDestroy() {
this.stateService.ref = '';
this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe();
this.fetchRbfSubscription.unsubscribe();