Compare commits

...

13 Commits

Author SHA1 Message Date
5e11c83213 Add Chart.js 2021-02-02 11:56:14 +01:00
Felix
2aae141844 + Added Dashboard Navbar
+ Added Transitions
+ Added Shadows
+ Added LogIn Page
2021-02-02 10:59:22 +01:00
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
31 changed files with 587 additions and 88 deletions

79
package-lock.json generated
View File

@@ -1552,6 +1552,14 @@
"semver-intersect": "1.4.0" "semver-intersect": "1.4.0"
} }
}, },
"@types/chart.js": {
"version": "2.9.30",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.30.tgz",
"integrity": "sha512-EgjxUUZFvf6ls3kW2CwyrnSJhgyKxgwrlp/W5G9wqyPEO9iFatO63zAA7L24YqgMxiDjQ+tG7ODU+2yWH91lPg==",
"requires": {
"moment": "^2.10.2"
}
},
"@types/glob": { "@types/glob": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
@@ -2817,6 +2825,32 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true "dev": true
}, },
"chart.js": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"requires": {
"chartjs-color-string": "^0.6.0",
"color-convert": "^1.9.3"
}
},
"chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"requires": {
"color-name": "^1.0.0"
}
},
"chokidar": { "chokidar": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
@@ -6913,6 +6947,11 @@
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
}, },
"lodash-es": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.20.tgz",
"integrity": "sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA=="
},
"lodash.memoize": { "lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -7432,6 +7471,11 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -7527,6 +7571,33 @@
"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"
}
},
"ng2-charts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-2.4.2.tgz",
"integrity": "sha512-mY3C2uKCaApHCQizS2YxEOqQ7sSZZLxdV6N1uM9u/VvUgVtYvlPtdcXbKpN52ak93ZE22I73DiLWVDnDNG4/AQ==",
"requires": {
"@types/chart.js": "^2.9.24",
"lodash-es": "^4.17.15",
"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 +7618,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,10 @@
"@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",
"chart.js": "^2.9.4",
"ng-push-ivy": "^1.0.7",
"ng2-charts": "^2.4.2",
"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,18 +6,21 @@ 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';
import { OverviewComponent } from './dashboard/overview/overview.component'; import { OverviewComponent } from './dashboard/overview/overview.component';
import { DashboardHeaderComponent } from './dashboard/header/header.component'; import { DashboardHeaderComponent } from './dashboard/header/header.component';
import { ChartComponent } from './dashboard/chart/chart.component';
import { ChartsModule } from 'ng2-charts';
const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} }; const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
@@ -33,15 +36,20 @@ const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
DashboardComponent, DashboardComponent,
LoginComponent, LoginComponent,
OverviewComponent, OverviewComponent,
DashboardHeaderComponent DashboardHeaderComponent,
ChartComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
QRCodeModule, QRCodeModule,
HttpClientModule, HttpClientModule,
AppRoutingModule, AppRoutingModule,
SocketIoModule.forRoot(config),
PushNotificationsModule,
ClipboardModule,
FormsModule, FormsModule,
SocketIoModule.forRoot(config) SocketIoModule.forRoot(config),
ChartsModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

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({
@@ -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>;
@@ -107,7 +107,7 @@ export class BackendService {
/** /**
* Subscribe to the real-time status of the selected invoice. * Subscribe to the real-time status of the selected invoice.
*/ */
subscribeTo(selector: string): void { subscribeTo(): void {
this.socket.on('subscribe', (status: boolean) => { this.socket.on('subscribe', (status: boolean) => {
if (status) { if (status) {
this.updateInvoice(); this.updateInvoice();
@@ -120,7 +120,7 @@ export class BackendService {
this.confirmations = update.count; this.confirmations = update.count;
}); });
this.socket.emit('subscribe', { selector }); this.socket.emit('subscribe', { selector: this.invoice.selector });
} }
/** /**
@@ -137,8 +137,10 @@ 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) => {
console.log('Sel.:', selector);
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 +149,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 +156,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 +233,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 +257,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 +279,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 +293,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

@@ -0,0 +1,2 @@
<script src="node_modules/chart.js/src/chart.js"></script>
<h1>Test</h1>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChartComponent } from './chart.component';
describe('ChartComponent', () => {
let component: ChartComponent;
let fixture: ComponentFixture<ChartComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChartComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.css']
})
export class ChartComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,8 @@
.bg {
position: fixed;
background-color: #1D1D28;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
}

View File

@@ -1,2 +1,4 @@
<dashboard-header></dashboard-header> <dashboard-header></dashboard-header>
<dashboard-chart></dashboard-chart>
<div class="bg"></div>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@@ -3,14 +3,64 @@
top: 1rem; top: 1rem;
left: 5vw; left: 5vw;
width: 90vw; width: 90vw;
height: 100%;
max-height: 35px;
padding: 15px;
margin: 0 auto; margin: 0 auto;
background-color: #27293D; background-color: #27293D;
z-index: 999; z-index: 999;
border-radius: 8px; border-radius: 8px;
transition: .2s ease;
box-shadow: 0 1.1px 12.5px -23px rgba(0, 0, 0, 0.014), 0 2.4px 23.6px -23px rgba(0, 0, 0, 0.027), 0 4.1px 33.4px -23px rgba(0, 0, 0, 0.038), 0 6.2px 42.1px -23px rgba(0, 0, 0, 0.045), 0 8.9px 49.9px -23px rgba(0, 0, 0, 0.05), 0 12.6px 57.5px -23px rgba(0, 0, 0, 0.054), 0 17.8px 65.8px -23px rgba(0, 0, 0, 0.058), 0 25.9px 76.9px -23px rgba(0, 0, 0, 0.062), 0 39.9px 96.3px -23px rgba(0, 0, 0, 0.066), 0 71px 148px -23px rgba(0, 0, 0, 0.07);
} }
.history-svg {} .header:hover {
max-height: 55px;
.admin-svg { transition: .2s ease;
fill: white; }
.icon::after {
content: ' ';
opacity: 0;
}
.header:hover .icon::after {
content: "Test";
color: white;
opacity: 1;
transition: .2s ease;
transition: .5s opacity ease;
}
.icon:hover {
transform: translateY(-5px);
}
.grid-container {
display: grid;
grid-template-columns: 32px 32px 1fr 32px 32px;
grid-template-rows: 1fr;
gap: 20px 30px;
grid-template-areas: "dashboard history . settings admin";
}
.dashboard {
grid-area: dashboard;
}
.history {
grid-area: history;
}
.settings {
grid-area: settings;
}
.admin {
grid-area: admin;
}
.icon {
grid-row: 1;
transition: .2s ease;
} }

View File

@@ -1,5 +1,18 @@
<div class="header" *ngIf="this.dashboard.user != undefined"> <div class="header" *ngIf="this.dashboard.user != undefined">
<div class="grid-container">
<div class="dashboard icon">
<img src="assets/dashboard.svg" class="dash-svg">
</div>
<div class="history icon">
<img src="assets/history.svg" class="history-svg"> <img src="assets/history.svg" class="history-svg">
<img src="assets/dash.svg" class="dash-svg"> </div>
<div class="settings icon">
<img src="assets/settings.svg" class="settings-svg">
</div>
<div class="admin icon">
<img src="assets/admin.svg" class="admin-svg"> <img src="assets/admin.svg" class="admin-svg">
</div>
</div>
</div> </div>

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { DashboardService } from 'src/app/dashboard.service';
@Component({ @Component({
selector: 'app-overview', selector: 'app-overview',
@@ -7,9 +8,12 @@ import { Component, OnInit } from '@angular/core';
}) })
export class OverviewComponent implements OnInit { export class OverviewComponent implements OnInit {
constructor() { } constructor(private dashboard: DashboardService) { }
ngOnInit(): void { ngOnInit(): void {
if (this.dashboard.user === undefined) {
this.dashboard.login('admin', 'password');
}
} }
} }

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;
@@ -33,7 +39,6 @@ export class PaymentComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
this.paymentSelector = params.id; this.paymentSelector = params.id;
this.backend.subscribeTo(this.paymentSelector);
this.get(); this.get();
}); });
@@ -49,10 +54,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 +89,47 @@ 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); console.log('Selector:', this.paymentSelector);
const res = await this.backend.setInvoice(this.paymentSelector);
this.backend.subscribeTo();
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

View File

@@ -1 +1 @@
<?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> <?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="32" height="32" 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" fill="#fff" fill-rule="evenodd" /></svg>

Before

Width:  |  Height:  |  Size: 549 B

After

Width:  |  Height:  |  Size: 581 B

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

View File

@@ -1 +1 @@
<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> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" 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

After

Width:  |  Height:  |  Size: 537 B

3
src/assets/dashboard.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16">
<path fill="#fff" fill-rule="evenodd" d="M13.5,2.13413878 C13.9783,2.41028878 14.1422,3.02187878 13.866,3.50016878 L12.6872,5.54193878 C12.6693,5.57941878 12.6488,5.61619878 12.6258,5.65205878 L12.6093,5.67680878 L9.62879,10.8392688 C9.8625,11.1665688 10,11.5673688 10,12.0001688 C10,13.1047688 9.10457,14.0001688 8,14.0001688 C6.89543,14.0001688 6,13.1047688 6,12.0001688 C6,10.9637688 6.78831,10.1114688 7.79803,10.0102688 L10.4011,5.50156878 C9.71258,5.20089878 8.97101,5.03106878 8.21451,5.00400878 C7.13787,4.96548878 6.07072,5.21756878 5.12511,5.73376878 C4.1795,6.24996878 3.39032,7.01124878 2.84044,7.93766878 C2.29055,8.86409878 2.00024,9.92148878 2,10.9987688 C1.99988,11.5510688 1.55206,11.9986688 0.999774,11.9985688 C0.44749,11.9984688 0,11.5506688 0,10.9983688 C0.000324182,9.56192878 0.387399,8.15206878 1.12058,6.91683878 C1.85376,5.68160878 2.906,4.66656878 4.16681,3.97829878 C5.42763,3.29002878 6.85049,2.95392878 8.28601,3.00528878 C9.36996,3.04405878 10.4309,3.30266878 11.4058,3.76135878 L12.134,2.50016878 C12.4101,2.02187878 13.0217,1.85799878 13.5,2.13413878 Z M13.4873,6.54043878 C13.9608,6.25621878 14.5751,6.40969878 14.8593,6.88323878 C15.6057,8.12678878 16,9.54983878 16,11.0001688 C16,11.5524688 15.5523,12.0001688 15,12.0001688 C14.4477,12.0001688 14,11.5524688 14,11.0001688 C14,9.91241878 13.7043,8.84512878 13.1445,7.91246878 C12.8603,7.43892878 13.0138,6.82464878 13.4873,6.54043878 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +1 @@
<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> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" 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="#fff" fill-rule="evenodd"/><rect x="0" y="0" width="32" height="32" fill="rgba(0, 0, 0, 0)" /></svg>

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 593 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.

4
src/assets/settings.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99994 6C6.89537 6 5.99994 6.89543 5.99994 8C5.99994 9.10457 6.89537 10 7.99994 10C9.10451 10 9.99994 9.10457 9.99994 8C9.99994 6.89543 9.10451 6 7.99994 6ZM6.99994 8C6.99994 7.44772 7.44765 7 7.99994 7C8.55222 7 8.99994 7.44772 8.99994 8C8.99994 8.55228 8.55222 9 7.99994 9C7.44765 9 6.99994 8.55228 6.99994 8Z" fill="#fff"/>
<path d="M10.618 4.39833C10.233 4.46825 9.86392 4.21413 9.7937 3.83074L9.53397 2.41496C9.50816 2.27427 9.39961 2.16301 9.25912 2.13325C8.84818 2.04621 8.42685 2.00195 8 2.00195C7.57289 2.00195 7.1513 2.04627 6.74013 2.13341C6.5996 2.1632 6.49104 2.27452 6.46529 2.41527L6.20629 3.8308C6.1994 3.86844 6.18942 3.90551 6.17647 3.9416C6.04476 4.30859 5.6392 4.49978 5.27062 4.36863L3.91115 3.88463C3.77603 3.83652 3.62511 3.87431 3.52891 3.98033C2.96005 4.60729 2.52892 5.34708 2.2672 6.15302C2.22305 6.28899 2.26562 6.43805 2.37502 6.53053L3.47694 7.46206C3.50626 7.48685 3.53352 7.51399 3.55843 7.5432C3.81177 7.84027 3.77528 8.28558 3.47693 8.53783L2.37502 9.46935C2.26562 9.56183 2.22305 9.71089 2.2672 9.84685C2.52892 10.6528 2.96005 11.3926 3.52891 12.0196C3.62511 12.1256 3.77603 12.1634 3.91115 12.1153L5.27068 11.6312C5.30687 11.6184 5.3441 11.6084 5.38196 11.6015C5.76701 11.5316 6.13608 11.7857 6.2063 12.1691L6.46529 13.5846C6.49104 13.7254 6.5996 13.8367 6.74013 13.8665C7.1513 13.9536 7.57289 13.9979 8 13.9979C8.42685 13.9979 8.84818 13.9537 9.25912 13.8666C9.39961 13.8369 9.50816 13.7256 9.53397 13.5849L9.79368 12.1692C9.8006 12.1314 9.81058 12.0944 9.82353 12.0583C9.95524 11.6913 10.3608 11.5001 10.7294 11.6312L12.0888 12.1153C12.224 12.1634 12.3749 12.1256 12.4711 12.0196C13.04 11.3926 13.4711 10.6528 13.7328 9.84685C13.777 9.71089 13.7344 9.56183 13.625 9.46935L12.5231 8.53782C12.4937 8.51303 12.4665 8.48589 12.4416 8.45667C12.1882 8.1596 12.2247 7.71429 12.5231 7.46205L13.625 6.53053C13.7344 6.43805 13.777 6.28899 13.7328 6.15302C13.4711 5.34708 13.04 4.60729 12.4711 3.98033C12.3749 3.87431 12.224 3.83652 12.0888 3.88463L10.7293 4.36865C10.6931 4.38152 10.6559 4.39146 10.618 4.39833ZM3.99863 4.97726L4.93522 5.3107C5.82017 5.62559 6.79872 5.16815 7.11769 4.2794C7.14903 4.19207 7.17324 4.1021 7.18996 4.01078L7.36738 3.04113C7.5757 3.01512 7.78684 3.00195 8 3.00195C8.213 3.00195 8.42397 3.0151 8.63214 3.04107L8.81011 4.01117C8.98053 4.9408 9.87266 5.55003 10.7967 5.38225C10.8877 5.36572 10.9775 5.34176 11.0647 5.31073L12.0014 4.97726C12.2564 5.31084 12.4684 5.67476 12.6319 6.06064L11.8774 6.6984C11.1566 7.30787 11.0675 8.38649 11.6807 9.10555C11.7408 9.17609 11.8067 9.24166 11.8775 9.3015L12.6319 9.93924C12.4684 10.3251 12.2564 10.689 12.0014 11.0226L11.0646 10.6891C10.1797 10.3742 9.20128 10.8317 8.88231 11.7205C8.85096 11.8078 8.82677 11.8978 8.81004 11.9891L8.63214 12.9588C8.42397 12.9848 8.213 12.9979 8 12.9979C7.78684 12.9979 7.5757 12.9848 7.36738 12.9587L7.18994 11.989C7.01965 11.0592 6.12743 10.4498 5.2033 10.6176C5.11227 10.6342 5.0225 10.6581 4.93528 10.6892L3.99863 11.0226C3.74357 10.689 3.53161 10.3251 3.36814 9.93924L4.12257 9.30148C4.84343 8.69201 4.93254 7.61339 4.31933 6.89433C4.25917 6.82378 4.19332 6.75822 4.12254 6.69838L3.36814 6.06064C3.53161 5.67476 3.74357 5.31084 3.99863 4.97726Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB