Add cart view
This commit is contained in:
@@ -12,6 +12,7 @@ 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';
|
||||||
|
|
||||||
const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
||||||
|
|
||||||
@@ -22,7 +23,8 @@ const config: SocketIoConfig = { url: 'http://localhost:2009', options: {} };
|
|||||||
PaymentComponent,
|
PaymentComponent,
|
||||||
PayComponent,
|
PayComponent,
|
||||||
HelloComponent,
|
HelloComponent,
|
||||||
NotFoundComponent
|
NotFoundComponent,
|
||||||
|
CartComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getCurrencySymbol } from '@angular/common';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Socket } from 'ngx-socket-io';
|
import { Socket } from 'ngx-socket-io';
|
||||||
@@ -26,8 +27,9 @@ export interface ICart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IPaymentMethod {
|
export interface IPaymentMethod {
|
||||||
method: any;
|
method: CryptoUnits;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
exRate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PaymentStatus {
|
export enum PaymentStatus {
|
||||||
@@ -43,19 +45,20 @@ export enum PaymentStatus {
|
|||||||
export interface IInvoice {
|
export interface IInvoice {
|
||||||
selector: string;
|
selector: string;
|
||||||
paymentMethods: IPaymentMethod[];
|
paymentMethods: IPaymentMethod[];
|
||||||
receiveAddress: string;
|
paymentMethod?: CryptoUnits;
|
||||||
paidWith?: CryptoUnits;
|
receiveAddress?: string;
|
||||||
paid?: number;
|
|
||||||
transcationHash?: string;
|
transcationHash?: string;
|
||||||
|
confirmation?: number;
|
||||||
cart?: ICart[];
|
cart?: ICart[];
|
||||||
totalPrice?: number;
|
totalPrice?: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
dueBy: number;
|
dueBy: Date;
|
||||||
status?: PaymentStatus;
|
status?: PaymentStatus;
|
||||||
email?: string;
|
email?: string;
|
||||||
successUrl: string;
|
successUrl: string;
|
||||||
cancelUrl: string;
|
cancelUrl: string;
|
||||||
createdAt?: number;
|
createdAt?: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -70,9 +73,8 @@ export class BackendService {
|
|||||||
selector: '',
|
selector: '',
|
||||||
paymentMethods: [],
|
paymentMethods: [],
|
||||||
receiveAddress: '',
|
receiveAddress: '',
|
||||||
paid: 0,
|
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
dueBy: Date.now(),
|
dueBy: new Date(),
|
||||||
successUrl: '',
|
successUrl: '',
|
||||||
cancelUrl: ''
|
cancelUrl: ''
|
||||||
};
|
};
|
||||||
@@ -102,6 +104,9 @@ export class BackendService {
|
|||||||
return this.socket;
|
return this.socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to the real-time status of the selected invoice.
|
||||||
|
*/
|
||||||
subscribeTo(selector: string): void {
|
subscribeTo(selector: string): void {
|
||||||
this.socket.on('subscribe', (status: boolean) => {
|
this.socket.on('subscribe', (status: boolean) => {
|
||||||
if (status) {
|
if (status) {
|
||||||
@@ -118,12 +123,18 @@ export class BackendService {
|
|||||||
this.socket.emit('subscribe', { selector });
|
this.socket.emit('subscribe', { selector });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will update the current invoice
|
||||||
|
*/
|
||||||
updateInvoice(): void {
|
updateInvoice(): void {
|
||||||
if (this.invoice !== undefined || this.invoice !== null) {
|
if (this.invoice !== undefined || this.invoice !== null) {
|
||||||
this.setInvoice(this.invoice.selector);
|
this.setInvoice(this.invoice.selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will set the current selected invoice by the `selector`.
|
||||||
|
*/
|
||||||
setInvoice(selector: string): Promise<IInvoice> {
|
setInvoice(selector: string): Promise<IInvoice> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (selector === undefined || selector === 'undefined' || selector === '') {
|
if (selector === undefined || selector === 'undefined' || selector === '') {
|
||||||
@@ -144,6 +155,9 @@ export class BackendService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will notify the backend that the user just cancelled the payment.
|
||||||
|
*/
|
||||||
cancelInvoice(): Promise<void> {
|
cancelInvoice(): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (this.invoice.selector === '') {
|
if (this.invoice.selector === '') {
|
||||||
@@ -162,6 +176,9 @@ export class BackendService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will set the payment method of the selected invoice.
|
||||||
|
*/
|
||||||
setPaymentMethod(method: CryptoUnits): Promise<void> {
|
setPaymentMethod(method: CryptoUnits): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (this.invoice === null) { reject('Invoice is not set!'); return; }
|
if (this.invoice === null) { reject('Invoice is not set!'); return; }
|
||||||
@@ -170,12 +187,20 @@ export class BackendService {
|
|||||||
responseType: 'json'
|
responseType: 'json'
|
||||||
}).toPromise().then(() => {
|
}).toPromise().then(() => {
|
||||||
this.setInvoice(this.invoice.selector);
|
this.setInvoice(this.invoice.selector);
|
||||||
|
resolve();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currencyPrefix(): string {
|
||||||
|
return getCurrencySymbol(this.invoice.currency, 'narrow');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used if the socket connection is broken or as initial call.
|
||||||
|
*/
|
||||||
getConfirmation(): Promise<number> {
|
getConfirmation(): Promise<number> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (this.invoice === null || this.invoice.status !== PaymentStatus.UNCONFIRMED) {
|
if (this.invoice === null || this.invoice.status !== PaymentStatus.UNCONFIRMED) {
|
||||||
@@ -197,7 +222,7 @@ export class BackendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns Path to icon
|
* @returns Path to icon in assets folder
|
||||||
*/
|
*/
|
||||||
getIcon(unit: CryptoUnits): string {
|
getIcon(unit: CryptoUnits): string {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
@@ -226,12 +251,30 @@ export class BackendService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The price to pay by cryptocurrency;
|
||||||
|
*/
|
||||||
getAmount(): string | undefined {
|
getAmount(): string | undefined {
|
||||||
return this.invoice?.paymentMethods.find(item => {
|
return this.invoice?.paymentMethods.find(item => {
|
||||||
return item.method === CryptoUnits.BITCOIN;
|
return item.method === this.invoice.paymentMethod;
|
||||||
})?.amount.toFixed(8);
|
})?.amount.toFixed(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the price in crypto of a specifc product.
|
||||||
|
* @param prodcut Index of product in cart
|
||||||
|
*/
|
||||||
|
calculateCryptoPrice(productNr: number): number {
|
||||||
|
if (this.invoice.cart === undefined) return 0;
|
||||||
|
if (this.invoice.paymentMethod === undefined) return 0;
|
||||||
|
|
||||||
|
const product = this.invoice.cart[productNr];
|
||||||
|
const exRate = this.invoice.paymentMethods.find(method => { return method.method === this.invoice.paymentMethod })?.exRate;
|
||||||
|
if (exRate === undefined) return 0;
|
||||||
|
|
||||||
|
return product.quantity * product.price / exRate;
|
||||||
|
}
|
||||||
|
|
||||||
getStatus(): string {
|
getStatus(): string {
|
||||||
switch (this.invoice?.status) {
|
switch (this.invoice?.status) {
|
||||||
case PaymentStatus.PENDING:
|
case PaymentStatus.PENDING:
|
||||||
|
|||||||
50
src/app/cart/cart.component.css
Normal file
50
src/app/cart/cart.component.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
.cart {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
border-radius: 8px;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 359px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart ul li {
|
||||||
|
display: grid;
|
||||||
|
padding: 1rem;
|
||||||
|
grid-template-columns: 64px 1fr auto;
|
||||||
|
grid-column-gap: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart ul li:nth-child(even) {
|
||||||
|
background-color: #292929;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
height: 64px;
|
||||||
|
width: 64px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 11pt;
|
||||||
|
width: 80%;
|
||||||
|
margin: auto 0;
|
||||||
|
line-break: loose;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
text-align: right;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
10
src/app/cart/cart.component.html
Normal file
10
src/app/cart/cart.component.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="cart" xyz="stagger-0.5 fade-100% down-1 ease-ease" >
|
||||||
|
<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}">
|
||||||
|
<img [src]="item.image" class="image">
|
||||||
|
<h5 class="name">{{ item.name }}</h5>
|
||||||
|
<span class="price"><b>{{ item.price.toFixed(2) }} {{ this.backend.currencyPrefix() }}</b><br>
|
||||||
|
{{ this.backend.calculateCryptoPrice(indexOfelement).toFixed(8) }} {{ this.backend.invoice.paymentMethod }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
25
src/app/cart/cart.component.spec.ts
Normal file
25
src/app/cart/cart.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CartComponent } from './cart.component';
|
||||||
|
|
||||||
|
describe('CartComponent', () => {
|
||||||
|
let component: CartComponent;
|
||||||
|
let fixture: ComponentFixture<CartComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ CartComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
20
src/app/cart/cart.component.ts
Normal file
20
src/app/cart/cart.component.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { BackendService } from '../backend.service';
|
||||||
|
import { StateService } from '../state.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cart',
|
||||||
|
templateUrl: './cart.component.html',
|
||||||
|
styleUrls: ['./cart.component.css']
|
||||||
|
})
|
||||||
|
export class CartComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public backend: BackendService,
|
||||||
|
public state: StateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
.header {
|
.header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: auto 1fr auto 1fr auto;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@@ -12,23 +13,43 @@
|
|||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header img {
|
.header .logo {
|
||||||
height: 3rem;
|
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
grid-column: 1;
|
grid-column: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header a {
|
.header .logo img {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cancel button */
|
||||||
|
.cancel {
|
||||||
color: red;
|
color: red;
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
border-block-end-width: 1px;
|
border-block-end-width: 1px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
margin: auto 0;
|
margin: auto;
|
||||||
height: fit-content;
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
grid-column: 2;
|
width: fit-content !important;
|
||||||
|
grid-column: 5;
|
||||||
transition: .2s ease;
|
transition: .2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cart {
|
||||||
|
display: flex;
|
||||||
|
grid-column: 1;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: auto auto;
|
||||||
|
padding-left: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
#cart span {
|
||||||
|
margin: auto auto;
|
||||||
|
padding-left: .5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<img src="assets/logo.svg">
|
<div id="cart" *ngIf="this.backend!.invoice!.status! > 0" (click)="this.state.toggleCart()">
|
||||||
<a *ngIf="this.backend.isInvoicePending()" (click)="cancel()" [style]="cancelProgressStyle">{{ startCancelling ? 'Are you sure?' : 'Cancel payment'}}</a>
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg" *ngIf="!this.state.showCart.value" class="icon icon-tabler icon-tabler-shopping-cart"
|
||||||
|
width="32" height="32" viewBox="0 0 24 24" stroke-width="1.5" stroke="#2c3e50" title="Shopping cart"
|
||||||
|
fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<circle cx="9" cy="19" r="2" />
|
||||||
|
<circle cx="17" cy="19" r="2" />
|
||||||
|
<path d="M3 3h2l2 12a3 3 0 0 0 3 2h7a3 3 0 0 0 3 -2l1 -7h-15.2" />
|
||||||
|
</svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" *ngIf="this.state.showCart.value" class="icon icon-tabler icon-tabler-x"
|
||||||
|
width="32" height="32" viewBox="0 0 24 24" stroke-width="1.5" stroke="#2c3e50" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" />
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
<span>Cart</span>
|
||||||
|
</div>
|
||||||
|
<a href="https://librepay.me" target="_blank" class="logo">
|
||||||
|
<img src="assets/logo.svg">
|
||||||
|
</a>
|
||||||
|
<a class="cancel" *ngIf="this.backend.isInvoicePending()" (click)="cancel()" [style]="cancelProgressStyle">{{ startCancelling ? 'Are you sure?' : 'Cancel payment'}}</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { BackendService } from '../backend.service';
|
import { BackendService } from '../backend.service';
|
||||||
|
import { StateService } from '../state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
@@ -12,7 +13,10 @@ export class HeaderComponent implements OnInit {
|
|||||||
cancelProgress = 0;
|
cancelProgress = 0;
|
||||||
cancelProgressStyle = ""; // This is the style of the cancel button
|
cancelProgressStyle = ""; // This is the style of the cancel button
|
||||||
|
|
||||||
constructor(public backend: BackendService) { }
|
constructor(
|
||||||
|
public backend: BackendService,
|
||||||
|
public state: StateService
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
@@ -21,9 +25,10 @@ export class HeaderComponent implements OnInit {
|
|||||||
if (!this.startCancelling) {
|
if (!this.startCancelling) {
|
||||||
this.startCancelling = true;
|
this.startCancelling = true;
|
||||||
const animation = setInterval(() => {
|
const animation = setInterval(() => {
|
||||||
this.cancelProgress += 0.3;
|
this.cancelProgress += 0.5;
|
||||||
this.cancelProgressStyle = `
|
this.cancelProgressStyle = `
|
||||||
background-image: linear-gradient(90deg, rgba(255,120,120,0.3) ${this.cancelProgress.toFixed(1)}%, rgba(255,255,255,0) ${this.cancelProgress.toFixed(1)}%);
|
background-image: linear-gradient(90deg, rgba(255,120,120,0.3) ${this.cancelProgress.toFixed(1)}%, rgba(255,255,255,0) ${this.cancelProgress.toFixed(1)}%);
|
||||||
|
margin-left: 16px;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { BackendService } from '../backend.service';
|
import { BackendService } from '../backend.service';
|
||||||
|
import { StateService } from '../state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pay',
|
selector: 'app-pay',
|
||||||
@@ -8,7 +9,10 @@ import { BackendService } from '../backend.service';
|
|||||||
})
|
})
|
||||||
export class PayComponent implements OnInit {
|
export class PayComponent implements OnInit {
|
||||||
|
|
||||||
constructor(public backend: BackendService) { }
|
constructor(
|
||||||
|
public backend: BackendService,
|
||||||
|
public state: StateService
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
-->
|
-->
|
||||||
<div class="payment request" xyz="stagger-2 fade-100% down-1" *ngIf="this.backend.isInvoiceRequested()">
|
<div class="payment request" 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) }} {{ currencyPrefix() }}</p>
|
<p id="price">{{ this.backend.invoice.totalPrice!.toFixed(2) }} {{ this.backend.currencyPrefix() }}</p>
|
||||||
|
|
||||||
<ul id="list">
|
<ul id="list">
|
||||||
<li class="xyz-in" *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)">
|
<li class="xyz-in" *ngFor="let coin of this.backend.invoice!!.paymentMethods" (click)="chooseMethod(coin.method)">
|
||||||
@@ -19,10 +19,11 @@
|
|||||||
<!--
|
<!--
|
||||||
Main view
|
Main view
|
||||||
-->
|
-->
|
||||||
<div class="payment main" xyz="stagger-2 fade-100% down-1 ease-ease" *ngIf="!this.backend.isInvoiceRequested() && ready"
|
<div class="payment main" xyz="stagger-0.5 fade-100% down-1 ease-ease"
|
||||||
|
*ngIf="!this.backend.isInvoiceRequested() && ready && !hideMain"
|
||||||
[ngClass]="{invalid: status === 'Expired' || status === 'Paid too little' || status === 'Cancelled by user'}">
|
[ngClass]="{invalid: status === 'Expired' || status === 'Paid too little' || status === 'Cancelled by user'}">
|
||||||
|
|
||||||
<div class="qrWrapper xyz-in">
|
<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="assets/Bitcoin.svg">
|
||||||
<qrcode
|
<qrcode
|
||||||
@@ -37,13 +38,13 @@
|
|||||||
|
|
||||||
<div class="data">
|
<div class="data">
|
||||||
<!-- Payment data -->
|
<!-- Payment data -->
|
||||||
<span id="target" class="xyz-in">Send to
|
<span id="target" [ngClass]="{'xyz-in': !this.state.showCart.value, 'xyz-out': this.state.showCart.value}">Send to
|
||||||
<h3>{{ this.backend.invoice?.receiveAddress }}</h3>
|
<h3>{{ this.backend.invoice?.receiveAddress }}</h3>
|
||||||
</span>
|
</span>
|
||||||
<span id="amount" class="xyz-in">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) }} {{ currencyPrefix() }}</span></h3>
|
<h3>{{ this.backend.getAmount() }} BTC <span class="price"> | {{ this.backend.invoice.totalPrice!.toFixed(2) }} {{ this.backend.currencyPrefix() }}</span></h3>
|
||||||
</span>
|
</span>
|
||||||
<span id="status" class="xyz-in">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'">
|
||||||
@@ -66,6 +67,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Cart view
|
||||||
|
-->
|
||||||
|
<app-cart id="cart" [ngStyle]="{'display': !hideMain ? 'none' : 'block'}"></app-cart>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Alerts
|
Alerts
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getCurrencySymbol } from '@angular/common';
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { BackendService, CryptoUnits } from '../backend.service';
|
import { BackendService, CryptoUnits } from '../backend.service';
|
||||||
|
import { StateService } from '../state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-payment',
|
selector: 'app-payment',
|
||||||
@@ -13,15 +13,21 @@ export class PaymentComponent implements OnInit {
|
|||||||
|
|
||||||
paymentSelector = '';
|
paymentSelector = '';
|
||||||
confirmations = 0;
|
confirmations = 0;
|
||||||
choosenPaymentMethod = CryptoUnits.BITCOIN;
|
|
||||||
status: string;
|
status: string;
|
||||||
ready = false;
|
ready = false;
|
||||||
|
|
||||||
|
// XYZ class (will be xyz-out if cart is shown for example)
|
||||||
|
xyzClass: string;
|
||||||
|
hideMain: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public backend: BackendService,
|
public backend: BackendService,
|
||||||
|
public state: StateService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
this.status = this.backend.getStatus();
|
this.status = this.backend.getStatus();
|
||||||
|
this.hideMain = false;
|
||||||
|
this.xyzClass = 'xyz-in';
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -31,6 +37,20 @@ export class PaymentComponent implements OnInit {
|
|||||||
this.get();
|
this.get();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.state.showCart.subscribe(cartStatus => {
|
||||||
|
if (cartStatus) {
|
||||||
|
this.xyzClass = 'xyz-out';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hideMain = true;
|
||||||
|
}, 700);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hideMain = false;
|
||||||
|
this.xyzClass = 'xyz-in';
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.backend.invoiceUpdate.subscribe(newInvoice => {
|
this.backend.invoiceUpdate.subscribe(newInvoice => {
|
||||||
this.status = this.backend.getStatus();
|
this.status = this.backend.getStatus();
|
||||||
});
|
});
|
||||||
@@ -40,10 +60,6 @@ export class PaymentComponent implements OnInit {
|
|||||||
this.backend.setPaymentMethod(coin);
|
this.backend.setPaymentMethod(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
currencyPrefix(): string {
|
|
||||||
return getCurrencySymbol(this.backend.invoice.currency, 'narrow');
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(): Promise<void> {
|
async get(): Promise<void> {
|
||||||
await this.backend.setInvoice(this.paymentSelector);
|
await this.backend.setInvoice(this.paymentSelector);
|
||||||
this.backend.getConfirmation().catch();
|
this.backend.getConfirmation().catch();
|
||||||
|
|||||||
16
src/app/state.service.spec.ts
Normal file
16
src/app/state.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StateService } from './state.service';
|
||||||
|
|
||||||
|
describe('StateService', () => {
|
||||||
|
let service: StateService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(StateService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/app/state.service.ts
Normal file
26
src/app/state.service.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { BackendService, PaymentStatus } from './backend.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The state service is responsible to exchange data between components.
|
||||||
|
*/
|
||||||
|
export class StateService {
|
||||||
|
|
||||||
|
showCart: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
|
constructor(private backend: BackendService) {
|
||||||
|
this.showCart = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
this.backend.invoiceUpdate.subscribe(invoice => {
|
||||||
|
this.showCart.next(false); // Hide cart if status changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCart() {
|
||||||
|
this.showCart.next(!this.showCart.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,3 +12,13 @@ body, html {
|
|||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
background-color: transparent;
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user