Merge branch 'master' into natsoni/acc-timeline-polish

This commit is contained in:
wiz 2024-07-08 21:58:34 +09:00 committed by GitHub
commit 07370a8dc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 9111 additions and 5739 deletions

View File

@ -123,7 +123,7 @@ class StatisticsReplication {
}; };
const intervals = [ // [start, end, label ] const intervals = [ // [start, end, label ]
[now - day, now - 60, '24h'] , // from 24 hours ago to now = 1 minute granularity [now - day + 600, now - 60, '24h'] , // from 24 hours ago to now = 1 minute granularity
startTime < now - day ? [now - day * 7, now - day, '1w' ] : null, // from 1 week ago to 24 hours ago = 5 minutes granularity startTime < now - day ? [now - day * 7, now - day, '1w' ] : null, // from 1 week ago to 24 hours ago = 5 minutes granularity
startTime < now - day * 7 ? [now - day * 30, now - day * 7, '1m' ] : null, // from 1 month ago to 1 week ago = 30 minutes granularity startTime < now - day * 7 ? [now - day * 30, now - day * 7, '1m' ] : null, // from 1 month ago to 1 week ago = 30 minutes granularity
startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity
@ -170,15 +170,24 @@ class StatisticsReplication {
return new Set<number>(); return new Set<number>();
} }
const roundedTimesAlreadyHere = new Set(rows.map(row => this.roundToNearestStep(row.added, step))); const roundedTimesAlreadyHere: number[] = Array.from(new Set(rows.map(row => this.roundToNearestStep(row.added, step))));
const missingTimes = new Set(timeSteps.filter(time => !roundedTimesAlreadyHere.has(time)));
const missingTimes = timeSteps.filter(time => !roundedTimesAlreadyHere.includes(time)).filter((time, i, arr) => {
// Remove outsiders
if (i === 0) {
return arr[i + 1] === time + step
} else if (i === arr.length - 1) {
return arr[i - 1] === time - step;
}
return (arr[i + 1] === time + step) && (arr[i - 1] === time - step)
});
// Don't bother fetching if very few rows are missing // Don't bother fetching if very few rows are missing
if (missingTimes.size < timeSteps.length * 0.005) { if (missingTimes.length < timeSteps.length * 0.01) {
return new Set(); return new Set();
} }
return missingTimes; return new Set(missingTimes);
} catch (e: any) { } catch (e: any) {
logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e)); logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e));
throw e; throw e;

View File

