Adjust visuals for small screens

This commit is contained in:
Rene Vergara 2021-11-11 09:18:38 -06:00
parent b66650d34d
commit 7145a437b4
20 changed files with 258 additions and 144 deletions

View file

@ -2,6 +2,7 @@ const express = require('express');
const app = express();
const bodyparser = require('body-parser');
const cors = require('cors');
const crypto = require('crypto');
const postmodel = require('./models/post');
const usermodel = require('./models/user');
const ownermodel = require('./models/owner');
@ -12,6 +13,8 @@ const txmodel = require('./models/tx');
const mongoose = require('mongoose');
const stdrpc = require('stdrpc');
const CoinGecko = require('coingecko-api');
var URLSafeBase64 = require('urlsafe-base64');
var Buffer = require('buffer/').Buffer;
var db = require('./config/db');
mongoose.connect('mongodb://'+db.user+':'+db.password+'@'+db.server+'/'+db.database).then(() => {
@ -74,16 +77,24 @@ function hexToString(hexString) {
return str;
}
function sendPin(pin, address) {
//var memo = URLSafeBase64.encode(Buffer.from('ZGO pin: '.concat(pin)));
var memo = Buffer.from('ZGO pin: '.concat(pin)).toString('hex');
//console.log(typeof(memo));
var amounts = [
{
address: address,
amount: 0.00000001,
memo: memo
}
];
rpc.z_sendmany(fullnode.addr, amounts).catch((err) => {
console.log('Sendmany', err);
});
}
var blockInterval = setInterval( function() {
console.log('Node periodic Zcash scan');
//usermodel.find({}, function (err, docs) {
//if (err) {
//console.log(err);
//} else {
//console.log(session, blocktime);
//console.log(docs);
//}
//});
rpc.z_listreceivedbyaddress(fullnode.addr, 1).then(txs => {
var re = /.*ZGO::(.*)\sReply-To:\s(z\w+)/;
async.each (txs, function(txData, callback) {
@ -95,17 +106,31 @@ var blockInterval = setInterval( function() {
var address = match[2];
var session = match[1];
var blocktime = txData.blocktime;
var amount = txData.amount;
var expiration = blocktime;
//console.log(' ', session, blocktime);
if (txData.confirmations >= 10 ) {
usermodel.findOne({address: address, session: session, blocktime: blocktime}).then(function(doc){
if (doc != null) {
console.log('Found user');
} else {
console.log('User not found', session, blocktime);
console.log('User not found', session, blocktime, amount);
if (amount >= 0.001 && amount < 0.005){
expiration = blocktime + 3600;
} else if (amount >= 0.005){
expiration = blocktime + 24*3600;
}
console.log('exp', expiration);
const n = crypto.randomInt(0, 10000000);
const pin = n.toString().padStart(6, '0');
sendPin(pin, address);
var user = new usermodel({
address: address,
session: session,
blocktime: blocktime
blocktime: blocktime,
expiration: expiration,
pin: pin,
validated: false
});
user.save(function(error) {
if (error) {
@ -181,6 +206,10 @@ app.use((req, res, next) => {
});
app.get('/api/test', (req, res, next) => {
sendPin('12345678', 'zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e');
res.status(200).send('Endpoint triggered');
});
app.get('/api/users', (req, res, next) => {
console.log('Get: /api/users');
@ -223,8 +252,7 @@ app.get('/api/pending', (req, res, next) => {
app.get('/api/getuser', (req, res, next) => {
console.log('Get: /api/getuser/', req.query.session);
var today = new Date().getTime() / 1000;
var expiration = today - (24*3600);
usermodel.find({'session': req.query.session, 'blocktime': { $gt: expiration }}).
usermodel.find({'session': req.query.session, 'expiration': { $gt: today }}).
then((documents) => {
if(documents.length > 0){
//console.log(documents);
@ -253,8 +281,6 @@ app.get('/api/blockheight', (req, res, next) => {
});
});
app.get('/api/txs', (req, res, next) => {
console.log('Get: /api/txs');
rpc.z_listreceivedbyaddress(fullnode.addr, 10).then(txs => {
@ -301,6 +327,21 @@ app.post('/api/addowner', (req, res, next) => {
});
});
app.post('/api/validateuser', (req, res, next) => {
console.log('Post: /api/validateuser');
usermodel.findByIdAndUpdate(req.body.user._id, req.body.user,
function(err, docs) {
if (err) {
console.log(err);
} else {
res.status(201).json({
message: 'User Validated',
user: docs
});
}
});
});
app.post('/api/updateowner', (req, res, next) => {
console.log('Post: /api/updateowner');
ownermodel.findByIdAndUpdate(req.body.owner._id, req.body.owner,

View file

@ -3,7 +3,10 @@ const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
address: {type: String, required:true},
session: {type: String, required:true},
blocktime: {type: Number, required:true}
blocktime: {type: Number, required:true},
expiration: {type: Number, required:true, default:0},
pin: {type: String, required:true},
validated: {type: Boolean, required:true}
});
module.exports = mongoose.model('User', userSchema);

View file

@ -11,7 +11,7 @@ const routes: Routes = [
//{ path: 'create', component: PostCreateComponent, canActivate: [AuthGuardService]},
{ path: 'shop', component: ViewerComponent, canActivate: [AuthGuardService]},
{ path: 'orders', component: ListOrdersComponent, canActivate: [AuthGuardService]},
{ path: 'login', component: LoginComponent}
{ path: 'login', component: LoginComponent, resolve: { response: NodeResolverService}}
];
@NgModule({

View file

@ -1,5 +1,3 @@
main{
margin-top: 16px;
width: 80%;
margin: auto;
}

View file

@ -1,5 +1,5 @@
<div align="center" mat-dialog-title>
<h2 class="text">Scan to make payment</h2>
<h4 class="text">Scan to make payment</h4>
</div>
<mat-dialog-content>

View file

@ -1,14 +1,10 @@
<mat-toolbar color="primary">
<span align="center">
<a routerLink = "/" class="text">
Z Go!
</a>
<p class="mini text">Last block seen: {{heightUpdate | async}}</p>
<img src="/assets/logo.png" height="45px" />
</span>
<span class="spacer"></span>
<span align="center">
<button mat-raised-button class="text" (click)="openSettings()">
{{ shortenZaddr((ownerUpdate | async)!.address) }} <mat-icon class="icon">manage_accounts</mat-icon>
</button>
<p class="mini text">Last block:</p>
<p class="mini text">{{heightUpdate | async}}</p>
</span>
</mat-toolbar>

View file

@ -3,7 +3,6 @@ import { MatDialog, MatDialogConfig} from '@angular/material/dialog';
import {FullnodeService} from '../fullnode.service';
import { UserService } from '../user.service';
import {Subscription, Observable} from 'rxjs';
import { SettingsComponent } from '../settings/settings.component';
import {Owner} from '../owner.model';
@ -42,28 +41,6 @@ export class HeaderComponent implements OnInit, OnDestroy {
ngOnDestroy(){
}
shortenZaddr(address:string) {
var addr = address;
var end = addr.length;
var last = end - 5;
return addr.substring(0,5).concat('...').concat(addr.substring(last, end));
}
openSettings() {
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
dialogConfig.data = this.owner;
const dialogRef = this.dialog.open(SettingsComponent, dialogConfig);
dialogRef.afterClosed().subscribe((val) => {
if (val != null) {
console.log('Saving settings');
this.userService.updateOwner(val);
}
});
}
}

View file

@ -20,3 +20,6 @@ img.icon{
.total{
font-size: large;
}
img.total{
margin-bottom:-2px;
}

View file

@ -1,6 +1,6 @@
<app-header></app-header>
<div align="center">
<h1 class="text">{{(ownerUpdate | async)!.name}}</h1>
<h3 class="text">{{(ownerUpdate | async)!.name}}</h3>
<button class="text" mat-raised-button [routerLink]="['/shop']" color="primary">
Back to Shop
</button>
@ -11,11 +11,11 @@
<tr>
<td width="50%" align="center">
<h3>Today's Total:</h3>
<p class="total"><img src="/assets/zec-roboto.png" height="14px" />{{todayTotal}}</p>
<p class="total"><img class="total" src="/assets/zec-roboto.png" height="18px" />{{todayTotal | number: '1.0-6'}}</p>
</td>
<td width="50%" align="center">
<h3>Overall Total:</h3>
<p class="total"><img src="/assets/zec-roboto.png" height="14px" />{{total}}</p>
<p class="total"><img class="total" src="/assets/zec-roboto.png" height="18px" />{{total | number: '1.0-6'}}</p>
</td>
</tr>
</table>
@ -25,13 +25,14 @@
<mat-expansion-panel class="text" *ngFor = "let order of orders">
<mat-expansion-panel-header class="text" >
<mat-panel-title>
<span class="price"><img src="/assets/zec-roboto.png" height="50%" />{{order.totalZec}}</span> <span class="mini"> (@{{order.price | currency: 'USD'}})</span>
<span class="price"><img src="/assets/zec-roboto.png" height="50%" />{{order.totalZec}}</span>
</mat-panel-title>
<mat-panel-description>
{{order.timestamp | date: 'medium'}}
{{order.timestamp | date: 'short'}}
</mat-panel-description>
</mat-expansion-panel-header>
<p class="text">Order: {{order._id}}</p>
<p class="text">Zcash price: {{order.price | currency: 'USD'}}</p>
<mat-list>
<mat-list-item class="text small" *ngFor="let item of order.lines">{{item.qty}} x {{item.name}}</mat-list-item>
</mat-list>

View file

@ -4,6 +4,7 @@
mat-card.coolcard{
background-color: #FF5722;
color: #FFFFFF;
margin: 5px;
}
.icon{
font-family: 'Material Icons';
@ -14,3 +15,7 @@ mat-card.coolcard{
font-size: 120%;
padding: 3px;
}
.alert-success{
margin: 5px;
background-color: #BBFFBB
}

View file

@ -1,61 +1,62 @@
<br>
<div align="center" class="text">
<mat-card class="coolcard">
<h3>
<pre>
__||__ _____ _
|___ / / ____| | |
/ / | | __ ___ | |
/ / | | |_ |/ _ \| |
/ /__ | |__| | (_) |_|
/__ _| \_____|\___/(_)
||
</pre>
</h3>
<img src="/assets/logo.png" />
<!--<h3>-->
<!--<pre>-->
<!--__||__ _____ _ -->
<!--|___ / / ____| | |-->
<!--/ / | | __ ___ | |-->
<!--/ / | | |_ |/ _ \| |-->
<!--/ /__ | |__| | (_) |_|-->
<!--/__ _| \_____|\___/(_)-->
<!--|| -->
<!--</pre>-->
<!--</h3>-->
<p class="text">Last block seen: {{ heightUpdate | async }}</p>
</mat-card>
</div>
<div align="center">
<table width="80%">
<colgroup>
<col span="1" style="width: 60%;">
<col span="1" style="width: 40%;">
</colgroup>
<tr>
<td>
<mat-card>
<p>A non-custodial point-of-sale application, powered by Zcash!</p>
<ul>
<li>Your Zcash shielded address is your login.</li>
<li>Your customer pays directly to your wallet.</li>
</ul>
</mat-card>
</td>
<td align="center">
<mat-card *ngIf = "txs.length > 0">
<h4>Login received!</h4>
<mat-list>
<mat-list-item *ngFor="let tx of txsUpdate | async">It has {{tx.confirmations}} confirmations, needs 10.</mat-list-item>
</mat-list>
</mat-card>
<mat-card [formGroup]="entryForm">
<div align="center" id="info">
<mat-form-field appearance="outline">
<mat-label>Session length</mat-label>
<mat-select formControlName="selectedSession">
<mat-option *ngFor="let ticket of tickets" [value]="ticket.value">
{{ticket.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-card-actions>
<button mat-raised-button color="primary" [disabled]="!entryForm.valid" (click)="login()">
<mat-icon class="icon">login</mat-icon><span class="bigbutton">Log in</span>
</button>
</mat-card-actions>
</div>
</mat-card>
</td>
</tr>
</table>
<mat-card>
<p>A non-custodial point-of-sale application, powered by Zcash!</p>
<p>Your Zcash shielded address is your login.</p>
<p>Your customer pays directly to your wallet.</p>
</mat-card>
<mat-card [formGroup]="pinForm" *ngIf="prompt">
<h4>
Check your wallet
</h4>
<mat-form-field appearance="outline">
<mat-label>PIN</mat-label>
<input matInput formControlName="pinValue">
</mat-form-field>
<mat-card-actions>
<button mat-raised-button color="primary" [disabled]="!pinForm.valid" (click)="confirmPin()">
Confirm
</button>
</mat-card-actions>
</mat-card>
<mat-card class="alert-success" *ngIf = "txs.length > 0">
<h4>Login received!</h4>
<mat-list>
<mat-list-item *ngFor="let tx of txsUpdate | async">It needs {{10 - tx.confirmations}} more confirmations.</mat-list-item>
</mat-list>
</mat-card>
<mat-card [formGroup]="entryForm" *ngIf="!prompt">
<div align="center" id="info">
<mat-form-field appearance="outline">
<mat-label>Session length</mat-label>
<mat-select formControlName="selectedSession">
<mat-option *ngFor="let ticket of tickets" [value]="ticket.value">
{{ticket.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-card-actions>
<button mat-raised-button color="primary" [disabled]="!entryForm.valid" (click)="login()">
<mat-icon class="icon">login</mat-icon><span class="bigbutton">Log in</span>
</button>
</mat-card-actions>
</div>
</mat-card>
</div>

View file

@ -6,6 +6,7 @@ import { UserService } from '../user.service';
import { FullnodeService } from '../fullnode.service';
import { ScanComponent} from '../scan/scan.component';
import { Tx } from '../tx.model';
import {User} from '../user.model';
import { Subscription, Observable } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';
var QRCode = require('easyqrcodejs');
@ -25,10 +26,19 @@ export class LoginComponent implements OnInit {
nodeAddress: string = '';
localToken: string | null = '';
selectedValue: number = 0.001;
public user:User = {
address: '',
session: '',
blocktime: 0,
expiration: 0,
pin: '',
validated: false
};
private FullnodeSub: Subscription = new Subscription();
private UserSub: Subscription = new Subscription();
public heightUpdate: Observable<number>;
public uZaddrUpdate: Observable<string>;
public userUpdate:Observable<User>;
public txsUpdate: Observable<Tx[]>;
tickets = [
{
@ -39,8 +49,10 @@ export class LoginComponent implements OnInit {
viewValue: 'One day'
}
];
prompt: boolean = false;
entryForm: FormGroup;
pinForm: FormGroup;
constructor(
private fb: FormBuilder,
@ -55,8 +67,15 @@ export class LoginComponent implements OnInit {
this.entryForm = fb.group({
selectedSession: [0.001, Validators.required]
});
this.pinForm = fb.group({
pinValue: [null, Validators.required]
});
this.heightUpdate = fullnodeService.heightUpdate;
this.uZaddrUpdate = userService.uZaddrUpdate;
this.userUpdate = userService.userUpdate;
this.userUpdate.subscribe((user) => {
this.user = user;
});
this.txsUpdate = userService.txUpdate;
this.txsUpdate.subscribe((txs) => {
this.txs = txs;
@ -76,26 +95,7 @@ export class LoginComponent implements OnInit {
localStorage.setItem('s4z_token', token);
this.localToken = token;
}
this.userService.findUser();
this.userService.uZaddrUpdate.
subscribe((userAddr: string) => {
if (userAddr.length != 0) {
console.log('Log in found!');
this.router.navigate(['/shop']);
} else {
console.log('No login for existing token found');
//console.log('Showing QR code for login');
////console.log(URLSafeBase64.encode(Buffer.from('S4ZEC::'.concat(localToken))));
//var codeString = `zcash:${this.nodeAddress}?amount=0.005&memo=${URLSafeBase64.encode(Buffer.from('ZGO::'.concat(this.localToken!)))}`;
//console.log(codeString);
//var qrcode = new QRCode(document.getElementById("qrcode"), {
//text: codeString,
//logo: "/assets/zcash.png",
//logoWidth: 80,
//logoHeight: 80
//});
}
});
this.loginCheck();
});
this.intervalHolder = setInterval(() => {
this.fullnodeService.getHeight();
@ -106,21 +106,25 @@ export class LoginComponent implements OnInit {
}
loginCheck(){
var today = new Date().getTime() / 1000;
this.userService.findUser();
this.userService.findPending();
this.txsUpdate.subscribe((txs) => {
this.txs = txs;
});
this.uZaddrUpdate.subscribe((userAddr: string) => {
if (userAddr.length != 0) {
this.userUpdate.subscribe((user) => {
if (user.expiration > today) {
this.prompt = true;
console.log('Log in found in blockchain!');
this.router.navigate(['/shop']);
if (user.validated) {
this.router.navigate(['/shop']);
}
}
});
}
login() {
console.log('Dropdown:', this.entryForm.value.selectedSession);
//console.log('Dropdown:', this.entryForm.value.selectedSession);
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = true;
@ -137,6 +141,13 @@ export class LoginComponent implements OnInit {
});
}
confirmPin(){
if (this.user.pin === this.pinForm.value.pinValue) {
this.userService.validateUser();
this.router.navigate(['/shop']);
}
}
ngOnDestroy(){
this.FullnodeSub.unsubscribe();
this.UserSub.unsubscribe();

View file

@ -16,7 +16,10 @@ export class OrderService {
user:{
address: '',
session: '',
blocktime: 0
blocktime: 0,
expiration: 0,
pin: '',
validated: false
},
order: {
address: '',

View file

@ -3,4 +3,5 @@ export interface Tx {
address: string;
session: string;
confirmations: number;
amount: number;
}

View file

@ -3,4 +3,7 @@ export interface User {
address: string;
session: string;
blocktime: number;
expiration: number;
pin: string;
validated: boolean;
}

View file

@ -13,7 +13,10 @@ export class UserService{
user: {
address: '',
session: '',
blocktime: 0
blocktime: 0,
expiration: 0,
pin: '',
validated: false
},
owner: {
address: '',
@ -99,6 +102,15 @@ export class UserService{
}
}
validateUser(){
var validatedUser: User = this.dataStore.user;
validatedUser.validated = true;
this.http.post<{message: string, user: User}>(this.beUrl+'api/validateuser', {user: validatedUser}, {headers: this.reqHeaders}).
subscribe((responseData) => {
console.log(responseData.message);
});
}
addOwner(address: string) {
const owner: Owner={_id: '', address: address, name: 'Zgo-'.concat(address.substring(0,5))};
let obs = this.http.post<{message: string}>(this.beUrl+'api/addowner', {address: owner.address, name: owner.name}, {headers: this.reqHeaders});

View file

@ -1,3 +1,9 @@
* {
font-family: 'Roboto Mono', monospace;
}
.icon{
font-family: 'Material Icons';
}
.small{
font-size: x-small;
}

View file

@ -1,15 +1,13 @@
<app-header></app-header>
<div align="center">
<h1>{{(ownerUpdate | async)!.name}}</h1>
<h3>{{(ownerUpdate | async)!.name}}</h3>
<p class="small">{{ shortenZaddr((ownerUpdate | async)!.address) }}</p>
<span align="center">
<button mat-raised-button class="text" (click)="openSettings()">
<mat-icon class="icon">manage_accounts</mat-icon>
</button>
</span>
<button mat-raised-button [routerLink]="['/orders']">View Orders</button>
</div>
<table cellspacing="0" width="100%">
<tr>
<td>
<app-item-list></app-item-list>
</td>
<td>
<app-order></app-order>
</td>
</tr>
</table>
<app-order></app-order>
<app-item-list></app-item-list>

View file

@ -5,8 +5,10 @@ import { UserService } from '../user.service';
import { FullnodeService } from '../fullnode.service';
import { ItemService } from '../items/items.service';
import { Subscription, Observable } from 'rxjs';
import { SettingsComponent } from '../settings/settings.component';
import {Owner} from '../owner.model';
import {User} from '../user.model';
@Component({
@ -16,9 +18,20 @@ import {Owner} from '../owner.model';
})
export class ViewerComponent implements OnInit {
intervalHolder: any;
public message: string = "Welcome to the inside!";
public user: User = {
address: '',
session: '',
blocktime: 0,
expiration: 0,
pin: '',
validated: false
};
private owner: Owner= {_id:'', address: 'none', name:''};
public addrUpdate: Observable<string>;
public ownerUpdate: Observable<Owner>;
public userUpdate: Observable<User>;
constructor(
public fullnodeService: FullnodeService,
@ -28,15 +41,57 @@ export class ViewerComponent implements OnInit {
){
this.addrUpdate = fullnodeService.addrUpdate;
this.ownerUpdate = userService.ownerUpdate;
this.ownerUpdate.subscribe((owner) => {
this.owner = owner;
});
this.userUpdate = userService.userUpdate;
this.userUpdate.subscribe((user) => {
this.user = user;
});
}
ngOnInit(){
this.ownerUpdate.subscribe((owner) => {
this.message = owner.name;
});
this.loginCheck();
this.intervalHolder = setInterval(() => {
this.loginCheck();
}, 60000);
}
ngOnDestroy(){
}
shortenZaddr(address:string) {
var addr = address;
var end = addr.length;
var last = end - 5;
return addr.substring(0,5).concat('...').concat(addr.substring(last, end));
}
openSettings() {
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
dialogConfig.data = this.owner;
const dialogRef = this.dialog.open(SettingsComponent, dialogConfig);
dialogRef.afterClosed().subscribe((val) => {
if (val != null) {
console.log('Saving settings');
this.userService.updateOwner(val);
}
});
}
loginCheck(){
var today = new Date().getTime() / 1000;
console.log('User check', this.user.validated);
if (this.user.expiration < today || !this.user.validated) {
console.log('Log in expired!');
this.router.navigate(['/login']);
}
}
}

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB