Feature and UI update

- added better deposit / withdraw user flow and UI
- added actionable notification when new payment request arrives
This commit is contained in:
sairaj mote 2022-04-23 01:34:14 +05:30
parent 2b171a619c
commit a02140c188
9 changed files with 779 additions and 703 deletions

View File

@ -671,11 +671,6 @@ ul {
z-index: 1;
}
.clip {
-webkit-clip-path: circle(0);
clip-path: circle(0);
}
#home {
padding: 0;
position: relative;
@ -931,46 +926,24 @@ ul {
fill: rgba(var(--background-color), 1);
}
#transaction_result {
display: grid;
gap: 0.5rem;
height: max(40vh, 24rem);
align-items: center;
justify-content: center;
text-align: center;
align-content: center;
}
#transaction_result.success .icon--failed {
display: none;
}
#transaction_result.failed .icon--success {
display: none;
}
#transaction_result h3 {
text-align: center;
width: 100%;
}
#transaction_result .icon {
.user-action-result__icon {
justify-self: center;
height: 4rem;
width: 4rem;
border-radius: 5rem;
margin-bottom: 1rem;
margin-bottom: 2rem;
-webkit-animation: popup 1s;
animation: popup 1s;
}
#transaction_result .icon--success {
.user-action-result__icon.success {
fill: rgba(var(--background-color), 1);
padding: 1rem;
background-color: #0bbe56;
}
#transaction_result .icon--failed {
.user-action-result__icon.failed {
background-color: rgba(var(--text-color), 0.03);
fill: var(--danger-color);
}
#transaction_result sm-copy {
font-size: 0.8rem;
}
@-webkit-keyframes popup {
0% {

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -627,9 +627,6 @@ ul {
z-index: 1;
}
}
.clip {
clip-path: circle(0);
}
#home {
padding: 0;
position: relative;
@ -874,48 +871,21 @@ ul {
fill: rgba(var(--background-color), 1);
}
}
#transaction_result {
display: grid;
gap: 0.5rem;
height: max(40vh, 24rem);
align-items: center;
justify-content: center;
text-align: center;
align-content: center;
.user-action-result__icon {
justify-self: center;
height: 4rem;
width: 4rem;
border-radius: 5rem;
margin-bottom: 2rem;
animation: popup 1s;
&.success {
.icon--failed {
display: none;
}
fill: rgba(var(--background-color), 1);
padding: 1rem;
background-color: #0bbe56;
}
&.failed {
.icon--success {
display: none;
}
}
h3 {
text-align: center;
width: 100%;
}
.icon {
justify-self: center;
height: 4rem;
width: 4rem;
border-radius: 5rem;
margin-bottom: 1rem;
animation: popup 1s;
&--success {
fill: rgba(var(--background-color), 1);
padding: 1rem;
background-color: #0bbe56;
}
&--failed {
background-color: rgba(var(--text-color), 0.03);
fill: var(--danger-color);
}
}
sm-copy {
font-size: 0.8rem;
background-color: rgba(var(--text-color), 0.03);
fill: var(--danger-color);
}
}
@keyframes popup {

View File

@ -1032,9 +1032,30 @@
<sm-input id="topup_wallet__txid" minlength="12" maxlength="12"
error-text="Please enter UPI transaction ID of money you sent to continue."
placeholder="UPI transaction ID" autofocus animate required></sm-input>
<button class="button button--primary cta" onclick="depositMoneyToWallet()"
type="submit">Confirm</button>
<div class="multi-state-button">
<button id="topup_wallet_button" class="button button--primary cta" onclick="depositMoneyToWallet()"
type="submit">Confirm</button>
</div>
</sm-form>
<div class="grid gap-0-5 hide justify-center text-center">
<svg class="icon user-action-result__icon success" 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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
<h4>Sent top-up request</h4>
<p>This may take upto 30 mins to complete</p>
</div>
<div class="grid gap-0-5 hide justify-center text-center">
<svg class="icon user-action-result__icon failed" 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="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
</svg>
<h4>Failed to top-up wallet</h4>
<p id="topup_failed_reason"></p>
</div>
</div>
</sm-popup>
<sm-popup id="withdraw_wallet_popup">
@ -1048,43 +1069,66 @@
</svg>
</button>
</header>
<sm-form>
<div class="grid gap-0-5">
<h4>Transfer to bank</h4>
<p>Money will be sent to your bank account linked to selected UPI ID</p>
</div>
<sm-input id="send_cashier_amount" type="number" min="1" error-text="Amount should al least be ₹1"
name="amount" placeholder="Amount" autofocus animate required>
<svg class="icon" slot="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>
<div id="withdraw_wallet_process">
<sm-form>
<div class="grid gap-0-5">
<h4>Transfer to bank</h4>
<p>Money will be sent to your bank account linked to selected UPI ID</p>
</div>
<sm-input id="send_cashier_amount" type="number" min="1" error-text="Amount should al least be ₹1"
name="amount" placeholder="Amount" autofocus animate required>
<svg class="icon" slot="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="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" />
<rect fill="none" height="24" width="24" />
</g>
</g>
</svg>
</sm-input>
<div class="grid gap-0-5 hide">
<p>
<b>Select UPI ID to receive money</b>
</p>
<sm-select id="select_upi_id"></sm-select>
</div>
<button class="button" onclick="showPopup('save_upi_popup')">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<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>
<div class="grid gap-0-5 hide">
<p>
<b>Select UPI ID to receive money</b>
</p>
<sm-select id="select_upi_id"></sm-select>
</div>
<button class="button" onclick="showPopup('save_upi_popup')">
<svg class="icon margin-right-0-5" 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 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</svg>
Add UPI ID
</button>
<div class="multi-state-button">
<button id="withdraw_rupee_button" class="button button--primary cta"
onclick="withdrawMoneyFromWallet()" type="submit">Transfer</button>
</div>
</sm-form>
<div class="grid gap-0-5 hide justify-center text-center">
<svg class="icon user-action-result__icon success" 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 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
Add UPI ID
</button>
<button class="button button--primary cta" onclick="withdrawMoneyFromWallet()"
type="submit">Transfer</button>
</sm-form>
<h4>Sent transfer to bank request</h4>
<p>This may take upto 30 mins to complete</p>
</div>
<div class="grid gap-0-5 hide justify-center text-center">
<svg class="icon user-action-result__icon failed" 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="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
</svg>
<h4>Failed to request</h4>
<p id="withdrawal_failed_reason"></p>
</div>
</div>
</sm-popup>
<sm-popup id="save_upi_popup">
<header slot="header" class="popup__header">
@ -1249,7 +1293,6 @@
}).catch(error => console.error(error))
} else {
userUI.renderSavedIds()
renderSavedUpiIds()
userUI.renderCashierRequests(User.cashierRequests);
userUI.renderMoneyRequests(User.moneyRequests);
User.init().then(result => {
@ -1258,6 +1301,7 @@
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.add('hide'))
document.querySelectorAll('.user-element').forEach(elem => elem.classList.remove('hide'))
showPage(window.location.hash, { firstLoad: true })
floGlobals.loaded = true
}).catch(error => console.error(error))
}
}).catch(error => console.error(error))

