Compare commits

...

11 Commits

Author SHA1 Message Date
967ab2498d Merge branch 'dashboard' of https://nicolasklier.de:3000/LibrePay/Frontend into dashboard 2021-02-01 13:19:33 +01:00
Felix
a3e3eb983b add SVG Images to HeaderBar 2021-02-01 13:17:05 +01:00
Felix
8cc0d96abf add component "dashboard" 2021-02-01 13:17:03 +01:00
Felix
02f1727358 add login with dummy Data
add login page
add subcomponents
2021-02-01 13:15:43 +01:00
Felix
695dbf9d34 add login subcomponent 2021-02-01 13:14:48 +01:00
Felix
b409c3c670 merge master 2021-02-01 13:14:47 +01:00
39659c4373 Fix layout of expire progress bar 2021-02-01 13:06:25 +01:00
dfda3e07e0 Timer implemented 2021-01-28 21:49:00 +01:00
b2c3308005 Clipboard icons have been added
without functionality yet
2021-01-26 20:21:47 +01:00
03227b8b0f Some new animations in cart
Long addresses will be capped (copy will come in next commit)
2021-01-24 19:58:02 +01:00
59ebcb54c6 Crypto currency isn't static anymore
- Notification when transaction got confirmed
2021-01-17 18:48:00 +01:00
18 changed files with 384 additions and 75 deletions

25
package-lock.json generated
View File

@@ -7527,6 +7527,23 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true "dev": true
}, },
"ng-push-ivy": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/ng-push-ivy/-/ng-push-ivy-1.0.7.tgz",
"integrity": "sha512-uUzIKBc6LA9Bw0sl7aj6x3eUr2UcCbXEw1PKpLFZ2OxzbnAhqh3IVX4ah0PRiDpfscFhmGUR2amLo19njAbMVg==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-clipboard": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-14.0.1.tgz",
"integrity": "sha512-y6fDrvAso1cbM+VvHgB2kJ3dcQ/EBPol33nLaqqKB1jNO/Kd3l17EHdXNW/oKY0wUKCHk7ZCuiinREgUHEYfXg==",
"requires": {
"ngx-window-token": ">=4.0.0",
"tslib": "^2.0.0"
}
},
"ngx-socket-io": { "ngx-socket-io": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-3.2.0.tgz", "resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-3.2.0.tgz",
@@ -7547,6 +7564,14 @@
} }
} }
}, },
"ngx-window-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-4.0.0.tgz",
"integrity": "sha512-z6tS3UQoKULdABWHpE57l1xtoxFFzlwLe1n+nu9+xzCZUdSvkGqhb5dSje4NOVhA6mMOqzR4SctSBZARwqPPuQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"nice-try": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@@ -20,6 +20,8 @@
"@angular/platform-browser-dynamic": "~11.0.5", "@angular/platform-browser-dynamic": "~11.0.5",
"@angular/router": "~11.0.5", "@angular/router": "~11.0.5",
"angularx-qrcode": "^10.0.11", "angularx-qrcode": "^10.0.11",
"ng-push-ivy": "^1.0.7",
"ngx-clipboard": "^14.0.1",
"ngx-socket-io": "^3.2.0", "ngx-socket-io": "^3.2.0",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"tslib": "^2.0.0", "tslib": "^2.0.0",

View File

