UI and UX update

-- implemented rule to include UPI ID while topping up
-- cashier can now see which UPI ID sent the money
-- Scanning QR code is collapsed by default to save space
-- decline request now shows a loader
This commit is contained in:
sairaj mote 2022-05-29 18:44:31 +05:30
parent 33a5e556f8
commit 7f2f7d909c
7 changed files with 161 additions and 100 deletions

View File

@ -179,10 +179,6 @@ a:any-link:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid;
}
details {
padding: 1rem 0;
}
details summary {
display: flex;
-webkit-user-select: none;
@ -414,6 +410,13 @@ ul {
height: 100%;
}
.label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
font-weight: 500;
margin-bottom: 0.2rem;
}
.ripple {
height: 8rem;
width: 8rem;
@ -869,7 +872,10 @@ ul {
background-color: rgba(var(--text-color), 0.03);
border-radius: 0.5rem;
height: 12rem;
justify-self: center;
width: -webkit-max-content;
width: -moz-max-content;
width: max-content;
margin: 0 auto;
}
#topup_wallet__qr_code svg {
width: 100%;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -158,9 +158,6 @@ button:disabled {
a:any-link:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid;
}
details {
padding: 1rem 0;
}
details summary {
display: flex;
@ -374,6 +371,12 @@ ul {
.h-100 {
height: 100%;
}
.label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
font-weight: 500;
margin-bottom: 0.2rem;
}
.ripple {
height: 8rem;
@ -808,7 +811,8 @@ ul {
background-color: rgba(var(--text-color), 0.03);
border-radius: 0.5rem;
height: 12rem;
justify-self: center;
width: max-content;
margin: 0 auto;
svg {
width: 100%;
height: 100%;

View File

@ -813,7 +813,23 @@
</g>
</svg>
</sm-input>
<strong id="low_user_flo_warning" class="warning"></strong>
<div class="grid gap-1">
<div class="grid gap-0-5 hide">
<p>
<b>Select UPI ID you'll send money from</b>
</p>
<sm-select id="select_topup_upi_id" style="z-index: 2;"></sm-select>
</div>
<button class="button button--small justify-self-start" 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>
<strong id="low_user_flo_warning" class="hide warning"></strong>
<button class="button button--primary cta" onclick="continueWalletTopup()"
type="submit">Continue</button>
</sm-form>
@ -822,12 +838,24 @@
<h4>Confirm</h4>
<p id="topup_wallet__details"></p>
</div>
<div class="grid gap-1 justify-center text-center">
<div class="grid gap-0-5">
<p>Scan QR code</p>
<div class="grid gap-1">
<details>
<summary class="interact">
<b style="font-size: 0.9rem;">Show QR code to scan</b>
<svg class="icon" 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="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
</svg>
</summary>
<div id="topup_wallet__qr_code"></div>
</details>
<div class="grid gap-0-5">
<p>
or send money to UPI ID below
</p>
<sm-copy id="topup_wallet__upi_id" style="font-weight: 700;"></sm-copy>
</div>
<sm-copy id="topup_wallet__upi_id" style="font-weight: 700;"></sm-copy>
</div>
<p>
After sending money, please enter the transaction ID of completed transaction. <br>
@ -906,7 +934,7 @@
<p>
<b>Select UPI ID to receive money</b>
</p>
<sm-select id="select_upi_id" style="z-index: 2;"></sm-select>
<sm-select id="select_withdraw_upi_id" style="z-index: 2;"></sm-select>
</div>
<button class="button button--small justify-self-start" onclick="showPopup('save_upi_popup')">
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
@ -1059,6 +1087,14 @@
Check if you have received UPI payment
</p>
<b id="top_up_amount" style="font-size: 1.5rem;"></b>
</div>
<div class="grid">
<div class="label">UPI ID</div>
<sm-copy id="top_up_upi_id"></sm-copy>
</div>
<div class="grid">
<div class="label">Transaction ID</div>
<sm-copy id="top_up_txid"></sm-copy>
</div>
<div class="flex justify-right gap-0-3">
@ -1081,7 +1117,9 @@
<p>Describe reason</p>
<sm-textarea id="top_up__specified_reason" rows="4"></sm-textarea>
</div>
<button class="button justify-right" style="margin-top: auto;" onclick="declineTopUp()">Decline</button>
<div class="multi-state-button" style="margin-top: auto;">
<button id="decline_button" class="button justify-right" onclick="declineTopUp()">Decline</button>
</div>
</div>
</div>
</sm-popup>
@ -1198,7 +1236,7 @@
</button>
</li>
</template>
<script src="//unpkg.com/uhtml"></script>
<script src="scripts/components.js"></script>
<script src="scripts/std_ui.js"></script>
<script src="scripts/lib.js"></script>

View File

@ -10,7 +10,7 @@ const cashierPubKeys = {};
const User = {};
const cashierStatus = {};
User.init = function() {
User.init = function () {
return new Promise((resolve, reject) => {
let promises;
//Request cashier for token-cash exchange
@ -56,7 +56,7 @@ User.init = function() {
})
}
User.getCashierUPI = function() {
User.getCashierUPI = function () {
return new Promise((resolve) => {
Promise.allSettled(floGlobals.subAdmins.map(cashierID => floCloudAPI.requestApplicationData(TYPE_CASHIER_UPI, {
senderID: cashierID,
@ -73,7 +73,7 @@ User.getCashierUPI = function() {
}
Object.defineProperty(User, 'cashierRequests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
senderID: myFloID,
group: "Cashiers",
@ -83,7 +83,7 @@ Object.defineProperty(User, 'cashierRequests', {
});
Object.defineProperty(User, 'moneyRequests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, {
receiverID: myFloID,
});
@ -91,7 +91,7 @@ Object.defineProperty(User, 'moneyRequests', {
}
});
User.findCashier = function() {
User.findCashier = function () {
let online = [];
for (let c in cashierStatus)
if (cashierStatus[c] && cashierUPI[c])
@ -107,38 +107,39 @@ User.findCashier = function() {
}
}
User.cashToToken = function(cashier, amount, upiTxID) {
User.cashToToken = function (cashier, amount, upiTxID, upiID) {
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))
mode: "cash-to-token",
amount: amount,
upi_txid: upiTxID,
upiID
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.tokenToCash = function(cashier, amount, blkTxID, upiID) {
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: floCrypto.encryptData(upiID, cashierPubKeys[cashier])
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
mode: "token-to-cash",
amount: amount,
token_txid: blkTxID,
upi_id: floCrypto.encryptData(upiID, cashierPubKeys[cashier])
}, TYPE_CASHIER_REQUEST, {
receiverID: cashier
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.sendToken = function(receiverID, amount, remark = '', options = {}) {
User.sendToken = function (receiverID, amount, remark = '', options = {}) {
return new Promise((resolve, reject) => {
floTokenAPI.sendToken(myPrivKey, amount, receiverID, remark, floTokenAPI.currency, options)
.then(result => resolve(result))
@ -146,23 +147,23 @@ User.sendToken = function(receiverID, amount, remark = '', options = {}) {
})
}
User.requestToken = function(floID, amount, remark = '') {
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))
amount: amount,
remark: remark
}, TYPE_MONEY_REQUEST, {
receiverID: floID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
User.decideRequest = function(request, note) {
User.decideRequest = function (request, note) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, note, {
receiverID: myFloID
}).then(result => resolve(result))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
@ -192,7 +193,7 @@ function startStatusInterval() {
status_interval_instance = setInterval(statusReconnect, 15 * 60 * 1000);
}
Cashier.init = function() {
Cashier.init = function () {
delegate(getRef('cashier_pending_request_list'), 'click', '.process-cashier-request', e => {
const requestID = e.delegateTarget.closest('.cashier-request').dataset.vc;
cashierUI.completeRequest(requestID)
@ -222,18 +223,18 @@ Cashier.init = function() {
})
}
Cashier.updateUPI = function(upi_id) {
Cashier.updateUPI = function (upi_id) {
return new Promise((resolve, reject) => {
floCloudAPI.sendApplicationData({
upi: upi_id
}, TYPE_CASHIER_UPI)
upi: upi_id
}, TYPE_CASHIER_UPI)
.then(result => resolve(result))
.catch(error => reject(error))
})
}
Object.defineProperty(Cashier, 'Requests', {
get: function() {
get: function () {
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
receiverID: myFloID
});
@ -242,29 +243,29 @@ Object.defineProperty(Cashier, 'Requests', {
}
});
Cashier.finishRequest = function(request, txid) {
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))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
Cashier.rejectRequest = function(request, reason) {
Cashier.rejectRequest = function (request, reason) {
return new Promise((resolve, reject) => {
floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, {
receiverID: myFloID
}).then(result => resolve(result))
receiverID: myFloID
}).then(result => resolve(result))
.catch(error => reject(error))
})
}
Cashier.checkIfUpiTxIsValid = function(upiTxID) {
Cashier.checkIfUpiTxIsValid = function (upiTxID) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)
@ -275,7 +276,7 @@ Cashier.checkIfUpiTxIsValid = function(upiTxID) {
})
}
Cashier.checkIfTokenTxIsValid = function(tokenTxID, sender, amount) {
Cashier.checkIfTokenTxIsValid = function (tokenTxID, sender, amount) {
return new Promise((resolve, reject) => {
let requests = Cashier.Requests;
for (let r in requests)

View File

@ -41,8 +41,11 @@ function continueWalletTopup() {
let cashier = User.findCashier();
if (!cashier)
return notify("No cashier online. Please try again in a while.", 'error');
const upiId = getRef('select_topup_upi_id').value;
if (!upiId)
return notify("Please add the UPI ID which you'll use to send the money", 'error');
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
getRef('topup_wallet__details').innerHTML = `Send <b>${formatAmount(amount)}</b> to UPI ID below`;
renderElem(getRef('topup_wallet__details'), html`Send <b>${formatAmount(amount)}</b> from your UPI ID <b>${upiId}</b>`);
getRef('topup_wallet__upi_id').value = cashierUPI[cashier];
getRef('topup_wallet__qr_code').innerHTML = ''
getRef('topup_wallet__qr_code').append(new QRCode({
@ -58,10 +61,11 @@ function depositMoneyToWallet() {
return notify("No cashier online. Please try again in a while.", 'error');
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
let upiTxID = getRef('topup_wallet__txid').value.trim();
const upiId = getRef('select_topup_upi_id').value;
if (upiTxID === '')
return notify("Please enter UPI transaction ID", 'error');
buttonLoader('topup_wallet_button', true);
User.cashToToken(cashier, amount, upiTxID).then(result => {
User.cashToToken(cashier, amount, upiTxID, upiId).then(result => {
console.log(result);
showChildElement('topup_wallet_process', 2);
refreshBalance()
@ -77,7 +81,7 @@ 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());
const upiId = getRef('select_upi_id').value;
const upiId = getRef('select_withdraw_upi_id').value;
if (!upiId)
return notify("Please add an UPI ID to continue", 'error');
buttonLoader('withdraw_rupee_button', true);
@ -135,6 +139,7 @@ async function renderSavedUpiIds() {
getRef('saved_upi_ids_list').append(frag);
}
function saveUpiId() {
const frag = document.createDocumentFragment();
const upiId = getRef('get_upi_id').value.trim();
if (upiId === '')
return notify("Please add an UPI ID to continue", 'error');
@ -146,15 +151,10 @@ function saveUpiId() {
if (pagesData.lastPage === 'settings') {
getRef('saved_upi_ids_list').append(render.savedUpiId(upiId));
} else if (pagesData.lastPage === 'home') {
getRef('select_upi_id').append(
createElement('sm-option', {
textContent: upiId,
attributes: {
value: upiId,
}
})
)
getRef('select_upi_id').parentNode.classList.remove('hide')
getRef('select_topup_upi_id').append(render.savedUpiIdOption(upiId));
getRef('select_topup_upi_id').parentNode.classList.remove('hide')
getRef('select_withdraw_upi_id').append(render.savedUpiIdOption(upiId));
getRef('select_withdraw_upi_id').parentNode.classList.remove('hide')
}
hidePopup();
}).catch(error => {
@ -352,10 +352,11 @@ cashierUI.completeRequest = function (reqID) {
}
function completeCashToTokenRequest(request) {
const { message: { upi_txid, amount }, vectorClock, senderID } = request;
const { message: { upi_txid, amount, upiID }, vectorClock, senderID } = request;
Cashier.checkIfUpiTxIsValid(upi_txid).then(_ => {
getRef('top_up_amount').textContent = formatAmount(amount);
getRef('top_up_txid').value = upi_txid;
getRef('top_up_upi_id').value = upiID;
showPopup('confirm_topup_popup');
}).catch(error => {
notify(error, 'error');
@ -413,12 +414,13 @@ function declineTopUp() {
}
if (reason.trim() === '')
return notify('Please specify a reason', 'error');
buttonLoader('decline_button', true);
Cashier.rejectRequest(floGlobals.cashierProcessingRequest, reason).then(result => {
console.log(result);
console.info('Rejected cash-to-token request:', vectorClock);
notify('Request top-up request', 'success');
notify('Rejected top-up request', 'success');
hidePopup()
}).catch(error => console.error(error))
}).catch(error => console.error(error)).finally(() => buttonLoader('decline_button', false));
}
@ -605,6 +607,14 @@ const render = {
`
})
},
savedUpiIdOption(upiId) {
return createElement('sm-option', {
textContent: upiId,
attributes: {
value: upiId,
}
})
},
paymentsHistory() {
let paymentTransactions = []
if (paymentsHistoryLoader)
@ -676,14 +686,14 @@ async function refreshBalance(button) {
buttonLoader(button, true)
floTokenAPI.getBalance(myFloID).then((balance = 0) => {
const [beforeDecimal, afterDecimal] = formatAmount(balance).split('₹')[1].split('.')
getRef('rupee_balance').innerHTML = `<span><b>${beforeDecimal}</b></span>.<span>${afterDecimal}</span>`
renderElem(getRef('rupee_balance'), html`<span><b>${beforeDecimal}</b></span>.<span>${afterDecimal}</span>`)
if (button)
buttonLoader(button, false)
})
try {
const [floBal, floRates] = await Promise.all([floBlockchainAPI.getBalance(myFloID), floExchangeAPI.getRates('FLO')])
const [beforeDecimal, afterDecimal = '00'] = String(floBal).split('.')
getRef('flo_balance').innerHTML = `<span><b>${beforeDecimal}</b></span>.<span>${afterDecimal}</span>`
renderElem(getRef('flo_balance'), html`<span><b>${beforeDecimal}</b></span>.<span>${afterDecimal}</span>`)
if (floBal < floGlobals.settings.user_flo_threshold) {
getRef('low_user_flo_warning').textContent = `Your FLO balance is low. You will receive ${floGlobals.settings.send_user_flo} FLO of worth ₹${parseFloat(floRates.rate.toFixed(2))} deducted from top-up amount.`;
getRef('low_user_flo_warning').classList.remove('hide');
@ -960,16 +970,16 @@ function applyPaymentsFilters() {
const filter = getRef('payments_type_filter').querySelector('input:checked').value;
getRef('history_applied_filters').innerHTML = ``;
if (filter !== 'all') {
getRef('history_applied_filters').append(
createElement('button', {
attributes: { 'data-filter': 'type', 'data-value': filter, title: 'Remove filter' },
className: 'applied-filter',
innerHTML: `
<span class="applied-filter__title">${filter}</span>
<svg class="icon" 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 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
`
})
);
renderElem(getRef('history_applied_filters'),
html`
<button class="applied-filter" data-filter="type" data-value=${filter} title="Remove filter">
<span class="applied-filter__title">${filter}</span>
<svg class="icon" 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 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
</svg>
</button>`);
}
toggleFilters()
render.paymentsHistory()

View File

@ -1,5 +1,6 @@
/*jshint esversion: 9 */
// Global variables
const { html, render: renderElem } = uhtml;
const domRefs = {};
const currentYear = new Date().getFullYear();
let paymentsHistoryLoader = null;
@ -72,7 +73,7 @@ const debounce = (callback, wait) => {
};
}
let zIndex = 100
let zIndex = 50
// function required for popups or modals to appear
function showPopup(popupId, pinned) {
zIndex++
@ -99,22 +100,20 @@ document.addEventListener('popupopened', async e => {
getRef('saved_ids_picker_list').append(frag)
getRef('search_saved_ids_picker').focusIn()
break;
case 'topup_wallet_popup':
case 'withdraw_wallet_popup':
let hasSavedIds = false
for (const upiId in floGlobals.savedUserData.upiIds) {
frag.append(createElement('sm-option', {
textContent: upiId,
attributes: {
value: upiId,
}
}))
frag.append(render.savedUpiIdOption(upiId))
hasSavedIds = true
}
if (hasSavedIds) {
getRef('select_upi_id').parentNode.classList.remove('hide')
getRef('select_upi_id').append(frag)
const clone = frag.cloneNode(true)
getRef('select_topup_upi_id').append(frag)
getRef('select_topup_upi_id').parentNode.classList.remove('hide')
getRef('select_withdraw_upi_id').append(clone)
getRef('select_withdraw_upi_id').parentNode.classList.remove('hide')
}
showChildElement('withdraw_wallet_process', 0)
break;
}
})
@ -126,11 +125,14 @@ document.addEventListener('popupclosed', e => {
getRef('search_saved_ids_picker').value = ''
break;
case 'topup_wallet_popup':
getRef('select_topup_upi_id').parentNode.classList.add('hide')
getRef('select_topup_upi_id').innerHTML = ''
showChildElement('topup_wallet_process', 0)
break;
case 'withdraw_wallet_popup':
getRef('select_upi_id').parentNode.classList.add('hide')
getRef('select_upi_id').innerHTML = ''
getRef('select_withdraw_upi_id').parentNode.classList.add('hide')
getRef('select_withdraw_upi_id').innerHTML = ''
showChildElement('withdraw_wallet_process', 0)
break;
case 'transfer_to_exchange_popup':
showChildElement('exchange_transfer_process', 0);