[accelerator] polish UI prepaid accel
This commit is contained in:
parent
f601bbc499
commit
fb086b5ad5
@ -1,6 +1,22 @@
|
|||||||
<div class="container card" style="padding: 20px; background: var(--bg)">
|
<div class="container card" style="padding: 20px; background: var(--bg)">
|
||||||
|
|
||||||
@if (!showCheckoutPage) {
|
@if (error) {
|
||||||
|
<app-mempool-error [error]="error"></app-mempool-error>
|
||||||
|
} @else {
|
||||||
|
|
||||||
|
@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>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if (step === 'cta') {
|
||||||
<!-- Show A/B CTAs -->
|
<!-- Show A/B CTAs -->
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
@ -48,7 +64,7 @@
|
|||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
@else {
|
@else if (step === 'checkout') {
|
||||||
<!-- Show checkout page -->
|
<!-- Show checkout page -->
|
||||||
<div class="row mb-3 text-center">
|
<div class="row mb-3 text-center">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
@ -83,9 +99,12 @@
|
|||||||
<div class="row text-center mt-1">
|
<div class="row text-center mt-1">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="form-group w-100">
|
<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;' : ''" (click)="submitCashappPay()"></div>
|
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||||
@if (loadingCashapp) {
|
@if (loadingCashapp) {
|
||||||
<div class="spinner-border text-light" style="width: 25px; height: 25px"></div>
|
<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>
|
</div>
|
||||||
@ -95,9 +114,34 @@
|
|||||||
<div class="row mt-2 text-center">
|
<div class="row mt-2 text-center">
|
||||||
<div class="col-sm d-flex flex-column">
|
<div class="col-sm d-flex flex-column">
|
||||||
<small>Changed your mind?</small>
|
<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()">Cancel</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@else if (step === 'processing') {
|
||||||
|
<div class="row mb-3 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 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="close-button" (click)="closeModal()">✖</span>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Subscription, tap, of, catchError } from 'rxjs';
|
import { Subscription, tap, of, catchError } from 'rxjs';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { ServicesApiServices } from '../../services/services-api.service';
|
import { ServicesApiServices } from '../../services/services-api.service';
|
||||||
import { nextRoundNumber } from '../../shared/common.utils';
|
import { nextRoundNumber } from '../../shared/common.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { AudioService } from '../../services/audio.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-accelerate-checkout',
|
selector: 'app-accelerate-checkout',
|
||||||
@ -13,47 +14,56 @@ import { StateService } from '../../services/state.service';
|
|||||||
export class AccelerateCheckout implements OnInit, OnDestroy {
|
export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||||
@Input() eta: number = Date.now() + 123456789;
|
@Input() eta: number = Date.now() + 123456789;
|
||||||
@Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad';
|
@Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad';
|
||||||
|
@Output() close = new EventEmitter<null>();
|
||||||
|
|
||||||
calculating = true;
|
calculating = true;
|
||||||
choosenOption: 'wait' | 'accelerate' = 'wait';
|
choosenOption: 'wait' | 'accelerate' = 'wait';
|
||||||
showCheckoutPage = false;
|
|
||||||
error = '';
|
error = '';
|
||||||
|
|
||||||
// accelerator stuff
|
// accelerator stuff
|
||||||
square: { appId: string, locationId: string};
|
square: { appId: string, locationId: string};
|
||||||
accelerationUUID: string;
|
accelerationUUID: string;
|
||||||
estimateSubscription: Subscription;
|
estimateSubscription: Subscription;
|
||||||
|
maxBidBoost: number; // sats
|
||||||
cost: number; // sats
|
cost: number; // sats
|
||||||
|
|
||||||
// square
|
// square
|
||||||
|
loadingCashapp = false;
|
||||||
cashappSubmit: any;
|
cashappSubmit: any;
|
||||||
payments: any;
|
payments: any;
|
||||||
cashAppPay: any;
|
cashAppPay: any;
|
||||||
cashAppSubscription: Subscription;
|
cashAppSubscription: Subscription;
|
||||||
conversionsSubscription: Subscription;
|
conversionsSubscription: Subscription;
|
||||||
loadingCashapp = true;
|
step: 'cta' | 'checkout' | 'processing' | 'completed' = 'completed';
|
||||||
processingPayment = true;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
) {}
|
private audioService: AudioService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
this.accelerationUUID = window.crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
|
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
|
||||||
this.processingPayment = true;
|
this.insertSquare();
|
||||||
window.scrollTo(0, 0);
|
this.setupSquare();
|
||||||
} else {
|
this.step = 'processing';
|
||||||
|
}
|
||||||
|
|
||||||
this.servicesApiService.setupSquare$().subscribe(ids => {
|
this.servicesApiService.setupSquare$().subscribe(ids => {
|
||||||
this.square = {
|
this.square = {
|
||||||
appId: ids.squareAppId,
|
appId: ids.squareAppId,
|
||||||
locationId: ids.squareLocationId
|
locationId: ids.squareLocationId
|
||||||
};
|
};
|
||||||
|
if (this.step === 'cta') {
|
||||||
this.estimate();
|
this.estimate();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@ -82,9 +92,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Make min extra fee at least 50% of the current tx fee
|
// Make min extra fee at least 50% of the current tx fee
|
||||||
const minExtraCost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee));
|
const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee));
|
||||||
const DEFAULT_BID_RATIO = 2;
|
const DEFAULT_BID_RATIO = 2;
|
||||||
this.cost = minExtraCost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee;
|
this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO;
|
||||||
|
this.cost = this.maxBidBoost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -143,8 +154,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async requestCashAppPayment() {
|
async requestCashAppPayment() {
|
||||||
this.loadingCashapp = true;
|
|
||||||
|
|
||||||
if (this.cashAppSubscription) {
|
if (this.cashAppSubscription) {
|
||||||
this.cashAppSubscription.unsubscribe();
|
this.cashAppSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
@ -155,11 +164,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||||
async (conversions) => {
|
async (conversions) => {
|
||||||
if (this.cashAppPay) {
|
if (this.cashAppPay) {
|
||||||
this.cashAppPay.destroy();
|
await this.cashAppPay.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
||||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
const costUSD = this.step === 'processing' ? 69.69 : (this.cost / 100_000_000 * conversions.USD); // When we're redirected to this component, the payment data is already linked to the payment token, so does not matter what amonut we put in there, therefore it's 69.69
|
||||||
const paymentRequest = this.payments.paymentRequest({
|
const paymentRequest = this.payments.paymentRequest({
|
||||||
countryCode: 'US',
|
countryCode: 'US',
|
||||||
currencyCode: 'USD',
|
currencyCode: 'USD',
|
||||||
@ -172,11 +181,15 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
||||||
});
|
});
|
||||||
this.cashAppPay = await this.payments.cashAppPay(paymentRequest, {
|
this.cashAppPay = await this.payments.cashAppPay(paymentRequest, {
|
||||||
redirectURL: `${redirectHostname}/tracker/${this.txid}?acceleration=false`,
|
redirectURL: `${redirectHostname}/tracker/${this.txid}`,
|
||||||
referenceId: `accelerator-${this.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
referenceId: `accelerator-${this.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
||||||
});
|
});
|
||||||
this.cashappSubmit = await this.cashAppPay.CashAppPayInstance.render('#cash-app-pay', { button: { theme: 'light', size: 'small', shape: 'semiround' }, manage: false });
|
|
||||||
|
if (this.step === 'checkout') {
|
||||||
|
await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' })
|
||||||
|
}
|
||||||
|
this.loadingCashapp = false;
|
||||||
|
|
||||||
const that = this;
|
const that = this;
|
||||||
this.cashAppPay.addEventListener('ontokenization', function (event) {
|
this.cashAppPay.addEventListener('ontokenization', function (event) {
|
||||||
@ -186,14 +199,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
} else if (tokenResult.status === 'OK') {
|
} else if (tokenResult.status === 'OK') {
|
||||||
that.servicesApiService.accelerateWithCashApp$(
|
that.servicesApiService.accelerateWithCashApp$(
|
||||||
that.txid,
|
that.txid,
|
||||||
that.cost,
|
|
||||||
tokenResult.token,
|
tokenResult.token,
|
||||||
tokenResult.details.cashAppPay.cashtag,
|
tokenResult.details.cashAppPay.cashtag,
|
||||||
tokenResult.details.cashAppPay.referenceId,
|
tokenResult.details.cashAppPay.referenceId,
|
||||||
that.accelerationUUID
|
that.accelerationUUID
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
that.estimateSubscription.unsubscribe();
|
that.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
that.step = 'completed';
|
||||||
|
setTimeout(() => {
|
||||||
|
that.closeModal();
|
||||||
|
}, 10000);
|
||||||
},
|
},
|
||||||
error: (response) => {
|
error: (response) => {
|
||||||
if (response.status === 403 && response.error === 'not_available') {
|
if (response.status === 403 && response.error === 'not_available') {
|
||||||
@ -205,33 +221,34 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.loadingCashapp = false;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
submitCashappPay(): void {
|
|
||||||
if (this.cashappSubmit) {
|
|
||||||
this.cashappSubmit?.begin();
|
|
||||||
this.processingPayment = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI events
|
* UI events
|
||||||
*/
|
*/
|
||||||
enableCheckoutPage() {
|
enableCheckoutPage() {
|
||||||
this.showCheckoutPage = true;
|
this.step = 'checkout';
|
||||||
|
this.loadingCashapp = true;
|
||||||
this.insertSquare();
|
this.insertSquare();
|
||||||
this.setupSquare();
|
this.setupSquare();
|
||||||
}
|
}
|
||||||
selectedOptionChanged(event) {
|
selectedOptionChanged(event) {
|
||||||
this.choosenOption = event.target.id;
|
this.choosenOption = event.target.id;
|
||||||
|
if (this.choosenOption === 'wait') {
|
||||||
|
this.restart();
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
restart() {
|
restart() {
|
||||||
this.showCheckoutPage = false
|
this.step = 'cta';
|
||||||
this.choosenOption = 'wait';
|
this.choosenOption = 'wait';
|
||||||
}
|
}
|
||||||
closeModal(): void {
|
closeModal(): void {
|
||||||
|
if (this.cashAppPay) {
|
||||||
|
this.cashAppPay.destroy();
|
||||||
|
}
|
||||||
this.close.emit();
|
this.close.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,6 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$(
|
that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$(
|
||||||
that.tx.txid,
|
that.tx.txid,
|
||||||
that.userBid,
|
|
||||||
tokenResult.token,
|
tokenResult.token,
|
||||||
tokenResult.details.cashAppPay.cashtag,
|
tokenResult.details.cashAppPay.cashtag,
|
||||||
tokenResult.details.cashAppPay.referenceId,
|
tokenResult.details.cashAppPay.referenceId,
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.adjustedTimeAvg * (mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
|
<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 (isMobile && paymentType === 'cashapp' && accelerationEligible && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
|
||||||
<a [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerate btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
<a class="btn btn-sm accelerate btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,6 +146,11 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') {
|
if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') {
|
||||||
this.paymentType = 'cashapp';
|
this.paymentType = 'cashapp';
|
||||||
}
|
}
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlParams.get('cash_request_id')) {
|
||||||
|
this.showAccelerationSummary = true;
|
||||||
|
}
|
||||||
|
this.showAccelerationSummary = true;
|
||||||
|
|
||||||
this.enterpriseService.page();
|
this.enterpriseService.page();
|
||||||
|
|
||||||
|
@ -132,8 +132,8 @@ export class ServicesApiServices {
|
|||||||
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID });
|
||||||
}
|
}
|
||||||
|
|
||||||
accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string, accelerationUUID: string) {
|
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string) {
|
||||||
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerations$(): Observable<Acceleration[]> {
|
getAccelerations$(): Observable<Acceleration[]> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user