better UI update

This commit is contained in:
sairaj mote 2022-03-11 15:50:25 +05:30
parent b485887df2
commit ae097df637
15 changed files with 5742 additions and 7946 deletions

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

244
fn_ui.js
View File

@ -1,244 +0,0 @@
const userUI = {};
userUI.requestTokenFromCashier = function() {
let cashier = User.findCashier();
if (!cashier)
return alert("No cashier online");
let amount = parseFloat(document.forms['request-cashier']['amount'].value);
//get UPI txid from user
let upiTxID = prompt(`Send Rs. ${amount} to ${cashierUPI[cashier]} and enter UPI txid`);
if (!upiTxID)
return alert("Cancelled");
User.cashToToken(cashier, amount, upiTxID).then(result => {
console.log(result);
alert("Requested cashier. please wait!");
}).catch(error => console.error(error))
}
userUI.withdrawCashFromCashier = function() {
let cashier = User.findCashier();
if (!cashier)
return alert("No cashier online");
let amount = parseFloat(document.forms['request-cashier']['amount'].value);
//get confirmation from user
let upiID = prompt(`${amount} ${floGlobals.currency}# will be sent to ${cashier}. Enter UPI ID`);
if (!upiID)
return alert("Cancelled");
User.sendToken(cashier, amount, 'for token-to-cash').then(txid => {
console.warn(`Withdraw ${amount} from cashier ${cashier}`, txid);
User.tokenToCash(cashier, amount, txid, upiID).then(result => {
console.log(result);
alert("Requested cashier. please wait!");
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}
userUI.sendMoneyToUser = function() {
let form = document.forms['user-money'];
let floID = form['flo-id'].value,
amount = parseFloat(form['amount'].value),
remark = form['remark'].value;
let confirmation = confirm(`Do you want to SEND ${amount} to ${floID}?`);
if (!confirmation)
return alert("Cancelled");
User.sendToken(floID, amount, "|" + remark).then(txid => {
console.warn(`Sent ${amount} to ${floID}`, txid);
alert(`Sent ${amount} to ${floID}. It may take a few mins to reflect in their wallet`);
}).catch(error => console.error(error));
}
userUI.requestMoneyFromUser = function() {
let form = document.forms['user-money'];
let floID = form['flo-id'].value,
amount = parseFloat(form['amount'].value),
remark = form['remark'].value;
let confirmation = confirm(`Do you want to REQUEST ${amount} from ${floID}?`);
if (!confirmation)
return alert("Cancelled");
User.requestToken(floID, amount, remark).then(result => {
console.log(`Requested ${amount} from ${floID}`, result);
alert(`Requested ${amount} from ${floID}`);
}).catch(error => console.error(error));
}
userUI.renderCashierRequests = function(requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
let table = document.getElementById('user-cashier-requests').getElementsByTagName('tbody')[0];
for (let r in requests) {
let oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
let row = table.insertRow();
renderUser_cashierRequestCard(requests[r], row);
}
}
function renderUser_cashierRequestCard(request, row) {
row.id = request.vectorClock;
row.insertCell().textContent = request.time;
row.insertCell().textContent = request.receiverID;
row.insertCell().textContent = request.message.mode;
let status = request.tag ? (request.tag + ":" + request.note) : (request.note || "PENDING");
row.insertCell().textContent = status; //Status
}
userUI.renderMoneyRequests = function(requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
let table = document.getElementById('user-money-requests').getElementsByTagName('tbody')[0];
for (let r in requests) {
let oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
let row = table.insertRow();
renderUser_moneyRequestCard(requests[r], row);
}
}
function renderUser_moneyRequestCard(request, row) {
row.id = request.vectorClock;
row.insertCell().textContent = request.time;
row.insertCell().textContent = request.senderID;
row.insertCell().textContent = request.message.amount;
row.insertCell().textContent = request.message.remark;
let status = request.note;
if (status)
row.insertCell().textContent = request.note;
else
row.insertCell().innerHTML =
`<input type="button" value="Accept" onclick="userUI.payRequest('${request.vectorClock}')" />` +
`<input type="button" value="Decline" onclick="userUI.declineRequest('${request.vectorClock}')" />`;
}
userUI.payRequest = function(reqID) {
let request = User.moneyRequests[reqID];
let confirmation = confirm(`Do you want to SEND ${request.message.amount} to ${request.senderID}?`);
if (!confirmation)
return alert("Cancelled");
User.sendToken(request.senderID, request.message.amount, "|" + request.message.remark).then(txid => {
console.warn(`Sent ${request.message.amount} to ${request.senderID}`, txid);
alert(`Sent ${request.message.amount} to ${request.senderID}. It may take a few mins to reflect in their wallet`);
User.decideRequest(request, 'PAID: ' + txid)
.then(result => console.log(result))
.catch(error => console.error(error))
}).catch(error => console.error(error));
}
userUI.declineRequest = function(reqID) {
let request = User.moneyRequests[reqID];
User.decideRequest(request, "DECLINED").then(result => {
console.log(result);
alert("Declined request");
}).catch(error => console.error(error))
}
//Cashier
const cashierUI = {};
cashierUI.renderRequests = function(requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
let table = document.getElementById('cashier-request-list').getElementsByTagName('tbody')[0];
for (let r in requests) {
let oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
let row = table.insertRow();
renderCashier_requestCard(requests[r], row);
}
}
function renderCashier_requestCard(request, row) {
row.id = request.vectorClock;
row.insertCell().textContent = request.senderID;
row.insertCell().textContent = request.time;
row.insertCell().textContent = request.message.mode;
let status = request.tag || request.note; //status tag for completed, note for rejected
if (status)
row.insertCell().textContent = status;
else
row.insertCell().innerHTML = `<input type="button" value="PENDING" onclick="cashierUI.completeRequest('${request.vectorClock}')" />`
}
cashierUI.completeRequest = function(reqID) {
let request = Cashier.Requests[reqID];
if (request.message.mode === "cash-to-token")
completeCashToTokenRequest(request);
else if (request.message.mode === "token-to-cash")
completeTokenToCashRequest(request);
}
function completeCashToTokenRequest(request) {
Cashier.checkIfUpiTxIsValid(request.message.upi_txid).then(_ => {
let confirmation = confirm(`Check if you have received UPI transfer\ntxid:${request.message.upi_txid}\namount:${request.message.amount}`);
if (!confirmation)
return alert("Cancelled");
User.sendToken(request.senderID, request.message.amount, 'for cash-to-token').then(txid => {
console.warn(`${request.message.amount} cash-to-token for ${request.senderID}`, txid);
Cashier.finishRequest(request, txid).then(result => {
console.log(result);
console.info('Completed cash-to-token request:', request.vectorClock);
alert("Completed request");
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
alert(error);
if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string')
Cashier.rejectRequest(request, error[1]).then(result => {
console.log(result);
console.info('Rejected cash-to-token request:', request.vectorClock);
}).catch(error => console.error(error))
})
}
function completeTokenToCashRequest(request) {
Cashier.checkIfTokenTxIsValid(request.message.token_txid, request.senderID, request.message.amount).then(result => {
let upiTxID = prompt(`Token transfer is verified!\n Send ${request.message.amount} to ${request.message.upi_id} and Enter UPI txid`);
if (!upiTxID)
return alert("Cancelled");
Cashier.finishRequest(request, upiTxID).then(result => {
console.log(result);
console.info('Completed token-to-cash request:', request.vectorClock);
alert("Completed request");
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
alert(error);
if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string')
Cashier.rejectRequest(request, error[1]).then(result => {
console.log(result);
console.info('Rejected token-to-cash request:', request.vectorClock);
}).catch(error => console.error(error))
})
}
function renderAllTokenTransactions() {
let table = document.getElementById('token-transactions').getElementsByTagName('tbody')[0];
tokenAPI.getAllTxs(myFloID).then(result => {
for (let txid in result.transactions) {
let row = table.insertRow();
renderTransactionCard(row, txid, tokenAPI.util.parseTxData(result.transactions[txid]));
}
}).catch(error => console.error(error))
}
function renderTransactionCard(row, txid, tx) {
row.setAttribute('title', txid);
row.insertCell().textContent = tx.time;
if (tx.sender === myFloID) {
row.insertCell().textContent = 'Sent';
row.insertCell().textContent = tx.receiver || 'Myself';
} else if (tx.receiver === myFloID) {
row.insertCell().textContent = 'Recieved';
row.insertCell().textContent = tx.sender;
} else { //This should not happen unless API returns transaction that doesnot involve myFloID
row.insertCell().textContent = tx.sender;
row.insertCell().textContent = tx.receiver;
}
row.insertCell().textContent = tx.tokenAmount;
}

441
new.html
View File

@ -3,13 +3,14 @@
<head>
<title>RanchiMall Pay</title>
<style>
table,
th,
td {
border: 1px solid black;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta charset="UTF-8">
<meta name="description"
content="This webapp allows monitoring FLO addresses and performing transactions based on blockchain.">
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="css/main.min.css">
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
const floGlobals = {
@ -42,133 +43,347 @@
lastVC: {}
}
</script>
<script src="std_op.js"></script>
<script src="fn_pay.js"></script>
<script src="fn_ui.js"></script>
</head>
<body onload="onLoadStartUp()">
<sm-notifications id="notification_drawer"></sm-notifications>
<sm-popup id="confirmation_popup">
<h4 id="confirm_title"></h4>
<p id="confirm_message"></p>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
</div>
</sm-popup>
<div id="main_card">
<header id="main_header" class="flex align-center space-between">
<div class="flex align-center">
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108"
style="enable-background:new 0 0 90.5 106.3;" xml:space="preserve">
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
c1.3-3.6,2.6-5.2,12.9-15.1c6.2-5.9,9.3-10.3,11.1-15.5c0.7-2.1,0.8-7.6,0.2-9.4C66.5,12,61.7,6.7,53.7,1.6c-3-1.9-4.3-2.1-4.3-0.8
c0,0.3-0.5,1.4-1,2.4l-1,1.8l-2.8-1.9c-1.5-1.1-3.4-2.2-4.1-2.6c-1.3-0.7-2.4-0.6-2.4,0.2c0,0.3-1.4,3.4-2,4.4
c0,0.1-0.4-0.1-0.9-0.4c-6.1-4.4-8.7-5.5-8.7-3.9c0,0.7-1.8,4.2-4,7.9C16,19.5,9.4,24.9,2.6,24.9c-3,0-2.9-0.1-2,3.4
c0.7,2.8,1.1,3.1,3.6,2.3c2.3-0.7,3.9-1.5,5.8-2.9c0.8-0.6,1.5-0.9,1.6-0.9c0.1,0.1,0.5,1,0.7,2.1s0.7,2,0.9,2.1
c0.8,0.3,5.1-1.3,7.5-2.9l2.3-1.5l0.5,1.8c0.6,2.4,1,2.7,3.3,2.1c3.9-1,7.7-3.7,11.5-8.2l2-2.4l-0.2,2.1c-0.6,5.4-4.3,11.4-11.3,18
c-1.8,1.7-4.7,4.5-6.5,6.2c-10.7,10.2-10,18.6,2,26.5c2.7,1.8,10.3,5.8,15.3,8c0.9,0.4,3.3,1.7,5.3,2.9c11,6.5,16.4,13.1,16.4,19.7
c0,1.3,0.1,2.4,0.2,2.6l0,0c0.3,0.3,0.1,0.3,3-0.5c1.4-0.4,2.6-0.9,2.8-1.1c0.4-0.6-0.6-3.7-1.8-6.1c-1.3-2.5-5.6-7-8.9-9.4
c-3.8-2.8-9.3-5.9-17-9.7c-8.5-4.2-11.8-6.2-14.7-9.1c-2.6-2.6-3.9-5.3-3.9-8.2c0-4.6,2.3-8.6,8.3-14.1c9.4-8.7,13-13,15.5-18.8
c1.3-3,1.4-3.4,1.4-6.7c0-3.1-0.1-3.8-1.1-6l-1.1-2.4l1-1.6c0.5-0.9,1.2-2.1,1.5-2.6l0.5-1l1.5,2.1c1.8,2.6,3.2,6.8,3.2,9.3
c0,1.7-0.6,4.7-1.4,6.4c-0.2,0.4-0.4,1-0.5,1.3c-0.1,0.3-1.1,2-2.2,3.7c-2,3-5.2,6.4-13.4,14.2c-5.7,5.4-7.6,8.6-7.8,13.1
c-0.2,3.7,0.7,5.9,3.7,9.2c3.2,3.4,6.9,5.8,17.4,11c12.1,6,17.3,9.6,21.3,14.5c2.5,3.2,3.7,5.8,3.9,9.3c0.1,1.6,0.3,3,0.5,3
c0.1,0.1,0.8,0,1.4-0.2s1.9-0.5,2.7-0.7l1.5-0.4l-0.2-1.5c-0.7-5.1-5.4-10.8-13.1-16c-4.4-2.9-5.8-3.7-17.3-9.4
c-5.7-2.8-9.2-5.1-11.8-7.6c-4.3-4.2-5.1-8.8-2.7-13.9c1.4-2.8,2.7-4.4,12.5-13.8c8-7.7,11.4-13.7,11.4-20.1c0-5.1-2.3-9.9-6.9-14.3
c-1.1-1-2-2-2.1-2.2c-0.2-0.4,1.5-3.9,1.9-3.9c1.2,0,7.8,6.3,9.7,9.2c2,3.3,2.5,5,2.5,8.9c0,3.9-0.6,5.9-2.9,9.8
c-2.4,4.1-4.2,6-14.2,15.5c-3.4,3.2-5.7,6.1-6.9,8.7c-0.9,2-1.1,2.7-1.1,5.1c0,2.3,0.2,3.2,1,4.9c1.9,4,7.4,8.5,15.4,12.4
c12.5,6.1,15.1,7.6,19.4,10.7c7.2,5.3,10.6,10.5,10.6,16c0,1.3,0.1,2.4,0.3,2.5c0.4,0.3,4.8-0.8,5.5-1.3
C90.7,104.4,90.7,104.3,90.2,102.5z M20.3,23.3L20.3,23.3c-2,1-3.3,1.4-4.8,1.5L13.3,25l2.3-2.8c3.7-4.5,6.4-8.9,10-16
c0.9-1.8,1.8-3.5,2-3.6c0.4-0.4,2.6,1.1,5.1,3.4l2.1,1.9l-1.9,2.8C28.2,17.5,24.5,21.2,20.3,23.3z M39.3,17.4
c-1.2,1.7-6.5,5.7-8.6,6.5v0c-1.1,0.4-2.8,0.8-3.9,0.9L24.9,25l2.1-2.6c2.5-3.1,5.1-7,7-10.4c0.7-1.4,1.4-2.5,1.5-2.6
c0.3-0.4,1.7,1.4,3,4.1l1.5,3L39.3,17.4z M44.6,10c-0.7,1.2-1.4,2.1-1.5,2.1c-0.1,0-1.5-1.4-3-3l-2.8-3l0.6-1.5
c1.1-2.6,1.3-2.7,3.4-1c1.9,1.5,4.5,3.8,4.5,4.1C45.8,7.8,45.3,8.9,44.6,10z" />
</svg>
<h4>RanchiMall Pay</h4>
</div>
<theme-toggle></theme-toggle>
</header>
<section id="pages_container" class="gap-2">
<section id="home" class="page hide">
<div id="user" class="hide grid gap-2 user-element">
<div class="flex">
<button class="button primary-action" onclick="showTokenTransfer('send')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
Send
</button>
<button class="button primary-action" onclick="showTokenTransfer('request')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<path
d="M20,2H4.01c-1.1,0-2,0.9-2,2L2,22l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M12,6c1.1,0,2,0.9,2,2s-0.9,2-2,2 s-2-0.9-2-2S10.9,6,12,6z M16,14H8v-0.57c0-0.81,0.48-1.53,1.22-1.85C10.07,11.21,11.01,11,12,11c0.99,0,1.93,0.21,2.78,0.58 C15.52,11.9,16,12.62,16,13.43V14z" />
</g>
</svg>
Request
</button>
</div>
<section id="wallet_section" class="grid gap-1-5">
<h4 class="flex align-center">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<path
d="M18,4H6C3.79,4,2,5.79,2,8v8c0,2.21,1.79,4,4,4h12c2.21,0,4-1.79,4-4V8C22,5.79,20.21,4,18,4z M16.14,13.77 c-0.24,0.2-0.57,0.28-0.88,0.2L4.15,11.25C4.45,10.52,5.16,10,6,10h12c0.67,0,1.26,0.34,1.63,0.84L16.14,13.77z M6,6h12 c1.1,0,2,0.9,2,2v0.55C19.41,8.21,18.73,8,18,8H6C5.27,8,4.59,8.21,4,8.55V8C4,6.9,4.9,6,6,6z" />
</g>
</svg>
Wallet
</h4>
<div class="grid gap-0-5">
<h5>Balance</h5>
<h1 class="h1" id="rupee_balance"></h1>
</div>
<div class="grid gap-1">
<sm-input id="request_cashier_amount" type="number" name="amount" placeholder="Amount"
animate>
</sm-input>
<div class="flex">
<button class="primary-action flex-1" onclick="userUI.requestTokenFromCashier()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<rect fill="none" height="24" width="24" />
<g>
<path
d="M19.83,7.5l-2.27-2.27c0.07-0.42,0.18-0.81,0.32-1.15C17.96,3.9,18,3.71,18,3.5C18,2.67,17.33,2,16.5,2 c-1.64,0-3.09,0.79-4,2l-5,0C4.46,4,2,6.46,2,9.5S4.5,21,4.5,21l5.5,0v-2h2v2l5.5,0l1.68-5.59L22,14.47V7.5H19.83z M13,9H8V7h5V9z M16,11c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1s1,0.45,1,1C17,10.55,16.55,11,16,11z" />
</g>
</svg>
Deposit
</button>
<button class="primary-action flex-1" onclick="userUI.withdrawCashFromCashier()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M19 14V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zm-9-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-6v11c0 1.1-.9 2-2 2H4v-2h17V7h2z" />
</svg>
Withdraw
</button>
</div>
</div>
</section>
</div>
<section id="cashier" class="hide admin-element">
<h4>Requests</h4>
<ul id="cashier_request_list" class="observe-empty-state"></ul>
<div class="empty-state">
<h4>No requests to process</h4>
</div>
</section>
</section>
<section id="history" class="page hide grid gap-1">
<h4>Transactions history</h4>
<ul id="token_transactions" class="observe-empty-state">
</ul>
<div class=" empty-state gap-1 justify-center text-center">
<h4>No transactions</h4>
</div>
</section>
<section id="activity" class="page hide grid gap-1">
<h4>Activity</h4>
<tab-header target="user_sections">
<sm-tab>Wallet transactions</sm-tab>
<sm-tab>Payment requests</sm-tab>
</tab-header>
<tab-panels id="user_sections">
<section>
<ul id="user-cashier-requests" class="observe-empty-state"></ul>
<div class=" empty-state gap-1 justify-center text-center">
<h4>No transactions</h4>
</div>
</section>
<section>
<ul id="user-money-requests" class="observe-empty-state"></ul>
<div class=" empty-state gap-1 justify-center text-center">
<h4>No requests</h4>
</div>
</section>
</tab-panels>
</section>
<section id="settings" class="page hide gap-1-5">
<h4>Settings</h4>
<section class="grid gap-1">
<div class="grid">
<h5>My FLO ID</h5>
<sm-copy id="logged_in_user_id" style="font-size: 0.9rem;"></sm-copy>
</div>
<sm-button class="danger justify-self-start" onclick="signOut()">Sign out</sm-button>
</section>
</section>
</section>
<nav id="main_navbar">
<ul>
<li>
<a href="#/home" class="nav-item interact">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
<span class="nav-item__title">Home</span>
</a>
</li>
<li>
<a href="#/history" class="nav-item interact">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0,0h24v24H0V0z" fill="none" />
<g>
<path
d="M19.5,3.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2v14H3v3c0,1.66,1.34,3,3,3h12c1.66,0,3-1.34,3-3V2 L19.5,3.5z M19,19c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H8V5h11V19z" />
<rect height="2" width="6" x="9" y="7" />
<rect height="2" width="2" x="16" y="7" />
<rect height="2" width="6" x="9" y="10" />
<rect height="2" width="2" x="16" y="10" />
</g>
</svg>
<span class="nav-item__title">History</span>
</a>
</li>
<li>
<a href="#/activity" class="nav-item interact user-element">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path
d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z" />
</svg>
<span class="nav-item__title">Activity</span>
</a>
</li>
<li>
<a href="#/settings" class="nav-item interact">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<path d="M0,0h24v24H0V0z" fill="none" />
<path
d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
</g>
</svg>
<span class="nav-item__title">Settings</span>
</button>
</a>
</li>
</ul>
</nav>
</div>
<!-- Popups -->
<sm-popup id="token_transfer_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close justify-self-start" onclick="hidePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>
</header>
<section class="grid gap-2">
<h4 id="token_transfer__title"></h4>
<sm-form>
<sm-input id="tt_flo_id" placeholder="FLO ID" error-text="Invalid FLO ID" data-flo-id animate required
autofocus>
</sm-input>
<sm-input id="tt_amount" type="number" placeholder="0" required min="1">
<svg slot="icon" class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<g>
<path
d="M13.66,7C13.1,5.82,11.9,5,10.5,5L6,5V3h12v2l-3.26,0c0.48,0.58,0.84,1.26,1.05,2L18,7v2l-2.02,0c-0.25,2.8-2.61,5-5.48,5 H9.77l6.73,7h-2.77L7,14v-2h3.5c1.76,0,3.22-1.3,3.46-3L6,9V7L13.66,7z" />
</g>
</g>
</svg>
</sm-input>
<sm-input id="tt_remark" placeholder="Add a message" animate></sm-input>
<button id="tt_button" class="button button--primary cta" onclick="executeUserAction()"
type="submit">Send</button>
</sm-form>
</section>
</sm-popup>
<!-- templates -->
<template id="transaction_template">
<li class="transaction grid">
<div class="transaction__icon"></div>
<div class="transaction__receiver breakable"></div>
<time class="transaction__time"></time>
<div class="transaction__amount"></div>
</li>
</template>
<template id="cashier_request_template">
<li class="cashier-request flex-wrap">
<div class="cashier-request__mode"></div>
<div class="grid gap-0-5 flex-1">
<div class="cashier-request__requestor breakable"></div>
<time class="cashier-request__time"></time>
</div>
<div class="cashier-request__status"></div>
</li>
</template>
<template id="wallet_request_template">
<li class="wallet-request flex-wrap">
<div class="grid gap-0-5 flex-1">
<div class="wallet-request__requestor breakable"></div>
<time class="wallet-request__time"></time>
</div>
<div class="wallet-request__mode"></div>
<div class="wallet-request__status"></div>
</li>
</template>
<template id="payment_request_template">
<li class="payment-request">
<div class="grid gap-0-5 flex-1">
<div class="payment-request__requestor breakable"></div>
<div class="payment-request__remark"></div>
</div>
<div class="payment-request__amount"></div>
<div class="flex space-between full-bleed align-center">
<time class="payment-request__time"></time>
<div class="payment-request__actions"></div>
</div>
</li>
</template>
<script src="scripts/components.js"></script>
<script src="scripts/std_ui.js"></script>
<script src="scripts/std_op.js"></script>
<script src="scripts/fn_pay.js"></script>
<script src="scripts/fn_ui.js"></script>
<script id="onLoadStartUp">
function onLoadStartUp() {
console.log("Starting the app! Please Wait!")
floDapps.launchStartUp().then(result => {
console.log(`Welcome ${myFloID}`);
if (floGlobals.subAdmins.includes(myFloID)) {
document.getElementById('cashier-id').textContent = myFloID;
getRef('logged_in_user_id').value = myFloID;
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(myFloID)
tokenAPI.getBalance(myFloID).then(balance => {
getRef('rupee_balance').textContent = balance.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' })
})
if (floGlobals.isSubAdmin) {
cashierUI.renderRequests(Cashier.Requests);
Cashier.init().then(result => {
console.log(result);
document.getElementById('cashier').hidden = false;
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.remove('hide'))
document.querySelectorAll('.user-element').forEach(elem => elem.classList.add('hide'))
}).catch(error => console.error(error))
} else {
document.getElementById('user-id').textContent = myFloID;
userUI.renderCashierRequests(User.cashierRequests);
userUI.renderMoneyRequests(User.moneyRequests);
User.init().then(result => {
console.log(result);
console.log("Cashiers:", cashierUPI);
document.getElementById('user').hidden = false;
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.add('hide'))
document.querySelectorAll('.user-element').forEach(elem => elem.classList.remove('hide'))
}).catch(error => console.error(error))
}
renderAllTokenTransactions();
showPage(window.location.hash, { firstLoad: true })
}).catch(error => console.error(error))
}
</script>
</head>
<body onload="onLoadStartUp()">
<section id="user" hidden>
<div id="user-id"></div>
<div>
<form id="request-cashier">
<fieldset>
<legend>Request Cashier</legend>
Amount: <input type="number" name="amount" /><br />
<input type="button" value="request-token" onclick="userUI.requestTokenFromCashier()" />
<input type="button" value="withdraw-cash" onclick="userUI.withdrawCashFromCashier()" />
</fieldset>
</form>
</div>
<div>
<form id="user-money">
<fieldset>
<legend>Token transfer</legend>
FLO-ID: <input type="text" name="flo-id" /><br />
Amount: <input type="number" name="amount" /><br />
Remark: <input type="text" name="remark" /><br />
<input type="button" value="send-money" onclick="userUI.sendMoneyToUser()" />
<input type="button" value="request-money" onclick="userUI.requestMoneyFromUser()" />
</fieldset>
</form>
</div>
<div>
<fieldset>
<legend>Cashier Requests</legend>
<table id="user-cashier-requests">
<thead>
<tr>
<td>Date-Time</td>
<td>Cashier</td>
<td>Mode</td>
<td>Status</td>
</tr>
</thead>
<tbody></tbody>
</table>
</fieldset>
</div>
<div>
<fieldset>
<legend>Money Request</legend>
<table id="user-money-requests">
<thead>
<tr>
<td>Date-Time</td>
<td>Requestor</td>
<td>Amount</td>
<td>Remark</td>
<td>Status</td>
</tr>
</thead>
<tbody></tbody>
</table>
</fieldset>
</div>
</section>
<section id="cashier" hidden>
<div id="cashier-id"></div>
<div>
<fieldset>
<legend>Requests</legend>
<table id="cashier-request-list">
<thead>
<tr>
<td>Requestor</td>
<td>Date-Time</td>
<td>Mode</td>
<td>Status</td>
</tr>
</thead>
<tbody></tbody>
</table>
</fieldset>
</div>
</section>
<div>
<fieldset>
<legend>Transactions</legend>
<table id="token-transactions">
<thead>
<tr>
<td>Time</td>
<td></td>
<td>FLO ID</td>
<td>Amount</td>
</tr>
</thead>
<tbody></tbody>
</table>
</fieldset>
</div>
</body>
</html>

3079
scripts/components.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
/*jshint esversion: 6 */
const TYPE_MONEY_REQUEST = "MoneyRequests",
TYPE_CASHIER_REQUEST = "CashierRequests",
TYPE_CASHIER_UPI = "CashierUPI";
@ -8,7 +9,7 @@ const cashierUPI = {};
const User = {};
const cashierStatus = {};
User.init = function() {
User.init = function () {
return new Promise((resolve, reject) => {
let promises;
//Request cashier for token-cash exchange
@ -44,7 +45,7 @@ User.init = function() {
})
}
User.getCashierUPI = function() {
User.getCashierUPI = function () {
return new Promise((resolve) => {
Promise.allSettled(floGlobals.subAdmins.map(cashierID => floCloudAPI.requestApplicationData(TYPE_CASHIER_UPI, {
senderID: cashierID,
@ -59,7 +60,7 @@ User.getCashierUPI = function() {
}
Object.defineProperty(User, 'cashierRequests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
senderID: myFloID,
group: "Cashiers",
@ -69,7 +70,7 @@ Object.defineProperty(User, 'cashierRequests', {
});
Object.defineProperty(User, 'moneyRequests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, {
receiverID: myFloID,
});
@ -77,7 +78,7 @@ Object.defineProperty(User, 'moneyRequests', {
}
});
User.findCashier = function() {
User.findCashier = function () {
let online = [];
for (let c in cashierStatus)
if (cashierStatus[c] && cashierUPI[c])
@ -88,38 +89,38 @@ User.findCashier = function() {
return online[floCrypto.randInt(0, online.length)];
}
User.cashToToken = function(cashier, amount, upiTxID) {
User.cashToToken = function (cashier, amount, upiTxID) {
return new Promise((resolve, reject) => {
if (!floGlobals.subAdmins.includes(cashier))
return reject("Invalid cashier");
floCloudAPI.sendGeneralData({
mode: "cash-to-token",
amount: amount,
upi_txid: upiTxID
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
mode: "cash-to-token",
amount: amount,
upi_txid: upiTxID
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.tokenToCash = function(cashier, amount, blkTxID, upiID) {
User.tokenToCash = function (cashier, amount, blkTxID, upiID) {
return new Promise((resolve, reject) => {
if (!floGlobals.subAdmins.includes(cashier))
return reject("Invalid cashier");
floCloudAPI.sendGeneralData({
mode: "token-to-cash",
amount: amount,
token_txid: blkTxID,
upi_id: upiID
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
mode: "token-to-cash",
amount: amount,
token_txid: blkTxID,
upi_id: upiID
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.sendToken = function(receiverID, amount, remark = '') {
User.sendToken = function (receiverID, amount, remark = '') {
return new Promise((resolve, reject) => {
tokenAPI.sendToken(myPrivKey, amount, receiverID, remark)
.then(result => resolve(result))
@ -127,30 +128,30 @@ User.sendToken = function(receiverID, amount, remark = '') {
})
}
User.requestToken = function(floID, amount, remark = '') {
User.requestToken = function (floID, amount, remark = '') {
return new Promise((resolve, reject) => {
floCloudAPI.sendGeneralData({
amount: amount,
remark: remark
}, TYPE_MONEY_REQUEST, {
receiverID: floID
}).then(result => resolve(result))
amount: amount,
remark: remark
}, TYPE_MONEY_REQUEST, {
receiverID: floID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.decideRequest = function(request, note) {
User.decideRequest = function (request, note) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, note, {
receiverID: myFloID
}).then(result => resolve(result))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
const Cashier = {};
Cashier.init = function() {
Cashier.init = function () {
return new Promise((resolve, reject) => {
let promises = [];
//Requests from user to cashier(self) for token-cash exchange
@ -171,18 +172,18 @@ Cashier.init = function() {
})
}
Cashier.updateUPI = function(upi_id) {
Cashier.updateUPI = function (upi_id) {
return new Promise((resolve, reject) => {
floCloudAPI.sendApplicationData({
upi: upi_id
}, TYPE_CASHIER_UPI)
upi: upi_id
}, TYPE_CASHIER_UPI)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
Object.defineProperty(Cashier, 'Requests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
receiverID: myFloID
});
@ -191,29 +192,29 @@ Object.defineProperty(Cashier, 'Requests', {
}
});
Cashier.finishRequest = function(request, txid) {
Cashier.finishRequest = function (request, txid) {
return new Promise((resolve, reject) => {
floCloudAPI.tagApplicationData(request.vectorClock, 'COMPLETED', {
receiverID: myFloID
}).then(result => {
floCloudAPI.noteApplicationData(request.vectorClock, txid, {
receiverID: myFloID
}).then(result => resolve(result))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
Cashier.rejectRequest = function(request, reason) {
Cashier.rejectRequest = function (request, reason) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, {
receiverID: myFloID
}).then(result => resolve(result))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
Cashier.checkIfUpiTxIsValid = function(upiTxID) {
Cashier.checkIfUpiTxIsValid = function (upiTxID) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)
@ -224,7 +225,7 @@ Cashier.checkIfUpiTxIsValid = function(upiTxID) {
})
}
Cashier.checkIfTokenTxIsValid = function(tokenTxID, sender, amount) {
Cashier.checkIfTokenTxIsValid = function (tokenTxID, sender, amount) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)

296
scripts/fn_ui.js Normal file
View File

@ -0,0 +1,296 @@
/*jshint esversion: 6 */
const userUI = {};
userUI.requestTokenFromCashier = function () {
let cashier = User.findCashier();
if (!cashier)
return alert("No cashier online");
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
//get UPI txid from user
let upiTxID = prompt(`Send Rs. ${amount} to ${cashierUPI[cashier]} and enter UPI txid`);
if (!upiTxID)
return alert("Cancelled");
User.cashToToken(cashier, amount, upiTxID).then(result => {
console.log(result);
alert("Requested cashier. please wait!");
}).catch(error => console.error(error))
}
userUI.withdrawCashFromCashier = function () {
let cashier = User.findCashier();
if (!cashier)
return alert("No cashier online");
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
//get confirmation from user
let upiID = prompt(`${amount} ${floGlobals.currency}# will be sent to ${cashier}. Enter UPI ID`);
if (!upiID)
return alert("Cancelled");
User.sendToken(cashier, amount, 'for token-to-cash').then(txid => {
console.warn(`Withdraw ${amount} from cashier ${cashier}`, txid);
User.tokenToCash(cashier, amount, txid, upiID).then(result => {
console.log(result);
alert("Requested cashier. please wait!");
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}
userUI.sendMoneyToUser = function (floID, amount, remark) {
getConfirmation('Confirm', { message: `Do you want to SEND ${amount} to ${floID}?` }).then(confirmation => {
if (confirmation) {
User.sendToken(floID, amount, "|" + remark).then(txid => {
console.warn(`Sent ${amount} to ${floID}`, txid);
notify(`Sent ${amount} to ${floID}. It may take a few mins to reflect in their wallet`, 'success');
hidePopup()
}).catch(error => console.error(error));
}
})
}
userUI.requestMoneyFromUser = function (floID, amount, remark) {
getConfirmation('Confirm', { message: `Do you want to REQUEST ${amount} from ${floID}?` }).then(confirmation => {
if (confirmation) {
User.requestToken(floID, amount, remark).then(result => {
console.log(`Requested ${amount} from ${floID}`, result);
notify(`Requested ${amount} from ${floID}`, 'success');
hidePopup()
}).catch(error => console.error(error));
}
})
}
userUI.renderCashierRequests = function (requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
const frag = document.createDocumentFragment()
for (let r in requests) {
let oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
frag.append(render.walletRequestCard(requests[r]))
}
getRef('user-cashier-requests').append(frag)
}
userUI.renderMoneyRequests = function (requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
const frag = document.createDocumentFragment()
for (let r in requests) {
let oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
frag.append(render.paymentRequestCard(requests[r]))
}
getRef('user-money-requests').append(frag)
}
userUI.payRequest = function (reqID) {
let request = User.moneyRequests[reqID];
getConfirmation('Pay?', { message: `Do you want to pay ${request.message.amount} to ${request.senderID}?` }).then(confirmation => {
if (confirmation) {
User.sendToken(request.senderID, request.message.amount, "|" + request.message.remark).then(txid => {
console.warn(`Sent ${request.message.amount} to ${request.senderID}`, txid);
notify(`Sent ${request.message.amount} to ${request.senderID}. It may take a few mins to reflect in their wallet`, 'success');
User.decideRequest(request, 'PAID: ' + txid)
.then(result => console.log(result))
.catch(error => console.error(error))
}).catch(error => console.error(error));
}
})
}
userUI.declineRequest = function (reqID) {
let request = User.moneyRequests[reqID];
getConfirmation('Decline payment?').then(confirmation => {
if (confirmation) {
User.decideRequest(request, "DECLINED").then(result => {
console.log(result);
notify("Request declined", 'success');
}).catch(error => console.error(error))
}
})
}
//Cashier
const cashierUI = {};
cashierUI.renderRequests = function (requests, error = null) {
if (error)
return console.error(error);
else if (typeof requests !== "object" || requests === null)
return;
const frag = document.createDocumentFragment();
for (let r in requests) {
const oldCard = document.getElementById(r);
if (oldCard) oldCard.remove();
frag.append(render.cashierRequestCard(requests[r]));
}
getRef('cashier_request_list').append(frag)
}
cashierUI.completeRequest = function (reqID) {
let request = Cashier.Requests[reqID];
if (request.message.mode === "cash-to-token")
completeCashToTokenRequest(request);
else if (request.message.mode === "token-to-cash")
completeTokenToCashRequest(request);
}
function completeCashToTokenRequest(request) {
Cashier.checkIfUpiTxIsValid(request.message.upi_txid).then(_ => {
let confirmation = confirm(`Check if you have received UPI transfer\ntxid:${request.message.upi_txid}\namount:${request.message.amount}`);
if (!confirmation)
return alert("Cancelled");
User.sendToken(request.senderID, request.message.amount, 'for cash-to-token').then(txid => {
console.warn(`${request.message.amount} cash-to-token for ${request.senderID}`, txid);
Cashier.finishRequest(request, txid).then(result => {
console.log(result);
console.info('Completed cash-to-token request:', request.vectorClock);
alert("Completed request");
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
alert(error);
if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string')
Cashier.rejectRequest(request, error[1]).then(result => {
console.log(result);
console.info('Rejected cash-to-token request:', request.vectorClock);
}).catch(error => console.error(error))
})
}
function completeTokenToCashRequest(request) {
Cashier.checkIfTokenTxIsValid(request.message.token_txid, request.senderID, request.message.amount).then(result => {
let upiTxID = prompt(`Token transfer is verified!\n Send ${request.message.amount} to ${request.message.upi_id} and Enter UPI txid`);
if (!upiTxID)
return alert("Cancelled");
Cashier.finishRequest(request, upiTxID).then(result => {
console.log(result);
console.info('Completed token-to-cash request:', request.vectorClock);
alert("Completed request");
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
alert(error);
if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string')
Cashier.rejectRequest(request, error[1]).then(result => {
console.log(result);
console.info('Rejected token-to-cash request:', request.vectorClock);
}).catch(error => console.error(error))
})
}
function renderAllTokenTransactions() {
tokenAPI.getAllTxs(myFloID).then(result => {
getRef('token_transactions').innerHTML = ''
const frag = document.createDocumentFragment();
for (let txid in result.transactions) {
frag.append(render.transactionCard(txid, tokenAPI.util.parseTxData(result.transactions[txid])))
}
getRef('token_transactions').append(frag)
}).catch(error => console.error(error))
}
const render = {
transactionCard(txid, transactionDetails) {
const { time, sender, receiver, tokenAmount } = transactionDetails
const clone = getRef('transaction_template').content.cloneNode(true).firstElementChild;
clone.dataset.txid = txid
clone.querySelector('.transaction__time').textContent = getFormattedTime(time * 1000)
clone.querySelector('.transaction__amount').textContent = tokenAmount
if (sender === myFloID) {
clone.querySelector('.transaction__amount').classList.add('sent')
clone.querySelector('.transaction__receiver').textContent = `Sent to ${receiver || 'Myself'}`
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>`
} else if (receiver === myFloID) {
clone.querySelector('.transaction__amount').classList.add('received')
clone.querySelector('.transaction__receiver').textContent = `Received from ${sender}`
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>`
} else { //This should not happen unless API returns transaction that does not involve myFloID
row.insertCell().textContent = tx.sender;
row.insertCell().textContent = tx.receiver;
}
return clone
},
cashierRequestCard(details) {
const { time, senderID, message: { mode }, note, tag, vectorClock } = details;
const clone = getRef('cashier_request_template').content.cloneNode(true).firstElementChild;
clone.id = vectorClock
const status = tag || note; //status tag for completed, note for rejected
clone.querySelector('.cashier-request__requestor').textContent = senderID
clone.querySelector('.cashier-request__time').textContent = getFormattedTime(time)
clone.querySelector('.cashier-request__mode').textContent = mode
if (status)
clone.querySelector('.cashier-request__status').textContent = status
else
clone.querySelector('.cashier-request__status').innerHTML = `<button class="button" onclick="cashierUI.completeRequest('${vectorClock}')">Process</button>`
return clone
},
walletRequestCard(details) {
const { time, receiverID, message: { mode }, note, tag, vectorClock } = details;
const clone = getRef('wallet_request_template').content.cloneNode(true).firstElementChild;
clone.id = vectorClock
clone.querySelector('.wallet-request__requestor').textContent = receiverID
clone.querySelector('.wallet-request__time').textContent = getFormattedTime(time)
clone.querySelector('.wallet-request__mode').textContent = mode === 'cash-to-token' ? 'Deposit' : 'Withdraw'
let status = tag ? (tag + ":" + note) : (note || "PENDING");
clone.querySelector('.wallet-request__status').textContent = status
return clone
},
paymentRequestCard(details) {
const { time, senderID, message: { amount, remark }, note, vectorClock } = details;
const clone = getRef('payment_request_template').content.cloneNode(true).firstElementChild;
clone.id = vectorClock
clone.querySelector('.payment-request__requestor').textContent = senderID
clone.querySelector('.payment-request__time').textContent = getFormattedTime(time)
clone.querySelector('.payment-request__amount').textContent = amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' })
clone.querySelector('.payment-request__remark').textContent = remark
let status = note;
if (status)
clone.querySelector('.payment-request__actions').textContent = note;
else
clone.querySelector('.payment-request__actions').innerHTML =
`<button class="button" onclick="userUI.payRequest('${vectorClock}')">Pay</button>
<button class="button" onclick="userUI.declineRequest('${vectorClock}')">Decline</button>`;
return clone
},
}
let currentUserAction
function showTokenTransfer(type) {
getRef('tt_button').textContent = type;
currentUserAction = type
if (type === 'send') {
getRef('token_transfer__title').textContent = 'Send money to FLO ID';
} else {
getRef('token_transfer__title').textContent = 'Request money from FLO ID';
}
showPopup('token_transfer_popup')
}
function executeUserAction() {
const floID = getRef('tt_flo_id').value.trim(),
amount = parseFloat(getRef('tt_amount').value),
remark = getRef('tt_remark').value.trim();
if (currentUserAction === 'send') {
userUI.sendMoneyToUser(floID, amount, remark)
} else {
userUI.requestMoneyFromUser(floID, amount, remark)
}
}
function signOut() {
getConfirmation('Sign out?', 'You are about to sign out of the app, continue?', 'Stay', 'Leave')
.then(async (res) => {
if (res) {
await floDapps.clearCredentials()
location.reload()
}
})
}

File diff suppressed because one or more lines are too long

507
scripts/std_ui.js Normal file
View File

@ -0,0 +1,507 @@
/*jshint esversion: 6 */
// Global variables
const domRefs = {};
const currentYear = new Date().getFullYear();
//Checks for internet connection status
if (!navigator.onLine)
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error"
);
window.addEventListener("offline", () => {
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error",
{ pinned: true }
);
});
window.addEventListener("online", () => {
getRef("notification_drawer").clearAll();
notify("We are back online.", "success");
});
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
}
}
// returns dom with specified element
function createElement(tagName, options = {}) {
const { className, textContent, innerHTML, attributes = {} } = options
const elem = document.createElement(tagName)
for (let attribute in attributes) {
elem.setAttribute(attribute, attributes[attribute])
}
if (className)
elem.className = className
if (textContent)
elem.textContent = textContent
if (innerHTML)
elem.innerHTML = innerHTML
return elem
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
let zIndex = 10
// function required for popups or modals to appear
function showPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
getRef(popupId).show({ pinned })
return getRef(popupId);
}
// hides the popup or modal
function hidePopup() {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide()
}
document.addEventListener('popupopened', async e => {
switch (e.target.id) {
case 'saved_ids_popup':
const frag = document.createDocumentFragment()
const allSavedIds = await getArrayOfSavedIds()
allSavedIds.forEach(({ floID, name }) => {
frag.append(render.savedIdPickerCard(floID, name))
})
getRef('saved_ids_picker_list').innerHTML = ''
getRef('saved_ids_picker_list').append(frag)
getRef('search_saved_ids_picker').focusIn()
break;
case 'get_private_key_popup':
break;
}
})
document.addEventListener('popupclosed', e => {
zIndex--
switch (e.target.id) {
case 'saved_ids_popup':
getRef('saved_ids_picker_list').innerHTML = ''
getRef('search_saved_ids_picker').value = ''
break;
case 'get_private_key_popup':
getRef('get_private_key').classList.remove('hide')
getRef('transaction_result').classList.add('hide')
getRef('confirm_transaction_button').classList.remove('hide')
getRef('confirm_transaction_button').nextElementSibling.classList.add('hide')
break;
case 'retrieve_flo_id_popup':
getRef('recovered_flo_id_wrapper').classList.add('hide')
break;
}
})
// displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, options = {}) => {
return new Promise(resolve => {
const { message, cancelText = 'Cancel', confirmText = 'OK' } = options
showPopup('confirmation_popup', true)
getRef('confirm_title').textContent = title;
getRef('confirm_message').textContent = message;
let cancelButton = getRef('confirmation_popup').children[2].children[0],
submitButton = getRef('confirmation_popup').children[2].children[1]
submitButton.textContent = confirmText
cancelButton.textContent = cancelText
submitButton.onclick = () => {
hidePopup()
resolve(true);
}
cancelButton.onclick = () => {
hidePopup()
resolve(false);
}
})
}
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
function notify(message, mode, options = {}) {
const { pinned = false, sound = false } = options
let icon
switch (mode) {
case 'success':
icon = `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
break;
case 'error':
icon = `<svg class="icon icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>`
break;
}
getRef("notification_drawer").push(message, { pinned, icon });
if (mode === 'error') {
console.error(message)
}
}
function getFormattedTime(time, format) {
try {
if (String(time).indexOf('_'))
time = String(time).split('_')[0]
const intTime = parseInt(time)
if (String(intTime).length < 13)
time *= 1000
let [day, month, date, year] = new Date(intTime).toString().split(' '),
minutes = new Date(intTime).getMinutes(),
hours = new Date(intTime).getHours(),
currentTime = new Date().toString().split(' ')
minutes = minutes < 10 ? `0${minutes}` : minutes
let finalHours = ``;
if (hours > 12)
finalHours = `${hours - 12}:${minutes}`
else if (hours === 0)
finalHours = `12:${minutes}`
else
finalHours = `${hours}:${minutes}`
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
switch (format) {
case 'date-only':
return `${month} ${date}, ${year}`;
break;
default:
return `${month} ${date} ${year}, ${finalHours}`;
}
} catch (e) {
console.error(e);
return time;
}
}
// implement event delegation
function delegate(el, event, selector, fn) {
el.addEventListener(event, function (e) {
const potentialTarget = e.target.closest(selector)
if (potentialTarget) {
e.delegateTarget = potentialTarget
fn.call(this, e)
}
})
}
window.addEventListener('hashchange', e => showPage(window.location.hash))
window.addEventListener("load", () => {
document.body.classList.remove('hide')
document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = floCrypto.validateAddr)
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => {
if (e.key === 'Escape') {
hidePopup()
}
})
document.addEventListener('copy', () => {
notify('copied', 'success')
})
document.addEventListener("pointerdown", (e) => {
if (e.target.closest("button:not([disabled]), sm-button:not([disabled]), .interact")) {
createRipple(e, e.target.closest("button, sm-button, .interact"));
}
});
});
function createRipple(event, target) {
const circle = document.createElement("span");
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
const targetDimensions = target.getBoundingClientRect();
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`;
circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`;
circle.classList.add("ripple");
const rippleAnimation = circle.animate(
[
{
transform: "scale(4)",
opacity: 0,
},
],
{
duration: 600,
fill: "forwards",
easing: "ease-out",
}
);
target.append(circle);
rippleAnimation.onfinish = () => {
circle.remove();
};
}
const pagesData = {
params: {}
}
let tempData
async function showPage(targetPage, options = {}) {
const { firstLoad, hashChange, isPreview } = options
let pageId
let params = {}
let searchParams
if (targetPage === '') {
pageId = 'home'
} else {
if (targetPage.includes('/')) {
if (targetPage.includes('?')) {
const splitAddress = targetPage.split('?')
searchParams = splitAddress.pop()
const pages = splitAddress.pop().split('/')
pageId = pages[1]
subPageId = pages[2]
} else {
const pages = targetPage.split('/')
pageId = pages[1]
subPageId = pages[2]
}
} else {
pageId = targetPage
}
}
if (searchParams) {
const urlSearchParams = new URLSearchParams('?' + searchParams);
params = Object.fromEntries(urlSearchParams.entries());
}
if (pagesData.lastPage !== pageId) {
pagesData.lastPage = pageId
}
if (params)
pagesData.params = params
switch (pageId) {
case 'transactions':
break;
default:
}
const animOptions = {
duration: 100,
fill: 'forwards',
}
let previousActiveElement = getRef('main_navbar').querySelector('.nav-item--active')
const currentActiveElement = document.querySelector(`.nav-item[href="#/${pageId}"]`)
if (currentActiveElement) {
if (getRef('main_navbar').classList.contains('hide')) {
getRef('main_navbar').classList.remove('hide-away')
getRef('main_navbar').classList.remove('hide')
getRef('main_navbar').animate([
{
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
opacity: 0,
},
{
transform: `none`,
opacity: 1,
},
], {
duration: 100,
fill: 'forwards',
easing: 'ease'
})
}
getRef('main_header').classList.remove('hide')
const previousActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(previousActiveElement)
const currentActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(currentActiveElement)
const isOnTop = previousActiveElementIndex < currentActiveElementIndex
const currentIndicator = createElement('div', { className: 'nav-item__indicator' });
let previousIndicator = getRef('main_navbar').querySelector('.nav-item__indicator')
if (!previousIndicator) {
previousIndicator = currentIndicator.cloneNode(true)
previousActiveElement = currentActiveElement
previousActiveElement.append(previousIndicator)
} else if (currentActiveElementIndex !== previousActiveElementIndex) {
const indicatorDimensions = previousIndicator.getBoundingClientRect()
const currentActiveElementDimensions = currentActiveElement.getBoundingClientRect()
let moveBy
if (isMobileView) {
moveBy = ((currentActiveElementDimensions.width - indicatorDimensions.width) / 2) + indicatorDimensions.width
} else {
moveBy = ((currentActiveElementDimensions.height - indicatorDimensions.height) / 2) + indicatorDimensions.height
}
indicatorObserver.observe(previousIndicator)
previousIndicator.animate([
{
transform: 'none',
opacity: 1,
},
{
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `${moveBy}px` : `-${moveBy}px`})`,
opacity: 0,
},
], { ...animOptions, easing: 'ease-in' }).onfinish = () => {
previousIndicator.remove()
}
tempData = {
currentActiveElement,
currentIndicator,
isOnTop,
animOptions,
moveBy
}
}
previousActiveElement.classList.remove('nav-item--active');
currentActiveElement.classList.add('nav-item--active')
} else {
if (!getRef('main_navbar').classList.contains('hide')) {
getRef('main_navbar').classList.add('hide-away')
getRef('main_navbar').animate([
{
transform: `none`,
opacity: 1,
},
{
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
opacity: 0,
},
], {
duration: 200,
fill: 'forwards',
easing: 'ease'
}).onfinish = () => {
getRef('main_navbar').classList.add('hide')
}
getRef('main_header').classList.add('hide')
}
}
document.querySelectorAll('.page').forEach(page => page.classList.add('hide'))
getRef(pageId).classList.remove('hide')
getRef(pageId).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards', easing: 'ease' })
}
const indicatorObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) {
const { currentActiveElement, currentIndicator, isOnTop, animOptions, moveBy } = tempData
currentActiveElement.append(currentIndicator)
currentIndicator.animate([
{
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `-${moveBy}px` : `${moveBy}px`})`,
opacity: 0,
},
{
transform: 'none',
opacity: 1
},
], { ...animOptions, easing: 'ease-out' })
}
})
}, {
threshold: 1
})
// class based lazy loading
class LazyLoader {
constructor(container, elementsToRender, renderFn, options = {}) {
const { batchSize = 10, freshRender } = options
this.elementsToRender = elementsToRender
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.renderFn = renderFn
this.intersectionObserver
this.batchSize = batchSize
this.freshRender = freshRender
this.lazyContainer = document.querySelector(container)
this.update = this.update.bind(this)
this.render = this.render.bind(this)
this.init = this.init.bind(this)
this.clear = this.clear.bind(this)
}
init() {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.disconnect()
this.render({ lazyLoad: true })
}
})
}, {
threshold: 0.3
})
this.mutationObserver = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length) {
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
}
}
})
})
this.mutationObserver.observe(this.lazyContainer, {
childList: true,
})
this.render()
}
update(elementsToRender) {
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.render()
}
render(options = {}) {
let { lazyLoad = false } = options
const frag = document.createDocumentFragment();
if (lazyLoad) {
this.updateStartIndex = this.updateEndIndex
this.updateEndIndex = this.arrayOfElements.length > this.updateEndIndex + this.batchSize ? this.updateEndIndex + this.batchSize : this.arrayOfElements.length
} else {
this.intersectionObserver.disconnect()
this.lazyContainer.innerHTML = ``;
this.updateStartIndex = 0
this.updateEndIndex = this.arrayOfElements.length > this.batchSize ? this.batchSize : this.arrayOfElements.length
}
for (let index = this.updateStartIndex; index < this.updateEndIndex; index++) {
frag.append(this.renderFn(this.arrayOfElements[index]))
}
this.lazyContainer.append(frag)
// Callback to be called if elements are updated or rendered for first time
if (!lazyLoad && this.freshRender)
this.freshRender()
}
clear() {
this.intersectionObserver.disconnect()
this.mutationObserver.disconnect()
this.lazyContainer.innerHTML = ``;
}
reset() {
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
this.render()
}
}
function animateTo(element, keyframes, options) {
const anime = element.animate(keyframes, { ...options, fill: 'both' })
anime.finished.then(() => {
anime.commitStyles()
anime.cancel()
})
return anime
}
let isMobileView = false
const mobileQuery = window.matchMedia('(max-width: 40rem)')
function handleMobileChange(e) {
isMobileView = e.matches
}
mobileQuery.addEventListener('change', handleMobileChange)
handleMobileChange(mobileQuery)