feature update

--added lazy loading to transactions and notifications

-- adding transaction details page
This commit is contained in:
sairaj mote 2021-08-29 17:01:59 +05:30
parent f0f475ba0e
commit 718950c1c4
5 changed files with 301 additions and 111 deletions

View File

@ -1630,7 +1630,6 @@ smCopy.innerHTML = `
}
}
</style>
</style>
<section class="copy">
<p class="copy-content"></p>
<button part="button" class="copy-button" title="copy">

View File

@ -29,6 +29,7 @@ body * {
--foreground-color: rgb(250, 252, 255);
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
--yellow: #f3a600;
scrollbar-width: thin;
}
@ -42,6 +43,7 @@ body[data-theme=dark] * {
--foreground-color: rgb(20, 20, 20);
--danger-color: rgb(255, 106, 106);
--green: #00E676;
--yellow: #ffd13a;
}
p {
@ -522,6 +524,11 @@ details[open] > summary .icon {
transition: -webkit-transform 0.3s;
transition: transform 0.3s;
transition: transform 0.3s, -webkit-transform 0.3s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.nav-item--active .icon {
fill: var(--accent-color);
@ -592,19 +599,21 @@ details[open] > summary .icon {
#recent_transactions_container {
display: grid;
gap: 1rem;
margin: 1.5rem 0;
padding-bottom: 5rem;
}
.activity-card {
display: grid;
gap: 0.8rem;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
grid-template-columns: auto minmax(0, 1fr) auto;
padding: 1rem 0;
gap: 0.4rem 1rem;
border-radius: 0.5rem;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
color: inherit;
cursor: pointer;
grid-template-columns: auto minmax(0, 1fr) auto;
}
.activity-card__icon {
display: -webkit-box;
@ -635,32 +644,74 @@ details[open] > summary .icon {
color: rgba(var(--text-color), 0.8);
}
#all_responses_list {
padding: 1.5rem 1.5rem 5rem 1.5rem;
#notifications,
#transactions {
padding-bottom: 5rem;
}
#notifications .activity-card,
#transactions .activity-card {
padding: 1rem 1.5rem;
}
.activity-card--request {
grid-template-areas: "icon . amount" "icon time .";
}
.activity-card--request .activity-card__icon {
grid-area: icon;
}
.activity-card--request .activity-card__time {
grid-area: time;
}
.activity-card--request .activity-card__amount {
grid-area: amount;
text-align: end;
}
.activity-card--request .activity-card__status:not(:empty) {
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.7rem;
border-radius: 0.2rem;
padding: 0.3rem 0.4rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.7);
}
.activity-card--request .activity-card__status:not(:empty).success {
color: var(--green);
background-color: rgba(0, 230, 118, 0.1);
}
.activity-card--request .activity-card__status:not(:empty).failed {
color: var(--danger-color);
background-color: rgba(255, 75, 75, 0.1);
}
.activity-card--request .activity-card__status:not(:empty).pending {
color: var(--yellow);
background-color: rgba(255, 252, 75, 0.1);
}
.activity-card--response {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
gap: 0.2rem 0.8rem;
border-radius: 0.5rem;
grid-template-areas: "icon . time" "icon . time";
grid-template-areas: "icon . time" "icon description description";
}
.activity-card--response .activity-card__icon {
grid-area: icon;
}
.activity-card--response .activity-card__description {
grid-area: description;
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
.activity-card--response .activity-card__time {
grid-area: time;
}
.activity-card--response .success .icon {
.success .icon {
fill: var(--green);
}
.activity-card--response .failed .icon {
.pending .icon {
fill: var(--yellow);
}
.failed .icon {
fill: var(--danger-color);
}
@ -730,6 +781,9 @@ details[open] > summary .icon {
height: 80%;
}
.nav-item--active {
background-color: var(--accent-color--light);
}
.nav-item:not(:last-of-type) {
margin-bottom: 0.5rem;
}
@ -784,13 +838,15 @@ details[open] > summary .icon {
background: rgba(var(--text-color), 0.5);
}
.nav-item {
.nav-item,
.interact {
-webkit-transition: background-color 0.3s, -webkit-transform 0.3s;
transition: background-color 0.3s, -webkit-transform 0.3s;
transition: background-color 0.3s, transform 0.3s;
transition: background-color 0.3s, transform 0.3s, -webkit-transform 0.3s;
}
.nav-item:hover {
.nav-item:hover,
.interact:hover {
background-color: var(--accent-color--light);
}
}

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -25,15 +25,16 @@ body {
--foreground-color: rgb(250, 252, 255);
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
--yellow: #f3a600;
scrollbar-width: thin;
}
color: rgba(var(--text-color), 1);
background: rgba(var(--background-color), 1);
}
body[data-theme='dark'] {
&,
* {
--accent-color: rgb(170, 190, 255);
@ -44,6 +45,7 @@ body[data-theme='dark'] {
--foreground-color: rgb(20, 20, 20);
--danger-color: rgb(255, 106, 106);
--green: #00E676;
--yellow: #ffd13a;
}
}
@ -471,6 +473,8 @@ details {
border-radius: 0.5rem;
color: inherit;
transition: transform 0.3s;
user-select: none;
-webkit-tap-highlight-color: transparent;
&--active{
.icon{
fill: var(--accent-color);
@ -525,16 +529,18 @@ details {
}
#recent_transactions_container{
display: grid;
gap: 1rem;
margin: 1.5rem 0;
padding-bottom: 5rem;
}
.activity-card{
display: grid;
gap: 0.8rem;
align-items: center;
grid-template-columns: auto minmax(0, 1fr) auto;
padding: 1rem 0;
gap: 0.4rem 1rem;
border-radius: 0.5rem;
align-items: flex-start;
color: inherit;
cursor: pointer;
grid-template-columns: auto minmax(0, 1fr) auto;
&__icon{
display: flex;
align-content: center;
@ -559,33 +565,74 @@ details {
color: rgba(var(--text-color), 0.8);
}
}
#all_responses_list{
padding: 1.5rem 1.5rem 5rem 1.5rem;
#notifications,
#transactions{
padding-bottom: 5rem;
.activity-card{
padding: 1rem 1.5rem;
}
}
.activity-card--request{
grid-template-areas: 'icon . amount' 'icon time .';
.activity-card__icon{
grid-area: icon;
}
.activity-card__time{
grid-area: time;
}
.activity-card__amount{
grid-area: amount;
text-align: end;
}
.activity-card__status:not(:empty){
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.7rem;
border-radius: 0.2rem;
padding: 0.3rem 0.4rem;
font-weight: 500;
color: rgba($color: #000000, $alpha: 0.7);
&.success{
color: var(--green);
background-color: rgb(0, 230, 118, 0.1);
}
&.failed{
color: var(--danger-color);
background-color: rgb(255, 75, 75, 0.1);
}
&.pending{
color: var(--yellow);
background-color: rgba(255, 252, 75, 0.1);
}
}
}
.activity-card--response{
align-items: flex-start;
gap: 0.2rem 0.8rem;
border-radius: 0.5rem;
grid-template-areas: 'icon . time' 'icon . time';
grid-template-areas: 'icon . time' 'icon description description';
.activity-card__icon{
grid-area: icon;
}
.activity-card__description{
grid-area: description;
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
.activity-card__time{
grid-area: time;
}
.success{
.icon{
fill: var(--green);
}
}
.success{
.icon{
fill: var(--green);
}
.failed{
.icon{
fill: var(--danger-color);
}
}
.pending{
.icon{
fill: var(--yellow);
}
}
.failed{
.icon{
fill: var(--danger-color);
}
}
#user_section{
@ -639,7 +686,7 @@ details {
}
.nav-item{
&--active{
// background-color: var(--accent-color--light);
background-color: var(--accent-color--light);
}
&:not(:last-of-type){
margin-bottom: 0.5rem;
@ -694,7 +741,8 @@ details {
background: rgba(var(--text-color), 0.5);
}
}
.nav-item{
.nav-item,
.interact{
transition: background-color 0.3s, transform 0.3s;
&:hover{
background-color: var(--accent-color--light);

View File

@ -182,9 +182,12 @@
<p class="empty-state">No transactions so far. Confirmed transactions will be shown here.</p>
</section>
</div>
<div id="transactions" class="sub-page hide-completely"></div>
<div id="notifications" class="sub-page hide-completely">
<div id="all_responses_list" class="observe-empty-state grid gap-1-5"></div>
<div id="all_responses_list" class="observe-empty-state grid"></div>
<p class="empty-state">No notifications so far.</p>
</div>
<div id="transactions" class="sub-page hide-completely">
<div id="all_requests_list" class="observe-empty-state grid"></div>
<p class="empty-state">No notifications so far.</p>
</div>
<div id="settings" class="sub-page hide-completely">
@ -208,14 +211,29 @@
</div>
</section>
</article>
<article id="transaction" class="page hide-completely">
<article id="transaction" class="page hide-completely page-layout">
<div id="transaction_top" class="grid gap-1-5 card">
<button class="icon-button" onclick="history.back()">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
</svg>
</button>
<div id="transaction_detail__icon"></div>
<div class="grid gap-0-5">
<div id="transaction_detail__type"></div>
<div id="transaction_detail__amount"></div>
</div>
<time id="transaction_detail__time"></time>
<sm-button id="transaction_action_button" variant="primary"></sm-button>
</div>
</article>
<template id="activity_template">
<a class="activity-card interact">
<template id="request_template">
<a class="activity-card activity-card--request interact">
<div class="activity-card__icon"></div>
<div class="activity-card__title"></div>
<time class="activity-card__time"></time>
<h4 class="activity-card__amount"></h4>
<span class="activity-card__status"></span>
</a>
</template>
<template id="response_template">
@ -487,14 +505,19 @@
})
});
let lastPage
const pagesData = {
openedPages: []
}
let responseLoader
let requestLoader
async function showPage(targetPage, options = {}) {
const { firstLoad, hashChange } = options
let pageId
let searchParams
let params
if (targetPage === '') {
pageId = 'homepage'
pageId = 'dashboard'
}else {
if(targetPage.includes('/')){
if (targetPage.includes('?')) {
@ -525,6 +548,9 @@
getRef('generated_flo_id').value = floID
getRef('generated_private_key').value = privKey
break;
case 'transaction':
const {requestId} = params
break;
}
document.querySelector('.page:not(.hide-completely)')?.classList.add('hide-completely')
getRef(pageId)?.classList.remove('hide-completely')
@ -540,7 +566,6 @@
duration: 300,
easing: 'ease'
})
lastPage = pageId
}else if(appSubPages.includes(pageId)){
switch (pageId) {
case 'dashboard':
@ -553,8 +578,21 @@
.catch(error => notify(error, 'error'))
break;
case 'notifications':
renderResponses()
break
if(pagesData.openedPages.includes(pageId)){
responseLoader.reset()
}else{
responseLoader = new LazyLoader('#all_responses_list', getArrayOfObj(bank_app.viewAllResponses()).reverse(), render.responseCard, {batchSize: 10})
responseLoader.init()
}
break;
case 'transactions':
if(pagesData.openedPages.includes(pageId)){
requestLoader.reset()
}else{
requestLoader = new LazyLoader('#all_requests_list', getRequests(), render.requestCard, {batchSize: 10})
requestLoader.init()
}
break;
}
if(getRef('homepage').classList.contains('hide-completely')){
document.querySelector('.page:not(.hide-completely)')?.classList.add('hide-completely')
@ -579,13 +617,85 @@
easing: 'ease'
})
}
pagesData.lastPage = pageId
if(!pagesData.openedPages.includes(pageId)){
pagesData.openedPages.push(pageId)
}
}
</script>
// class based lazy loading
class LazyLoader{
constructor(container, arrayOfElements, renderFn, options = {}){
const {batchSize = 10} = options
this.arrayOfElements = arrayOfElements
this.renderFn = renderFn
this.intersectionObserver
this.batchSize = batchSize
this.lazyContainer = document.querySelector(container)
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()
console.log(this.arrayOfElements.length)
}
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)
}
clear(){
this.intersectionObserver.disconnect()
this.mutationObserver.disconnect()
this.lazyContainer.innerHTML = ``;
}
reset(){
this.render()
}
}
</script>
<script id="ui_functions">
const render = {
activityCard(activityDetails = {}){
requestCard(activityDetails = {}){
const {requestID, amount, rtype, timestamp, status} = activityDetails
const card = getRef('activity_template').content.cloneNode(true).firstElementChild
const card = getRef('request_template').content.cloneNode(true).firstElementChild
let icon
let action = ''
switch(rtype){
@ -603,22 +713,29 @@
icon = `
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.61,8.68H8.39L3.37,15a5.18,5.18,0,0,0,.19,4.27h0A5.06,5.06,0,0,0,8.06,22h7.88a5.06,5.06,0,0,0,4.5-2.77h0A5.18,5.18,0,0,0,20.63,15Z"/><path d="M8.35,6.77h7.26l.88-1.2a1.12,1.12,0,0,0-1.18-1.75h0a1.16,1.16,0,0,1-.92-.18l-.28-.21a3.64,3.64,0,0,0-4.22,0h0a1.14,1.14,0,0,1-1,.16l-.22-.06A1.13,1.13,0,0,0,7.43,5.19Z"/></svg>
`
action = 'Get loan of'
action = 'Get loan'
break
case 'closeLoan':
icon = ``
action = 'Repay loan of'
action = 'Repay loan'
break
}
card.setAttribute('href', `#/transaction?requestID=${requestID}`)
card.querySelector('.activity-card__icon').innerHTML = icon
card.querySelector('.activity-card__title').textContent = `${action} ${amount} (${status})`
card.querySelector('.activity-card__title').textContent = action
card.querySelector('.activity-card__time').textContent = getFormatedTime(timestamp, true)
card.querySelector('.activity-card__amount').textContent = `₹${amount}`
card.querySelector('.activity-card__status').classList.add(status)
card.querySelector('.activity-card__status').textContent = status
return card
},
responseCard(responseDetails){
const {requestID, reason = 'none', rtype, status = 'pending', timestamp} = responseDetails
const {amount = undefined, index = undefined} = bank_app.viewAllRequests()[requestID]
const {id, requestID, reason = 'none', rtype, status = 'pending'} = responseDetails
let {amount = undefined, index = undefined} = bank_app.viewAllRequests()[requestID]
if(!amount){
amount = bank_app.accounts[index].amount
}
const timestamp = id.split('_')[0]
const card = getRef('response_template').content.cloneNode(true).firstElementChild
let icon
let action = ''
@ -627,7 +744,7 @@
action = 'deposit'
break
case 'closeDeposit':
action = 'withdraw'
action = 'withdrawal'
break
case 'openLoan':
action = 'loan'
@ -684,59 +801,22 @@
}
})
}
let updateStartIndex = 0
let updateEndIndex = 0
const intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.isIntersecting){
observer.disconnect()
renderFilteredUpdates({lazyLoad: true})
}
})
},{
threshold: 0.3
})
function renderResponses(options = {}) {
let {lazyLoad = false} = options
const frag = document.createDocumentFragment();
const allResponses = bank_app.viewAllResponses()
const updates = []
for (key in allResponses) {
updates.push({id: key, ...allResponses[key]});
}
if(lazyLoad){
updateStartIndex = updateEndIndex
updateEndIndex = updates.length > updateEndIndex + 10 ? updateEndIndex + 10 : updates.length
}else{
intersectionObserver.disconnect()
getRef('all_responses_list').innerHTML = ``;
updateStartIndex = 0
updateEndIndex = updates.length > 10 ? 10 : updates.length
}
for (let index = updateStartIndex; index < updateEndIndex; index++) {
const {id} = updates[index]
const responseDetails = {
...updates[index],
responseID: id,
timestamp: id.split('_')[0],
}
frag.append(render.responseCard(responseDetails))
}
getRef('all_responses_list').append(frag)
if(updateEndIndex !== updates.length && getRef('all_responses_list').lastElementChild){
intersectionObserver.observe(getRef('all_responses_list').lastElementChild)
function getArrayOfObj(obj) {
const arrayOfObj = []
for (key in obj) {
arrayOfObj.push({id: key, ...obj[key]});
}
return arrayOfObj
}
function renderRecentTransactions() {
const activityFrag = document.createDocumentFragment()
function getRequests() {
const transactions = bank_app.viewAllRequests()
const responses = bank_app.viewAllResponses()
let index = 0
const requests = []
for (const key in transactions) {
if(index > 5){
break
let {amount, rtype, index} = transactions[key]
if(!amount){
amount = bank_app.accounts[index].amount
}
const {amount, rtype} = transactions[key]
const transactionDetails = {
amount,
rtype,
@ -744,9 +824,16 @@
timestamp: key.split('_')[0],
status: requestResponsePairs[key] ? responses[requestResponsePairs[key]].status : 'pending'
}
activityFrag.append(render.activityCard(transactionDetails))
index++
requests.push(transactionDetails)
}
return requests.reverse()
}
function renderRecentTransactions() {
const activityFrag = document.createDocumentFragment()
const recentRequests = getRequests().splice(0, 5)
recentRequests.forEach(request => {
activityFrag.append(render.requestCard(request))
})
getRef('recent_transactions_container').innerHTML = ''
getRef('recent_transactions_container').append(activityFrag)
}