@@ -6,13 +6,14 @@ import { HeaderComponent } from './header/header.component';
import { PaymentComponent } from './payment/payment.component'; import { PaymentComponent } from './payment/payment.component';
import { QRCodeModule } from 'angularx-qrcode'; import { QRCodeModule } from 'angularx-qrcode';
import { PayComponent } from './pay/pay.component'; import { PayComponent } from './pay/pay.component';
import { RouterModule } from '@angular/router';
import { HelloComponent } from './hello/hello.component'; import { HelloComponent } from './hello/hello.component';
import { SocketIoConfig, SocketIoModule } from 'ngx-socket-io'; import { SocketIoConfig, SocketIoModule } from 'ngx-socket-io';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from 'src/routes'; import { AppRoutingModule } from 'src/routes';
import { NotFoundComponent } from './not-found/not-found.component'; import { NotFoundComponent } from './not-found/not-found.component';
import { CartComponent } from './cart/cart.component'; import { CartComponent } from './cart/cart.component';
import { PushNotificationsModule } from 'ng-push-ivy';
import { ClipboardModule } from 'ngx-clipboard';
import { DashboardComponent } from './dashboard/dashboard.component'; import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './dashboard/login/login.component'; import { LoginComponent } from './dashboard/login/login.component';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -40,6 +41,9 @@ const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
QRCodeModule, QRCodeModule,
HttpClientModule, HttpClientModule,
AppRoutingModule, AppRoutingModule,
SocketIoModule.forRoot(config),
PushNotificationsModule,
ClipboardModule,
FormsModule, FormsModule,
SocketIoModule.forRoot(config) SocketIoModule.forRoot(config)
], ],

View File

