Merge pull request #4962 from mempool/nymkappa/accel-checkout
[accelerator] polish prepaid accel UI
This commit is contained in:
		
						commit
						42bd9811e3
					
				| @ -8,6 +8,7 @@ import { ClockComponent } from './components/clock/clock.component'; | |||||||
| import { StatusViewComponent } from './components/status-view/status-view.component'; | import { StatusViewComponent } from './components/status-view/status-view.component'; | ||||||
| import { AddressGroupComponent } from './components/address-group/address-group.component'; | import { AddressGroupComponent } from './components/address-group/address-group.component'; | ||||||
| import { TrackerComponent } from './components/tracker/tracker.component'; | import { TrackerComponent } from './components/tracker/tracker.component'; | ||||||
|  | import { AccelerateCheckout } from './components/accelerate-checkout/accelerate-checkout.component'; | ||||||
| 
 | 
 | ||||||
| const browserWindow = window || {}; | const browserWindow = window || {}; | ||||||
| // @ts-ignore
 | // @ts-ignore
 | ||||||
|  | |||||||
| @ -1,47 +1,147 @@ | |||||||
| <div class="container card" style="padding: 20px; background: var(--bg)"> | <div class="container card" style="padding: 20px; background: var(--bg)"> | ||||||
| 
 | 
 | ||||||
