[accelerator] polish UI prepaid accel

This commit is contained in:
nymkappa 2024-04-13 23:07:19 +09:00
parent f601bbc499
commit fb086b5ad5
No known key found for this signature in database
GPG Key ID: 92358FC85D9645DE
6 changed files with 192 additions and 127 deletions

View File

@ -1,6 +1,22 @@
<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 -->
<div class="row mb-3">
<div class="col-sm">
@ -48,7 +64,7 @@
</form>
}
@else {
@else if (step === 'checkout') {
<!-- Show checkout page -->
<div class="row mb-3 text-center">
<div class="col-sm">
@ -83,9 +99,12 @@
<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;' : ''" (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) {
<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>
@ -95,9 +114,34 @@
<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()">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>
}
@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>

View File

@ -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 { WebsocketService } from '../../services/websocket.service';
import { ServicesApiServices } from '../../services/services-api.service';
import { nextRoundNumber } from '../../shared/common.utils';
import { StateService } from '../../services/state.service';
import { AudioService } from '../../services/audio.service';
@Component({
selector: 'app-accelerate-checkout',
@ -13,47 +14,56 @@ import { StateService } from '../../services/state.service';
export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() eta: number = Date.now() + 123456789;
@Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad';
@Output() close = new EventEmitter<null>();
calculating = true;
choosenOption: 'wait' | 'accelerate' = 'wait';
showCheckoutPage = false;
error = '';
// accelerator stuff
square: { appId: string, locationId: string};
accelerationUUID: string;
estimateSubscription: Subscription;
maxBidBoost: number; // sats
cost: number; // sats
// square
loadingCashapp = false;
cashappSubmit: any;
payments: any;
cashAppPay: any;
cashAppSubscription: Subscription;
conversionsSubscription: Subscription;
loadingCashapp = true;
processingPayment = true;
step: 'cta' | 'checkout' | 'processing' | 'completed' = 'completed';
constructor(
private websocketService: WebsocketService,
private servicesApiService: ServicesApiServices,
private stateService: StateService
) {}
private stateService: StateService,
private audioService: AudioService,
private cd: ChangeDetectorRef
) {
this.accelerationUUID = window.crypto.randomUUID();
}
ngOnInit() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
this.processingPayment = true;
window.scrollTo(0, 0);
} else {
this.insertSquare();
this.setupSquare();
this.step = 'processing';
}
this.servicesApiService.setupSquare$().subscribe(ids => {
this.square = {
appId: ids.squareAppId,
locationId: ids.squareLocationId
};
if (this.step === 'cta') {
this.estimate();
});
}
});
}
ngOnDestroy() {
@ -82,9 +92,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return;
}
// 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;
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() {
this.loadingCashapp = true;
if (this.cashAppSubscription) {
this.cashAppSubscription.unsubscribe();
}
@ -155,11 +164,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.conversionsSubscription = this.stateService.conversions$.subscribe(
async (conversions) => {
if (this.cashAppPay) {
this.cashAppPay.destroy();
await this.cashAppPay.destroy();
}
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({
countryCode: 'US',
currencyCode: 'USD',
@ -172,11 +181,15 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
button: { shape: 'semiround', size: 'small', theme: 'light'}
});
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)}`,
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;
this.cashAppPay.addEventListener('ontokenization', function (event) {
@ -186,14 +199,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
} else if (tokenResult.status === 'OK') {
that.servicesApiService.accelerateWithCashApp$(
that.txid,
that.cost,
tokenResult.token,
tokenResult.details.cashAppPay.cashtag,
tokenResult.details.cashAppPay.referenceId,
that.accelerationUUID
).subscribe({
next: () => {
that.estimateSubscription.unsubscribe();
that.audioService.playSound('ascend-chime-cartoon');
that.step = 'completed';
setTimeout(() => {
that.closeModal();
}, 10000);
},
error: (response) => {
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
*/
enableCheckoutPage() {
this.showCheckoutPage = true;
this.step = 'checkout';
this.loadingCashapp = true;
this.insertSquare();
this.setupSquare();
}
selectedOptionChanged(event) {
this.choosenOption = event.target.id;
if (this.choosenOption === 'wait') {
this.restart();
this.closeModal();
}
}
restart() {
this.showCheckoutPage = false
this.step = 'cta';
this.choosenOption = 'wait';
}
closeModal(): void {
if (this.cashAppPay) {
this.cashAppPay.destroy();
}
this.close.emit();
}
}

View File

@ -362,7 +362,6 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$(
that.tx.txid,
that.userBid,
tokenResult.token,
tokenResult.details.cashAppPay.cashtag,
tokenResult.details.cashAppPay.referenceId,

View File

@ -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>
}
@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>
</div>

View File

@ -146,6 +146,11 @@ export class TrackerComponent implements OnInit, OnDestroy {
if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') {
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();

View File

@ -132,8 +132,8 @@ export class ServicesApiServices {
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) {
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string) {
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
}
getAccelerations$(): Observable<Acceleration[]> {