More acceleration checkout refactoring

This commit is contained in:
Mononaut
2024-06-28 07:02:12 +00:00
parent 473da82caa
commit c75afe20cd
18 changed files with 532 additions and 1278 deletions

View File

@@ -6,327 +6,367 @@
</div>
}
@else if (step === 'quote') {
@if (!simpleMode) {
<span id="successAlert" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="showSuccess">
<div class="col">
<div class="alert alert-success">
Transaction has now been <a class="alert-link" routerLink="/services/accelerator/history">submitted</a> to mining pools for acceleration.
<span id="successAlert" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="showSuccess">
<div class="col">
<div class="alert alert-success">
Transaction has now been <a class="alert-link" routerLink="/services/accelerator/history">submitted</a> to mining pools for acceleration.
</div>
</div>
</div>
<span id="mempoolError" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="error">
<div class="col">
<app-mempool-error [error]="error" [alertClass]="error === 'waitlisted' ? 'alert-mempool' : 'alert-danger'"></app-mempool-error>
</div>
</div>
<div class="accelerate-cols">
<ng-container *ngIf="!isMobile">
<app-accelerate-fee-graph
[tx]="tx"
[estimate]="estimate"
[showEstimate]="isLoggedIn()"
[maxRateOptions]="maxRateOptions"
[maxRateIndex]="selectFeeRateIndex"
(setUserBid)="setUserBid($event)"
></app-accelerate-fee-graph>
</ng-container>
<ng-container *ngIf="estimate else loadingEstimate">
<div [class]="{estimateDisabled: error || showSuccess }">
<button class="btn btn-sm btn-outline-info float-right" (click)="showDetails = !showDetails">Details</button>
<div *ngIf="user && !estimate.hasAccess">
<div class="alert alert-mempool">You are currently on the waitlist</div>
</div>
</div>
</div>
<span id="mempoolError" class="m-0 p-0 d-block" style="height: 1px;"></span>
<div class="row" *ngIf="error">
<div class="col">
<app-mempool-error [error]="error" [alertClass]="error === 'waitlisted' ? 'alert-mempool' : 'alert-danger'"></app-mempool-error>
</div>
</div>
<div class="accelerate-cols">
<ng-container *ngIf="!isMobile">
<app-accelerate-fee-graph
[tx]="tx"
[estimate]="estimate"
[showEstimate]="isLoggedIn()"
[maxRateOptions]="maxRateOptions"
[maxRateIndex]="selectFeeRateIndex"
(setUserBid)="setUserBid($event)"
></app-accelerate-fee-graph>
</ng-container>
<ng-container *ngIf="estimate else loadingEstimate">
<div [class]="{estimateDisabled: error || showSuccess }">
<div *ngIf="user && !estimate.hasAccess">
<div class="alert alert-mempool">You are currently on the waitlist</div>
</div>
@if (showDetails) {
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
<div class="row">
<div class="col">
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
</small>
<table class="table table-borderless table-border table-dark table-background table-accelerator">
<tbody>
<tr class="group-first">
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td style="text-align: end;" [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
</tr>
<tr class="info">
<td class="info" colspan=3>
<i><small i18n="accelerator.transaction-vbytes-size-description">Size in vbytes of this transaction (including unconfirmed ancestors)</small></i>
</td>
</tr>
<tr>
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
<td style="text-align: end;">
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
</td>
</tr>
<tr class="info group-last">
<td class="info" colspan=3>
<i><small i18n="accelerator.fees-already-paid-description">Fees already paid by this transaction (including unconfirmed ancestors)</small></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<br>
}
<h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5>
<div class="row">
<div class="col">
<ng-container *ngIf="(etaInfo$ | async) as etaInfo; else loadingEstimate">
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to <strong>{{ etaInfo.hashratePercentage | number : '1.1-1' }}%</strong> of miners.</small>
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <strong><app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></strong></small>
</ng-container>
</div>
<div class="col pie">
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div>
</div>
<div class="row">
<div class="col">
<div class="form-group">
<div class="fee-card">
<div class="d-flex mb-0">
<ng-container *ngFor="let option of maxRateOptions">
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)">
<span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats">sats</span></span>
<span class="rate">~<app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
</button>
</ng-container>
</div>
</div>
</div>
</div>
</div>
<h5>Summary</h5>
@if (showDetails) {
<h5 class="mb-4" i18n="accelerator.your-transaction">Your transaction</h5>
<div class="row">
<div class="col">
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
</small>
<table class="table table-borderless table-border table-dark table-background table-accelerator">
<tbody>
<!-- ESTIMATED FEE -->
<ng-container *ngIf="showDetails">
<tr class="group-first">
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
<td class="amt" style="font-size: 16px">
{{ estimate.targetFeeRate | number : '1.0-0' }}
</td>
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
<tr class="info">
<td class="info">
<i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i>
</td>
<td class="amt">
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
</td>
</tr>
<!-- MEMPOOL BASE FEE -->
<tr>
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
</tr>
<tr class="info" [class.group-last]="!estimate.vsizeFee" [class.dashed-bottom]="!estimate.vsizeFee">
<td class="info">
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
</td>
<td class="amt">
+{{ estimate.mempoolBaseFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
</td>
</tr>
<tr class="info group-last dashed-bottom" *ngIf="estimate.vsizeFee">
<td class="info">
<i><small i18n="accelerator.tx-size-surcharge">Transaction Size Surcharge</small></i>
</td>
<td class="amt">
+{{ estimate.vsizeFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
</ng-container>
<!-- NEXT BLOCK ESTIMATE -->
<ng-container *ngIf="isLoggedIn()">
<tr class="group-first">
<td class="item">
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB
</td>
<td class="amt">
<span style="background-color: #5E35B1" class="p-1 pl-0">
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }}
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
</ng-container>
<!-- MAX COST -->
<ng-container>
<tr class="group-first" [class.group-last]="!isLoggedIn() || estimate.userBalance >= cost">
<td class="item">
@if (isLoggedIn()) {
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
} @else {
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.cost">Acceleration cost</b>
}
</td>
<td class="amt">
<span style="background-color: var(--primary)" class="p-1 pl-0">
{{ cost | number }}
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="cost" [colorClass]="isLoggedIn() && estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
</ng-container>
<!-- USER BALANCE -->
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < cost">
<tr class="group-first group-last dashed-top">
<td class="item" i18n="accelerator.available-balance">Available balance</td>
<td class="amt">
{{ estimate.userBalance | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
</ng-container>
<tr class="group-first group-last" style="border-top: 1px dashed grey">
<td class="item"></td>
<td colspan="2">
<div class="d-flex">
<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)="accelerate()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
<tr class="group-first">
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td style="text-align: end;" [innerHTML]="'&lrm;' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
</tr>
<tr class="info">
<td class="info" colspan=3>
<i><small i18n="accelerator.transaction-vbytes-size-description">Size in vbytes of this transaction (including unconfirmed ancestors)</small></i>
</td>
</tr>
<tr>
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
<td style="text-align: end;">
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
</td>
</tr>
<tr class="info group-last">
<td class="info" colspan=3>
<i><small i18n="accelerator.fees-already-paid-description">Fees already paid by this transaction (including unconfirmed ancestors)</small></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<ng-template #loadingEstimate>
<div class="skeleton-loader"></div>
<br>
</ng-template>
}
@else {
<!-- Show A/B CTAs -->
<div class="row mb-1">
<div class="col-sm">
<h1 style="font-size: larger;"><ng-content select="[slot='cta-title']"></ng-content><span class="default-slot">Accelerate your Bitcoin transaction?</span></h1>
</div>
</div>
<form>
<div class="row">
<div class="col-md">
<div class="form-group form-check mb-2">
<input type="radio" [checked]="choosenOption === 'wait'" class="form-check-input" id="wait" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="wait">
<span class="font-weight-bold">Wait</span>
@if (eta.blocks < 7) {
<span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected <app-time kind="within" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time></span>
} @else {
<span style="color: rgb(186, 186, 186); font-size: 14px;">
<span>Confirmation expected within several hours</span>
</span>
}
</label>
<br>
}
<h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5>
<div class="row">
<div class="col">
<ng-container *ngIf="(etaInfo$ | async) as etaInfo; else loadingEstimate">
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to <strong>{{ etaInfo.hashratePercentage | number : '1.1-1' }}%</strong> of miners.</small>
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <strong><app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></strong></small>
</ng-container>
</div>
<div class="col pie">
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div>
</div>
<div class="col-md">
<div class="form-group form-check mb-2">
<input type="radio" [checked]="choosenOption === 'accel'" class="form-check-input" id="accel" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accel">
<span class="font-weight-bold">Accelerate</span>
<span style="color: rgb(186, 186, 186); font-size: 14px;" *ngIf="(etaInfo$ | async) as etaInfo">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><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</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
<div class="row">
<div class="col">
<div class="form-group">
<div class="fee-card">
<div class="d-flex mb-0">
<ng-container *ngFor="let option of maxRateOptions">
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)">
<span class="fee">{{ option.fee + estimate.mempoolBaseFee + estimate.vsizeFee | number }} <span class="symbol" i18n="shared.sats">sats</span></span>
<span class="rate">~<app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
</button>
</ng-container>
</div>
</div>
</div>
</div>
</div>
<h5>Summary</h5>
<div class="row">
<div class="col">
<table class="table table-borderless table-border table-dark table-background table-accelerator">
<tbody>
<!-- ESTIMATED FEE -->
<ng-container *ngIf="showDetails">
<tr class="group-first">
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
<td class="amt" style="font-size: 16px">
{{ estimate.targetFeeRate | number : '1.0-0' }}
</td>
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
<tr class="info">
<td class="info">
<i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i>
</td>
<td class="amt">
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
</td>
</tr>
<!-- MEMPOOL BASE FEE -->
<tr>
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
</tr>
<tr class="info" [class.group-last]="!estimate.vsizeFee" [class.dashed-bottom]="!estimate.vsizeFee">
<td class="info">
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
</td>
<td class="amt">
+{{ estimate.mempoolBaseFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
</td>
</tr>
<tr class="info group-last dashed-bottom" *ngIf="estimate.vsizeFee">
<td class="info">
<i><small i18n="accelerator.tx-size-surcharge">Transaction Size Surcharge</small></i>
</td>
<td class="amt">
+{{ estimate.vsizeFee | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
</ng-container>
<!-- NEXT BLOCK ESTIMATE -->
<ng-container *ngIf="isLoggedIn()">
<tr class="group-first">
<td class="item">
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB
</td>
<td class="amt">
<span style="background-color: #5E35B1" class="p-1 pl-0">
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }}
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
</td>
</tr>
</ng-container>
<!-- MAX COST -->
<ng-container>
<tr class="group-first" [class.group-last]="!isLoggedIn() || estimate.userBalance >= cost">
<td class="item">
@if (isLoggedIn()) {
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
} @else {
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.cost">Acceleration cost</b>
}
</td>
<td class="amt">
<span style="background-color: var(--primary)" class="p-1 pl-0">
{{ cost | number }}
</span>
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="cost" [colorClass]="isLoggedIn() && estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
</ng-container>
<!-- USER BALANCE -->
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < cost">
<tr class="group-first group-last dashed-top">
<td class="item" i18n="accelerator.available-balance">Available balance</td>
<td class="amt">
{{ estimate.userBalance | number }}
</td>
<td class="units">
<span class="symbol" i18n="shared.sats">sats</span>
<span class="fiat ml-1">
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < cost ? 'red-color' : 'green-color'"></app-fiat>
</span>
</td>
</tr>
</ng-container>
<tr class="group-first group-last" style="border-top: 1px dashed grey">
<td class="item"></td>
<td colspan="2">
<div class="d-flex">
<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)="accelerate()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<ng-template #loadingEstimate>
<div class="skeleton-loader"></div>
<br>
</ng-template>
}
@else if (step === 'summary') {
<!-- Show A/B CTAs -->
<div class="row mb-1">
<div class="col-sm">
<h1 style="font-size: larger;"><ng-content select="[slot='cta-title']"></ng-content><span class="default-slot">Accelerate your Bitcoin transaction?</span></h1>
</div>
</div>
<form>
<div class="row">
<div class="col-md">
<div class="form-group form-check mb-2">
<input type="radio" [checked]="choosenOption === 'wait'" class="form-check-input" id="wait" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="wait">
<span class="font-weight-bold">Wait</span>
@if (eta.blocks < 7) {
<span class="checkout-text">Confirmation expected <app-time kind="within" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time></span>
} @else {
<span class="checkout-text">
<span>Confirmation expected within several hours</span>
</span>
</label>
</div>
</div>
<div class="col-md pie d-none d-lg-flex" *ngIf="estimate && !forceMobile">
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description" *ngIf="(etaInfo$ | async) as etaInfo">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
}
</label>
</div>
</div>
<div class="row mt-2 mb-2" [style]="(choosenOption !== 'accel' || 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 class="col-md">
<div class="form-group form-check mb-2" *ngIf="estimate">
<input type="radio" [checked]="choosenOption === 'accel'" class="form-check-input" id="accel" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accel">
<span><b>Accelerate</b> to ~{{ ((userBid + estimate.txSummary.effectiveFee) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB <button *ngIf="advancedEnabled && isLoggedIn()" class="btn btn-sm btn-accelerate btn-small-height ml-3" i18n="accelerator.customize" (click)="moveToStep('quote')">customize</button></span>
<span class="checkout-text" *ngIf="(etaInfo$ | async) as etaInfo">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><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</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
</span>
</label>
</div>
</div>
</form>
}
} @else if (step === 'paymentMethod') {
<div class="row text-center">
<div class="col-sm">
<h1 class="mb-2" style="font-size: larger;"><ng-content select="[slot='payment-title']"></ng-content><span class="default-slot">Select your payment method</span></h1>
<div class="col-md pie d-none d-lg-flex" *ngIf="estimate && !forceMobile">
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description" *ngIf="(etaInfo$ | async) as etaInfo">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div>
</div>
</div>
<div class="row text-center">
<div class="col-sm">
<app-fiat [value]="cost"></app-fiat> (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span>)
<div class="row mt-2 mb-2" [style]="(choosenOption !== 'accel' || 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)="accelerate()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
</div>
</div>
<div class="pt-2 d-flex justify-content-center position-relative">
@if (cashappEnabled) {
<img class="paymentMethod mx-2" src="/resources/cash-app.svg" height=55 (click)="selectPaymentMethod('cashapp')">
}
<img class="paymentMethod mx-2" src="/resources/btcpay.svg" height=55 (click)="selectPaymentMethod('btcpay')">
</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)="step = 'quote'">Go Back</button>
</div>
</div>
</form>
} @else if (step === 'checkout') {
<div class="row">
<div class="col-md">
<div class="d-flex flex-column" *ngIf="estimate">
<span><b>Accelerate</b> to ~{{ ((userBid + estimate.txSummary.effectiveFee) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB <button *ngIf="advancedEnabled && isLoggedIn()" class="btn btn-sm btn-accelerate btn-small-height ml-3" i18n="accelerator.customize" (click)="moveToStep('quote')">customize</button></span>
<span class="checkout-text">
@if (!calculating) {
For an additional <app-fiat [value]="cost"></app-fiat> (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
</span>
<span class="checkout-text" *ngIf="(etaInfo$ | async) as etaInfo">
Reducing expected confirmation time to <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time>
</span>
</div>
</div>
<div class="col-md pie d-none d-md-flex" *ngIf="estimate && !forceMobile">
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description" *ngIf="(etaInfo$ | async) as etaInfo">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div>
</div>
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) {
<div class="d-flex 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" [class.estimateDisabled]="!canPayWithBalance" style="width: 200px" (click)="accelerate()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div>
} @else {
<div class="payment-area mt-2 p-2">
<div class="row text-center justify-content-center mx-2" style="font-size: 14px;">
<p>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>
<div class="row">
@if (canPayWithBitcoin) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
@if (invoice) {
<p>Pay <span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p>
<app-bitcoin-invoice style="width: 100%;" [invoiceId]="invoice.btcpayInvoiceId" [minimal]="true" (completed)="closeModal(2000)"></app-bitcoin-invoice>
} @else {
<span>Loading invoice...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
}
</div>
@if (canPayWithCashapp) {
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
<p class="text-nowrap">&mdash;<span i18n="or">OR</span>&mdash;</p>
</div>
}
}
@if (cashappEnabled) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p>Pay <app-fiat [value]="cost"></app-fiat> with</p>
<img class="paymentMethod mx-2" src="/resources/cash-app.svg" height=55 (click)="moveToStep('cashapp')">
</div>
}
</div>
</div>
}
@if (showSummary) {
<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)="moveToStep('summary')">Go Back</button>
</div>
</div>
}
} @else if (step === 'cashapp') {
<!-- Show checkout page -->
<div class="row mb-md-1 text-center">
<div class="col-sm" id="confirm-payment-title">
@@ -342,44 +382,40 @@
</div>
</div>
@if (paymentMethod === 'cashapp') {
@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>
}
@if (!loadingCashapp) {
<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>
}
<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>
} @else if (paymentMethod === 'btcpay' && invoice?.btcpayInvoiceId) {
<app-bitcoin-invoice [invoiceId]="invoice.btcpayInvoiceId" (completed)="closeModal(2000)"></app-bitcoin-invoice>
}
<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">
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="step = 'paymentMethod'">Go Back</button>
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('checkout')">Go Back</button>
</div>
</div>
}
@@ -404,5 +440,4 @@
</div>
</div>
}
</div>
</div>

View File

@@ -141,6 +141,10 @@
margin-top: 1em;
}
.payment-area {
background: var(--bg);
}
.col.pie {
flex-grow: 0;
padding: 0 1em;
@@ -154,4 +158,17 @@
.table-background {
background-color: var(--bg);
}
.checkout-text {
color: rgb(186, 186, 186);
font-size: 14px;
}
.btn-accelerate {
background-color: var(--tertiary);
}
.btn-small-height {
line-height: 1;
}

View File

@@ -41,6 +41,8 @@ export const MIN_BID_RATIO = 1;
export const DEFAULT_BID_RATIO = 2;
export const MAX_BID_RATIO = 4;
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'processing';
@Component({
selector: 'app-accelerate-checkout',
templateUrl: './accelerate-checkout.component.html',
@@ -51,9 +53,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() miningStats: MiningStats;
@Input() eta: ETA;
@Input() scrollEvent: boolean;
@Input() cashappEnabled: boolean;
@Input() showDetails: boolean;
@Input() cashappEnabled: boolean = true;
@Input() advancedEnabled: boolean = false;
@Input() forceSummary: boolean = false;
@Input() forceMobile: boolean = false;
@Output() changeMode = new EventEmitter<boolean>();
@Output() close = new EventEmitter<null>();
@@ -64,8 +66,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
math = Math;
isMobile: boolean = window.innerWidth <= 767.98;
step: 'quote' | 'paymentMethod' | 'checkout' | 'processing' = 'quote';
private _step: CheckoutStep = 'summary';
simpleMode: boolean = true;
showDetails: boolean = false;
paymentMethod: 'cashapp' | 'btcpay';
user: any = undefined;
@@ -117,9 +120,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.user = this.storageService.getAuth()?.user ?? null;
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
this.moveToStep('processing');
this.insertSquare();
this.setupSquare();
this.step = 'processing';
} else if (this.isLoggedIn() || this.forceSummary) {
this.moveToStep('summary');
} else {
this.moveToStep('checkout');
}
this.servicesApiService.setupSquare$().subscribe(ids => {
@@ -127,9 +134,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
appId: ids.squareAppId,
locationId: ids.squareLocationId
};
if (this.step === 'quote') {
this.fetchEstimate();
}
});
}
@@ -145,6 +149,21 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
}
moveToStep(step: CheckoutStep) {
this._step = step;
if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) {
this.fetchEstimate();
}
if (this._step === 'checkout' && this.canPayWithBitcoin) {
this.loadingBtcpayInvoice = true;
this.requestBTCPayInvoice();
} else if (this._step === 'cashapp' && this.cashappEnabled) {
this.loadingCashapp = true;
this.insertSquare();
this.setupSquare();
}
}
/**
* Scroll to element id with or without setTimeout
*/
@@ -214,6 +233,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}
this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
if (this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
this.loadingBtcpayInvoice = true;
this.requestBTCPayInvoice();
}
this.calculating = false;
this.cd.markForCheck();
}
@@ -244,9 +268,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
*/
accelerate(): void {
if (this.isLoggedIn()) {
this.accelerateWithMempoolAccount();
if (this.step !== 'summary') {
this.moveToStep('summary');
} else {
this.accelerateWithMempoolAccount();
}
} else {
this.step = 'paymentMethod';
this.moveToStep('checkout');
}
}
@@ -356,7 +384,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
button: { shape: 'semiround', size: 'small', theme: 'light'}
});
if (this.step === 'checkout') {
if (this.step === 'cashapp') {
await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' })
}
this.loadingCashapp = false;
@@ -410,7 +438,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
* BTCPay
*/
async requestBTCPayInvoice() {
this.servicesApiService.generateBTCPayAcceleratorInvoice$(this.tx.txid).subscribe({
this.servicesApiService.generateBTCPayAcceleratorInvoice$(this.tx.txid, this.userBid).subscribe({
next: (response) => {
this.invoice = response;
this.cd.markForCheck();
@@ -425,27 +453,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
/**
* UI events
*/
enableCheckoutPage() {
this.step = 'paymentMethod';
}
selectPaymentMethod(paymentMethod: 'cashapp' | 'btcpay') {
this.step = 'checkout';
this.paymentMethod = paymentMethod;
if (paymentMethod === 'cashapp') {
this.loadingCashapp = true;
this.insertSquare();
this.setupSquare();
} else if (paymentMethod === 'btcpay') {
this.loadingBtcpayInvoice = true;
this.requestBTCPayInvoice();
}
}
selectedOptionChanged(event) {
this.choosenOption = event.target.id;
}
closeModal(timeout: number = 0): void {
setTimeout(() => {
this.step = 'processing';
this._step = 'processing';
this.cd.markForCheck();
this.close.emit();
}, timeout);
@@ -456,6 +469,26 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return auth !== null;
}
get step() {
return this._step;
}
get canPayWithBitcoin() {
return this.estimate?.availablePaymentMethods?.includes('bitcoin');
}
get canPayWithCashapp() {
return this.cashappEnabled && this.estimate?.availablePaymentMethods?.includes('bitcoin');
}
get canPayWithBalance() {
return this.isLoggedIn() && this.estimate?.availablePaymentMethods?.includes('balance');
}
get showSummary() {
return this.canPayWithBalance || this.forceSummary;
}
@HostListener('window:resize', ['$event'])
onResize(): void {
this.isMobile = window.innerWidth <= 767.98;