/*jshint esversion: 8 */ /** * @yaireo/relative-time - javascript function to transform timestamp or date to local relative-time * * @version v1.0.0 * @homepage https://github.com/yairEO/relative-time */ !function (e, t) { var o = o || {}; "function" == typeof o && o.amd ? o([], t) : "object" == typeof exports && "object" == typeof module ? module.exports = t() : "object" == typeof exports ? exports.RelativeTime = t() : e.RelativeTime = t() }(this, (function () { const e = { year: 31536e6, month: 2628e6, day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 }, t = "en", o = { numeric: "auto" }; function n(e) { e = { locale: (e = e || {}).locale || t, options: { ...o, ...e.options } }, this.rtf = new Intl.RelativeTimeFormat(e.locale, e.options) } return n.prototype = { from(t, o) { const n = t - (o || new Date); for (let t in e) if (Math.abs(n) > e[t] || "second" == t) return this.rtf.format(Math.round(n / e[t]), t) } }, n })); const relativeTime = new RelativeTime({ style: 'narrow' }); const userUI = {}; getRef('wallet_popup__cta').addEventListener('click', function () { let amount = parseFloat(getRef('request_cashier_amount').value.trim()); if (walletAction === 'deposit') { //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)) } else { //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)) } }) function walletAction(type) { let cashier = User.findCashier(); if (!cashier) return notify("No cashier online. Please try again in a while.", 'error'); showPopup('wallet_popup') } 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; if (pagesData.lastPage === 'history' && pagesData.params.type === 'wallet') { const frag = document.createDocumentFragment() for (let transactionID in requests) { let oldCard = getRef('wallet_history').querySelector(`#${transactionID}`); if (oldCard) oldCard.remove(); frag.append(render.walletRequestCard(transactionID, requests[transactionID])) } getRef('wallet_history').prepend(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.renderSavedIds = async function () { floGlobals.savedIds = {} const frag = document.createDocumentFragment() const savedIds = await floCloudAPI.requestApplicationData('savedIds', { mostRecent: true, senderIDs: [myFloID], receiverID: myFloID }); if (savedIds.length && await compactIDB.readData('savedIds', 'lastSyncTime') !== savedIds[0].time) { await compactIDB.clearData('savedIds'); const dataToDecrypt = floCloudAPI.util.decodeMessage(savedIds[0].message) const data = JSON.parse(Crypto.AES.decrypt(dataToDecrypt, myPrivKey)); for (let key in data) { floGlobals.savedIds[key] = data[key]; compactIDB.addData('savedIds', data[key], key); } compactIDB.addData('savedIds', savedIds[0].time, 'lastSyncTime'); } else { const idsToRender = await compactIDB.readAllData('savedIds'); for (const key in idsToRender) { if (key !== 'lastSyncTime') floGlobals.savedIds[key] = idsToRender[key]; } } for (const key in floGlobals.savedIds) { frag.append(render.savedId(key, floGlobals.savedIds[key])); } getRef('saved_ids_list').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 getFloIdTitle(floID) { return floGlobals.savedIds[floID] ? floGlobals.savedIds[floID].title : floID; } function formatAmount(amount) { return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' }) } function getStatusIcon(status) { switch (status) { case 'PENDING': return ''; case 'COMPLETED': return ''; case 'REJECTED': return ''; default: break; } } const render = { savedId(floID, details) { const { title } = details.hasOwnProperty('title') ? details : { title: details }; const clone = getRef('saved_id_template').content.cloneNode(true).firstElementChild; clone.dataset.floId = floID; clone.querySelector('.saved-id__initials').textContent = title.charAt(0); clone.querySelector('.saved-id__title').textContent = title; return clone; }, transactionCard(transactionDetails) { const { txid, 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 = formatAmount(tokenAmount); if (sender === myFloID) { clone.classList.add('sent'); clone.querySelector('.transaction__receiver').textContent = `Sent to ${getFloIdTitle(receiver) || 'Myself'}`; clone.querySelector('.transaction__icon').innerHTML = ``; } else if (receiver === myFloID) { clone.classList.add('received'); clone.querySelector('.transaction__receiver').textContent = `Received from ${getFloIdTitle(sender)}`; clone.querySelector('.transaction__icon').innerHTML = ``; } 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 = ``; return clone; }, walletRequestCard(details) { const { time, message: { mode, amount }, note, tag, vectorClock } = details; const clone = getRef('wallet_request_template').content.cloneNode(true).firstElementChild; clone.id = vectorClock; clone.querySelector('.wallet-request__details').textContent = `${mode === 'cash-to-token' ? 'Deposit' : 'Withdraw'} ${formatAmount(amount)}`; clone.querySelector('.wallet-request__time').textContent = getFormattedTime(time); let status = tag ? tag : (note ? 'REJECTED' : "PENDING"); let icon = ''; switch (status) { case 'COMPLETED': clone.children[1].append( createElement('div', { className: 'flex flex-wrap align-center wallet-request__note', innerHTML: `Transaction ID:` }) ); icon = `` break; case 'REJECTED': clone.children[1].append( createElement('div', { className: 'wallet-request__note', innerHTML: note.split(':')[1] }) ); icon = `` break; case 'PENDING': icon = `` break; default: break; } clone.querySelector('.wallet-request__status').innerHTML = `${icon}${status}`; clone.querySelector('.wallet-request__status').classList.add(status.toLowerCase()); 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 = ` `; return clone; }, transactionMessage(details) { const { tokenAmount, time, sender, receiver } = tokenAPI.util.parseTxData(details) let messageType = sender === receiver ? 'self' : sender === myFloID ? 'sent' : 'received'; const clone = getRef('transaction_message_template').content.cloneNode(true).firstElementChild; clone.classList.add(messageType); clone.querySelector('.transaction-message__amount').textContent = formatAmount(tokenAmount); clone.querySelector('.transaction-message__time').textContent = getFormattedTime(time * 1000); 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'); } async function saveId() { const floID = getRef('flo_id_to_save').value.trim(); const title = getRef('flo_id_title_to_save').value.trim(); floGlobals.savedIds[floID] = { title } getRef('saved_ids_list').append(render.savedId(floID, { title })); syncSavedIds().then(() => { notify(`Saved ${floID}`, 'success'); hidePopup(); }).catch(error => { notify(error, 'error'); }) } function syncSavedIds() { const dataToSend = Crypto.AES.encrypt(JSON.stringify(floGlobals.savedIds), myPrivKey); return floCloudAPI.sendApplicationData(dataToSend, 'savedIds', { receiverID: myFloID }); } delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => { if (e.target.closest('.edit-saved')) { const target = e.target.closest('.saved-id'); getRef('edit_saved_id').setAttribute('value', target.dataset.floId); getRef('newAddrLabel').value = getFloIdTitle(target.dataset.floId); showPopup('edit_saved_popup'); } else { const target = e.target.closest('.saved-id'); window.location.hash = `#/contact?floId=${target.dataset.floId}`; } }); function deleteSaved() { getConfirmation('Do you want delete this FLO ID?', { confirmText: 'Delete', }).then(res => { if (res) { const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${getRef('edit_saved_id').value}"]`); if (toDelete) toDelete.remove(); delete floGlobals.savedIds[getRef('edit_saved_id').value]; hidePopup(); syncSavedIds().then(() => { notify(`Deleted saved ID`, 'success'); }).catch(error => { notify(error, 'error'); }); } }); } 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 getSignedIn() { return new Promise((resolve, reject) => { if (window.location.hash.includes('sign_in') || window.location.hash.includes('sign_up')) { showPage(window.location.hash); } else { location.hash = `#/sign_in`; } getRef('sign_in_button').onclick = () => { resolve(getRef('private_key_field').value.trim()); getRef('private_key_field').value = ''; showPage('loading'); }; getRef('sign_up_button').onclick = () => { resolve(getRef('generated_private_key').value.trim()); getRef('generated_private_key').value = ''; showPage('loading'); }; }); } 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(); } }); }