Merge pull request #9 from ranchimall/new-cloud

New cloud
This commit is contained in:
Sai Raj 2022-03-11 21:26:55 +05:30 committed by GitHub
commit f536bc53ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 15498 additions and 22241 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

15077
index.html

File diff suppressed because it is too large Load Diff

3079
scripts/components.js Normal file

File diff suppressed because it is too large Load Diff

251
scripts/fn_pay.js Normal file
View File

@ -0,0 +1,251 @@
/*jshint esversion: 6 */
const TYPE_MONEY_REQUEST = "MoneyRequests",
TYPE_CASHIER_REQUEST = "CashierRequests",
TYPE_CASHIER_UPI = "CashierUPI";
const cashierUPI = {};
//For regular users
const User = {};
const cashierStatus = {};
User.init = function () {
return new Promise((resolve, reject) => {
let promises;
//Request cashier for token-cash exchange
promises = floGlobals.subAdmins.map(cashierID => floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
senderID: myFloID,
receiverID: cashierID,
group: "Cashiers",
callback: userUI.renderCashierRequests //UI_fn
}));
//Request received from other Users for token
promises.push(floCloudAPI.requestGeneralData(TYPE_MONEY_REQUEST, {
receiverID: myFloID,
callback: userUI.renderMoneyRequests //UI_fn
}));
//Check online status of cashiers
promises.push(floCloudAPI.requestStatus(Array.from(floGlobals.subAdmins), {
callback: (d, e) => {
if (e) return console.error(e);
for (let i in d)
cashierStatus[i] = d[i];
//Add any UI_fn if any
}
}))
/*
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
callback: UI_RENDER_FN
}));
*/
promises.push(User.getCashierUPI());
Promise.all(promises)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
User.getCashierUPI = function () {
return new Promise((resolve) => {
Promise.allSettled(floGlobals.subAdmins.map(cashierID => floCloudAPI.requestApplicationData(TYPE_CASHIER_UPI, {
senderID: cashierID,
mostRecent: true
}))).then(result => {
for (let r of result)
if (r.status === "fulfilled" && r.value.length)
cashierUPI[r.value[0].senderID] = floCloudAPI.util.decodeMessage(r.value[0].message).upi;
resolve(cashierUPI);
})
})
}
Object.defineProperty(User, 'cashierRequests', {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
senderID: myFloID,
group: "Cashiers",
});
return floGlobals.generalData[fk];
}
});
Object.defineProperty(User, 'moneyRequests', {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, {
receiverID: myFloID,
});
return floGlobals.generalData[fk];
}
});
User.findCashier = function () {
let online = [];
for (let c in cashierStatus)
if (cashierStatus[c] && cashierUPI[c])
online.push(c);
if (!online.length)
return null;
else
return online[floCrypto.randInt(0, online.length)];
}
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))
.catch(error => reject(error))
})
}
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))
.catch(error => reject(error))
})
}
User.sendToken = function (receiverID, amount, remark = '') {
return new Promise((resolve, reject) => {
tokenAPI.sendToken(myPrivKey, amount, receiverID, remark)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
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))
.catch(error => reject(error))
})
}
User.decideRequest = function (request, note) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, note, {
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
const Cashier = {};
Cashier.init = function () {
return new Promise((resolve, reject) => {
let promises = [];
//Requests from user to cashier(self) for token-cash exchange
promises.push(floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
receiverID: myFloID,
callback: cashierUI.renderRequests //UI_fn
}));
//Set online status of cashier(self)
promises.push(floCloudAPI.setStatus());
/*
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
callback: UI_RENDER_FN
}));
*/
Promise.all(promises)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
Cashier.updateUPI = function (upi_id) {
return new Promise((resolve, reject) => {
floCloudAPI.sendApplicationData({
upi: upi_id
}, TYPE_CASHIER_UPI)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
Object.defineProperty(Cashier, 'Requests', {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
receiverID: myFloID
});
console.debug(fk, floGlobals.generalData[fk]);
return floGlobals.generalData[fk];
}
});
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))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
Cashier.rejectRequest = function (request, reason) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, {
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
Cashier.checkIfUpiTxIsValid = function (upiTxID) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)
if (requests[r].message.mode === "cash-to-token" && requests[r].note)
if (requests[r].message.upi_txid === upiTxID)
return reject([true, "UPI transaction is already used for another request"]);
return resolve(true);
})
}
Cashier.checkIfTokenTxIsValid = function (tokenTxID, sender, amount) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)
if (requests[r].message.mode === "token-to-cash" && requests[r].note)
if (requests[r].message.token_txid === tokenTxID)
return reject([true, "Token transaction is already used for another request"]);
tokenAPI.getTx(tokenTxID).then(tx => {
let parsedTxData = tokenAPI.util.parseTxData(tx);
console.debug(parsedTxData);
if (parsedTxData.type !== "transfer" || parsedTxData.transferType !== "token")
reject([true, "Invalid token transfer type"]);
else if (parsedTxData.tokenAmount !== amount)
reject([true, "Incorrect token amount: " + parsedTxData.tokenAmount]);
else if (parsedTxData.tokenIdentification !== floGlobals.currency)
reject([true, "Incorrect token: " + parsedTxData.tokenIdentification]);
else if (parsedTxData.sender !== sender)
reject([true, "Incorrect senderID: " + parsedTxData.sender]);
else if (parsedTxData.receiver !== myFloID)
reject([true, "Incorrect receiverID: " + parsedTxData.receive])
else resolve(true);
}).catch(error => reject([null, error]))
})
}

305
scripts/fn_ui.js Normal file
View File

@ -0,0 +1,305 @@
/*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 changeUpi() {
const upiID = getRef('upi_id').value.trim()
Cashier.updateUPI(upiID).then(() => {
notify('UPI ID updated successfully', 'success')
}).catch(err => {
notify(err, 'error')
})
}
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

9521
scripts/std_op.js Normal file

File diff suppressed because it is too large Load Diff

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)