|   <div class="row"> |   @if (error) { | ||||||
|     <div class="col-sm"> |     <app-mempool-error [error]="error"></app-mempool-error> | ||||||
|       <h1 style="font-size: larger;">Accelerate your Bitcoin transaction?</h1> |   } @else { | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| 
 | 
 | ||||||
|   <form class="mt-3"> |     @if (step === 'completed') { | ||||||
|     <div class="row"> |       <div class="row text-center mt-3"> | ||||||
|       <div class="col-sm"> |         <div class="col-sm"> | ||||||
|         <div class="form-group form-check"> |           <div class="form-group w-100"> | ||||||
|           <input type="radio" class="form-check-input" id="accelerate" name="accelerate"> |             <div display="d-flex flex-row justify-content-center"> | ||||||
|           <label class="form-check-label d-flex flex-column" for="accelerate"> |               <div class="alert alert-success">Transaction is now being accelerated!</div> | ||||||
|             <span class="font-weight-bold">Accelerate</span> |             </div> | ||||||
|             <span class="text-muted">Settlement expected in 1 hour or less<br>10,000 sats ($7.00) fee</span> |           </div> | ||||||
|           </label> |  | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     } | ||||||
|     <div class="row"> | 
 | ||||||
|       <div class="col-sm"> |     @else if (step === 'cta') { | ||||||
|         <div class="form-group form-check"> |       <!-- Show A/B CTAs --> | ||||||
|           <input type="radio" class="form-check-input" id="wait" name="accelerate" checked="checked"> |       <div class="row mb-3"> | ||||||
|           <label class="form-check-label d-flex flex-column" for="wait"> |         <div class="col-sm"> | ||||||
|             <span class="font-weight-bold">Wait</span> |           <h1 style="font-size: larger;">Accelerate your Bitcoin transaction?</h1> | ||||||
|             @if (!eta) { |         </div> | ||||||
|               <span class="text-muted">Settlement unlikely to occur within 24 hours</span> |       </div> | ||||||
|             } @else { | 
 | ||||||
|               <span class="text-muted">Settlement estimated to occur <app-time kind="until" [time]="eta" [fastRender]="false" [fixedRender]="true"></app-time></span> |       <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> <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> | ||||||
|  |               </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> | ||||||
|       </div> |       </div> | ||||||
|     </div> |  | ||||||
|     <div class="row mt-2"> |  | ||||||
|       <div class="col-sm d-flex flex-row justify-content-center"> |  | ||||||
|         <button type="submit" class="btn btn-light w-100 rounded-pill" style="max-width: 250px"> |  | ||||||
|           <img src="/resources/mempool-accelerator-sparkles-compressed.svg" height="25" class="mr-2" style="margin-left: -33px"> |  | ||||||
|           <span>Accelerate</span> |  | ||||||
|         </button> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </form> |  | ||||||
| 
 | 
 | ||||||
|   <span class="close-button" (click)="closeModal()">✖</span> |       <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="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> | ||||||
| @ -3,3 +3,7 @@ | |||||||
|   top: 0.5em; |   top: 0.5em; | ||||||
|   right: 0.5em; |   right: 0.5em; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .estimating { | ||||||
|  |   color: var(--green) | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| import { Component, OnInit, OnDestroy, Output, EventEmitter, Input } from '@angular/core'; | import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef } from '@angular/core'; | ||||||
| import { Transaction } from '../../interfaces/electrs.interface'; | import { Subscription, tap, of, catchError } from 'rxjs'; | ||||||
| import { MempoolPosition } from '../../interfaces/node-api.interface'; | 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({ | @Component({ | ||||||
|   selector: 'app-accelerate-checkout', |   selector: 'app-accelerate-checkout', | ||||||
| @ -8,21 +12,243 @@ import { MempoolPosition } from '../../interfaces/node-api.interface'; | |||||||
|   styleUrls: ['./accelerate-checkout.component.scss'] |   styleUrls: ['./accelerate-checkout.component.scss'] | ||||||
| }) | }) | ||||||
| export class AccelerateCheckout implements OnInit, OnDestroy { | export class AccelerateCheckout implements OnInit, OnDestroy { | ||||||
|   @Input() tx: Transaction ; |   @Input() eta: number = Date.now() + 123456789; | ||||||
|   @Input() eta: number; |   @Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad'; | ||||||
|   @Output() close = new EventEmitter<null>(); |   @Output() close = new EventEmitter<null>(); | ||||||
| 
 | 
 | ||||||
|   constructor() { |   calculating = true; | ||||||
|  |   choosenOption: 'wait' | 'accelerate' = 'wait'; | ||||||
|  |   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; | ||||||
|  |   step: 'cta' | 'checkout' | 'processing' | 'completed' = 'completed'; | ||||||
|  | 
 | ||||||
|  |   constructor( | ||||||
|  |     private websocketService: WebsocketService, | ||||||
|  |     private servicesApiService: ServicesApiServices, | ||||||
|  |     private stateService: StateService, | ||||||
|  |     private audioService: AudioService, | ||||||
|  |     private cd: ChangeDetectorRef | ||||||
|  |   ) { | ||||||
|  |     this.accelerationUUID = window.crypto.randomUUID(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|  |     const urlParams = new URLSearchParams(window.location.search); | ||||||
|  |     if (urlParams.get('cash_request_id')) { // Redirected from cashapp
 | ||||||
|  |       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() { |   ngOnDestroy() { | ||||||
|  |     if (this.estimateSubscription) { | ||||||
|  |       this.estimateSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Accelerator | ||||||
|  |    */ | ||||||
|  |   estimate() { | ||||||
|  |     if (this.estimateSubscription) { | ||||||
|  |       this.estimateSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|  |     this.calculating = true; | ||||||
|  |     this.estimateSubscription = this.servicesApiService.estimate$(this.txid).pipe( | ||||||
|  |       tap((response) => { | ||||||
|  |         this.calculating = false; | ||||||
|  |         if (response.status === 204) { | ||||||
|  |           this.error = `cannot_accelerate_tx`; | ||||||
|  |         } else { | ||||||
|  |           const estimation = response.body; | ||||||
|  |           if (!estimation) { | ||||||
|  |             this.error = `cannot_accelerate_tx`; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           // Make min extra fee at least 50% of the current tx fee
 | ||||||
|  |           const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee)); | ||||||
|  |           const DEFAULT_BID_RATIO = 2; | ||||||
|  |           this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO; | ||||||
|  |           this.cost = this.maxBidBoost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee; | ||||||
|  |         } | ||||||
|  |       }), | ||||||
|  | 
 | ||||||
|  |       catchError((response) => { | ||||||
|  |         this.error = `cannot_accelerate_tx`; | ||||||
|  |         return of(null); | ||||||
|  |       }) | ||||||
|  |     ).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Square | ||||||
|  |    */ | ||||||
|  |   insertSquare(): void { | ||||||
|  |     //@ts-ignore
 | ||||||
|  |     if (window.Square) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js'; | ||||||
|  |     if (document.location.hostname === 'mempool-staging.fmt.mempool.space' || | ||||||
|  |         document.location.hostname === 'mempool-staging.va1.mempool.space' || | ||||||
|  |         document.location.hostname === 'mempool-staging.fra.mempool.space' || | ||||||
|  |         document.location.hostname === 'mempool-staging.tk7.mempool.space' || | ||||||
|  |         document.location.hostname === 'mempool.space') { | ||||||
|  |       statsUrl = 'https://web.squarecdn.com/v1/square.js'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     (function() { | ||||||
|  |       const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; | ||||||
|  |       // @ts-ignore
 | ||||||
|  |       g.type='text/javascript'; g.src=statsUrl; s.parentNode.insertBefore(g, s); | ||||||
|  |     })(); | ||||||
|  |   } | ||||||
|  |   setupSquare() { | ||||||
|  |     const init = () => { | ||||||
|  |       this.initSquare(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     //@ts-ignore
 | ||||||
|  |     if (!window.Square) { | ||||||
|  |       console.debug('Square.js failed to load properly. Retrying in 1 second.'); | ||||||
|  |       setTimeout(init, 1000); | ||||||
|  |     } else { | ||||||
|  |       init(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async initSquare(): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       //@ts-ignore
 | ||||||
|  |       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'; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async requestCashAppPayment() { | ||||||
|  |     if (this.cashAppSubscription) { | ||||||
|  |       this.cashAppSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|  |     if (this.conversionsSubscription) { | ||||||
|  |       this.conversionsSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     this.conversionsSubscription = this.stateService.conversions$.subscribe( | ||||||
|  |       async (conversions) => { | ||||||
|  |         if (this.cashAppPay) { | ||||||
|  |           await this.cashAppPay.destroy(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`; | ||||||
|  |         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', | ||||||
|  |           total: { | ||||||
|  |             amount: costUSD.toString(), | ||||||
|  |             label: 'Total', | ||||||
|  |             pending: true, | ||||||
|  |             productUrl: `${redirectHostname}/tracker/${this.txid}`, | ||||||
|  |           }, | ||||||
|  |           button: { shape: 'semiround', size: 'small', theme: 'light'} | ||||||
|  |         }); | ||||||
|  |         this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { | ||||||
|  |           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'} | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         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) { | ||||||
|  |           const { tokenResult, error } = event.detail; | ||||||
|  |           if (error) { | ||||||
|  |             this.error = error; | ||||||
|  |           } else if (tokenResult.status === 'OK') { | ||||||
|  |             that.servicesApiService.accelerateWithCashApp$( | ||||||
|  |               that.txid, | ||||||
|  |               tokenResult.token, | ||||||
|  |               tokenResult.details.cashAppPay.cashtag, | ||||||
|  |               tokenResult.details.cashAppPay.referenceId, | ||||||
|  |               that.accelerationUUID | ||||||
|  |             ).subscribe({ | ||||||
|  |               next: () => { | ||||||
|  |                 that.audioService.playSound('ascend-chime-cartoon'); | ||||||
|  |                 that.step = 'completed'; | ||||||
|  |                 setTimeout(() => { | ||||||
|  |                   that.closeModal(); | ||||||
|  |                 }, 10000); | ||||||
|  |               }, | ||||||
|  |               error: (response) => { | ||||||
|  |                 if (response.status === 403 && response.error === 'not_available') { | ||||||
|  |                   that.error = 'waitlisted'; | ||||||
|  |                 } else { | ||||||
|  |                   that.error = response.error; | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * UI events | ||||||
|  |    */ | ||||||
|  |   enableCheckoutPage() { | ||||||
|  |     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.step = 'cta'; | ||||||
|  |     this.choosenOption = 'wait'; | ||||||
|  |   } | ||||||
|   closeModal(): void { |   closeModal(): void { | ||||||
|     console.log('close modal') |     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, | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { | |||||||
| 
 | 
 | ||||||
|   @Input() time: number; |   @Input() time: number; | ||||||
|   @Input() dateString: number; |   @Input() dateString: number; | ||||||
|   @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain'; |   @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within' = 'plain'; | ||||||
|   @Input() fastRender = false; |   @Input() fastRender = false; | ||||||
|   @Input() fixedRender = false; |   @Input() fixedRender = false; | ||||||
|   @Input() relative = false; |   @Input() relative = false; | ||||||
| @ -80,6 +80,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|         seconds = Math.floor((+new Date() - +new Date(this.dateString || this.time * 1000)) / 1000); |         seconds = Math.floor((+new Date() - +new Date(this.dateString || this.time * 1000)) / 1000); | ||||||
|         break; |         break; | ||||||
|       case 'until': |       case 'until': | ||||||
|  |       case 'within': | ||||||
|         seconds = (+new Date(this.time) - +new Date()) / 1000; |         seconds = (+new Date(this.time) - +new Date()) / 1000; | ||||||
|         break; |         break; | ||||||
|       default: |       default: | ||||||
| @ -91,7 +92,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     } else if (seconds < 60) { |     } else if (seconds < 60) { | ||||||
|       if (this.relative || this.kind === 'since') { |       if (this.relative || this.kind === 'since') { | ||||||
|         return $localize`:@@date-base.just-now:Just now`; |         return $localize`:@@date-base.just-now:Just now`; | ||||||
|       } else if (this.kind === 'until') { |       } else if (this.kind === 'until' || this.kind === 'within') { | ||||||
|         seconds = 60; |         seconds = 60; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -112,12 +113,12 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|       if (counter > 0) { |       if (counter > 0) { | ||||||
|         let rounded; |         let rounded; | ||||||
|         const roundFactor = Math.pow(10,this.fractionDigits || 0); |         const roundFactor = Math.pow(10,this.fractionDigits || 0); | ||||||
|         if (this.kind === 'until' && usedUnits < this.numUnits) { |         if ((this.kind === 'until' || this.kind === 'within') && usedUnits < this.numUnits) { | ||||||
|           rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; |           rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; | ||||||
|         } else { |         } else { | ||||||
|           rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; |           rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; | ||||||
|         } |         } | ||||||
|         if (this.kind !== 'until' || this.numUnits === 1) { |         if ((this.kind !== 'until' && this.kind !== 'within')|| this.numUnits === 1) { | ||||||
|           return this.formatTime(this.kind, precisionUnit, rounded); |           return this.formatTime(this.kind, precisionUnit, rounded); | ||||||
|         } else { |         } else { | ||||||
|           if (!usedUnits) { |           if (!usedUnits) { | ||||||
| @ -185,6 +186,29 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|  |       case 'within': | ||||||
|  |         if (number === 1) { | ||||||
|  |           switch (unit) { // singular (In ~1 day)
 | ||||||
|  |             case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break; | ||||||
|  |             case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break; | ||||||
|  |             case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break; | ||||||
|  |             case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break; | ||||||
|  |             case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break; | ||||||
|  |             case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`; | ||||||
|  |             case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           switch (unit) { // plural (In ~2 days)
 | ||||||
|  |             case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break; | ||||||
|  |             case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break; | ||||||
|  |             case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break; | ||||||
|  |             case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break; | ||||||
|  |             case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break; | ||||||
|  |             case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break; | ||||||
|  |             case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|       case 'span': |       case 'span': | ||||||
|         if (number === 1) { |         if (number === 1) { | ||||||
|           switch (unit) { // singular (1 day)
 |           switch (unit) { // singular (1 day)
 | ||||||
|  | |||||||
| @ -42,7 +42,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> | ||||||
| @ -78,7 +78,7 @@ | |||||||
| 
 | 
 | ||||||
|     <div class="bottom-panel"> |     <div class="bottom-panel"> | ||||||
|       @if (showAccelerationSummary) { |       @if (showAccelerationSummary) { | ||||||
|         <app-accelerate-checkout *ngIf="(da$ | async) as da;" [tx]="tx" [eta]="mempoolPosition?.block >= 7 ? null : da.adjustedTimeAvg * (mempoolPosition.block + 1) + now + da.timeOffset" (close)="showAccelerationSummary = false"></app-accelerate-checkout> |         <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> | ||||||
|       } @else { |       } @else { | ||||||
|         @if (tx?.acceleration && !tx.status?.confirmed) { |         @if (tx?.acceleration && !tx.status?.confirmed) { | ||||||
|           <div class="progress-icon"> |           <div class="progress-icon"> | ||||||
|  | |||||||
| @ -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