@ -1,12 +1,24 @@
<div class="box card w-100" style="background: var(--box-bg)" id=acceleratePreviewAnchor> <div class="box card w-100" style="background: var(--box-bg)" id=acceleratePreviewAnchor>
@if (error) { @if (accelerateError) {
<div class="row mt-2"> <div class="row mb-1 text-center">
<div class="col"> <div class="col-sm">
<app-mempool-error [error]="error" [alertClass]="error === 'waitlisted' ? 'alert-mempool' : 'alert-danger'"></app-mempool-error> <h1 style="font-size: larger;">Sorry, something went wrong!</h1>
</div> </div>
</div> </div>
} <div class="row text-center mt-1">
@else if (step === 'quote') { <div class="col-sm">
<div class="d-flex flex-row justify-content-center align-items-center">
<span i18n="accelerator.error-failed-to-accelerate">We were not able to accelerate this transaction. Please try again later.</span>
</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)="closeModal()" i18n="close">Close</button>
</div>
</div>
} @else if (step === 'quote') {
<div class="accelerate-cols"> <div class="accelerate-cols">
<ng-container *ngIf="!isMobile"> <ng-container *ngIf="!isMobile">
<app-accelerate-fee-graph <app-accelerate-fee-graph
@ -20,7 +32,7 @@
</ng-container> </ng-container>
<ng-container *ngIf="estimate else loadingEstimate"> <ng-container *ngIf="estimate else loadingEstimate">
<div [class.disabled]="error || showSuccess"> <div>
@if (showDetails) { @if (showDetails) {
<h5 i18n="accelerator.your-transaction">Your transaction</h5> <h5 i18n="accelerator.your-transaction">Your transaction</h5>
<div class="row"> <div class="row">
@ -264,7 +276,7 @@
} }
@if (!advancedEnabled) { @if (!advancedEnabled) {
<form [class.disabled]="error || showSuccess"> <form>
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<div class="form-group form-check mb-2"> <div class="form-group form-check mb-2">
@ -295,7 +307,7 @@
</div> </div>
</form> </form>
} @else { } @else {
<div [class.disabled]="error || showSuccess"> <div>
<div class="row summary-row"> <div class="row summary-row">
<div> <div>
<div class="mb-2"> <div class="mb-2">
@ -342,18 +354,18 @@
</div> </div>
<div class="col-md pie d-none d-md-flex" *ngIf="!forceMobile"> <div class="col-md pie d-none d-md-flex" *ngIf="!forceMobile">
<small class="form-text checkout-text mb-2" *ngIf="(etaInfo$ | async) as etaInfo"><ng-container *ngTemplateOutlet="prioritizedBy; context: {$implicit:etaInfo.hashratePercentage}"></ng-container></small> <small class="form-text checkout-text mb-2" *ngIf="(etaInfo$ | async) as etaInfo"><ng-container *ngTemplateOutlet="prioritizedBy; context: {$implicit:etaInfo.hashratePercentage}"></ng-container></small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> <app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true" class="ml-2"></app-active-acceleration-box>
</div> </div>
</div> </div>
<div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess" style="font-size: 14px;"> <div class="payment-area mt-2 p-2" style="font-size: 14px;">
<div class="row text-center justify-content-center mx-2"> <div class="row text-center justify-content-center mx-2">
<p i18n="accelerator.payment-to-mempool-space">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> <p i18n="accelerator.payment-to-mempool-space">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>
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) { @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) {
<div class="row"> <div class="row">
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p>Your account will be debited no more than <span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p> <p><ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than</ng-container>&nbsp;<small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></p>
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || error || showSuccess"> <div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || quoteError || accelerateError || showSuccess">
<ng-container *ngTemplateOutlet="accountPayButton"></ng-container> <ng-container *ngTemplateOutlet="accountPayButton"></ng-container>
</div> </div>
</div> </div>
@ -365,6 +377,11 @@
@if (invoice) { @if (invoice) {
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<span><small style="font-family: monospace;">{{ ((invoice.btcDue * 100_000_000) || cost) | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p> <p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<span><small style="font-family: monospace;">{{ ((invoice.btcDue * 100_000_000) || cost) | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p>
<app-bitcoin-invoice style="width: 100%;" [invoice]="invoice" [minimal]="true" (completed)="bitcoinPaymentCompleted()"></app-bitcoin-invoice> <app-bitcoin-invoice style="width: 100%;" [invoice]="invoice" [minimal]="true" (completed)="bitcoinPaymentCompleted()"></app-bitcoin-invoice>
} @else if (btcpayInvoiceFailed) {
<p i18n="accelerator.failed-to-load-invoice">Failed to load invoice</p>
<div class="d-flex flex-column align-items-center justify-content-center" style="width: 100%; height: 292px;">
<fa-icon style="font-size: 24px; color: var(--red)" [icon]="['fas', 'circle-xmark']"></fa-icon>
</div>
} @else { } @else {
<p i18n="accelerator.loading-invoice">Loading invoice...</p> <p i18n="accelerator.loading-invoice">Loading invoice...</p>
<div class="d-flex align-items-center justify-content-center" style="width: 100%; height: 292px;"> <div class="d-flex align-items-center justify-content-center" style="width: 100%; height: 292px;">
@ -486,9 +503,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="d-flex flex-row justify-content-center align-items-center"> <div class="d-flex flex-row flex-column justify-content-center align-items-center">
<span i18n="accelerator.confirming-acceleration-with-miners">Confirming your acceleration with our mining pool partners...</span> <span i18n="accelerator.confirming-acceleration-with-miners">Confirming your acceleration with our mining pool partners...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div> @if (timeSincePaid > 20000) {
<span i18n="accelerator.confirming-acceleration-with-miners">...sorry, this is taking longer than expected...</span>
}
<div class="m-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
</div> </div>
</div> </div>
</div> </div>
@ -532,16 +552,21 @@
<ng-template #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template> <ng-template #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template>
<ng-template #accelerateButton> <ng-template #accelerateButton>
@if (canPayWithBalance || canPayWithBitcoin || canPayWithCashapp) { @if (!couldPay && !quoteError && !(estimate?.availablePaymentMethods.bitcoin || estimate?.availablePaymentMethods.balance)) {
<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.disabled]="!canPay || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
</button>
} @else {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px"> <button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px"> <img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Coming soon</span> <span>Coming soon</span>
</button> </button>
} @else {
<div class="position-relative">
<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.disabled]="!canPay || quoteError || cantPayReason || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
</button>
@if (quoteError || cantPayReason) {
<div class="btn-error-wrapper"><span class="btn-error"><app-mempool-error [error]="quoteError || cantPayReason" [textOnly]="true" alertClass=""></app-mempool-error></span></div>
}
</div>
} }
</ng-template> </ng-template>

View File

@ -189,3 +189,17 @@
flex-direction: column; flex-direction: column;
} }
} }
.btn-error {
position: absolute;
right: 0;
font-size: 12px;
color: var(--red);
text-align: center;
width: 200px;
white-space: normal;
}
.btn-error-wrapper {
height: 26px;
}

View File

@ -23,6 +23,10 @@ export type AccelerationEstimate = {
vsizeFee: number; vsizeFee: number;
pools: number[]; pools: number[];
availablePaymentMethods: {[method: string]: {min: number, max: number}}; availablePaymentMethods: {[method: string]: {min: number, max: number}};
unavailable?: boolean;
options: { // recommended bid options
fee: number; // recommended userBid in sats
}[];
} }
export type TxSummary = { export type TxSummary = {
txid: string; // txid of the current transaction txid: string; // txid of the current transaction
@ -59,19 +63,25 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() forceMobile: boolean = false; @Input() forceMobile: boolean = false;
@Input() showDetails: boolean = false; @Input() showDetails: boolean = false;
@Input() noCTA: boolean = false; @Input() noCTA: boolean = false;
@Output() unavailable = new EventEmitter<boolean>();
@Output() completed = new EventEmitter<boolean>(); @Output() completed = new EventEmitter<boolean>();
@Output() hasDetails = new EventEmitter<boolean>(); @Output() hasDetails = new EventEmitter<boolean>();
@Output() changeMode = new EventEmitter<boolean>(); @Output() changeMode = new EventEmitter<boolean>();
calculating = true; calculating = true;
selectedOption: 'wait' | 'accel'; selectedOption: 'wait' | 'accel';
error = ''; cantPayReason = '';
quoteError = ''; // error fetching estimate or initial data
accelerateError = ''; // error executing acceleration
btcpayInvoiceFailed = false;
timePaid: number = 0; // time acceleration requested
math = Math; math = Math;
isMobile: boolean = window.innerWidth <= 767.98; isMobile: boolean = window.innerWidth <= 767.98;
private _step: CheckoutStep = 'summary'; private _step: CheckoutStep = 'summary';
simpleMode: boolean = true; simpleMode: boolean = true;
paymentMethod: 'cashapp' | 'btcpay'; paymentMethod: 'cashapp' | 'btcpay';
timeoutTimer: any;
authSubscription$: Subscription; authSubscription$: Subscription;
auth: IAuth | null = null; auth: IAuth | null = null;
@ -98,6 +108,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
// square // square
loadingCashapp = false; loadingCashapp = false;
cashappError = false;
cashappSubmit: any; cashappSubmit: any;
payments: any; payments: any;
cashAppPay: any; cashAppPay: any;
@ -125,7 +136,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
if (this.auth?.user?.userId !== auth?.user?.userId) { if (this.auth?.user?.userId !== auth?.user?.userId) {
this.auth = auth; this.auth = auth;
this.estimate = null; this.estimate = null;
this.error = null; this.quoteError = null;
this.accelerateError = null;
this.timePaid = 0;
this.btcpayInvoiceFailed = false;
this.moveToStep('summary'); this.moveToStep('summary');
} else { } else {
this.auth = auth; this.auth = auth;
@ -178,10 +192,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
moveToStep(step: CheckoutStep) { moveToStep(step: CheckoutStep) {
this._step = step; this._step = step;
if (this.timeoutTimer) {
clearTimeout(this.timeoutTimer);
}
if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) { if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) {
this.fetchEstimate(); this.fetchEstimate();
} }
if (this._step === 'checkout' && this.canPayWithBitcoin) { if (this._step === 'checkout' && this.canPayWithBitcoin) {
this.btcpayInvoiceFailed = false;
this.loadingBtcpayInvoice = true; this.loadingBtcpayInvoice = true;
this.invoice = null; this.invoice = null;
this.requestBTCPayInvoice(); this.requestBTCPayInvoice();
@ -189,6 +207,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.loadingCashapp = true; this.loadingCashapp = true;
this.insertSquare(); this.insertSquare();
this.setupSquare(); this.setupSquare();
} else if (this._step === 'paid') {
this.timePaid = Date.now();
this.timeoutTimer = setTimeout(() => {
if (this.step === 'paid') {
this.accelerateError = 'internal_server_error';
}
}, 120000)
} }
this.hasDetails.emit(this._step === 'quote'); this.hasDetails.emit(this._step === 'quote');
} }
@ -226,47 +251,47 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.estimateSubscription.unsubscribe(); this.estimateSubscription.unsubscribe();
} }
this.calculating = true; this.calculating = true;
this.quoteError = null;
this.accelerateError = null;
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
tap((response) => { tap((response) => {
if (response.status === 204) { if (response.status === 204) {
this.error = `cannot_accelerate_tx`; this.quoteError = `cannot_accelerate_tx`;
if (this.step === 'summary') {
this.unavailable.emit(true);
}
} else { } else {
this.estimate = response.body; this.estimate = response.body;
if (!this.estimate) { if (!this.estimate) {
this.error = `cannot_accelerate_tx`; this.quoteError = `cannot_accelerate_tx`;
if (this.step === 'summary') {
this.unavailable.emit(true);
}
return; return;
} }
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
if (this.isLoggedIn()) { if (this.isLoggedIn()) {
this.error = `not_enough_balance`; this.quoteError = `not_enough_balance`;
} }
} }
if (this.estimate.unavailable) {
this.quoteError = `temporarily_unavailable`;
}
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats); this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats);
// Make min extra fee at least 50% of the current tx fee this.maxRateOptions = this.estimate.options.map((option, index) => ({
this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee)); fee: option.fee,
rate: (this.estimate.txSummary.effectiveFee + option.fee) / this.estimate.txSummary.effectiveVsize,
this.maxRateOptions = [1, 2, 4].map((multiplier, index) => { index
return { }));
fee: this.minExtraCost * multiplier,
rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize,
index,
};
});
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
this.defaultBid = this.maxRateOptions[1].fee;
this.userBid = this.defaultBid; this.userBid = this.defaultBid;
if (this.userBid < this.minBidAllowed) {
this.userBid = this.minBidAllowed;
} else if (this.userBid > this.maxBidAllowed) {
this.userBid = this.maxBidAllowed;
}
this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
this.validateChoice();
if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) { if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
this.loadingBtcpayInvoice = true; this.loadingBtcpayInvoice = true;
this.requestBTCPayInvoice(); this.requestBTCPayInvoice();
@ -279,13 +304,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
catchError((response) => { catchError((response) => {
this.estimate = undefined; this.estimate = undefined;
this.error = `cannot_accelerate_tx`; this.quoteError = `cannot_accelerate_tx`;
this.estimateSubscription.unsubscribe(); this.estimateSubscription.unsubscribe();
if (this.step === 'summary') {
this.unavailable.emit(true);
} else {
this.accelerateError = 'cannot_accelerate_tx';
}
return of(null); return of(null);
}) })
).subscribe(); ).subscribe();
} }
validateChoice(): void {
if (!this.canPay) {
if (this.estimate?.availablePaymentMethods?.balance) {
if (this.cost >= this.estimate?.userBalance) {
this.cantPayReason = 'not_enough_balance';
}
} else {
this.cantPayReason = 'cannot_accelerate_tx';
}
} else {
this.cantPayReason = '';
}
}
/** /**
* User changed his bid * User changed his bid
*/ */
@ -319,11 +363,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.moveToStep('paid') this.moveToStep('paid')
}, },
error: (response) => { error: (response) => {
if (response.status === 403 && response.error === 'not_available') { this.accelerateError = response.error;
this.error = 'waitlisted';
} else {
this.error = response.error;
}
} }
}); });
} }
@ -371,6 +411,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
await this.requestCashAppPayment(); await this.requestCashAppPayment();
} catch (e) { } catch (e) {
console.debug('Error loading Square Payments', e); console.debug('Error loading Square Payments', e);
this.cashappError = true;
return; return;
} }
} }
@ -417,7 +458,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.cashAppPay.addEventListener('ontokenization', function (event) { this.cashAppPay.addEventListener('ontokenization', function (event) {
const { tokenResult, error } = event.detail; const { tokenResult, error } = event.detail;
if (error) { if (error) {
this.error = error; this.accelerateError = error;
} else if (tokenResult.status === 'OK') { } else if (tokenResult.status === 'OK') {
that.servicesApiService.accelerateWithCashApp$( that.servicesApiService.accelerateWithCashApp$(
that.tx.txid, that.tx.txid,
@ -440,10 +481,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}, 1000); }, 1000);
}, },
error: (response) => { error: (response) => {
if (response.status === 403 && response.error === 'not_available') { that.accelerateError = response.error;
that.error = 'waitlisted'; if (!(response.status === 403 && response.error === 'not_available')) {
} else {
that.error = response.error;
setTimeout(() => { setTimeout(() => {
// Reset everything by reloading the page :D, can be improved // Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@ -468,6 +507,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
}), }),
catchError(error => { catchError(error => {
console.log(error); console.log(error);
this.btcpayInvoiceFailed = true;
return of(null); return of(null);
}) })
).subscribe((invoice) => { ).subscribe((invoice) => {
@ -497,6 +537,32 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return this._step; return this._step;
} }
get paymentMethods() {
return Object.keys(this.estimate?.availablePaymentMethods || {});
}
get couldPayWithBitcoin() {
return !!this.estimate?.availablePaymentMethods?.bitcoin;
}
get couldPayWithCashapp() {
if (!this.cashappEnabled || this.stateService.referrer !== 'https://cash.app/') {
return false;
}
return !!this.estimate?.availablePaymentMethods?.cashapp;
}
get couldPayWithBalance() {
if (!this.hasAccessToBalanceMode) {
return false;
}
return !!this.estimate?.availablePaymentMethods?.balance;
}
get couldPay() {
return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp;
}
get canPayWithBitcoin() { get canPayWithBitcoin() {
const paymentMethod = this.estimate?.availablePaymentMethods?.bitcoin; const paymentMethod = this.estimate?.availablePaymentMethods?.bitcoin;
return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max; return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max;
@ -523,7 +589,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return false; return false;
} }
const paymentMethod = this.estimate?.availablePaymentMethods?.balance; const paymentMethod = this.estimate?.availablePaymentMethods?.balance;
return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max; return paymentMethod && this.cost >= paymentMethod.min && this.cost <= paymentMethod.max && this.cost <= this.estimate?.userBalance;
} }
get canPay() { get canPay() {
@ -534,6 +600,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return this.isLoggedIn() && this.estimate?.hasAccess; return this.isLoggedIn() && this.estimate?.hasAccess;
} }
get timeSincePaid(): number {
return Date.now() - this.timePaid;
}
@HostListener('window:resize', ['$event']) @HostListener('window:resize', ['$event'])
onResize(): void { onResize(): void {
this.isMobile = window.innerWidth <= 767.98; this.isMobile = window.innerWidth <= 767.98;

View File

@ -80,7 +80,7 @@ export class ActiveAccelerationBox implements OnChanges {
data.push(getDataItem( data.push(getDataItem(
(this.miningStats.lastEstimatedHashrate - totalAcceleratedHashrate), (this.miningStats.lastEstimatedHashrate - totalAcceleratedHashrate),
'rgba(127, 127, 127, 0.3)', 'rgba(127, 127, 127, 0.3)',
`not accelerating (${notAcceleratedByPercentage})`, $localize`not accelerating` + ` (${notAcceleratedByPercentage})`,
false, false,
) as PieSeriesOption); ) as PieSeriesOption);

View File

@ -78,7 +78,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
} }
calculate() { calculate() {
if (!this.time) { if (this.time == null) {
return; return;
} }

View File

@ -132,6 +132,7 @@
[miningStats]="miningStats" [miningStats]="miningStats"
[eta]="eta" [eta]="eta"
[scrollEvent]="scrollIntoAccelPreview" [scrollEvent]="scrollIntoAccelPreview"
(unavailable)="eligibleForAcceleration = false"
class="h-100 w-100" class="h-100 w-100"
></app-accelerate-checkout> ></app-accelerate-checkout>
</ng-container> </ng-container>

View File

@ -128,7 +128,7 @@
<div class="title float-left mb-1"> <div class="title float-left mb-1">
<h2><a [href]="[ isMempoolSpaceBuild ? '/accelerator' : 'https://mempool.space/accelerator']" [target]="isMempoolSpaceBuild ? '' : 'blank'"><app-svg-images name="accelerator" [height]="isMobile ? '35px' : '45px'"></app-svg-images></a></h2> <h2><a [href]="[ isMempoolSpaceBuild ? '/accelerator' : 'https://mempool.space/accelerator']" [target]="isMempoolSpaceBuild ? '' : 'blank'"><app-svg-images name="accelerator" [height]="isMobile ? '35px' : '45px'"></app-svg-images></a></h2>
</div> </div>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="closeAccelerator()" i18n="accelerator.hide">Hide accelerator</button> <button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" [class.hide-on-mobile]="hasAccelerationDetails" (click)="closeAccelerator()" i18n="accelerator.hide">Hide accelerator</button>
<button *ngIf="hasAccelerationDetails" class="btn btn-sm btn-outline-info details-button float-right ml-2" (click)="showAccelerationDetails = !showAccelerationDetails" i18n="transaction.details|Transaction Details">Details</button> <button *ngIf="hasAccelerationDetails" class="btn btn-sm btn-outline-info details-button float-right ml-2" (click)="showAccelerationDetails = !showAccelerationDetails" i18n="transaction.details|Transaction Details">Details</button>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -146,6 +146,7 @@
[noCTA]="true" [noCTA]="true"
(hasDetails)="setHasAccelerationDetails($event)" (hasDetails)="setHasAccelerationDetails($event)"
(completed)="onAccelerationCompleted()" (completed)="onAccelerationCompleted()"
(unavailable)="eligibleForAcceleration = false"
class="h-100 w-100" class="h-100 w-100"
></app-accelerate-checkout> ></app-accelerate-checkout>
</ng-container> </ng-container>

View File

@ -167,6 +167,12 @@
} }
} }
@media (max-width: 767px){
.hide-on-mobile {
display: none;
}
}
.effective-fee-rating { .effective-fee-rating {
@media (max-width: 767px){ @media (max-width: 767px){
margin-right: 0px !important; margin-right: 0px !important;

View File

@ -35,9 +35,13 @@ const routes: Routes = [
loadChildren: () => import('../components/about/about.module').then(m => m.AboutModule), loadChildren: () => import('../components/about/about.module').then(m => m.AboutModule),
}, },
{ {
path: 'blocks', path: 'blocks/:page',
component: BlocksList, component: BlocksList,
}, },
{
path: 'blocks',
redirectTo: 'blocks/1',
},
{ {
path: 'terms-of-service', path: 'terms-of-service',
loadChildren: () => import('../components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule), loadChildren: () => import('../components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),

View File

@ -1,2 +1,9 @@
<div class="alert" [class]="alertClass" [innerHTML]="errorContent"> @if (!textOnly) {
</div> <div class="alert" [class]="alertClass" [innerHTML]="errorContent">
</div>
} @else {
<span [class]="alertClass" [innerHTML]="errorContent">
</span>
}
<ng-template #lowBalance i18n="accelerator.low-balance">Your balance is too low.<br/>Please <a style="color:#105fb0" href="/services/accelerator/overview">top up your account</a>.</ng-template>

View File

@ -1,9 +1,10 @@
import { Component, Input, OnInit } from "@angular/core"; import { Component, EmbeddedViewRef, Input, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
const MempoolErrors = { export const MempoolErrors = {
'bad_request': `Your request was not valid. Please try again.`, 'bad_request': `Your request was not valid. Please try again.`,
'internal_server_error': `Something went wrong, please try again later`, 'internal_server_error': `Something went wrong, please try again later`,
'temporarily_unavailable': `Acceleration temporarily unavailable`,
'acceleration_duplicated': `This transaction has already been accelerated.`, 'acceleration_duplicated': `This transaction has already been accelerated.`,
'acceleration_outbid': `Your fee delta is too low.`, 'acceleration_outbid': `Your fee delta is too low.`,
'cannot_accelerate_tx': `Cannot accelerate this transaction.`, 'cannot_accelerate_tx': `Cannot accelerate this transaction.`,
@ -15,7 +16,7 @@ const MempoolErrors = {
'mempool_rejected_raw_tx': `Our mempool rejected this transaction`, 'mempool_rejected_raw_tx': `Our mempool rejected this transaction`,
'no_mining_pool_available': `No mining pool available at the moment`, 'no_mining_pool_available': `No mining pool available at the moment`,
'not_available': `You current subscription does not allow you to access this feature.`, 'not_available': `You current subscription does not allow you to access this feature.`,
'not_enough_balance': `Your balance is too low. Please <a style="color:#105fb0" href="/services/accelerator/overview">top up your account</a>.`, 'not_enough_balance': ``,
'not_verified': `You must verify your account to use this feature.`, 'not_verified': `You must verify your account to use this feature.`,
'recommended_fees_not_available': `Recommended fees are not available right now.`, 'recommended_fees_not_available': `Recommended fees are not available right now.`,
'too_many_relatives': `This transaction has too many relatives.`, 'too_many_relatives': `This transaction has too many relatives.`,
@ -42,14 +43,30 @@ export function isMempoolError(error: string) {
templateUrl: './mempool-error.component.html' templateUrl: './mempool-error.component.html'
}) })
export class MempoolErrorComponent implements OnInit { export class MempoolErrorComponent implements OnInit {
@ViewChild('lowBalance', { static: true }) lowBalance!: TemplateRef<any>;
@Input() error: string; @Input() error: string;
@Input() alertClass = 'alert-danger'; @Input() alertClass = 'alert-danger';
@Input() textOnly = false;
errorContent: SafeHtml; errorContent: SafeHtml;
constructor(private sanitizer: DomSanitizer) { } constructor(
private sanitizer: DomSanitizer,
) { }
ngOnInit(): void { ngOnInit(): void {
if (Object.keys(MempoolErrors).includes(this.error)) { // Special hack for the i18n string with a href link inside
const embeddedViewRef: EmbeddedViewRef<any> = this.lowBalance.createEmbeddedView({});
embeddedViewRef.detectChanges();
const rawHtml = embeddedViewRef.rootNodes.map((node) => {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent;
} else if (node.nodeType === Node.ELEMENT_NODE) {
return node.outerHTML;
}
return '';
}).join('');
MempoolErrors['not_enough_balance'] = rawHtml;
if (Object.keys(MempoolErrors).includes(this.error)) {
this.errorContent = this.sanitizer.bypassSecurityTrustHtml(MempoolErrors[this.error]); this.errorContent = this.sanitizer.bypassSecurityTrustHtml(MempoolErrors[this.error]);
} else { } else {
this.errorContent = this.error; this.errorContent = this.error;

View File

@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline } from '@fortawesome/free-solid-svg-icons'; faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark} from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MenuComponent } from '../components/menu/menu.component'; import { MenuComponent } from '../components/menu/menu.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@ -433,5 +433,6 @@ export class SharedModule {
library.addIcons(faWandMagicSparkles); library.addIcons(faWandMagicSparkles);
library.addIcons(faFaucetDrip); library.addIcons(faFaucetDrip);
library.addIcons(faTimeline); library.addIcons(faTimeline);
library.addIcons(faCircleXmark);
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -131,7 +131,6 @@
"node208.fra.mempool.space", "node208.fra.mempool.space",
"node209.fra.mempool.space", "node209.fra.mempool.space",
"node210.fra.mempool.space", "node210.fra.mempool.space",
"node211.fra.mempool.space",
"node212.fra.mempool.space", "node212.fra.mempool.space",
"node213.fra.mempool.space", "node213.fra.mempool.space",
"node214.fra.mempool.space", "node214.fra.mempool.space",