@@ -52,13 +52,12 @@ export interface IInvoice {
cart?: ICart[]; cart?: ICart[];
totalPrice?: number; totalPrice?: number;
currency: string; currency: string;
dueBy: Date; dueBy: string;
status?: PaymentStatus; status?: PaymentStatus;
email?: string; email?: string;
successUrl: string; successUrl: string;
cancelUrl: string; cancelUrl: string;
createdAt?: number; createdAt: string;
} }
@Injectable({ @Injectable({
@@ -66,7 +65,7 @@ export interface IInvoice {
}) })
export class BackendService { export class BackendService {
SERVER_URL = 'http://localhost:2009'; SERVER_URL = 'http://192.168.178.26:2009';
// Fill with empty data // Fill with empty data
invoice: IInvoice = { invoice: IInvoice = {
@@ -74,9 +73,10 @@ export class BackendService {
paymentMethods: [], paymentMethods: [],
receiveAddress: '', receiveAddress: '',
currency: 'USD', currency: 'USD',
dueBy: new Date(), dueBy: '',
successUrl: '', successUrl: '',
cancelUrl: '' cancelUrl: '',
createdAt: ''
}; };
invoiceUpdate: BehaviorSubject<IInvoice | null>; invoiceUpdate: BehaviorSubject<IInvoice | null>;
@@ -138,7 +138,7 @@ export class BackendService {
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 === '') { if (selector === undefined || selector === 'undefined' || selector === '') {
reject(); reject('There is no selector. Please set one before calling setInvoice(...)');
return; return;
} }
@@ -147,7 +147,6 @@ export class BackendService {
responseType: 'json' responseType: 'json'
}).toPromise().then((invoice) => { }).toPromise().then((invoice) => {
this.invoice = invoice as IInvoice; this.invoice = invoice as IInvoice;
this.invoiceUpdate.next(this.invoice);
resolve(this.invoice); resolve(this.invoice);
}).catch(err => { }).catch(err => {
reject(err); reject(err);
@@ -155,6 +154,14 @@ export class BackendService {
}); });
} }
setInvoiceExpired() {
// Don't set expired if status is not pending
if (this.invoice.status !== PaymentStatus.PENDING) { return; }
this.invoice.status = PaymentStatus.TOOLATE;
this.invoiceUpdate.next(this.invoice);
}
/** /**
* This will notify the backend that the user just cancelled the payment. * This will notify the backend that the user just cancelled the payment.
*/ */
@@ -224,7 +231,14 @@ export class BackendService {
/** /**
* @returns Path to icon in assets folder * @returns Path to icon in assets folder
*/ */
getIcon(unit: CryptoUnits): string { getIcon(unit?: CryptoUnits): string {
if (unit === undefined) {
if (this.invoice.paymentMethod === undefined) {
return 'assets/Bitcoin.svg';
}
unit = this.invoice.paymentMethod;
}
switch (unit) { switch (unit) {
case CryptoUnits.BITCOIN: case CryptoUnits.BITCOIN:
return 'assets/Bitcoin.svg'; return 'assets/Bitcoin.svg';
@@ -241,7 +255,15 @@ export class BackendService {
} }
} }
findCryptoBySymbol(symbol: string): string | null { findCryptoBySymbol(symbol?: string): string | null {
if (symbol === undefined) {
if (this.invoice.paymentMethod === undefined) {
return null;
}
symbol = this.invoice.paymentMethod;
}
for (const coin in CryptoUnits) { for (const coin in CryptoUnits) {
// @ts-ignore: This actually works but I think 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()) {
@@ -255,9 +277,13 @@ export class BackendService {
* @returns The price to pay by cryptocurrency; * @returns The price to pay by cryptocurrency;
*/ */
getAmount(): string | undefined { getAmount(): string | undefined {
return this.invoice?.paymentMethods.find(item => { const amount = this.invoice?.paymentMethods.find(item => {
return item.method === this.invoice.paymentMethod; return item.method === this.invoice.paymentMethod;
})?.amount.toFixed(8); })?.amount.toString();
if (amount === undefined) { return '0.00'; }
return amount;
} }
/** /**
@@ -265,12 +291,12 @@ export class BackendService {
* @param prodcut Index of product in cart * @param prodcut Index of product in cart
*/ */
calculateCryptoPrice(productNr: number): number { calculateCryptoPrice(productNr: number): number {
if (this.invoice.cart === undefined) return 0; if (this.invoice.cart === undefined) { return 0; }
if (this.invoice.paymentMethod === undefined) return 0; if (this.invoice.paymentMethod === undefined) { return 0; }
const product = this.invoice.cart[productNr]; const product = this.invoice.cart[productNr];
const exRate = this.invoice.paymentMethods.find(method => { return method.method === this.invoice.paymentMethod })?.exRate; const exRate = this.invoice.paymentMethods.find(method => method.method === this.invoice.paymentMethod)?.exRate;
if (exRate === undefined) return 0; if (exRate === undefined) { return 0; }
return product.quantity * product.price / exRate; return product.quantity * product.price / exRate;
} }

View File

@@ -17,24 +17,39 @@
height: 359px; height: 359px;
} }
.cart ul li { .item {
display: grid; display: grid;
padding: 1rem; padding: 1rem;
grid-template-columns: 64px 1fr auto; grid-template-columns: 64px 1fr auto;
grid-column-gap: 1rem; grid-column-gap: 1rem;
border-radius: 12px; border-radius: 12px;
transition: .2s !important;
} }
.cart ul li:nth-child(even) { .item:hover {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.item:hover img {
transform: rotate(5deg);
}
.item:nth-child(even) {
background-color: #292929; background-color: #292929;
} }
.item:nth-child(even):hover img {
transform: rotate(-5deg);
}
.image { .image {
height: 64px; height: 64px;
width: 64px; width: 64px;
padding: 0; padding: 0;
margin: 0; margin: 0;
border-radius: 8px; border-radius: 8px;
transition: .25s ease-out;
} }
.name { .name {
@@ -48,3 +63,8 @@
text-align: right; text-align: right;
margin: auto; margin: auto;
} }
.quantity {
font-weight: lighter;
font-size: 10pt;
}

View File

@@ -1,9 +1,9 @@
<div class="cart" xyz="stagger-0.5 fade-100% down-1 ease-ease"> <div class="cart" xyz="stagger-0.5 fade-100% down-1 ease-ease">
<ul> <ul>
<li *ngFor="let item of this.backend.invoice.cart;let indexOfelement=index;" [ngClass]="{'xyz-in': this.state.showCart.value, 'xyz-out': !this.state.showCart.value}"> <li *ngFor="let item of this.backend.invoice.cart;let indexOfelement=index;" class="item" [ngClass]="{'xyz-in': this.state.showCart.value, 'xyz-out': !this.state.showCart.value}">
<img [src]="item.image" class="image"> <img [src]="item.image" class="image">
<h5 class="name">{{ item.name }}</h5> <h5 class="name">{{ item.name }}<span class="quantity" *ngIf="item.quantity !== 1"> x{{ item.quantity }}</span></h5>
<span class="price"><b>{{ item.price.toFixed(2) }} {{ this.backend.currencyPrefix() }}</b><br> <span class="price"><b>{{ (item.price * item.quantity).toFixed(2) }} {{ this.backend.currencyPrefix() }}</b><br>
{{ this.backend.calculateCryptoPrice(indexOfelement).toFixed(8) }} {{ this.backend.invoice.paymentMethod }}</span> {{ this.backend.calculateCryptoPrice(indexOfelement).toFixed(8) }} {{ this.backend.invoice.paymentMethod }}</span>
</li> </li>
</ul> </ul>

View File

@@ -21,7 +21,7 @@ export class HeaderComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
cancel() { cancel(): void {
if (!this.startCancelling) { if (!this.startCancelling) {
this.startCancelling = true; this.startCancelling = true;
const animation = setInterval(() => { const animation = setInterval(() => {

View File

@@ -25,7 +25,7 @@
.smaller { .smaller {
min-width: 200px; min-width: 200px;
width: 40vw !important; width: 450px !important;
} }
@media (max-width: 800px) { @media (max-width: 800px) {

View File

@@ -6,6 +6,7 @@
background-color: #1c1c1c; background-color: #1c1c1c;
border-radius: 8px; border-radius: 8px;
transform: translateY(-8px); transform: translateY(-8px);
overflow: hidden;
} }
/* Apply effect when invoice expired */ /* Apply effect when invoice expired */
@@ -27,10 +28,6 @@
to { filter: blur(10px) } to { filter: blur(10px) }
} }
.request {
transform: translateY(-40px);
}
.loader { .loader {
position: absolute; position: absolute;
display: inline; display: inline;
@@ -41,7 +38,7 @@
.main { .main {
display: grid; display: grid;
height: 400px; height: 400px;
grid-template-columns: 1fr 1fr; grid-template-columns: .8fr 1fr;
} }
.qr { .qr {
@@ -91,15 +88,57 @@
/* Data */ /* Data */
.main .data { .main .data {
display: grid; display: grid;
grid-template-columns: 1fr 50px; grid-template-columns: 30px 1fr;
grid-template-rows: 1fr 4rem 4rem 4rem 1fr; grid-template-rows: 1fr auto auto auto 1fr;
column-gap: 10px;
row-gap: 10px;
width: 500px;
} }
.main #target {
.clipboard {
cursor: pointer;
margin: auto auto;
grid-column: 1;
}
.clipboard-target {
grid-row: 2; grid-row: 2;
} }
.clipboard-amount {
grid-row: 3;
}
.clipboard-click {
animation: clipboard-clicked .5s linear;
}
@keyframes clipboard-clicked {
30% {
transform: scale(1.2);
}
50% {
transform: scale(1.2) rotate(5deg);
}
70% {
transform: scale(1.2) rotate(-5deg);
}
100% {
transform: scale(1);
}
}
#target, #amount, #status {
grid-column: 2;
}
#target {
user-select: none;
grid-row: 2;
}
#amount { #amount {
grid-row: 3; grid-row: 3;
} }
#status { #status {
grid-row: 4; grid-row: 4;
} }
@@ -129,8 +168,7 @@
#list { #list {
overflow-y: scroll; overflow-y: scroll;
scroll-behavior: smooth; scroll-behavior: smooth;
-ms-overflow-style: none; height: 315px;
scrollbar-width: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -172,3 +210,26 @@
grid-row: 2; grid-row: 2;
grid-column: 2; grid-column: 2;
} }
.progress {
position: absolute;
bottom: -11px;
left: 0;
width: 100%;
line-height: 1.5;
text-align: center;
}
.progress div {
vertical-align: middle;
align-items: center;
padding-bottom: .5rem;
}
.progress div img {
transform: translateY(10px);
}
.progress div * {
padding: .2rem;
}

View File

@@ -1,7 +1,7 @@
<!-- <!--
Select payment method Select payment method
--> -->
<div class="payment request" xyz="stagger-2 fade-100% down-1" *ngIf="this.backend.isInvoiceRequested()"> <div class="payment" xyz="stagger-2 fade-100% down-1" *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) }} {{ this.backend.currencyPrefix() }}</p> <p id="price">{{ this.backend.invoice.totalPrice!.toFixed(2) }} {{ this.backend.currencyPrefix() }}</p>
@@ -25,9 +25,9 @@
<div class="qrWrapper" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}"> <div class="qrWrapper" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">
<div class="qr"> <div class="qr">
<img src="assets/Bitcoin.svg"> <img [src]="this.backend.getIcon()">
<qrcode <qrcode
[qrdata]="'bitcoin:' + this.backend.invoice!!.receiveAddress + '?amount=' + this.backend.getAmount()" [qrdata]="this.backend.findCryptoBySymbol()!.toLowerCase() + ':' + this.backend.invoice.receiveAddress + '?amount=' + this.backend.getAmount()"
[width]="256" [width]="256"
[errorCorrectionLevel]="'M'" [errorCorrectionLevel]="'M'"
[elementType]="'svg'" [elementType]="'svg'"
@@ -38,33 +38,46 @@
<div class="data"> <div class="data">
<!-- Payment data --> <!-- Payment data -->
<span id="target" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Send to <img
<h3>{{ this.backend.invoice?.receiveAddress }}</h3> ngxClipboard
[cbContent]="this.backend.invoice.receiveAddress"
class="clipboard clipboard-target"
src="assets/clipboard.svg"
[ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">
<span id="target" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">
Send to
<h3>{{ getReceiveAddress() }}</h3>
</span> </span>
<img
ngxClipboard
[cbContent]="this.backend.getAmount()"
class="clipboard clipboard-amount"
src="assets/clipboard.svg"
[ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">
<span id="amount" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Amount <span id="amount" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Amount
<h3>{{ this.backend.getAmount() }} BTC <span class="price"> | {{ this.backend.invoice.totalPrice!.toFixed(2) }} {{ this.backend.currencyPrefix() }}</span></h3> <h3>{{ this.backend.getAmount() }} {{ this.backend.invoice.paymentMethod }} <span class="price"> | {{ this.backend.invoice.totalPrice!.toFixed(2) }} {{ this.backend.currencyPrefix() }}</span></h3>
</span> </span>
<span id="status" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Status <span id="status" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Status
<h3> <h3>
{{ status }} {{ status }}
<div class="loader" *ngIf="this.status === 'Unconfirmed'"> <div class="loader" *ngIf="this.status === 'Unconfirmed'">
<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" <img src="assets/loader.svg">
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> </div>
</h3> </h3>
<small *ngIf="status === 'Unconfirmed'">Confirmations: {{ this.backend.confirmations }}</small> <small *ngIf="status === 'Unconfirmed'">Confirmations: {{ this.backend.confirmations }}</small>
</span> </span>
</div> </div>
<div class="progress" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}" *ngIf="status === 'Pending'">
<div>
<img src="assets/clock.svg">
<span>{{ formatedTime }}</span>
</div>
<svg viewBox="0, 0, 1000, 10">
<rect [attr.width]='progressTime' height="5" fill="#fff"></rect>
</svg>
</div>
</div> </div>
<!-- <!--

View File

@@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { PushNotificationsService } from 'ng-push-ivy';
import { BackendService, CryptoUnits } from '../backend.service'; import { BackendService, CryptoUnits, PaymentStatus } from '../backend.service';
import { StateService } from '../state.service'; import { StateService } from '../state.service';
@Component({ @Component({
@@ -15,6 +16,10 @@ export class PaymentComponent implements OnInit {
confirmations = 0; confirmations = 0;
status: string; status: string;
ready = false; ready = false;
emittedNotification = false;
formatedTime = ''; // Time that will be shown to the user
progressTime = 0; // This value will be used to show the progressbar
// XYZ class (will be xyz-out if cart is shown for example) // XYZ class (will be xyz-out if cart is shown for example)
xyzClass: string; xyzClass: string;
@@ -23,7 +28,8 @@ export class PaymentComponent implements OnInit {
constructor( constructor(
public backend: BackendService, public backend: BackendService,
public state: StateService, public state: StateService,
private route: ActivatedRoute private route: ActivatedRoute,
private push: PushNotificationsService
) { ) {
this.status = this.backend.getStatus(); this.status = this.backend.getStatus();
this.hideMain = false; this.hideMain = false;
@@ -49,10 +55,34 @@ export class PaymentComponent implements OnInit {
this.xyzClass = 'xyz-in'; this.xyzClass = 'xyz-in';
}, 600); }, 600);
} }
}) });
this.backend.invoiceUpdate.subscribe(newInvoice => { this.backend.invoiceUpdate.subscribe(newInvoice => {
if (newInvoice?.status === PaymentStatus.UNCONFIRMED) {
this.push.requestPermission();
}
if (newInvoice?.status === PaymentStatus.DONE) {
if (this.emittedNotification) { return; }
this.push.create('Transaction confirmed!', {
body: 'Your transaction just got confirmed.',
lang: 'en',
icon: this.backend.getIcon(),
sticky: true,
vibrate: [250, 400, 250],
sound: 'assets/pay_success.mp3'
}).subscribe(
(res: any) => {
console.log('Success');
},
(err: any) => {
console.error('Error:', err);
}
);
this.emittedNotification = true;
}
this.status = this.backend.getStatus(); this.status = this.backend.getStatus();
this.updateRemainingTime();
}); });
} }
@@ -60,8 +90,44 @@ export class PaymentComponent implements OnInit {
this.backend.setPaymentMethod(coin); this.backend.setPaymentMethod(coin);
} }
getReceiveAddress(): string {
const address = this.backend.invoice.receiveAddress;
if (address === undefined) {
return '';
}
if (address.length > 35) {
return address.slice(0, -(address.length - 35)) + '...';
}
return address;
}
updateRemainingTime(): void {
setInterval(() => {
const createdAt = new Date(this.backend.invoice.createdAt);
const dueBy = new Date(this.backend.invoice.dueBy);
const timeTotal = Math.abs(dueBy.getTime() - createdAt.getTime());
const timeLeft = Math.abs(dueBy.getTime() - Date.now());
const timeLeftDate = new Date(timeLeft);
const timeLeftFormat = timeLeftDate.getMinutes() + ':' +
(timeLeftDate.getSeconds() < 10 ? '0' + timeLeftDate.getSeconds() : timeLeftDate.getSeconds());
this.progressTime = timeLeft / timeTotal * 1000;
this.formatedTime = `${timeLeftFormat} left`;
// Flag invoice as expired in advance
if (timeLeftDate.getMinutes() === 0 && timeLeftDate.getSeconds() === 0) {
this.formatedTime = '00:00 left';
this.backend.setInvoiceExpired();
}
}, 200);
}
async get(): Promise<void> { async get(): Promise<void> {
await this.backend.setInvoice(this.paymentSelector); const res = await this.backend.setInvoice(this.paymentSelector);
this.status = this.backend.getStatus();
this.backend.getConfirmation().catch(); this.backend.getConfirmation().catch();
this.ready = true; this.ready = true;
} }

