"use strict"; // Global variables const appPages = ['dashboard', 'settings']; // Global variables const { html, render: renderElem } = uhtml; //Checks for internet connection status if (!navigator.onLine) floGlobals.connectionErrorNotification = notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error') window.addEventListener('offline', () => { floGlobals.connectionErrorNotification = notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error') }) window.addEventListener('online', () => { getRef('notification_drawer').remove(floGlobals.connectionErrorNotification) notify('We are back online.', 'success') }) // Use instead of document.getElementById const domRefs = {}; 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; } } } // 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); }; } //Function for displaying toast notifications. pass in error for mode param if you want to show an error. function notify(message, mode, options = {}) { let icon switch (mode) { case 'success': icon = `` break; case 'error': icon = `` options.pinned = true break; } if (mode === 'error') { console.error(message) } return getRef("notification_drawer").push(message, { icon, ...options }); } function getFormattedTime(timestamp, format) { try { timestamp = parseInt(timestamp) if (String(timestamp).length < 13) timestamp *= 1000 let [day, month, date, year] = new Date(timestamp).toString().split(' '), minutes = new Date(timestamp).getMinutes(), hours = new Date(timestamp).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}`; case 'time-only': return finalHours; case 'relative': return relativeTime.from(timestamp) default: return `${month} ${date}, ${year} at ${finalHours}`; } } catch (e) { console.error(e); return timestamp; } } window.addEventListener('hashchange', e => routeTo(window.location.hash)) window.addEventListener("load", () => { document.body.classList.remove('hidden') document.addEventListener("pointerdown", (e) => { if (e.target.closest("button, .interact")) { createRipple(e, e.target.closest("button, .interact")); } }); document.addEventListener('copy', () => { notify('copied', 'success') }) document.addEventListener('keydown', e => { if (e.key === '/') { e.preventDefault(); getRef('search_payments').focusIn() } }) getRef('search_payments').addEventListener('input', e => { const searchQuery = e.target.value.toLowerCase(); const filteredInterns = [] floGlobals.internTxs.forEach((intern, floId) => { if (floId.toLowerCase().includes(searchQuery) || floGlobals.appObjects.RIBC.internList[floId].toLowerCase().includes(searchQuery)) filteredInterns.push({ floId, name: floGlobals.appObjects.RIBC.internList[floId] }) }) // sort filtered by relevance to search query (name first, then floId) filteredInterns.sort((a, b) => { if (a.name.toLowerCase().includes(searchQuery) && b.name.toLowerCase().includes(searchQuery)) { return a.name.toLowerCase().indexOf(searchQuery) - b.name.toLowerCase().indexOf(searchQuery) } else if (a.name.toLowerCase().includes(searchQuery)) { return -1 } else if (b.name.toLowerCase().includes(searchQuery)) { return 1 } else { return a.floId.toLowerCase().indexOf(searchQuery) - b.floId.toLowerCase().indexOf(searchQuery) } }) renderElem(getRef("intern_payment_list"), html`${filteredInterns.map(intern => render.internCard(intern.floId))}`); }) }); 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(3)", opacity: 0, }, ], { duration: 1000, fill: "forwards", easing: "ease-out", } ); target.append(circle); rippleAnimation.onfinish = () => { circle.remove(); }; } const appState = { params: {}, } function routeTo(targetPage) { const routingAnimation = { in: slideInUp, out: slideOutUp } let pageId let subPageId1 let searchParams let params if (targetPage === '') { pageId = 'home' history.replaceState(null, null, '#/home'); } else { if (targetPage.includes('/')) { if (targetPage.includes('?')) { const splitAddress = targetPage.split('?') searchParams = splitAddress.pop(); [, pageId, subPageId1] = splitAddress.pop().split('/') } else { [, pageId, subPageId1] = targetPage.split('/') } } else { pageId = targetPage } } if (!getRef(pageId)?.classList.contains('page')) return appState.currentPage = pageId if (searchParams) { const urlSearchParams = new URLSearchParams('?' + searchParams); params = Object.fromEntries(urlSearchParams.entries()); } if (params) appState.params = params switch (pageId) { case 'intern': if (params && params.id) { render.intern(params.id) } break; } switch (appState.lastPage) { case 'intern': routingAnimation.in = slideInRight; routingAnimation.out = slideOutRight; break; } switch (pageId) { case 'intern': routingAnimation.in = slideInLeft; routingAnimation.out = slideOutLeft; break; } if (appState.lastPage !== pageId) { document.querySelectorAll('.page').forEach(page => page.classList.add('hidden')) getRef(pageId).closest('.page').classList.remove('hidden') if (appState.lastPage) { getRef(appState.lastPage).animate(routingAnimation.out, { duration: floGlobals.prefersReducedMotion ? 0 : 150, fill: 'forwards', easing: 'ease' }).onfinish = (e) => { e.target.effect.target.classList.add('hidden') } } getRef(pageId).classList.remove('hidden') getRef(pageId).animate(routingAnimation.in, { duration: floGlobals.prefersReducedMotion ? 0 : 150, fill: 'forwards', easing: 'ease' }).onfinish = (e) => { appState.lastPage = pageId } } } const slideInLeft = [ { opacity: 0, transform: 'translateX(1rem)' }, { opacity: 1, transform: 'translateX(0)' } ] const slideOutLeft = [ { opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(-1rem)' }, ] const slideInRight = [ { opacity: 0, transform: 'translateX(-1rem)' }, { opacity: 1, transform: 'translateX(0)' } ] const slideOutRight = [ { opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(1rem)' }, ] const slideInDown = [ { opacity: 0, transform: 'translateY(-1rem)' }, { opacity: 1, transform: 'translateY(0)' }, ] const slideOutDown = [ { opacity: 1, transform: 'translateY(0)' }, { opacity: 0, transform: 'translateY(1rem)' }, ] const slideInUp = [ { opacity: 0, transform: 'translateY(1rem)' }, { opacity: 1, transform: 'translateY(0)' }, ] const slideOutUp = [ { opacity: 1, transform: 'translateY(0)' }, { opacity: 0, transform: 'translateY(-1rem)' }, ] floGlobals.payer = 'FThgnJLcuStugLc24FJQggmp2WgaZjrBSn'; floGlobals.internTxs = new Map() function formatAmount(amount = 0) { if (!amount) return '₹0'; return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'inr' }) } function fetchRibcData() { return floCloudAPI.requestObjectData("RIBC", { application: "InternManage", receiverID: "FMyRTrz9CG4TFNM6rCQgy3VQ5NF23bY2xD", senderID: [ "FCja6sLv58e3RMy41T5AmWyvXEWesqBCkX", "FFS5hFXG7DBtdgzrLwixZLpenAmsCKRddm", "FS4jMAcSimRMrhoRhk5cjuJERS2otiwq4A" ], }) } function fetchTransactions() { return floTokenAPI.getAllTxs(floGlobals.payer) } const render = { internCard(floId) { const { total, txs } = floGlobals.internTxs.get(floId); return html`
  • ${floGlobals.appObjects.RIBC.internList[floId]}

    Last payment: ${formatAmount(txs[0].amount)} on ${getFormattedTime(txs[0].time, 'date-only')}

    Total paid: ${formatAmount(total)}

    View details
  • `; }, internPaymentList() { const renderedList = [] floGlobals.internTxs.forEach((data, internId) => { renderedList.push(render.internCard(internId)); }) renderElem(getRef("intern_payment_list"), html`${renderedList}`); }, paymentCard(tx) { const { txid, amount, time } = tx; return html`
  • ${formatAmount(amount)}

    View transaction
  • `; }, intern(floId) { renderElem(getRef('intern'), html` Back

    ${floGlobals.appObjects.RIBC.internList[floId]}

    FLO Address

    Total paid: ${formatAmount(floGlobals.internTxs.get(floId).total)}

    Payment history

    `) } } const oldInterns = { "FEvLovuDjWo4pXX3Y4SKDh8sq1AxJzqz9Z": "Megha Rani", "F765ofUHBhfXhvzrSgnPjvCvJXXCpoW6be": "Madhu Verma", "FHZtDh1NPepaPbbPwW65GjnDdVV1uo8NSA": "Vridhi Raj", "FKa43RxHUAdJbgV6KipQ4PvXi6Kgw4HmFn": "Aakriti Sinha", "FFaB6N1ETZsykXVS2PdM5xhj5BBoqsfsXC": "Ritika Agrawal", "FSdjJCJdU43a1dyWY6dRES1ekoupEjFPqQ": "Muskan Kumari", "FK96PZh4NskoJfWoyqcvLpSo7YnTLWMmdD": "Shambhavi Singh", "FJK9EDGhKj4Wr2zeCo3zRPXCNU6CXFFQAN": "Shivam Kumar Pandey", "FPtrQK6aSCgFeSNpzC68YTznHPfiz7CCvW": "Shruti Kashyap", "FHWXdnjRRJErqazye4Y9MRmE42D4Bp6Bj7": "Rashi Sanghvi", "FCTGD4M3DvMKupX3j2y5f3cQNDD9i6LUp7": "Gunjan Kumar Ranjan", "F8zYh6rCuorGmnMtqGFpaKGeBqQaj9WVtG": "Kriti Shreya", "FFoVnVMJv8BTfbk7ij9T5jPHs7VKSz886A": "Jaidev", "F87Ai2ErAMFe3UmAR7S63UYX2jE9ofaXSH": "Keerthana A V", "FEzy6pzEkm1TMXf1BGQz8dXvVZM3L1HFu2": "Saloni Jaitley", "FB4tu13HCxHAadvUDmgDBhvE9MtCkgRacn": "Divyansh Bhardwaj", "FLzcrXhzK1XzLnku5sT6yzURBcqQ5ZDNJy": "Tanishk Goyal", "F7HVKrF68Y6YKE9XXpHhAcxt6MwRLcUD67": "Salomi Sarkar", "FBYnAqhBt99XbTtCH6LAzjJ5yNZVPkYXhk": "Divyansh Bhardwaj (New FLO ID)", "FF7jVqwGS8fGG9fxmbVkEvD1Qo11hDyg8b": "Ahana Chakraborty", "FKknmmQd3PVaGbBbPFAJcQsARvw48NfeDF": "Prattay Mazumdar", "FSoa46pVWsNuZDp26X9H9Fi6ijMk7cy7mc": "Jayant Kumar", "FCqLr9nymnbh7ahta1gGC78z634y4GHJGQ": "Rakhijeet Singh", "FEHKFxQxycsxw2qQQSn2Y1BCT6Mfb8EMko": "Abhijeet Anand", } function getReceiverAddress(vout) { for(const output of vout) { for (const address of output.scriptPubKey.addresses) { if(address !== floGlobals.payer) return address } } } function main() { return Promise.all([fetchTransactions(), fetchRibcData()]).then(([txData]) => { console.log(floGlobals.appObjects.RIBC.internList) floGlobals.appObjects.RIBC.internList = { ...floGlobals.appObjects.RIBC.internList, ...oldInterns } for (const txid in txData.transactions) { const { parsedFloData: { tokenAmount }, transactionDetails } = txData.transactions[txid] const floId = getReceiverAddress(transactionDetails.vout); if (!floGlobals.appObjects.RIBC.internList[floId]) continue; // not an intern if (!floGlobals.internTxs.has(floId)) floGlobals.internTxs.set(floId, { total: 0, txs: [] }); floGlobals.internTxs.get(floId).total += tokenAmount; floGlobals.internTxs.get(floId).txs.push({ txid, amount: tokenAmount, time: transactionDetails.time }); } floGlobals.internTxs.forEach((intern) => { intern.txs.sort((a,b) => b.time - a.time) }) // sort floGlobals.internTxs by date of last payment floGlobals.internTxs = new Map([...floGlobals.internTxs.entries()].sort((a,b) => b[1].txs[0].time - a[1].txs[0].time)); render.internPaymentList(); routeTo(window.location.hash) }).catch(err => { notify(`Error fetching data: ${err}`, "error") }) }