Compare commits
5 Commits
c2ef9fe16d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
39659c4373
|
|||
|
dfda3e07e0
|
|||
|
b2c3308005
|
|||
|
03227b8b0f
|
|||
|
59ebcb54c6
|
25
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ 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 { DashboardComponent } from './dashboard/dashboard.component';
|
import { PushNotificationsModule } from 'ng-push-ivy';
|
||||||
import { LoginComponent } from './dashboard/login/login.component';
|
import { ClipboardModule } from 'ngx-clipboard';
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { OverviewComponent } from './dashboard/overview/overview.component';
|
|
||||||
import { DashboardHeaderComponent } from './dashboard/header/header.component';
|
|
||||||
|
|
||||||
const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
||||||
|
|
||||||
@@ -29,19 +26,16 @@ const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
|||||||
PayComponent,
|
PayComponent,
|
||||||
HelloComponent,
|
HelloComponent,
|
||||||
NotFoundComponent,
|
NotFoundComponent,
|
||||||
CartComponent,
|
CartComponent
|
||||||
DashboardComponent,
|
|
||||||
LoginComponent,
|
|
||||||
OverviewComponent,
|
|
||||||
DashboardHeaderComponent
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
QRCodeModule,
|
QRCodeModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule,
|
SocketIoModule.forRoot(config),
|
||||||
SocketIoModule.forRoot(config)
|
PushNotificationsModule,
|
||||||
|
ClipboardModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DashboardService } from './dashboard.service';
|
|
||||||
|
|
||||||
describe('DashboardService', () => {
|
|
||||||
let service: DashboardService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
service = TestBed.inject(DashboardService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
export interface IUser {
|
|
||||||
username: string;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class DashboardService {
|
|
||||||
|
|
||||||
user: IUser | undefined;
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
login(username: string, password: string): boolean {
|
|
||||||
if (username === 'admin' && password === 'password') {
|
|
||||||
this.user = {
|
|
||||||
username: 'admin',
|
|
||||||
token: 'abc'
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<dashboard-header></dashboard-header>
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DashboardComponent } from './dashboard.component';
|
|
||||||
|
|
||||||
describe('DashboardComponent', () => {
|
|
||||||
let component: DashboardComponent;
|
|
||||||
let fixture: ComponentFixture<DashboardComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ DashboardComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(DashboardComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { DashboardService } from '../dashboard.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-dashboard',
|
|
||||||
templateUrl: './dashboard.component.html',
|
|
||||||
styleUrls: ['./dashboard.component.css']
|
|
||||||
})
|
|
||||||
export class DashboardComponent implements OnInit {
|
|
||||||
|
|
||||||
constructor(public dashboard: DashboardService) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
.header {
|
|
||||||
position: fixed;
|
|
||||||
top: 1rem;
|
|
||||||
left: 5vw;
|
|
||||||
width: 90vw;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #27293D;
|
|
||||||
z-index: 999;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-svg {}
|
|
||||||
|
|
||||||
.admin-svg {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<div class="header" *ngIf="this.dashboard.user != undefined">
|
|
||||||
<img src="assets/history.svg" class="history-svg">
|
|
||||||
<img src="assets/dash.svg" class="dash-svg">
|
|
||||||
<img src="assets/admin.svg" class="admin-svg">
|
|
||||||
</div>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { HeaderComponent } from './header.component';
|
|
||||||
|
|
||||||
describe('HeaderComponent', () => {
|
|
||||||
let component: HeaderComponent;
|
|
||||||
let fixture: ComponentFixture<HeaderComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ HeaderComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(HeaderComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { DashboardService } from 'src/app/dashboard.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'dashboard-header',
|
|
||||||
templateUrl: './header.component.html',
|
|
||||||
styleUrls: ['./header.component.css']
|
|
||||||
})
|
|
||||||
export class DashboardHeaderComponent implements OnInit {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public dashboard: DashboardService,
|
|
||||||
public router: Router
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
.frame {
|
|
||||||
background-color: #1D1D28;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400&display=swap');
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
*display: inline;
|
|
||||||
*zoom: 1;
|
|
||||||
padding: 4px 10px 4px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: #333333;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: #e6e6e6 #e6e6e6 #e6e6e6;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
cursor: pointer;
|
|
||||||
*margin-left: .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:active,
|
|
||||||
.btn.active,
|
|
||||||
.btn.disabled,
|
|
||||||
.btn[disabled] {
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-large {
|
|
||||||
padding: 9px 14px;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: normal;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
color: #333333;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
transition: background-position 0.1s linear;
|
|
||||||
transition: ease 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary,
|
|
||||||
.btn-primary:hover {
|
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary.active {
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: #1D1D28;
|
|
||||||
background-image: linear-gradient(top, #6eb6de, #4a77d4);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border: 1px solid #3762bc;
|
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
||||||
transition: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover,
|
|
||||||
.btn-primary:active,
|
|
||||||
.btn-primary.active,
|
|
||||||
.btn-primary.disabled,
|
|
||||||
.btn-primary[disabled] {
|
|
||||||
filter: none;
|
|
||||||
background-color: #4a77d4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-block {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin: -150px 0 0 -150px;
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login h1 {
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background: #1D1D28;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
|
|
||||||
border: 1px solid rgb(138, 138, 138);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 -5px 45px rgba(100, 100, 100, 0), 0 1px 1px rgba(255, 255, 255, 0.2);
|
|
||||||
transition: box-shadow .5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
box-shadow: inset 0 -5px 45px rgba(100, 100, 100, 0.4), 0 1px 1px rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<div class="frame">
|
|
||||||
<div class="login">
|
|
||||||
<h1>Login</h1>
|
|
||||||
<form (submit)="login()">
|
|
||||||
<input type="text" placeholder="Username" required="required" [(ngModel)]="username" [ngModelOptions]="{standalone: true}" />
|
|
||||||
<input type="password" placeholder="Password" required="required" [(ngModel)]="password" [ngModelOptions]="{standalone: true}" />
|
|
||||||
<button type="submit" value="login" id="login-form-submit" onclick="return check(this.form)" class="btn btn-primary btn-block btn-large">Login</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { LoginComponent } from './login.component';
|
|
||||||
|
|
||||||
describe('LoginComponent', () => {
|
|
||||||
let component: LoginComponent;
|
|
||||||
let fixture: ComponentFixture<LoginComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ LoginComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(LoginComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { DashboardService } from 'src/app/dashboard.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-login',
|
|
||||||
templateUrl: './login.component.html',
|
|
||||||
styleUrls: ['./login.component.css']
|
|
||||||
})
|
|
||||||
export class LoginComponent implements OnInit {
|
|
||||||
|
|
||||||
username = '';
|
|
||||||
password = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public dashboard: DashboardService,
|
|
||||||
private router: Router
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
|
|
||||||
const loginStatus = this.dashboard.login("admin", "password");
|
|
||||||
if (loginStatus) {
|
|
||||||
this.router.navigate(['dashboard', 'overview']);
|
|
||||||
} else {
|
|
||||||
// TODO: Meldung anzeigen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
login() {
|
|
||||||
const loginStatus = this.dashboard.login(this.username, this.password);
|
|
||||||
|
|
||||||
if (loginStatus) {
|
|
||||||
this.router.navigate(['dashboard', 'overview']);
|
|
||||||
} else {
|
|
||||||
// TODO: Meldung anzeigen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<p>overview works!</p>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { OverviewComponent } from './overview.component';
|
|
||||||
|
|
||||||
describe('OverviewComponent', () => {
|
|
||||||
let component: OverviewComponent;
|
|
||||||
let fixture: ComponentFixture<OverviewComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ OverviewComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(OverviewComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-overview',
|
|
||||||
templateUrl: './overview.component.html',
|
|
||||||
styleUrls: ['./overview.component.css']
|
|
||||||
})
|
|
||||||
export class OverviewComponent implements OnInit {
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class StateService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCart() {
|
toggleCart(): void {
|
||||||
this.showCart.next(!this.showCart.value);
|
this.showCart.next(!this.showCart.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 |
@@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /></svg>
|
|
||||||
|
Before Width: | Height: | Size: 549 B |
5
src/assets/clipboard.svg
Normal 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
@@ -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 |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M21 16V4H3v12h18m0-14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-7v2h2v2H8v-2h2v-2H3a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2h18M5 6h9v5H5V6m10 0h4v2h-4V6m4 3v5h-4V9h4M5 12h4v2H5v-2m5 0h4v2h-4v-2z" fill="white"/><rect x="0" y="0" width="24" height="24" fill="rgba(0, 0, 0, 0)" /></svg>
|
|
||||||
|
Before Width: | Height: | Size: 537 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M13.5 8H12v5l4.28 2.54l.72-1.21l-3.5-2.08V8M13 3a9 9 0 0 0-9 9H1l3.96 4.03L9 12H6a7 7 0 0 1 7-7a7 7 0 0 1 7 7a7 7 0 0 1-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.896 8.896 0 0 0 13 21a9 9 0 0 0 9-9a9 9 0 0 0-9-9" fill="white"/><rect x="0" y="0" width="24" height="24" fill="rgba(0, 0, 0, 0)" /></svg>
|
|
||||||
|
Before Width: | Height: | Size: 574 B |
5
src/assets/loader.svg
Normal 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
@@ -1,22 +1,11 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
import { DashboardComponent } from "./app/dashboard/dashboard.component";
|
|
||||||
import { LoginComponent } from "./app/dashboard/login/login.component";
|
|
||||||
import { OverviewComponent } from "./app/dashboard/overview/overview.component";
|
|
||||||
import { HelloComponent } from "./app/hello/hello.component";
|
import { HelloComponent } from "./app/hello/hello.component";
|
||||||
import { NotFoundComponent } from "./app/not-found/not-found.component";
|
import { NotFoundComponent } from "./app/not-found/not-found.component";
|
||||||
import { PayComponent } from "./app/pay/pay.component";
|
import { PayComponent } from "./app/pay/pay.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: 'pay/:id', component: PayComponent, data: { title: 'Payment' } },
|
{ path: 'pay/:id', component: PayComponent, data: { title: 'Payment' } },
|
||||||
{ path: 'dashboard', component: DashboardComponent, children: [
|
|
||||||
{
|
|
||||||
path: 'login', component: LoginComponent
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'overview', component: OverviewComponent
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
{ path: '', component: HelloComponent },
|
{ path: '', component: HelloComponent },
|
||||||
{ path: '**', component: NotFoundComponent }
|
{ path: '**', component: NotFoundComponent }
|
||||||
]
|
]
|
||||||
|
|||||||