Alerts added

Better status handling
This commit is contained in:
2021-01-01 19:33:07 +01:00
parent 84e4910c70
commit b2043e1546
8 changed files with 188 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ import { BehaviorSubject } from 'rxjs';
/* /*
* The following interfaces are copied from the backend. * The following interfaces are copied from the backend.
* *
* Checkout src/helper/types.ts and src/models/invoice/invoice.interface.ts * Checkout src/helper/types.ts and src/models/invoice/invoice.interface.ts
* (in the backend repository) for more information. * (in the backend repository) for more information.
*/ */
@@ -31,11 +31,14 @@ export interface IPaymentMethod {
} }
export enum PaymentStatus { export enum PaymentStatus {
CANCELLED = -2, TOOLITTLE = -3,
REQUESTED = -1, TOOLATE = -2,
PENDING = 0, CANCELLED = -1,
UNCONFIRMED = 1, REQUESTED = 0,
DONE = 2, PENDING = 1,
UNCONFIRMED = 2,
DONE = 3,
TOOMUCH = 4
} }
export interface IInvoice { export interface IInvoice {
selector: string; selector: string;
@@ -43,7 +46,7 @@ export interface IInvoice {
receiveAddress: string; receiveAddress: string;
paidWith?: CryptoUnits; paidWith?: CryptoUnits;
paid?: number; paid?: number;
transcationHashes?: string[]; transcationHash?: string;
cart?: ICart[]; cart?: ICart[];
totalPrice?: number; totalPrice?: number;
currency: string; currency: string;
@@ -62,16 +65,32 @@ export class BackendService {
SERVER_URL = 'http://localhost:2009'; SERVER_URL = 'http://localhost:2009';
invoice: IInvoice | null = null; // Fill with empty data
invoice: IInvoice = {
selector: '',
paymentMethods: [],
receiveAddress: '',
paid: 0,
currency: 'USD',
dueBy: Date.now(),
successUrl: '',
cancelUrl: ''
};
invoiceUpdate: BehaviorSubject<IInvoice | null>; invoiceUpdate: BehaviorSubject<IInvoice | null>;
// This value is s
confirmations: number;
constructor( constructor(
private socket: Socket, private socket: Socket,
private http: HttpClient private http: HttpClient
) { ) {
this.confirmations = 0;
this.invoiceUpdate = new BehaviorSubject<IInvoice | null>(null); this.invoiceUpdate = new BehaviorSubject<IInvoice | null>(null);
this.socket.on('status', (data: any) => { this.socket.on('status', (data: any) => {
console.log('Status has been updated to: ', data); console.log('Status has been updated to: ', data);
this.invoice.status = data;
this.invoiceUpdate.next(this.invoice);
}); });
this.socket.on('subscribe', (success: boolean) => { this.socket.on('subscribe', (success: boolean) => {
if (success) { console.log('We\'re getting the progress of this invoice!'); } if (success) { console.log('We\'re getting the progress of this invoice!'); }
@@ -92,17 +111,24 @@ export class BackendService {
else { console.log('Failed to subscribe'); } else { console.log('Failed to subscribe'); }
}); });
this.socket.on('confirmationUpdate', (update: any) => {
this.confirmations = update.count;
});
this.socket.emit('subscribe', { selector }); this.socket.emit('subscribe', { selector });
} }
updateInvoice(): void { updateInvoice(): void {
if (this.invoice !== undefined || this.invoice !== null) { if (this.invoice !== undefined || this.invoice !== null) {
this.setInvoice(this.invoice?.selector!); this.setInvoice(this.invoice.selector);
} }
} }
setInvoice(selector: string): Promise<IInvoice> { setInvoice(selector: string): Promise<IInvoice> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (selector === undefined || selector === 'undefined' || selector === '') {
reject();
}
this.http.get(this.SERVER_URL + '/invoice/' + selector, { this.http.get(this.SERVER_URL + '/invoice/' + selector, {
observe: 'body', observe: 'body',
responseType: 'json' responseType: 'json'
@@ -118,10 +144,32 @@ export class BackendService {
setPaymentMethod(method: CryptoUnits): Promise<void> { setPaymentMethod(method: CryptoUnits): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (this.invoice === null) { reject('Invoice is not set!'); return; }
this.http.post(`${this.SERVER_URL}/invoice/${this.invoice?.selector}/setmethod`, { method }, { this.http.post(`${this.SERVER_URL}/invoice/${this.invoice?.selector}/setmethod`, { method }, {
responseType: 'json' responseType: 'json'
}).toPromise().then(() => { }).toPromise().then(() => {
this.setInvoice(this.invoice!!.selector); this.setInvoice(this.invoice.selector);
}).catch(err => {
reject(err);
});
});
}
getConfirmation(): Promise<number> {
return new Promise(async (resolve, reject) => {
if (this.invoice === null || this.invoice.status !== PaymentStatus.UNCONFIRMED) {
reject('Invoice is not set!');
return;
}
this.http.get(`${this.SERVER_URL}/invoice/${this.invoice.selector}/confirmation`, {
observe: 'body',
responseType: 'json'
}).toPromise().then((res: any) => {
this.confirmations = res.confirmation;
this.invoiceUpdate.next(this.invoice);
resolve(res.confirmation);
}).catch(err => { }).catch(err => {
reject(err); reject(err);
}); });
@@ -150,7 +198,7 @@ export class BackendService {
findCryptoBySymbol(symbol: string): string | null { findCryptoBySymbol(symbol: string): string | null {
for (const coin in CryptoUnits) { for (const coin in CryptoUnits) {
// @ts-ignore: This actually works but I thing it's too hacky for TS. Allow me this one, please? // @ts-ignore: This actually works but I think it's too hacky for TS. Allow me this one, please.
if (CryptoUnits[coin] === symbol.toUpperCase()) { if (CryptoUnits[coin] === symbol.toUpperCase()) {
return coin.charAt(0).toUpperCase() + coin.toLowerCase().slice(1); return coin.charAt(0).toUpperCase() + coin.toLowerCase().slice(1);
} }
@@ -173,7 +221,13 @@ export class BackendService {
case PaymentStatus.DONE: case PaymentStatus.DONE:
return 'Paid'; return 'Paid';
case PaymentStatus.CANCELLED: case PaymentStatus.CANCELLED:
return 'Cancelled'; return 'Cancelled by user';
case PaymentStatus.TOOLATE:
return 'Expired';
case PaymentStatus.TOOLITTLE:
return 'Paid too little';
case PaymentStatus.TOOMUCH:
return 'Paid too much';
default: default:
return 'Unknown'; return 'Unknown';
} }

View File

@@ -12,9 +12,10 @@
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.header h2 { .header img {
font-weight: bolder; height: 3rem;
width: fit-content; margin-top: 0.5rem;
margin-bottom: 1rem;
grid-column: 1; grid-column: 1;
} }

View File

@@ -1,4 +1,4 @@
<div class="header"> <div class="header">
<h2>LibrePay</h2> <img src="assets/logo.svg">
<a *ngIf="this.backend.isInvoicePending()">Cancel payment</a> <a *ngIf="this.backend.isInvoicePending()">Cancel payment</a>
</div> </div>

View File

@@ -3,15 +3,41 @@
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 500px; height: 500px;
background-color: hsl(0, 0%, 11%); background-color: #1c1c1c;
border-radius: 8px; border-radius: 8px;
transform: translateY(-8px); transform: translateY(-8px);
} }
/* Apply effect when invoice expired */
.invalid {
filter: grayscale(1);
}
.invalid .qrWrapper, .invalid .data {
filter: blur(10px);
animation: blurFade 0.2s ease;
cursor: not-allowed;
user-select: none;
}
.invalid svg, .invalid img {
display: none;
}
@keyframes blurFade {
from { filter: blur(0) }
to { filter: blur(10px) }
}
.request { .request {
transform: translateY(-40px); transform: translateY(-40px);
} }
.loader {
position: absolute;
display: inline;
transform: translateY(-24px) translateX(32px);
}
/* Styles for payment screen (not for payment choosing) */
.main { .main {
display: grid; display: grid;
height: 400px; height: 400px;
@@ -38,6 +64,18 @@
z-index: 2; z-index: 2;
} }
.alert {
text-align: center;
background-color: #C03A08;
color: #fff;
padding: 1rem;
border-radius: 8px;
border: 3px solid #a93206;
}
.alert p {
margin: 0;
}
@keyframes coinRoll { @keyframes coinRoll {
0% { 0% {
transform: translateY(96px) rotate(-45deg) scale(0.5); transform: translateY(96px) rotate(-45deg) scale(0.5);
@@ -65,6 +103,11 @@
grid-row: 4; grid-row: 4;
} }
.price {
font-size: 10pt;
font-weight: light;
}
.main h3 { .main h3 {
line-height: 0; line-height: 0;
} }

View File

@@ -1,6 +1,6 @@
<div class="payment request" *ngIf="this.backend.isInvoiceRequested()"> <div class="payment request" *ngIf="this.backend.isInvoiceRequested()">
<h3 id="title">Choose your<br>payment method</h3> <h3 id="title">Choose your<br>payment method</h3>
<p id="price">{{ this.backend.invoice!!.totalPrice!!.toFixed(2) }} €</p> <p id="price">{{ this.backend.invoice.totalPrice!.toFixed(2) }} €</p>
<ul id="list"> <ul id="list">
<li *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)"> <li *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)">
@@ -12,12 +12,12 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="payment main" *ngIf="!this.backend.isInvoiceRequested() && ready"> <div class="payment main" *ngIf="!this.backend.isInvoiceRequested() && ready" [ngClass]="{invalid: status === 'Expired' || status === 'Paid too little'}">
<div class="qrWrapper"> <div class="qrWrapper">
<div class="qr"> <div class="qr">
<img src="assets/Bitcoin.svg"> <img src="assets/Bitcoin.svg">
<qrcode <qrcode
[qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress" [qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress + '?amount=' + this.backend.getAmount()"
[width]="256" [width]="256"
[errorCorrectionLevel]="'M'" [errorCorrectionLevel]="'M'"
[elementType]="'svg'" [elementType]="'svg'"
@@ -29,13 +29,43 @@
<div class="data"> <div class="data">
<!-- Payment data --> <!-- Payment data -->
<span id="target">Send to <span id="target">Send to
<h3>{{ this.backend.invoice!.receiveAddress }}</h3> <h3>{{ this.backend.invoice?.receiveAddress }}</h3>
</span> </span>
<span id="amount">Amount <span id="amount">Amount
<h3>{{ this.backend.getAmount() }} BTC</h3> <h3>{{ this.backend.getAmount() }} BTC <span class="price"> | {{ this.backend.invoice.totalPrice!.toFixed(2) }} €</span></h3>
</span> </span>
<span id="status">Status <span id="status">Status
<h3>{{ this.backend.getStatus() }}</h3> <h3>
{{ status }}
<div class="loader">
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="32px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<path fill="#f7a12f" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="0 25 25"
to="360 25 25"
dur="1s"
repeatCount="indefinite"/>
</path>
</svg>
</div>
</h3>
<small *ngIf="status === 'Unconfirmed'">Confirmations: {{ this.backend.confirmations }}</small>
</span> </span>
</div> </div>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Expired'">
<p><b>This invoice expired</b>
<br>You cannot pay this invoice anymore. Please try to request a new invoice. </p>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Paid too much'">
<p><b>Looks like you paid too much.</b>
<br>Technically <u><b>this invoice is paid</b></u> but we would like to pay the rest back. Since sending back funds is complicated we would like you to contact support@example.org</p>
</div>
<div class="alert xyz-in" xyz="fade-100% duration-3 down-1" *ngIf="status === 'Paid too little'">
<p><b>Looks like you paid not the requested amount of money.</b>
<br><u><b>You cannot pay twice!</b></u> Since sending back funds is complicated we would like you to contact support@example.org</p>
</div> </div>

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { BackendService, IInvoice, CryptoUnits, PaymentStatus, IPaymentMethod } from '../backend.service';
import { BackendService, CryptoUnits } from '../backend.service';
@Component({ @Component({
selector: 'app-payment', selector: 'app-payment',
@@ -10,13 +11,17 @@ import { BackendService, IInvoice, CryptoUnits, PaymentStatus, IPaymentMethod }
export class PaymentComponent implements OnInit { export class PaymentComponent implements OnInit {
paymentSelector = ''; paymentSelector = '';
confirmations = 0;
choosenPaymentMethod = CryptoUnits.BITCOIN; choosenPaymentMethod = CryptoUnits.BITCOIN;
status: string;
ready = false; ready = false;
constructor( constructor(
public backend: BackendService, public backend: BackendService,
private route: ActivatedRoute private route: ActivatedRoute
) { } ) {
this.status = this.backend.getStatus();
}
ngOnInit(): void { ngOnInit(): void {
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
@@ -24,14 +29,19 @@ export class PaymentComponent implements OnInit {
this.backend.subscribeTo(this.paymentSelector); this.backend.subscribeTo(this.paymentSelector);
this.get(); this.get();
}); });
this.backend.invoiceUpdate.subscribe(newInvoice => {
this.status = this.backend.getStatus();
});
} }
chooseMethod(coin: CryptoUnits) { chooseMethod(coin: CryptoUnits): void {
this.backend.setPaymentMethod(coin); this.backend.setPaymentMethod(coin);
} }
async get(): Promise<void> { async get(): Promise<void> {
await this.backend.setInvoice(this.paymentSelector); await this.backend.setInvoice(this.paymentSelector);
this.backend.getConfirmation().catch();
this.ready = true; this.ready = true;
} }

23
src/assets/logo.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 628.97 179.28">
<defs>
<style>
.cls-1{fill:#f7a12f;}.cls-2{fill:#1c1c1c;}
</style>
</defs>
<path class="cls-1" d="M427.87,393.05c-1.2.66-2.49,1.17-3.75,1.76l-2.58-5.55a21.44,21.44,0,0,1,4.06-1.64l.26-.05a2.63,2.63,0,0,1,3.31,1.79A3,3,0,0,1,427.87,393.05Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M434.75,399.9a4.47,4.47,0,0,1-2.18,2.27c-1.31.7-2.68,1.3-4,1.93,0,0-.07,0-.13,0l-2.79-6,.3-.17c1.15-.53,2.27-1.1,3.44-1.58a9.13,9.13,0,0,1,2.23-.62A2.92,2.92,0,0,1,434.75,399.9Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M427.34,364.26a31.54,31.54,0,1,0,31.53,31.54A31.54,31.54,0,0,0,427.34,364.26Zm11.23,39.18a30.93,30.93,0,0,1-3,1.84,2.72,2.72,0,0,1-.48.22l2.23,4.8-3.07,1.43L432,407l-1.78.82,2.21,4.77L429.28,414l-2.22-4.79-5.71,2.64-1.53-3.32,2.05-1c.95-.43,1.14-.93.7-1.89l-6.06-13.09a.94.94,0,0,0-1.3-.58,5,5,0,0,0-.68.26l-2.15,1-1.5-3.24,5.9-2.73-2.19-4.72,3.16-1.46,2.17,4.68,1.75-.81-2.17-4.67,3.17-1.47,2.17,4.69c1-.23,1.9-.51,2.84-.64a12.45,12.45,0,0,1,2.66-.11c4.31.3,5.83,4.91,4.22,7.89-.06.13-.13.26-.23.44l.56,0a5.77,5.77,0,0,1,5.71,3.62,11.48,11.48,0,0,1,.74,2.68A6.25,6.25,0,0,1,438.57,403.44Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M456.06,334a15.65,15.65,0,1,0,15.65,15.65A15.65,15.65,0,0,0,456.06,334Zm6.76,24.65a3.76,3.76,0,0,1-4.45-3.3,2.58,2.58,0,0,1,.06-1,1.06,1.06,0,0,0-.54-1.32,3.39,3.39,0,0,0-3.41-.7c-.54,0-.79.32-1,.89a3.72,3.72,0,0,1-7.05-.35,3.72,3.72,0,0,1,5.79-4,2.48,2.48,0,0,1,.3.25c.32.28.44.84,1,.74a3.29,3.29,0,0,0,1.95-.82,3.4,3.4,0,0,0,.61-.85.9.9,0,0,0-.25-1.25,3.7,3.7,0,0,1-.94-4.16,3.66,3.66,0,0,1,3.33-2.49,3.7,3.7,0,0,1,3.76,2.12,3.84,3.84,0,0,1-2.25,5.43c-1,.31-1.21.67-1,1.73a2.88,2.88,0,0,0,.93,1.61,1.14,1.14,0,0,0,1.29.26,3.66,3.66,0,0,1,4.72,2.68A3.71,3.71,0,0,1,462.82,358.61Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M423,336.19a11.3,11.3,0,1,0,11.3,11.3A11.3,11.3,0,0,0,423,336.19Zm3.86,10.19-3,3.65-3.08-8.64Zm-6.25-4.85c1,2.91,2,5.7,3.05,8.53l-4.58-.9C419.59,346.62,420.09,344.14,420.61,341.53Zm-1,8.33a.42.42,0,0,1,0-.1l4.1.83a.33.33,0,0,1,.21.19c.34.92.67,1.85.93,2.84Zm5.66,3.61h-.1c-.33-.92-.66-1.85-1-2.78a.32.32,0,0,1,.05-.28c.9-1.1,1.81-2.19,2.79-3.25C426.42,349.26,425.84,351.37,425.25,353.47Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M515,433l-9.48,2.37L395.32,463q-18.92,4.73-37.83,9.4c-.51.13-1.49.89-1.63-.1s-.6-2.47,1.1-2.92l1-.26,165.59-41.43c2.17-.54,2.1-.54,1.92,1.73-.73,9.44-1.35,18.88-2.15,28.31-.33,3.94-.15,8-2.26,11.63s-5.07,6.6-9.35,7.67Q462.89,489.22,414,501.35c-2.68.67-5.41.91-7.54-1.53a8.86,8.86,0,0,1-1.76-4.37c-.77-3.83.1-7.62.34-11.42.42-6.58,1.09-13.14,1.61-19.71.06-.7.25-1.07.92-1.21.45-.1.89-.22,1.34-.34l96-24,7-1.75C514.78,436.28,514.78,436.28,515,433Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M387.77,445c-.56.14-2.16,1.07-2.32-.47-.11-1-.59-2.37,1.22-2.81,6.76-1.63,13.49-3.39,20.26-5a2.17,2.17,0,0,0,2-2.41,15.22,15.22,0,0,1,6.76-12.93,13.45,13.45,0,0,1,4.63-2q48.76-12.21,97.51-24.43c3.28-.83,6-.33,8.2,2.43a6.56,6.56,0,0,1,1.46,4.81c-.31,2.27-.09,4.53-.34,6.78-.12,1.11-.76,1.42-1.66,1.64C520.39,411.83,399.55,442.12,387.77,445Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M537.78,406.36c-2.11.44-3.29.58-3.26,3.41s-.47,5.86-.7,8.8c-.82,10.67-1.69,21.34-2.52,32-.28,3.52-.48,7.05-.77,10.56-.32,3.95-.87,7.8-2.83,11.4a22.91,22.91,0,0,1-15.36,12c-14,3.38-27.88,6.93-41.81,10.42,0,0-29.68,7.33-44.5,11l-1.17.3a.83.83,0,0,0-.72,1.13c.66,3.34,3.07,6.38,7.09,5.76A161.25,161.25,0,0,0,448.82,509l81.86-20.54a14.91,14.91,0,0,0,10.22-8.52c1.92-4.05,1.5-8.59,2-12.91.31-3,.5-6,.62-9,.06-1.43.5-2.86.44-4.21a61.28,61.28,0,0,1,.52-8.26c.22-2.63.25-5.29.68-7.93a24.22,24.22,0,0,0,.32-3.6c0-1.71.07-3.43.22-5.14s.28-3.47.41-5.21c.12-1.55.2-3.11.36-4.66.24-2.48.66-5,.05-7.44C545.41,407.28,542.08,405.45,537.78,406.36Z" transform="translate(-355.7 -333.96)" />
<path class="cls-2" d="M612.24,460.71v13.1H564V406.13h14.59v54.58Z" transform="translate(-355.7 -333.96)" />
<path class="cls-2" d="M620.52,408a7.78,7.78,0,1,1,7.88,7.88A7.87,7.87,0,0,1,620.52,408Zm.59,14.88h14.38v50.93H621.11Z" transform="translate(-355.7 -333.96)" />
<path class="cls-2" d="M702.88,448.3c0,15.66-9.85,26.7-23.45,26.7a20.78,20.78,0,0,1-17-8.38v7.19H648V404.94h14.38v25a21,21,0,0,1,17-8.28C693,421.69,702.88,432.73,702.88,448.3Zm-14,0c0-8.48-5.61-14.49-13.59-14.49-7,0-12.12,5.13-12.91,12.51v3.95c.79,7.48,5.91,12.61,12.91,12.61C683.28,462.88,688.89,456.87,688.89,448.3Z" transform="translate(-355.7 -333.96)" />
<path class="cls-2" d="M744.86,421.69v12.12c-10.65,0-17.44,6.11-17.44,15.67v24.33H713V422.88h14.39v9.45A19,19,0,0,1,744.86,421.69Z" transform="translate(-355.7 -333.96)" />
<path class="cls-2" d="M788.9,457.75l9.16,9.36c-4.53,4.73-13.3,7.89-20.79,7.89-15.17,0-27-11.14-27-26.8,0-15.37,11.34-26.51,26.21-26.51,16,0,25.32,12.12,25.32,31.33H765.05c1.68,5.52,6.11,9.27,12.12,9.27A18.47,18.47,0,0,0,788.9,457.75Zm-23.65-15.07H788c-1.38-5-5.12-8.28-10.94-8.28A12.31,12.31,0,0,0,765.25,442.68Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M812,406.13H842.4c14.68,0,25.51,9.45,25.51,22.85s-10.83,22.86-25.51,22.86h-16v22H812Zm28.87,32.41c7.19,0,12.12-3.74,12.12-9.56s-4.93-9.55-12.12-9.55H826.43v19.11Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M921.71,443.37v30.44H907.82v-5.12c-3.65,4.14-9.46,6.31-15,6.31-10.84,0-19.21-6.51-19.21-16.26,0-10.05,9.36-16.95,21.18-16.95a36.9,36.9,0,0,1,13,2.56v-1c0-5.42-3.35-10.15-11.73-10.15a29.1,29.1,0,0,0-13.2,3.65l-4.83-9.56c7.19-3.65,14.19-5.62,21.28-5.62C912.64,421.69,921.71,430.07,921.71,443.37Zm-13.89,14.38V453a32.3,32.3,0,0,0-10.94-1.87c-5.22,0-9.36,3.06-9.36,7.19s3.65,6.7,8.57,6.7C901.61,465,906.83,462.48,907.82,457.75Z" transform="translate(-355.7 -333.96)" />
<path class="cls-1" d="M932.65,488.4l3.05-9.86a15.74,15.74,0,0,0,6.7,2.27c2.76,0,4.93-.89,6-2.76l1.77-3.74L929,422.88h14.88l13.4,35.76L970,422.88h14.68l-20.89,53.5c-3.84,9.95-10.35,15.17-18.33,15.17A29.26,29.26,0,0,1,932.65,488.4Z" transform="translate(-355.7 -333.96)" />
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -8,6 +8,7 @@
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@animxyz/core@0.3.0/dist/animxyz.min.css">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>