View File

@@ -20,7 +20,7 @@ export class StateService {
}); });
} }
toggleCart() { toggleCart(): void {
this.showCart.next(!this.showCart.value); this.showCart.next(!this.showCart.value);
} }
} }

View File

@@ -1,18 +1,95 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg
<svg version="1.1" id="monero" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xmlns:dc="http://purl.org/dc/elements/1.1/"
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve"> xmlns:cc="http://creativecommons.org/ns#"
<style type="text/css"> xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="260"
height="260"
viewBox="0 0 68.791665 68.791669"
version="1.1"
id="svg8"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)"
sodipodi:docname="Monero.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="75.330028"
inkscape:cy="121.91215"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1024"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#ffffff;stroke-width:1"
id="path855"
sodipodi:type="arc"
sodipodi:cx="34.428963"
sodipodi:cy="34.35778"
sodipodi:rx="34.188118"
sodipodi:ry="34.21748"
sodipodi:start="0"
sodipodi:end="6.2720152"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="M 68.617081,34.35778 A 34.188118,34.21748 0 0 1 34.524434,68.575127 34.188118,34.21748 0 0 1 0.24137794,34.548886 34.188118,34.21748 0 0 1 34.142552,0.1415015 34.188118,34.21748 0 0 1 68.614948,33.975575" />
<g
id="g839"
transform="matrix(0.26458333,0,0,0.26458333,7.9242938,7.9375003)">
<path
class="st0"
d="m 197.5,100 c 0,53.8 -43.7,97.5 -97.5,97.5 C 46.2,197.5 2.5,153.8 2.5,100 2.5,46.1 46.1,2.5 100,2.5 c 53.8,0 97.5,43.6 97.5,97.5 z"
id="path835" />
<path
id="_149931032_1_"
class="st1"
d="M 100,2.5 C 46.2,2.5 2.4,46.2 2.5,100 c 0,10.8 1.7,21.1 4.9,30.8 h 29.2 v -82 l 63.4,63.4 63.4,-63.4 v 82 h 29.2 c 3.2,-9.7 4.9,-20 5,-30.8 C 197.6,46.2 153.8,2.5 100,2.5 Z" />
<path
id="_149931160_1_"
class="st2"
d="M 85.4,126.7 57.8,99 v 51.6 H 47.2 36.6 16.6 c 17.1,28.1 48,46.9 83.3,46.9 35.3,0 66.2,-18.8 83.3,-46.9 h -20 -18.9 -2.2 V 99 l -27.7,27.7 -14.4,14.6 z" />
</g>
</g>
<style
type="text/css"
id="style833">
.st0{fill:none;} .st0{fill:none;}
.st1{fill:#F16822;} .st1{fill:#F16822;}
.st2{fill:#4D4D4D;} .st2{fill:#4D4D4D;}
</style> </style>
<g>
<path class="st0" d="M197.5,100c0,53.8-43.7,97.5-97.5,97.5c-53.8,0-97.5-43.7-97.5-97.5C2.5,46.1,46.1,2.5,100,2.5
C153.8,2.5,197.5,46.1,197.5,100z"/>
<path id="_149931032_1_" class="st1" d="M100,2.5C46.2,2.5,2.4,46.2,2.5,100c0,10.8,1.7,21.1,4.9,30.8h29.2v-82l63.4,63.4
l63.4-63.4v82h29.2c3.2-9.7,4.9-20,5-30.8C197.6,46.2,153.8,2.5,100,2.5L100,2.5z"/>
<path id="_149931160_1_" class="st2" d="M85.4,126.7L57.8,99v51.6H47.2H36.6l-20,0c17.1,28.1,48,46.9,83.3,46.9
c35.3,0,66.2-18.8,83.3-46.9l-20,0h-18.9h-2.2V99l-27.7,27.7L100,141.3L85.4,126.7L85.4,126.7z"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1020 B

After

Width:  |  Height:  |  Size: 2.9 KiB

5
src/assets/clipboard.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard" width="30" height="30" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fff" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" />
<rect x="9" y="3" width="6" height="4" rx="2" />
</svg>

After

Width:  |  Height:  |  Size: 430 B

5
src/assets/clock.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="28" height="28" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fff" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<polyline points="12 7 12 12 15 15" />
</svg>

After

Width:  |  Height:  |  Size: 355 B

5
src/assets/loader.svg Normal file
View File

@@ -0,0 +1,5 @@
<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>

After

Width:  |  Height:  |  Size: 599 B

BIN
src/assets/pay_success.mp3 Normal file

Binary file not shown.