View File

@ -802,6 +802,20 @@ smNotifications.innerHTML = `
.close:active{
transform: scale(0.9);
}
.action{
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem 0.8rem;
border-radius: 0.2rem;
border: none;
background-color: rgba(var(--text-color, (17,17,17)), 0.06);
font-family: inherit;
font-size: inherit;
color: rgba(var(--text-color, (17,17,17)), 0.9);
font-weight: 500;
cursor: pointer;
}
@media screen and (min-width: 640px){
.notification-panel{
max-width: 28rem;
@ -879,7 +893,7 @@ customElements.define('sm-notifications', class extends HTMLElement {
}
createNotification(message, options = {}) {
const { pinned = false, icon = '' } = options;
const { pinned = false, icon = '', action } = options;
const notification = document.createElement('output')
notification.id = this.randString(8)
notification.classList.add('notification');
@ -888,6 +902,11 @@ customElements.define('sm-notifications', class extends HTMLElement {
<div class="icon-container">${icon}</div>
<p>${message}</p>
`;
if (action) {
composition += `
<button class="action">${action.label}</button>
`
}
if (pinned) {
notification.classList.add('pinned');
composition += `
@ -929,6 +948,8 @@ customElements.define('sm-notifications', class extends HTMLElement {
e.target.commitStyles()
e.target.cancel()
}
if (notification.querySelector('.action'))
notification.querySelector('.action').addEventListener('click', options.action.callback)
return notification.id;
}

View File

@ -39,6 +39,7 @@ User.init = function () {
}));
*/
promises.push(User.getCashierUPI());
promises.push(organizeSyncedData('savedUserData'));
Promise.all(promises)
.then(result => resolve(result))
.catch(error => reject(error))

View File

@ -25,12 +25,14 @@ async function organizeSyncedData(obsName) {
compactIDB.addData(obsName, decryptedData[key], key);
}
compactIDB.addData(obsName, fetchedData[0].time, 'lastSyncTime');
return true;
} else {
const idbData = await compactIDB.readAllData(obsName);
for (const key in idbData) {
if (key !== 'lastSyncTime')
floGlobals[obsName][key] = idbData[key];
}
return true;
}
}
@ -53,10 +55,15 @@ function depositMoneyToWallet() {
let upiTxID = getRef('topup_wallet__txid').value.trim();
if (upiTxID === '')
return notify("Please enter UPI transaction ID", 'error');
buttonLoader('topup_wallet_button', true);
User.cashToToken(cashier, amount, upiTxID).then(result => {
console.log(result);
notify("Requested cashier. please wait!");
}).catch(error => console.error(error))
showProcessStage('topup_wallet_process', 2);
}).catch(error => {
console.error(error)
getRef('topup_failed_reason').textContent = error;
showProcessStage('topup_wallet_process', 3);
})
}
function withdrawMoneyFromWallet() {
@ -64,25 +71,36 @@ function withdrawMoneyFromWallet() {
if (!cashier)
return notify("No cashier online. Please try again in a while.", 'error');
let amount = parseFloat(getRef('send_cashier_amount').value.trim());
// let upiId = prompt(`${amount} ${floGlobals.currency}# will be sent to ${cashier}. Enter UPI ID`);
const upiId = getRef('select_upi_id').value;
if (!upiId)
return notify("Please add an UPI ID to continue", 'error');
buttonLoader('withdraw_rupee_button', true);
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 => {
showProcessStage('withdraw_wallet_process', 1);
console.log(result);
notify("Requested cashier. please wait!");
}).catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
getRef('withdrawal_failed_reason').textContent = error;
showProcessStage('withdraw_wallet_process', 2);
console.error(error)
}).finally(() => {
buttonLoader('withdraw_rupee_button', false);
});
}).catch(error => {
getRef('withdrawal_failed_reason').textContent = error;
showProcessStage('withdraw_wallet_process', 2);
buttonLoader('withdraw_rupee_button', false);
console.error(error)
})
}
async function renderSavedUpiIds() {
const frag = document.createDocumentFragment();
await organizeSyncedData('savedUserData');
for (const upiId in floGlobals.savedUserData.upiIds) {
frag.append(render.savedUpiId(upiId));
}
getRef('saved_upi_ids_list').innerHTML = '';
getRef('saved_upi_ids_list').append(frag);
}
function saveUpiId() {
@ -115,7 +133,7 @@ function saveUpiId() {
delegate(getRef('saved_upi_ids_list'), 'click', '.saved-upi', e => {
if (e.target.closest('.delete-upi')) {
const upiId = e.delegateTarget.dataset.upiId;
getConfirmation('Do you want delete this FLO ID?', {
getConfirmation('Do you want delete this UPI ID?', {
confirmText: 'Delete',
}).then(res => {
if (res) {
@ -165,20 +183,25 @@ userUI.renderCashierRequests = function (requests, error = null) {
return;
if (pagesData.lastPage === 'wallet') {
for (let transactionID in requests) {
let oldCard = getRef('pending_wallet_transactions').querySelector(`[data-vc-${transactionID}]`);
if (oldCard) {
oldCard.remove()
getRef('wallet_history').prepend(render.walletRequestCard(requests[transactionID]))
}
const { note, tag } = requests[transactionID];
let status = tag ? 'done' : (note ? 'failed' : "pending");
getRef('wallet_history_wrapper').querySelectorAll(`[data-vc="${transactionID}"]`).forEach(card => card.remove());
getRef(status !== 'pending' ? 'wallet_history' : 'pending_wallet_transactions').prepend(render.walletRequestCard(requests[transactionID]))
}
}
};
delegate(getRef('wallet_history_wrapper'), 'click', '.wallet-request', e => {
let transactionID = e.delegateTarget.dataset.vc;
let request = User.cashierRequests[transactionID];
console.log(request)
})
const pendingTransactionsObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.target.children.length)
mutation.target.parentNode.classList.remove('hide')
else
mutation.target.parentNode.classList.add('hide')
}
})
});
userUI.renderMoneyRequests = function (requests, error = null) {
if (error)
@ -187,9 +210,26 @@ userUI.renderMoneyRequests = function (requests, error = null) {
return;
if (pagesData.lastPage === 'requests') {
for (let r in requests) {
let oldCard = getRef('payment_request_history').querySelector(`[data-vc-${r}]`);
if (oldCard)
oldCard.replaceWith(render.paymentRequestCard(requests[r]));
getRef('requests_history_wrapper').querySelectorAll(`[data-vc="${r}"]`).forEach(card => card.remove());
if (requests[r].note) {
getRef('payment_request_history').prepend(render.paymentRequestCard(requests[r]));
} else {
getRef('pending_payment_requests').prepend(render.paymentRequestCard(requests[r]));
}
}
}
if (floGlobals.loaded) {
for (let r in requests) {
if (!requests[r].note) {
notify(`You have received payment request from ${getFloIdTitle(requests[r].senderID)}`, '', {
action: {
label: 'View',
callback: () => {
window.location.hash = `#/requests`
}
}
});
}
}
}
};
@ -425,6 +465,33 @@ const render = {
}
};
function buttonLoader(id, show) {
getRef(id).disabled = show;
const animOptions = {
duration: 200,
fill: 'forwards',
easing: 'ease'
}
if (show) {
getRef(id).animate([
{
clipPath: 'circle(100%)',
},
{
clipPath: 'circle(0)',
},
], animOptions).onfinish = e => {
e.target.commitStyles()
e.target.cancel()
}
getRef(id).parentNode.append(createElement('sm-spinner'))
} else {
getRef(id).style = ''
const potentialTarget = getRef(id).parentNode.querySelector('sm-spinner')
if (potentialTarget) potentialTarget.remove();
}
}
let currentUserAction;
function showTokenTransfer(type) {
getRef('tt_button').textContent = type;

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
/*jshint esversion: 6 */
/*jshint esversion: 9 */
// Global variables
const domRefs = {};
const currentYear = new Date().getFullYear();
@ -115,6 +115,7 @@ document.addEventListener('popupopened', async e => {
getRef('select_upi_id').parentNode.classList.remove('hide')
getRef('select_upi_id').append(frag)
}
showProcessStage('withdraw_wallet_process', 0)
break;
}
})
@ -159,7 +160,6 @@ const getConfirmation = (title, options = {}) => {
//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':
@ -169,7 +169,7 @@ function notify(message, mode, options = {}) {
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 });
getRef("notification_drawer").push(message, { icon, ...options });
if (mode === 'error') {
console.error(message)
}
@ -377,17 +377,15 @@ async function showPage(targetPage, options = {}) {
paymentRequests.unshift(User.moneyRequests[transactionId])
}
}
if (arePaymentsPending) {
getRef('pending_payment_requests').innerHTML = ''
getRef('pending_payment_requests').append(pendingPaymentRequests)
getRef('pending_payment_requests').parentNode.classList.remove('hide')
} else {
getRef('pending_payment_requests').parentNode.classList.add('hide')
}
if (paymentRequestsLoader) {
paymentRequestsLoader.update(paymentRequests)
} else {
paymentRequestsLoader = new LazyLoader('#payment_request_history', paymentRequests, render.paymentRequestCard);
pendingTransactionsObserver.observe(getRef('pending_payment_requests'), { childList: true });
}
if (arePaymentsPending) {
getRef('pending_payment_requests').innerHTML = ''
getRef('pending_payment_requests').append(pendingPaymentRequests)
}
paymentRequestsLoader.init()
break;
@ -403,20 +401,18 @@ async function showPage(targetPage, options = {}) {
areTransactionsPending = true
pendingWalletTransactions.prepend(render.walletRequestCard(User.cashierRequests[transactionId]))
} else {
walletTransactions.push(User.cashierRequests[transactionId])
walletTransactions.unshift(User.cashierRequests[transactionId])
}
}
if (areTransactionsPending) {
getRef('pending_wallet_transactions').innerHTML = ''
getRef('pending_wallet_transactions').append(pendingWalletTransactions)
getRef('pending_wallet_transactions').parentNode.classList.remove('hide')
} else {
getRef('pending_wallet_transactions').parentNode.classList.add('hide')
}
if (walletHistoryLoader) {
walletHistoryLoader.update(walletTransactions)
} else {
walletHistoryLoader = new LazyLoader('#wallet_history', walletTransactions, render.walletRequestCard);
pendingTransactionsObserver.observe(getRef('pending_wallet_transactions'), { childList: true });
}
if (areTransactionsPending) {
getRef('pending_wallet_transactions').innerHTML = ''
getRef('pending_wallet_transactions').append(pendingWalletTransactions)
}
walletHistoryLoader.init()
break;
@ -439,7 +435,6 @@ async function showPage(targetPage, options = {}) {
getRef('transaction__remark').textContent = remark
getRef('transaction__remark').classList.remove('hide')
}
console.log(status)
} else if (params.type === 'wallet') {
transactionDetails = User.cashierRequests[params.transactionId]
const { message: { amount, mode, upi_id, upi_txid }, note, tag } = transactionDetails
@ -465,12 +460,14 @@ async function showPage(targetPage, options = {}) {
getRef('transaction__note').classList.remove('hide')
}
}
const { message: { amount, remark }, note, senderID, receiverID, time } = transactionDetails
console.table(transactionDetails)
const { message: { amount }, time } = transactionDetails
getRef('transaction__time').textContent = getFormattedTime(time)
getRef('transaction__amount').textContent = formatAmount(amount)
getRef('transaction__status').textContent = status
break;
case 'settings':
renderSavedUpiIds()
break;
default:
break;
}
@ -486,6 +483,9 @@ async function showPage(targetPage, options = {}) {
if (walletHistoryLoader)
walletHistoryLoader.clear()
}
if (pageId !== 'settings') {
getRef('saved_upi_ids_list').innerHTML = '';
}
if (pagesData.lastPage !== pageId) {
const animOptions = {