UI update
1
css/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><title>favicon</title><path d="M112,0H16A16,16,0,0,0,0,16v96a16,16,0,0,0,16,16h96a16,16,0,0,0,16-16V16A16,16,0,0,0,112,0ZM64.65,79.32H44.06L57.94,62.16h4.68c9.28,0,15.64-4.83,15.64-12.33S71.9,37.5,62.62,37.5H49.11V67.26L37.54,82l11.57,0V98.83l-18.69,8.84V20.33H64.65c18.95,0,32.93,12.21,32.93,29.5S83.6,79.32,64.65,79.32Z"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
1
css/focus.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><title>focus</title><g style="opacity:0.5"><path d="M64,0a64,64,0,1,0,64,64A64,64,0,0,0,64,0Zm0,89A25,25,0,1,1,89,64,25,25,0,0,1,64,89Z" style="fill:#562787"/></g><g style="opacity:0.5"><path d="M64,20a44,44,0,1,0,44,44A44,44,0,0,0,64,20Zm0,69A25,25,0,1,1,89,64,25,25,0,0,1,64,89Z" style="fill:#6b31a8"/></g></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
2554
css/main.css
2
css/main.min.css
vendored
2481
css/main.scss
1
css/sign-in-bg.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="1080" viewBox="0 0 1920 1080"><defs><style>.a,.b,.c{opacity:0.6;}.a{fill:url(#a);}.b{fill:url(#b);}.c{fill:url(#c);}</style><linearGradient id="a" x1="222.28" y1="490.07" x2="392.81" y2="490.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#662d91"/><stop offset="1" stop-color="#ff7bac"/></linearGradient><linearGradient id="b" x1="-116.86" y1="38.37" x2="869.09" y2="38.37" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b1464"/><stop offset="1" stop-color="#ed1e79"/></linearGradient><linearGradient id="c" x1="1406.14" y1="835.71" x2="1784.51" y2="1294.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d4145a"/><stop offset="0.99" stop-color="#f7931e"/></linearGradient></defs><title>sign-in-bg</title><path class="a" d="M226.53,423.18c-19.65,20.72,31.9,140.63,92.68,136.47,43.87-3,84.77-70.12,70.82-94.66-5.28-9.29-16.61-9.31-44.3-14.22C260.17,435.57,236.83,412.31,226.53,423.18Z"/><path class="b" d="M-110.68-19.48c28.79-111.07,152-161.1,230.4-193,285.16-115.83,551.79,33.94,599,60.48,63.85,35.86,158.51,89,149.76,135.36-16.21,85.87-359.81.52-576,210.24-75.17,72.92-83,131.12-144,138.24C27.63,346-152.55,142-110.68-19.48Z"/><path class="c" d="M717.8,1107.08c-17.52-81.33,84.59-196.52,201.6-239,242.11-88,432.84,177,601.92,83.52C1634.54,889,1583.59,751.06,1757.48,580c43.66-42.94,163.34-160.65,241.92-129.6,116.44,46,165.06,427.55-5.76,650.88-204.9,267.88-643.48,200.88-921.6,158.4C945.66,1240.41,739.74,1209,717.8,1107.08Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
css/success-art.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><defs><style>.a{fill:#d4145a;}.b{fill:#2e3192;}.c{fill:#fff;}.d{fill:#662d91;}.e{fill:#9e005d;}.f{fill:#00a99d;}</style></defs><title>success-art</title><polygon class="a" points="87.96 46.76 59.68 69.04 48.54 54.9 43.83 58.62 54.97 72.75 58.68 77.47 91.67 51.47 87.96 46.76"/><polygon class="b" points="84.33 42.6 61.5 70.43 47.58 59.02 43.78 63.66 57.7 75.07 62.33 78.88 88.97 46.41 84.33 42.6"/><polygon class="c" points="86.09 44.39 60.64 69.85 47.91 57.12 43.66 61.36 56.39 74.09 60.64 78.33 90.33 48.64 86.09 44.39"/><circle class="a" cx="27" cy="33" r="2"/><circle class="a" cx="111.5" cy="93.5" r="5.5"/><circle class="d" cx="47" cy="88" r="2"/><circle class="d" cx="22.5" cy="82.5" r="4.5"/><circle class="d" cx="45.5" cy="114.5" r="3.5"/><circle class="d" cx="82" cy="18" r="5"/><circle class="e" cx="102.5" cy="51.5" r="1.5"/><circle class="e" cx="60.5" cy="37.5" r="2.5"/><circle class="e" cx="103" cy="77" r="3"/><circle class="e" cx="87.5" cy="108.5" r="3.5"/><circle class="f" cx="39" cy="20" r="3"/><circle class="f" cx="85" cy="83" r="2"/><circle class="f" cx="32.5" cy="101.5" r="3.5"/><circle class="f" cx="112" cy="113" r="1"/><circle class="f" cx="115" cy="29" r="2"/><circle class="f" cx="18" cy="53" r="1"/><circle class="f" cx="11" cy="5" r="1"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
css/welcome.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><title>welcome</title><path d="M31.64,126S61,90.77,61,90.61a44,44,0,0,0,2-13.27V3.65A1.65,1.65,0,0,0,61.41,2h0A5.66,5.66,0,0,0,55.85,6.5L46.71,43.13,45.8,72.32A10.83,10.83,0,0,1,42,79.71L13.93,104.42" style="fill:#ffd2a1"/><path d="M62.6,4V81.45a17.45,17.45,0,0,1-2.38,8.8L56,97.52a6.76,6.76,0,0,1-.67.92L31.14,126.05l-7.55-10L52.9,85.94a12.61,12.61,0,0,0,3.56-8.38l-.77-34.82a5.3,5.3,0,0,1,0-.69l3.74-40,.67.2A1.88,1.88,0,0,1,62.6,4Z" style="fill:#e8a380"/><path d="M61.41,0a7.66,7.66,0,0,0-7.5,6L44.77,42.65a2,2,0,0,0-.05.42l-.91,29.09a8.83,8.83,0,0,1-3.09,6L12.61,102.92a2,2,0,1,0,2.64,3L43.34,81.23a12.82,12.82,0,0,0,4.46-8.85l.91-29L57.81,6.9A3.67,3.67,0,0,1,61.07,4V31.43a2.2,2.2,0,0,0-.27.26C53.3,40.25,53.3,55.75,53.3,65a2,2,0,0,0,4,0c0-7.25,0-18.54,3.77-26.41V77.4a35.07,35.07,0,0,1-8.91,22.49l-22,24.78a2,2,0,0,0,.17,2.82,2,2,0,0,0,2.82-.17l22-24.77a39.12,39.12,0,0,0,9.92-25V3.65A3.65,3.65,0,0,0,61.41,0Z"/><path d="M57.68,109.08h0c-2.37,2.11-6.31,1.58-8.79-1.16L29.72,86.76C27.23,84,27.13,80.08,29.5,78h0c2.37-2.1,6.31-1.58,8.8,1.17L57.47,100.3C60,103.05,60.05,107,57.68,109.08Z" style="fill:#fbb03b"/><path d="M58.72,101.69,48.83,90.77c1.8,2,1.39,5.24-.9,7.27l-.28.25c-2.29,2-5.6,2.08-7.4.1l9.89,10.92c1.8,2,5.11,1.93,7.4-.1l.28-.25C60.12,106.92,60.52,103.67,58.72,101.69Z" style="fill:#f7931e"/><path d="M50.68,118.08h0c-2.37,2.11-6.31,1.58-8.79-1.16L22.72,95.76C20.23,93,20.13,89.08,22.5,87h0c2.37-2.1,6.31-1.58,8.8,1.17L50.47,109.3C53,112.05,53.05,116,50.68,118.08Z" style="fill:#fbb03b"/><path d="M51.72,110.69,41.83,99.77c1.8,2,1.39,5.24-.9,7.27l-.28.25c-2.29,2-5.6,2.08-7.4.1l9.89,10.92c1.8,2,5.11,1.93,7.4-.1l.28-.25C53.12,115.92,53.52,112.67,51.72,110.69Z" style="fill:#f7931e"/><path d="M60,99,40.78,77.8c-3.22-3.55-8.42-4.14-11.6-1.31a7.16,7.16,0,0,0-2.38,5.05A8.34,8.34,0,0,0,27,83.76a7.33,7.33,0,0,0-5.81,1.73,7.16,7.16,0,0,0-2.38,5.05,9.15,9.15,0,0,0,2.44,6.57L40.4,118.26a9.23,9.23,0,0,0,6.76,3.1,7.08,7.08,0,0,0,7.23-6.83,8.43,8.43,0,0,0-.2-2.23,8,8,0,0,0,1,.06,7.08,7.08,0,0,0,7.23-6.83A9.22,9.22,0,0,0,60,99Zm-10.6,17.63c-1.55,1.37-4.23.92-6-1L24.2,94.42a5.16,5.16,0,0,1-1.41-3.67,3.23,3.23,0,0,1,1-2.27,3.27,3.27,0,0,1,2.21-.8,5.25,5.25,0,0,1,3.78,1.81L49,110.64a5.13,5.13,0,0,1,1.41,3.67A3.19,3.19,0,0,1,49.35,116.59Zm8-9c-1.55,1.37-4.23.92-6-1L32.2,85.42a5.16,5.16,0,0,1-1.41-3.67,3.23,3.23,0,0,1,1-2.27,3.27,3.27,0,0,1,2.21-.8,5.25,5.25,0,0,1,3.78,1.81L57,101.64a5.13,5.13,0,0,1,1.41,3.67A3.19,3.19,0,0,1,57.35,107.59Z"/><path d="M96.36,126S67,90.77,67,90.61a44,44,0,0,1-2-13.27V3.65A1.65,1.65,0,0,1,66.59,2h0A5.66,5.66,0,0,1,72.15,6.5l9.14,36.63.91,29.19A10.83,10.83,0,0,0,86,79.71l28.11,24.71" style="fill:#ffd2a1"/><path d="M65.4,4V81.45a17.45,17.45,0,0,0,2.38,8.8L72,97.52a6.76,6.76,0,0,0,.67.92l24.16,27.61,7.55-10L75.1,85.94a12.61,12.61,0,0,1-3.56-8.38l.77-34.82a5.3,5.3,0,0,0,0-.69l-3.74-40-.67.2A1.88,1.88,0,0,0,65.4,4Z" style="fill:#e8a380"/><path d="M66.59,0a7.66,7.66,0,0,1,7.5,6l9.14,36.64a2,2,0,0,1,0,.42l.91,29.09a8.83,8.83,0,0,0,3.09,6l28.11,24.71a2,2,0,1,1-2.64,3L84.66,81.23a12.82,12.82,0,0,1-4.46-8.85l-.91-29L70.19,6.9A3.67,3.67,0,0,0,66.93,4V31.43a2.2,2.2,0,0,1,.27.26c7.5,8.56,7.5,24.06,7.5,33.32a2,2,0,0,1-4,0c0-7.25,0-18.54-3.77-26.41V77.4a35.07,35.07,0,0,0,8.91,22.49l22,24.78a2,2,0,0,1-.17,2.82,2,2,0,0,1-2.82-.17l-22-24.77a39.12,39.12,0,0,1-9.92-25V3.65A3.65,3.65,0,0,1,66.59,0Z"/><path d="M70.32,109.08h0c2.37,2.11,6.31,1.58,8.79-1.16L98.28,86.76c2.49-2.74,2.59-6.68.22-8.78h0c-2.37-2.1-6.31-1.58-8.8,1.17L70.53,100.3C68.05,103.05,68,107,70.32,109.08Z" style="fill:#fbb03b"/><path d="M69.28,101.69l9.89-10.92c-1.8,2-1.39,5.24.9,7.27l.28.25c2.29,2,5.6,2.08,7.4.1l-9.89,10.92c-1.8,2-5.11,1.93-7.4-.1l-.28-.25C67.88,106.92,67.48,103.67,69.28,101.69Z" style="fill:#f7931e"/><path d="M77.32,118.08h0c2.37,2.11,6.31,1.58,8.79-1.16l19.17-21.16c2.49-2.74,2.59-6.68.22-8.78h0c-2.37-2.1-6.31-1.58-8.8,1.17L77.53,109.3C75.05,112.05,75,116,77.32,118.08Z" style="fill:#fbb03b"/><path d="M76.28,110.69l9.89-10.92c-1.8,2-1.39,5.24.9,7.27l.28.25c2.29,2,5.6,2.08,7.4.1l-9.89,10.92c-1.8,2-5.11,1.93-7.4-.1l-.28-.25C74.88,115.92,74.48,112.67,76.28,110.69Z" style="fill:#f7931e"/><path d="M68.05,99,87.22,77.8c3.22-3.55,8.42-4.14,11.6-1.31a7.16,7.16,0,0,1,2.38,5.05,8.34,8.34,0,0,1-.19,2.22,7.33,7.33,0,0,1,5.81,1.73,7.16,7.16,0,0,1,2.38,5.05,9.15,9.15,0,0,1-2.44,6.57L87.6,118.26a9.23,9.23,0,0,1-6.76,3.1,7.08,7.08,0,0,1-7.23-6.83,8.43,8.43,0,0,1,.2-2.23,8,8,0,0,1-1,.06,7.08,7.08,0,0,1-7.23-6.83A9.22,9.22,0,0,1,68.05,99Zm10.6,17.63c1.55,1.37,4.23.92,6-1L103.8,94.42a5.16,5.16,0,0,0,1.41-3.67,3.23,3.23,0,0,0-1-2.27,3.27,3.27,0,0,0-2.21-.8,5.25,5.25,0,0,0-3.78,1.81L79,110.64a5.13,5.13,0,0,0-1.41,3.67A3.19,3.19,0,0,0,78.65,116.59Zm-8-9c1.55,1.37,4.23.92,6-1L95.8,85.42a5.16,5.16,0,0,0,1.41-3.67,3.23,3.23,0,0,0-1-2.27,3.27,3.27,0,0,0-2.21-.8,5.25,5.25,0,0,0-3.78,1.81L71,101.64a5.13,5.13,0,0,0-1.41,3.67A3.19,3.19,0,0,0,70.65,107.59Z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
BIN
favicon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
1
favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><title>favicon</title><path d="M65,0H14.79V128l27.38-13V90.5l-17-.08,17-21.64V25.15H62c13.6,0,22.92,7.08,22.92,18.08S75.58,61.3,62,61.3H55.12L34.78,86.45H65c27.76,0,48.25-17.88,48.25-43.22S92.72,0,65,0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 298 B |
BIN
favicon_1.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
389
new.html
Normal file
@ -0,0 +1,389 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>RanchiMall Pay</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description"
|
||||
content="This webapp allows monitoring FLO addresses and performing transactions based on blockchain.">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/main.min.css">
|
||||
<script id="floGlobals">
|
||||
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
||||
const floGlobals = {
|
||||
|
||||
//Required for all
|
||||
blockchain: "FLO",
|
||||
|
||||
//Required for blockchain API operators
|
||||
apiURL: {
|
||||
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
|
||||
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
|
||||
},
|
||||
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf",
|
||||
sendAmt: 0.001,
|
||||
fee: 0.0005,
|
||||
|
||||
//Required for token API operations
|
||||
tokenURL: "https://ranchimallflo.duckdns.org/",
|
||||
currency: "rupee",
|
||||
|
||||
//Required for Supernode operations
|
||||
SNStorageID: "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
|
||||
supernodes: {}, //each supnernode must be stored as floID : {uri:<uri>,pubKey:<publicKey>}
|
||||
|
||||
//for cloud apps
|
||||
subAdmins: [],
|
||||
application: "TEST_MODE",
|
||||
appObjects: {},
|
||||
generalData: {},
|
||||
lastVC: {}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="onLoadStartUp()">
|
||||
<sm-notifications id="notification_drawer"></sm-notifications>
|
||||
<sm-popup id="confirmation_popup">
|
||||
<h4 id="confirm_title"></h4>
|
||||
<p id="confirm_message"></p>
|
||||
<div class="flex align-center">
|
||||
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
|
||||
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
|
||||
</div>
|
||||
</sm-popup>
|
||||
<div id="main_card">
|
||||
<header id="main_header" class="flex align-center space-between">
|
||||
<div class="flex align-center">
|
||||
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108"
|
||||
style="enable-background:new 0 0 90.5 106.3;" xml:space="preserve">
|
||||
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
|
||||
c1.3-3.6,2.6-5.2,12.9-15.1c6.2-5.9,9.3-10.3,11.1-15.5c0.7-2.1,0.8-7.6,0.2-9.4C66.5,12,61.7,6.7,53.7,1.6c-3-1.9-4.3-2.1-4.3-0.8
|
||||
c0,0.3-0.5,1.4-1,2.4l-1,1.8l-2.8-1.9c-1.5-1.1-3.4-2.2-4.1-2.6c-1.3-0.7-2.4-0.6-2.4,0.2c0,0.3-1.4,3.4-2,4.4
|
||||
c0,0.1-0.4-0.1-0.9-0.4c-6.1-4.4-8.7-5.5-8.7-3.9c0,0.7-1.8,4.2-4,7.9C16,19.5,9.4,24.9,2.6,24.9c-3,0-2.9-0.1-2,3.4
|
||||
c0.7,2.8,1.1,3.1,3.6,2.3c2.3-0.7,3.9-1.5,5.8-2.9c0.8-0.6,1.5-0.9,1.6-0.9c0.1,0.1,0.5,1,0.7,2.1s0.7,2,0.9,2.1
|
||||
c0.8,0.3,5.1-1.3,7.5-2.9l2.3-1.5l0.5,1.8c0.6,2.4,1,2.7,3.3,2.1c3.9-1,7.7-3.7,11.5-8.2l2-2.4l-0.2,2.1c-0.6,5.4-4.3,11.4-11.3,18
|
||||
c-1.8,1.7-4.7,4.5-6.5,6.2c-10.7,10.2-10,18.6,2,26.5c2.7,1.8,10.3,5.8,15.3,8c0.9,0.4,3.3,1.7,5.3,2.9c11,6.5,16.4,13.1,16.4,19.7
|
||||
c0,1.3,0.1,2.4,0.2,2.6l0,0c0.3,0.3,0.1,0.3,3-0.5c1.4-0.4,2.6-0.9,2.8-1.1c0.4-0.6-0.6-3.7-1.8-6.1c-1.3-2.5-5.6-7-8.9-9.4
|
||||
c-3.8-2.8-9.3-5.9-17-9.7c-8.5-4.2-11.8-6.2-14.7-9.1c-2.6-2.6-3.9-5.3-3.9-8.2c0-4.6,2.3-8.6,8.3-14.1c9.4-8.7,13-13,15.5-18.8
|
||||
c1.3-3,1.4-3.4,1.4-6.7c0-3.1-0.1-3.8-1.1-6l-1.1-2.4l1-1.6c0.5-0.9,1.2-2.1,1.5-2.6l0.5-1l1.5,2.1c1.8,2.6,3.2,6.8,3.2,9.3
|
||||
c0,1.7-0.6,4.7-1.4,6.4c-0.2,0.4-0.4,1-0.5,1.3c-0.1,0.3-1.1,2-2.2,3.7c-2,3-5.2,6.4-13.4,14.2c-5.7,5.4-7.6,8.6-7.8,13.1
|
||||
c-0.2,3.7,0.7,5.9,3.7,9.2c3.2,3.4,6.9,5.8,17.4,11c12.1,6,17.3,9.6,21.3,14.5c2.5,3.2,3.7,5.8,3.9,9.3c0.1,1.6,0.3,3,0.5,3
|
||||
c0.1,0.1,0.8,0,1.4-0.2s1.9-0.5,2.7-0.7l1.5-0.4l-0.2-1.5c-0.7-5.1-5.4-10.8-13.1-16c-4.4-2.9-5.8-3.7-17.3-9.4
|
||||
c-5.7-2.8-9.2-5.1-11.8-7.6c-4.3-4.2-5.1-8.8-2.7-13.9c1.4-2.8,2.7-4.4,12.5-13.8c8-7.7,11.4-13.7,11.4-20.1c0-5.1-2.3-9.9-6.9-14.3
|
||||
c-1.1-1-2-2-2.1-2.2c-0.2-0.4,1.5-3.9,1.9-3.9c1.2,0,7.8,6.3,9.7,9.2c2,3.3,2.5,5,2.5,8.9c0,3.9-0.6,5.9-2.9,9.8
|
||||
c-2.4,4.1-4.2,6-14.2,15.5c-3.4,3.2-5.7,6.1-6.9,8.7c-0.9,2-1.1,2.7-1.1,5.1c0,2.3,0.2,3.2,1,4.9c1.9,4,7.4,8.5,15.4,12.4
|
||||
c12.5,6.1,15.1,7.6,19.4,10.7c7.2,5.3,10.6,10.5,10.6,16c0,1.3,0.1,2.4,0.3,2.5c0.4,0.3,4.8-0.8,5.5-1.3
|
||||
C90.7,104.4,90.7,104.3,90.2,102.5z M20.3,23.3L20.3,23.3c-2,1-3.3,1.4-4.8,1.5L13.3,25l2.3-2.8c3.7-4.5,6.4-8.9,10-16
|
||||
c0.9-1.8,1.8-3.5,2-3.6c0.4-0.4,2.6,1.1,5.1,3.4l2.1,1.9l-1.9,2.8C28.2,17.5,24.5,21.2,20.3,23.3z M39.3,17.4
|
||||
c-1.2,1.7-6.5,5.7-8.6,6.5v0c-1.1,0.4-2.8,0.8-3.9,0.9L24.9,25l2.1-2.6c2.5-3.1,5.1-7,7-10.4c0.7-1.4,1.4-2.5,1.5-2.6
|
||||
c0.3-0.4,1.7,1.4,3,4.1l1.5,3L39.3,17.4z M44.6,10c-0.7,1.2-1.4,2.1-1.5,2.1c-0.1,0-1.5-1.4-3-3l-2.8-3l0.6-1.5
|
||||
c1.1-2.6,1.3-2.7,3.4-1c1.9,1.5,4.5,3.8,4.5,4.1C45.8,7.8,45.3,8.9,44.6,10z" />
|
||||
</svg>
|
||||
<h4>RanchiMall Pay</h4>
|
||||
</div>
|
||||
<theme-toggle></theme-toggle>
|
||||
</header>
|
||||
<section id="pages_container" class="gap-2">
|
||||
<section id="home" class="page hide">
|
||||
<div id="user" class="hide grid gap-2 user-element">
|
||||
<div class="flex">
|
||||
<button class="button primary-action" onclick="showTokenTransfer('send')">
|
||||
<svg class="icon" 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="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
||||
</svg>
|
||||
Send
|
||||
</button>
|
||||
<button class="button primary-action" onclick="showTokenTransfer('request')">
|
||||
<svg class="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>
|
||||
<path
|
||||
d="M20,2H4.01c-1.1,0-2,0.9-2,2L2,22l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z M12,6c1.1,0,2,0.9,2,2s-0.9,2-2,2 s-2-0.9-2-2S10.9,6,12,6z M16,14H8v-0.57c0-0.81,0.48-1.53,1.22-1.85C10.07,11.21,11.01,11,12,11c0.99,0,1.93,0.21,2.78,0.58 C15.52,11.9,16,12.62,16,13.43V14z" />
|
||||
</g>
|
||||
</svg>
|
||||
Request
|
||||
</button>
|
||||
</div>
|
||||
<section id="wallet_section" class="grid gap-1-5">
|
||||
<h4 class="flex align-center">
|
||||
<svg class="icon margin-right-0-5" 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>
|
||||
<path
|
||||
d="M18,4H6C3.79,4,2,5.79,2,8v8c0,2.21,1.79,4,4,4h12c2.21,0,4-1.79,4-4V8C22,5.79,20.21,4,18,4z M16.14,13.77 c-0.24,0.2-0.57,0.28-0.88,0.2L4.15,11.25C4.45,10.52,5.16,10,6,10h12c0.67,0,1.26,0.34,1.63,0.84L16.14,13.77z M6,6h12 c1.1,0,2,0.9,2,2v0.55C19.41,8.21,18.73,8,18,8H6C5.27,8,4.59,8.21,4,8.55V8C4,6.9,4.9,6,6,6z" />
|
||||
</g>
|
||||
</svg>
|
||||
Wallet
|
||||
</h4>
|
||||
<div class="grid gap-0-5">
|
||||
<h5>Balance</h5>
|
||||
<h1 class="h1" id="rupee_balance"></h1>
|
||||
</div>
|
||||
<div class="grid gap-1">
|
||||
<sm-input id="request_cashier_amount" type="number" name="amount" placeholder="Amount"
|
||||
animate>
|
||||
</sm-input>
|
||||
<div class="flex">
|
||||
<button class="primary-action flex-1" onclick="userUI.requestTokenFromCashier()">
|
||||
<svg class="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">
|
||||
<rect fill="none" height="24" width="24" />
|
||||
<g>
|
||||
<path
|
||||
d="M19.83,7.5l-2.27-2.27c0.07-0.42,0.18-0.81,0.32-1.15C17.96,3.9,18,3.71,18,3.5C18,2.67,17.33,2,16.5,2 c-1.64,0-3.09,0.79-4,2l-5,0C4.46,4,2,6.46,2,9.5S4.5,21,4.5,21l5.5,0v-2h2v2l5.5,0l1.68-5.59L22,14.47V7.5H19.83z M13,9H8V7h5V9z M16,11c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1s1,0.45,1,1C17,10.55,16.55,11,16,11z" />
|
||||
</g>
|
||||
</svg>
|
||||
Deposit
|
||||
</button>
|
||||
<button class="primary-action flex-1" onclick="userUI.withdrawCashFromCashier()">
|
||||
<svg class="icon" 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="M19 14V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zm-9-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm13-6v11c0 1.1-.9 2-2 2H4v-2h17V7h2z" />
|
||||
</svg>
|
||||
Withdraw
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<section id="cashier" class="hide admin-element">
|
||||
<h4>Requests</h4>
|
||||
<ul id="cashier_request_list" class="observe-empty-state"></ul>
|
||||
<div class="empty-state">
|
||||
<h4>No requests to process</h4>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="history" class="page hide grid gap-1">
|
||||
<h4>Transactions history</h4>
|
||||
<ul id="token_transactions" class="observe-empty-state">
|
||||
</ul>
|
||||
<div class=" empty-state gap-1 justify-center text-center">
|
||||
<h4>No transactions</h4>
|
||||
</div>
|
||||
</section>
|
||||
<section id="activity" class="page hide grid gap-1">
|
||||
<h4>Activity</h4>
|
||||
<tab-header target="user_sections">
|
||||
<sm-tab>Wallet transactions</sm-tab>
|
||||
<sm-tab>Payment requests</sm-tab>
|
||||
</tab-header>
|
||||
<tab-panels id="user_sections">
|
||||
<section>
|
||||
<ul id="user-cashier-requests" class="observe-empty-state"></ul>
|
||||
<div class=" empty-state gap-1 justify-center text-center">
|
||||
<h4>No transactions</h4>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<ul id="user-money-requests" class="observe-empty-state"></ul>
|
||||
<div class=" empty-state gap-1 justify-center text-center">
|
||||
<h4>No requests</h4>
|
||||
</div>
|
||||
</section>
|
||||
</tab-panels>
|
||||
</section>
|
||||
<section id="settings" class="page hide gap-1-5">
|
||||
<h4>Settings</h4>
|
||||
<section class="grid gap-1">
|
||||
<div class="grid">
|
||||
<h5>My FLO ID</h5>
|
||||
<sm-copy id="logged_in_user_id" style="font-size: 0.9rem;"></sm-copy>
|
||||
</div>
|
||||
<sm-button class="danger justify-self-start" onclick="signOut()">Sign out</sm-button>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<nav id="main_navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#/home" class="nav-item interact">
|
||||
<svg class="icon" 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="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">Home</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/history" class="nav-item interact">
|
||||
<svg class="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">
|
||||
<path d="M0,0h24v24H0V0z" fill="none" />
|
||||
<g>
|
||||
<path
|
||||
d="M19.5,3.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2v14H3v3c0,1.66,1.34,3,3,3h12c1.66,0,3-1.34,3-3V2 L19.5,3.5z M19,19c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H8V5h11V19z" />
|
||||
<rect height="2" width="6" x="9" y="7" />
|
||||
<rect height="2" width="2" x="16" y="7" />
|
||||
<rect height="2" width="6" x="9" y="10" />
|
||||
<rect height="2" width="2" x="16" y="10" />
|
||||
</g>
|
||||
</svg>
|
||||
<span class="nav-item__title">History</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/activity" class="nav-item interact user-element">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path
|
||||
d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">Activity</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/settings" class="nav-item interact">
|
||||
<svg class="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="M0,0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span class="nav-item__title">Settings</span>
|
||||
</button>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- Popups -->
|
||||
<sm-popup id="token_transfer_popup">
|
||||
<header slot="header" class="popup__header">
|
||||
<button class="popup__header__close justify-self-start" onclick="hidePopup()">
|
||||
<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>
|
||||
</header>
|
||||
<section class="grid gap-2">
|
||||
<h4 id="token_transfer__title"></h4>
|
||||
<sm-form>
|
||||
<sm-input id="tt_flo_id" placeholder="FLO ID" error-text="Invalid FLO ID" data-flo-id animate required
|
||||
autofocus>
|
||||
</sm-input>
|
||||
<sm-input id="tt_amount" type="number" placeholder="0" required min="1">
|
||||
<svg slot="icon" class="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>
|
||||
<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>
|
||||
<sm-input id="tt_remark" placeholder="Add a message" animate></sm-input>
|
||||
<button id="tt_button" class="button button--primary cta" onclick="executeUserAction()"
|
||||
type="submit">Send</button>
|
||||
</sm-form>
|
||||
</section>
|
||||
</sm-popup>
|
||||
|
||||
<!-- templates -->
|
||||
<template id="transaction_template">
|
||||
<li class="transaction grid">
|
||||
<div class="transaction__icon"></div>
|
||||
<div class="transaction__receiver breakable"></div>
|
||||
<time class="transaction__time"></time>
|
||||
<div class="transaction__amount"></div>
|
||||
</li>
|
||||
</template>
|
||||
<template id="cashier_request_template">
|
||||
<li class="cashier-request flex-wrap">
|
||||
<div class="cashier-request__mode"></div>
|
||||
<div class="grid gap-0-5 flex-1">
|
||||
<div class="cashier-request__requestor breakable"></div>
|
||||
<time class="cashier-request__time"></time>
|
||||
</div>
|
||||
<div class="cashier-request__status"></div>
|
||||
</li>
|
||||
</template>
|
||||
<template id="wallet_request_template">
|
||||
<li class="wallet-request flex-wrap">
|
||||
<div class="grid gap-0-5 flex-1">
|
||||
<div class="wallet-request__requestor breakable"></div>
|
||||
<time class="wallet-request__time"></time>
|
||||
</div>
|
||||
<div class="wallet-request__mode"></div>
|
||||
<div class="wallet-request__status"></div>
|
||||
</li>
|
||||
</template>
|
||||
<template id="payment_request_template">
|
||||
<li class="payment-request">
|
||||
<div class="grid gap-0-5 flex-1">
|
||||
<div class="payment-request__requestor breakable"></div>
|
||||
<div class="payment-request__remark"></div>
|
||||
</div>
|
||||
<div class="payment-request__amount"></div>
|
||||
<div class="flex space-between full-bleed align-center">
|
||||
<time class="payment-request__time"></time>
|
||||
<div class="payment-request__actions"></div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<script src="scripts/components.js"></script>
|
||||
<script src="scripts/std_ui.js"></script>
|
||||
<script src="scripts/std_op.js"></script>
|
||||
<script src="scripts/fn_pay.js"></script>
|
||||
<script src="scripts/fn_ui.js"></script>
|
||||
<script id="onLoadStartUp">
|
||||
function onLoadStartUp() {
|
||||
console.log("Starting the app! Please Wait!")
|
||||
floDapps.launchStartUp().then(result => {
|
||||
console.log(`Welcome ${myFloID}`);
|
||||
getRef('logged_in_user_id').value = myFloID;
|
||||
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(myFloID)
|
||||
tokenAPI.getBalance(myFloID).then(balance => {
|
||||
getRef('rupee_balance').textContent = balance.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' })
|
||||
})
|
||||
if (floGlobals.isSubAdmin) {
|
||||
cashierUI.renderRequests(Cashier.Requests);
|
||||
Cashier.init().then(result => {
|
||||
console.log(result);
|
||||
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.remove('hide'))
|
||||
document.querySelectorAll('.user-element').forEach(elem => elem.classList.add('hide'))
|
||||
}).catch(error => console.error(error))
|
||||
} else {
|
||||
userUI.renderCashierRequests(User.cashierRequests);
|
||||
userUI.renderMoneyRequests(User.moneyRequests);
|
||||
User.init().then(result => {
|
||||
console.log(result);
|
||||
console.log("Cashiers:", cashierUPI);
|
||||
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.add('hide'))
|
||||
document.querySelectorAll('.user-element').forEach(elem => elem.classList.remove('hide'))
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
renderAllTokenTransactions();
|
||||
showPage(window.location.hash, { firstLoad: true })
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
14749
old_index.html
Normal file
3079
scripts/components.js
Normal file
251
scripts/fn_pay.js
Normal file
@ -0,0 +1,251 @@
|
||||
/*jshint esversion: 6 */
|
||||
const TYPE_MONEY_REQUEST = "MoneyRequests",
|
||||
TYPE_CASHIER_REQUEST = "CashierRequests",
|
||||
TYPE_CASHIER_UPI = "CashierUPI";
|
||||
|
||||
const cashierUPI = {};
|
||||
|
||||
//For regular users
|
||||
const User = {};
|
||||
const cashierStatus = {};
|
||||
|
||||
User.init = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promises;
|
||||
//Request cashier for token-cash exchange
|
||||
promises = floGlobals.subAdmins.map(cashierID => floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
|
||||
senderID: myFloID,
|
||||
receiverID: cashierID,
|
||||
group: "Cashiers",
|
||||
callback: userUI.renderCashierRequests //UI_fn
|
||||
}));
|
||||
//Request received from other Users for token
|
||||
promises.push(floCloudAPI.requestGeneralData(TYPE_MONEY_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
callback: userUI.renderMoneyRequests //UI_fn
|
||||
}));
|
||||
//Check online status of cashiers
|
||||
promises.push(floCloudAPI.requestStatus(Array.from(floGlobals.subAdmins), {
|
||||
callback: (d, e) => {
|
||||
if (e) return console.error(e);
|
||||
for (let i in d)
|
||||
cashierStatus[i] = d[i];
|
||||
//Add any UI_fn if any
|
||||
}
|
||||
}))
|
||||
/*
|
||||
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
|
||||
callback: UI_RENDER_FN
|
||||
}));
|
||||
*/
|
||||
promises.push(User.getCashierUPI());
|
||||
Promise.all(promises)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.getCashierUPI = function () {
|
||||
return new Promise((resolve) => {
|
||||
Promise.allSettled(floGlobals.subAdmins.map(cashierID => floCloudAPI.requestApplicationData(TYPE_CASHIER_UPI, {
|
||||
senderID: cashierID,
|
||||
mostRecent: true
|
||||
}))).then(result => {
|
||||
for (let r of result)
|
||||
if (r.status === "fulfilled" && r.value.length)
|
||||
cashierUPI[r.value[0].senderID] = floCloudAPI.util.decodeMessage(r.value[0].message).upi;
|
||||
resolve(cashierUPI);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Object.defineProperty(User, 'cashierRequests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
|
||||
senderID: myFloID,
|
||||
group: "Cashiers",
|
||||
});
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(User, 'moneyRequests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
});
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
User.findCashier = function () {
|
||||
let online = [];
|
||||
for (let c in cashierStatus)
|
||||
if (cashierStatus[c] && cashierUPI[c])
|
||||
online.push(c);
|
||||
if (!online.length)
|
||||
return null;
|
||||
else
|
||||
return online[floCrypto.randInt(0, online.length)];
|
||||
}
|
||||
|
||||
User.cashToToken = function (cashier, amount, upiTxID) {
|
||||
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))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
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: upiID
|
||||
}, TYPE_CASHIER_REQUEST, {
|
||||
receiverID: cashier
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.sendToken = function (receiverID, amount, remark = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
tokenAPI.sendToken(myPrivKey, amount, receiverID, remark)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.decideRequest = function (request, note) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.noteApplicationData(request.vectorClock, note, {
|
||||
receiverID: myFloID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const Cashier = {};
|
||||
|
||||
Cashier.init = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promises = [];
|
||||
//Requests from user to cashier(self) for token-cash exchange
|
||||
promises.push(floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
callback: cashierUI.renderRequests //UI_fn
|
||||
}));
|
||||
//Set online status of cashier(self)
|
||||
promises.push(floCloudAPI.setStatus());
|
||||
/*
|
||||
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
|
||||
callback: UI_RENDER_FN
|
||||
}));
|
||||
*/
|
||||
Promise.all(promises)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.updateUPI = function (upi_id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.sendApplicationData({
|
||||
upi: upi_id
|
||||
}, TYPE_CASHIER_UPI)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Object.defineProperty(Cashier, 'Requests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
|
||||
receiverID: myFloID
|
||||
});
|
||||
console.debug(fk, floGlobals.generalData[fk]);
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
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))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.rejectRequest = function (request, reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, {
|
||||
receiverID: myFloID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.checkIfUpiTxIsValid = function (upiTxID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let requests = Cashier.Requests;
|
||||
for (let r in requests)
|
||||
if (requests[r].message.mode === "cash-to-token" && requests[r].note)
|
||||
if (requests[r].message.upi_txid === upiTxID)
|
||||
return reject([true, "UPI transaction is already used for another request"]);
|
||||
return resolve(true);
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.checkIfTokenTxIsValid = function (tokenTxID, sender, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let requests = Cashier.Requests;
|
||||
for (let r in requests)
|
||||
if (requests[r].message.mode === "token-to-cash" && requests[r].note)
|
||||
if (requests[r].message.token_txid === tokenTxID)
|
||||
return reject([true, "Token transaction is already used for another request"]);
|
||||
tokenAPI.getTx(tokenTxID).then(tx => {
|
||||
let parsedTxData = tokenAPI.util.parseTxData(tx);
|
||||
console.debug(parsedTxData);
|
||||
if (parsedTxData.type !== "transfer" || parsedTxData.transferType !== "token")
|
||||
reject([true, "Invalid token transfer type"]);
|
||||
else if (parsedTxData.tokenAmount !== amount)
|
||||
reject([true, "Incorrect token amount: " + parsedTxData.tokenAmount]);
|
||||
else if (parsedTxData.tokenIdentification !== floGlobals.currency)
|
||||
reject([true, "Incorrect token: " + parsedTxData.tokenIdentification]);
|
||||
else if (parsedTxData.sender !== sender)
|
||||
reject([true, "Incorrect senderID: " + parsedTxData.sender]);
|
||||
else if (parsedTxData.receiver !== myFloID)
|
||||
reject([true, "Incorrect receiverID: " + parsedTxData.receive])
|
||||
else resolve(true);
|
||||
}).catch(error => reject([null, error]))
|
||||
})
|
||||
}
|
||||
296
scripts/fn_ui.js
Normal file
@ -0,0 +1,296 @@
|
||||
/*jshint esversion: 6 */
|
||||
const userUI = {};
|
||||
|
||||
userUI.requestTokenFromCashier = function () {
|
||||
let cashier = User.findCashier();
|
||||
if (!cashier)
|
||||
return alert("No cashier online");
|
||||
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
|
||||
//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))
|
||||
}
|
||||
|
||||
userUI.withdrawCashFromCashier = function () {
|
||||
let cashier = User.findCashier();
|
||||
if (!cashier)
|
||||
return alert("No cashier online");
|
||||
let amount = parseFloat(getRef('request_cashier_amount').value.trim());
|
||||
//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))
|
||||
}
|
||||
|
||||
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;
|
||||
const frag = document.createDocumentFragment()
|
||||
for (let r in requests) {
|
||||
let oldCard = document.getElementById(r);
|
||||
if (oldCard) oldCard.remove();
|
||||
frag.append(render.walletRequestCard(requests[r]))
|
||||
}
|
||||
getRef('user-cashier-requests').append(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.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 renderAllTokenTransactions() {
|
||||
tokenAPI.getAllTxs(myFloID).then(result => {
|
||||
getRef('token_transactions').innerHTML = ''
|
||||
const frag = document.createDocumentFragment();
|
||||
for (let txid in result.transactions) {
|
||||
frag.append(render.transactionCard(txid, tokenAPI.util.parseTxData(result.transactions[txid])))
|
||||
}
|
||||
getRef('token_transactions').append(frag)
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
const render = {
|
||||
transactionCard(txid, transactionDetails) {
|
||||
const { 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 = tokenAmount
|
||||
if (sender === myFloID) {
|
||||
clone.querySelector('.transaction__amount').classList.add('sent')
|
||||
clone.querySelector('.transaction__receiver').textContent = `Sent to ${receiver || 'Myself'}`
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" 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="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>`
|
||||
} else if (receiver === myFloID) {
|
||||
clone.querySelector('.transaction__amount').classList.add('received')
|
||||
clone.querySelector('.transaction__receiver').textContent = `Received from ${sender}`
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon 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="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>`
|
||||
} 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 = `<button class="button" onclick="cashierUI.completeRequest('${vectorClock}')">Process</button>`
|
||||
return clone
|
||||
},
|
||||
walletRequestCard(details) {
|
||||
const { time, receiverID, message: { mode }, note, tag, vectorClock } = details;
|
||||
const clone = getRef('wallet_request_template').content.cloneNode(true).firstElementChild;
|
||||
clone.id = vectorClock
|
||||
clone.querySelector('.wallet-request__requestor').textContent = receiverID
|
||||
clone.querySelector('.wallet-request__time').textContent = getFormattedTime(time)
|
||||
clone.querySelector('.wallet-request__mode').textContent = mode === 'cash-to-token' ? 'Deposit' : 'Withdraw'
|
||||
let status = tag ? (tag + ":" + note) : (note || "PENDING");
|
||||
clone.querySelector('.wallet-request__status').textContent = status
|
||||
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 =
|
||||
`<button class="button" onclick="userUI.payRequest('${vectorClock}')">Pay</button>
|
||||
<button class="button" onclick="userUI.declineRequest('${vectorClock}')">Decline</button>`;
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
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 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
7
scripts/qrcode-scanner.min.js
vendored
Normal file
1
scripts/qrcode.min.js
vendored
Normal file
9521
scripts/std_op.js
Normal file
507
scripts/std_ui.js
Normal file
@ -0,0 +1,507 @@
|
||||
/*jshint esversion: 6 */
|
||||
// Global variables
|
||||
const domRefs = {};
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
//Checks for internet connection status
|
||||
if (!navigator.onLine)
|
||||
notify(
|
||||
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
||||
"error"
|
||||
);
|
||||
window.addEventListener("offline", () => {
|
||||
notify(
|
||||
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
||||
"error",
|
||||
{ pinned: true }
|
||||
);
|
||||
});
|
||||
window.addEventListener("online", () => {
|
||||
getRef("notification_drawer").clearAll();
|
||||
notify("We are back online.", "success");
|
||||
});
|
||||
|
||||
// Use instead of document.getElementById
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns dom with specified element
|
||||
function createElement(tagName, options = {}) {
|
||||
const { className, textContent, innerHTML, attributes = {} } = options
|
||||
const elem = document.createElement(tagName)
|
||||
for (let attribute in attributes) {
|
||||
elem.setAttribute(attribute, attributes[attribute])
|
||||
}
|
||||
if (className)
|
||||
elem.className = className
|
||||
if (textContent)
|
||||
elem.textContent = textContent
|
||||
if (innerHTML)
|
||||
elem.innerHTML = innerHTML
|
||||
return elem
|
||||
}
|
||||
|
||||
// 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);
|
||||
};
|
||||
}
|
||||
|
||||
let zIndex = 10
|
||||
// function required for popups or modals to appear
|
||||
function showPopup(popupId, pinned) {
|
||||
zIndex++
|
||||
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
|
||||
getRef(popupId).show({ pinned })
|
||||
return getRef(popupId);
|
||||
}
|
||||
|
||||
// hides the popup or modal
|
||||
function hidePopup() {
|
||||
if (popupStack.peek() === undefined)
|
||||
return;
|
||||
popupStack.peek().popup.hide()
|
||||
}
|
||||
|
||||
document.addEventListener('popupopened', async e => {
|
||||
switch (e.target.id) {
|
||||
case 'saved_ids_popup':
|
||||
const frag = document.createDocumentFragment()
|
||||
const allSavedIds = await getArrayOfSavedIds()
|
||||
allSavedIds.forEach(({ floID, name }) => {
|
||||
frag.append(render.savedIdPickerCard(floID, name))
|
||||
})
|
||||
getRef('saved_ids_picker_list').innerHTML = ''
|
||||
getRef('saved_ids_picker_list').append(frag)
|
||||
getRef('search_saved_ids_picker').focusIn()
|
||||
break;
|
||||
case 'get_private_key_popup':
|
||||
break;
|
||||
}
|
||||
})
|
||||
document.addEventListener('popupclosed', e => {
|
||||
zIndex--
|
||||
switch (e.target.id) {
|
||||
case 'saved_ids_popup':
|
||||
getRef('saved_ids_picker_list').innerHTML = ''
|
||||
getRef('search_saved_ids_picker').value = ''
|
||||
break;
|
||||
case 'get_private_key_popup':
|
||||
getRef('get_private_key').classList.remove('hide')
|
||||
getRef('transaction_result').classList.add('hide')
|
||||
getRef('confirm_transaction_button').classList.remove('hide')
|
||||
getRef('confirm_transaction_button').nextElementSibling.classList.add('hide')
|
||||
break;
|
||||
case 'retrieve_flo_id_popup':
|
||||
getRef('recovered_flo_id_wrapper').classList.add('hide')
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
// displays a popup for asking permission. Use this instead of JS confirm
|
||||
const getConfirmation = (title, options = {}) => {
|
||||
return new Promise(resolve => {
|
||||
const { message, cancelText = 'Cancel', confirmText = 'OK' } = options
|
||||
showPopup('confirmation_popup', true)
|
||||
getRef('confirm_title').textContent = title;
|
||||
getRef('confirm_message').textContent = message;
|
||||
let cancelButton = getRef('confirmation_popup').children[2].children[0],
|
||||
submitButton = getRef('confirmation_popup').children[2].children[1]
|
||||
submitButton.textContent = confirmText
|
||||
cancelButton.textContent = cancelText
|
||||
submitButton.onclick = () => {
|
||||
hidePopup()
|
||||
resolve(true);
|
||||
}
|
||||
cancelButton.onclick = () => {
|
||||
hidePopup()
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//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':
|
||||
icon = `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
|
||||
break;
|
||||
case 'error':
|
||||
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 });
|
||||
if (mode === 'error') {
|
||||
console.error(message)
|
||||
}
|
||||
}
|
||||
|
||||
function getFormattedTime(time, format) {
|
||||
try {
|
||||
if (String(time).indexOf('_'))
|
||||
time = String(time).split('_')[0]
|
||||
const intTime = parseInt(time)
|
||||
if (String(intTime).length < 13)
|
||||
time *= 1000
|
||||
let [day, month, date, year] = new Date(intTime).toString().split(' '),
|
||||
minutes = new Date(intTime).getMinutes(),
|
||||
hours = new Date(intTime).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}`;
|
||||
break;
|
||||
default:
|
||||
return `${month} ${date} ${year}, ${finalHours}`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return time;
|
||||
}
|
||||
}
|
||||
// implement event delegation
|
||||
function delegate(el, event, selector, fn) {
|
||||
el.addEventListener(event, function (e) {
|
||||
const potentialTarget = e.target.closest(selector)
|
||||
if (potentialTarget) {
|
||||
e.delegateTarget = potentialTarget
|
||||
fn.call(this, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', e => showPage(window.location.hash))
|
||||
window.addEventListener("load", () => {
|
||||
document.body.classList.remove('hide')
|
||||
document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = floCrypto.validateAddr)
|
||||
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
hidePopup()
|
||||
}
|
||||
})
|
||||
document.addEventListener('copy', () => {
|
||||
notify('copied', 'success')
|
||||
})
|
||||
document.addEventListener("pointerdown", (e) => {
|
||||
if (e.target.closest("button:not([disabled]), sm-button:not([disabled]), .interact")) {
|
||||
createRipple(e, e.target.closest("button, sm-button, .interact"));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
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(4)",
|
||||
opacity: 0,
|
||||
},
|
||||
],
|
||||
{
|
||||
duration: 600,
|
||||
fill: "forwards",
|
||||
easing: "ease-out",
|
||||
}
|
||||
);
|
||||
target.append(circle);
|
||||
rippleAnimation.onfinish = () => {
|
||||
circle.remove();
|
||||
};
|
||||
}
|
||||
|
||||
const pagesData = {
|
||||
params: {}
|
||||
}
|
||||
|
||||
let tempData
|
||||
async function showPage(targetPage, options = {}) {
|
||||
const { firstLoad, hashChange, isPreview } = options
|
||||
let pageId
|
||||
let params = {}
|
||||
let searchParams
|
||||
if (targetPage === '') {
|
||||
pageId = 'home'
|
||||
} else {
|
||||
if (targetPage.includes('/')) {
|
||||
if (targetPage.includes('?')) {
|
||||
const splitAddress = targetPage.split('?')
|
||||
searchParams = splitAddress.pop()
|
||||
const pages = splitAddress.pop().split('/')
|
||||
pageId = pages[1]
|
||||
subPageId = pages[2]
|
||||
} else {
|
||||
const pages = targetPage.split('/')
|
||||
pageId = pages[1]
|
||||
subPageId = pages[2]
|
||||
}
|
||||
} else {
|
||||
pageId = targetPage
|
||||
}
|
||||
}
|
||||
if (searchParams) {
|
||||
const urlSearchParams = new URLSearchParams('?' + searchParams);
|
||||
params = Object.fromEntries(urlSearchParams.entries());
|
||||
}
|
||||
if (pagesData.lastPage !== pageId) {
|
||||
pagesData.lastPage = pageId
|
||||
}
|
||||
if (params)
|
||||
pagesData.params = params
|
||||
switch (pageId) {
|
||||
case 'transactions':
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
const animOptions = {
|
||||
duration: 100,
|
||||
fill: 'forwards',
|
||||
}
|
||||
let previousActiveElement = getRef('main_navbar').querySelector('.nav-item--active')
|
||||
const currentActiveElement = document.querySelector(`.nav-item[href="#/${pageId}"]`)
|
||||
if (currentActiveElement) {
|
||||
if (getRef('main_navbar').classList.contains('hide')) {
|
||||
getRef('main_navbar').classList.remove('hide-away')
|
||||
getRef('main_navbar').classList.remove('hide')
|
||||
getRef('main_navbar').animate([
|
||||
{
|
||||
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
transform: `none`,
|
||||
opacity: 1,
|
||||
},
|
||||
], {
|
||||
duration: 100,
|
||||
fill: 'forwards',
|
||||
easing: 'ease'
|
||||
})
|
||||
}
|
||||
getRef('main_header').classList.remove('hide')
|
||||
const previousActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(previousActiveElement)
|
||||
const currentActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(currentActiveElement)
|
||||
const isOnTop = previousActiveElementIndex < currentActiveElementIndex
|
||||
const currentIndicator = createElement('div', { className: 'nav-item__indicator' });
|
||||
let previousIndicator = getRef('main_navbar').querySelector('.nav-item__indicator')
|
||||
if (!previousIndicator) {
|
||||
previousIndicator = currentIndicator.cloneNode(true)
|
||||
previousActiveElement = currentActiveElement
|
||||
previousActiveElement.append(previousIndicator)
|
||||
} else if (currentActiveElementIndex !== previousActiveElementIndex) {
|
||||
const indicatorDimensions = previousIndicator.getBoundingClientRect()
|
||||
const currentActiveElementDimensions = currentActiveElement.getBoundingClientRect()
|
||||
let moveBy
|
||||
if (isMobileView) {
|
||||
moveBy = ((currentActiveElementDimensions.width - indicatorDimensions.width) / 2) + indicatorDimensions.width
|
||||
} else {
|
||||
moveBy = ((currentActiveElementDimensions.height - indicatorDimensions.height) / 2) + indicatorDimensions.height
|
||||
}
|
||||
indicatorObserver.observe(previousIndicator)
|
||||
previousIndicator.animate([
|
||||
{
|
||||
transform: 'none',
|
||||
opacity: 1,
|
||||
},
|
||||
{
|
||||
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `${moveBy}px` : `-${moveBy}px`})`,
|
||||
opacity: 0,
|
||||
},
|
||||
], { ...animOptions, easing: 'ease-in' }).onfinish = () => {
|
||||
previousIndicator.remove()
|
||||
}
|
||||
tempData = {
|
||||
currentActiveElement,
|
||||
currentIndicator,
|
||||
isOnTop,
|
||||
animOptions,
|
||||
moveBy
|
||||
}
|
||||
}
|
||||
previousActiveElement.classList.remove('nav-item--active');
|
||||
currentActiveElement.classList.add('nav-item--active')
|
||||
} else {
|
||||
if (!getRef('main_navbar').classList.contains('hide')) {
|
||||
getRef('main_navbar').classList.add('hide-away')
|
||||
getRef('main_navbar').animate([
|
||||
{
|
||||
transform: `none`,
|
||||
opacity: 1,
|
||||
},
|
||||
{
|
||||
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
|
||||
opacity: 0,
|
||||
},
|
||||
], {
|
||||
duration: 200,
|
||||
fill: 'forwards',
|
||||
easing: 'ease'
|
||||
}).onfinish = () => {
|
||||
getRef('main_navbar').classList.add('hide')
|
||||
}
|
||||
getRef('main_header').classList.add('hide')
|
||||
}
|
||||
}
|
||||
document.querySelectorAll('.page').forEach(page => page.classList.add('hide'))
|
||||
getRef(pageId).classList.remove('hide')
|
||||
getRef(pageId).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards', easing: 'ease' })
|
||||
}
|
||||
|
||||
const indicatorObserver = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (!entry.isIntersecting) {
|
||||
const { currentActiveElement, currentIndicator, isOnTop, animOptions, moveBy } = tempData
|
||||
currentActiveElement.append(currentIndicator)
|
||||
currentIndicator.animate([
|
||||
{
|
||||
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `-${moveBy}px` : `${moveBy}px`})`,
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
transform: 'none',
|
||||
opacity: 1
|
||||
},
|
||||
], { ...animOptions, easing: 'ease-out' })
|
||||
}
|
||||
})
|
||||
}, {
|
||||
threshold: 1
|
||||
})
|
||||
|
||||
// class based lazy loading
|
||||
class LazyLoader {
|
||||
constructor(container, elementsToRender, renderFn, options = {}) {
|
||||
const { batchSize = 10, freshRender } = options
|
||||
|
||||
this.elementsToRender = elementsToRender
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
this.renderFn = renderFn
|
||||
this.intersectionObserver
|
||||
|
||||
this.batchSize = batchSize
|
||||
this.freshRender = freshRender
|
||||
|
||||
this.lazyContainer = document.querySelector(container)
|
||||
|
||||
this.update = this.update.bind(this)
|
||||
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()
|
||||
}
|
||||
update(elementsToRender) {
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
this.render()
|
||||
}
|
||||
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)
|
||||
// Callback to be called if elements are updated or rendered for first time
|
||||
if (!lazyLoad && this.freshRender)
|
||||
this.freshRender()
|
||||
}
|
||||
clear() {
|
||||
this.intersectionObserver.disconnect()
|
||||
this.mutationObserver.disconnect()
|
||||
this.lazyContainer.innerHTML = ``;
|
||||
}
|
||||
reset() {
|
||||
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
function animateTo(element, keyframes, options) {
|
||||
const anime = element.animate(keyframes, { ...options, fill: 'both' })
|
||||
anime.finished.then(() => {
|
||||
anime.commitStyles()
|
||||
anime.cancel()
|
||||
})
|
||||
return anime
|
||||
}
|
||||
let isMobileView = false
|
||||
const mobileQuery = window.matchMedia('(max-width: 40rem)')
|
||||
function handleMobileChange(e) {
|
||||
isMobileView = e.matches
|
||||
}
|
||||
mobileQuery.addEventListener('change', handleMobileChange)
|
||||
handleMobileChange(mobileQuery)
|
||||