[accelerator] proper error handling

This commit is contained in:
Mononaut 2024-07-05 10:41:59 +00:00
parent 20d948c280
commit b8820684c3
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
8 changed files with 132 additions and 38 deletions

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">
@ -345,7 +357,7 @@
<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"></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>
@ -353,7 +365,7 @@
<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>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>
<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;">
@ -532,16 +549,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) {
<span class="btn-error"><app-mempool-error [error]="quoteError || cantPayReason" [textOnly]="true" alertClass=""></app-mempool-error></span>
}
</div>
} }
</ng-template> </ng-template>

View File

@ -189,3 +189,10 @@
flex-direction: column; flex-direction: column;
} }
} }
.btn-error {
position: absolute;
right: 0;
font-size: 12px;
color: var(--red);
}

View File

@ -59,13 +59,18 @@ 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;
@ -98,6 +103,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 +131,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;
@ -182,6 +191,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
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();
@ -226,19 +236,27 @@ 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`;
} }
} }
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
@ -267,6 +285,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
} }
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 +299,27 @@ 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();
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 +353,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 +401,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 +448,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 +471,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 +497,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 +527,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 +579,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() {

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

@ -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

@ -1,2 +1,7 @@
<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>
}

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } 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`,
'acceleration_duplicated': `This transaction has already been accelerated.`, 'acceleration_duplicated': `This transaction has already been accelerated.`,
@ -44,6 +44,7 @@ export function isMempoolError(error: string) {
export class MempoolErrorComponent implements OnInit { export class MempoolErrorComponent implements OnInit {
@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) { }

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);
} }
} }