4487 lines
271 KiB
HTML
4487 lines
271 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<title>FLOpay</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta charset="UTF-8">
|
|
<meta name="description"
|
|
content="This webapp allows monitoring FLO addresses and performing transactions based on blockchain.">
|
|
<link rel="shortcut icon" href="flo-favicon.png" type="image/png">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;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 beginning!! */
|
|
const floGlobals = {
|
|
blockchain: "FLO",
|
|
adminID: "FH64hLqB71e6i11rnZZW7hvzoH8daCwy1J",
|
|
currency: "rupee",
|
|
application: "blockchainUPI"
|
|
}
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
|
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous" defer></script>
|
|
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
|
<script src="scripts/components.js" defer></script>
|
|
<script src="scripts/lib.js" defer></script>
|
|
<script src="scripts/floCrypto.js" defer></script>
|
|
<script src="scripts/floBlockchainAPI.js" defer></script>
|
|
<script src="scripts/compactIDB.js" defer></script>
|
|
<script src="scripts/floCloudAPI.js" defer></script>
|
|
<script src="scripts/floDapps.js" defer></script>
|
|
<script src="scripts/fn_pay.js" defer></script>
|
|
<script src="scripts/floTokenAPI.js" defer></script>
|
|
<script src="scripts/floExchangeAPI.js" defer></script>
|
|
<script src="scripts/btcOperator.js" defer></script>
|
|
<script src="scripts/qrcode.min.js" defer></script>
|
|
</head>
|
|
|
|
<body onload="onLoadStartUp()" class="hidden">
|
|
<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 gap-0-5 margin-left-auto">
|
|
<button class="button cancel-button">Cancel</button>
|
|
<button class="button button--primary confirm-button">OK</button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="prompt_popup">
|
|
<h4 id="prompt_title"></h4>
|
|
<p id="prompt_message"></p>
|
|
<sm-form>
|
|
<sm-input id="prompt_input"></sm-input>
|
|
<div class="flex align-center gap-0-5 margin-left-auto">
|
|
<button class="button cancel-button">Cancel</button>
|
|
<button class="button confirm-button button--primary" type="submit">OK</button>
|
|
</div>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<div id="secondary_pages" class="page hidden">
|
|
<header class="flex align-center gap-1">
|
|
<div class="flex align-center flex-1">
|
|
<svg class="icon flo-icon" width="64" height="64" viewBox="0 0 64 64" fill="none"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<mask id="path-1-outside-1_16_6" maskUnits="userSpaceOnUse" x="3" y="3" width="58" height="58">
|
|
<rect fill="white" x="3" y="3" width="58" height="58" />
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" />
|
|
</mask>
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" />
|
|
<path
|
|
d="M31.946 43.6019L32.6728 42.915L31.918 42.1163L31.1938 42.943L31.946 43.6019ZM31.9811 55.1957L31.2716 55.9004L31.9657 56.5992L32.6749 55.9159L31.9811 55.1957ZM31.9487 10.6835L32.7051 11.3376L32.9487 11.0559V10.6835H31.9487ZM31.9433 37.4108L31.18 38.0569L31.9442 38.9597L32.7074 38.056L31.9433 37.4108ZM31.9487 10.6808L32.7068 10.0287L30.9487 7.98495V10.6808H31.9487ZM59 28.7654L59.9166 29.1651L60.4326 27.9819L59.158 27.778L59 28.7654ZM45.7214 42.53L45.1042 41.7432L46.0802 43.4634L45.7214 42.53ZM49.7984 39.3089L49.1332 38.5622L49.1326 38.5628L49.7984 39.3089ZM54.0293 32.6075L53.0647 32.3439L53.0646 32.3441L54.0293 32.6075ZM53.1896 31.406L53.3484 30.4187L53.348 30.4186L53.1896 31.406ZM45.1733 32.702L45.588 33.612L45.5881 33.6119L45.1733 32.702ZM34.1735 40.8938L33.467 40.1861L33.4585 40.1946L33.4501 40.2033L34.1735 40.8938ZM33.9899 41.2259L33.114 40.7435L32.3319 42.1635L33.9519 42.2252L33.9899 41.2259ZM48.119 49.9064L48.3359 50.8826L49.5751 50.6072L49.0173 49.4669L48.119 49.9064ZM37.778 48.5213L38.0715 47.5653L37.2372 49.3625L37.778 48.5213ZM45.176 48.5483L45.6045 49.4518L46.6008 48.9794L46.0335 48.0338L45.176 48.5483ZM44.231 47.0228L44.8645 46.2491L44.8637 46.2484L44.231 47.0228ZM40.505 44.3498L40.9866 43.4734L40.9845 43.4723L40.505 44.3498ZM33.5768 42.1682L33.7777 41.1886L31.6127 40.7446L32.7083 42.664L33.5768 42.1682ZM31.946 59L31.1626 59.6216L31.9457 60.6085L32.7292 59.6218L31.946 59ZM28.2389 50.8946L29.2314 50.7725L29.2314 50.7721L28.2389 50.8946ZM30.1991 42.3896L31.0736 42.8747L31.7652 41.6279L30.3574 41.4022L30.1991 42.3896ZM18.7916 48.5645L17.855 48.2141L17.5413 49.0527L18.3402 49.4568L18.7916 48.5645ZM26.33 48.3161L27.0473 49.0128L25.9814 47.3788L26.33 48.3161ZM15.8108 49.8335L14.9131 49.393L14.4073 50.4237L15.4977 50.7832L15.8108 49.8335ZM30.0263 41.2286L30.0635 42.2279L32.3372 42.1433L30.7373 40.5255L30.0263 41.2286ZM28.8167 40.0082L28.1226 40.7281L28.1228 40.7282L28.8167 40.0082ZM19.823 33.2366L19.3644 34.1252L19.3645 34.1253L19.823 33.2366ZM10.9481 31.3871L10.8001 30.3981L10.7998 30.3982L10.9481 31.3871ZM9.6656 31.6436L9.46564 30.6638L8.49474 30.8619L8.68401 31.8346L9.6656 31.6436ZM18.1274 42.3464L18.0073 43.3392L18.6607 41.5005L18.1274 42.3464ZM5 28.7546L4.84003 27.7675L3.75363 27.9435L4.03268 29.0082L5 28.7546ZM26.897 35.2319L26.173 35.9217L27.8093 34.8223L26.897 35.2319ZM31.946 5L32.7265 4.37488L31.9461 3.40036L31.1655 4.37483L31.946 5ZM31.1938 42.943C27.7974 46.8199 27.6566 52.261 31.2716 55.9004L32.6906 54.491C29.9012 51.6828 29.9116 47.4417 32.6982 44.2609L31.1938 42.943ZM32.6749 55.9159C36.1304 52.5867 36.081 46.5214 32.6728 42.915L31.2192 44.2888C33.94 47.1678 33.8852 51.9727 31.2873 54.4755L32.6749 55.9159ZM31.1923 10.0294C27.4048 14.4092 25.5379 19.2455 25.5737 24.1102C25.6095 28.971 27.5438 33.7608 31.18 38.0569L32.7066 36.7647C29.3201 32.7637 27.6054 28.4127 27.5736 24.0955C27.5419 19.7822 29.1891 15.4034 32.7051 11.3376L31.1923 10.0294ZM32.7074 38.056C36.4083 33.6729 38.31 28.8504 38.3133 23.9938C38.3165 19.1359 36.42 14.3451 32.7068 10.0287L31.1906 11.3329C34.6405 15.3433 36.3161 19.6839 36.3133 23.9925C36.3104 28.3024 34.6279 32.6815 31.1792 36.7656L32.7074 38.056ZM30.9487 10.6808V10.6835H32.9487V10.6808H30.9487ZM37.8259 35.7503C43.6131 29.8345 50.6632 28.444 58.842 29.7528L59.158 27.778C50.532 26.3976 42.7591 27.8475 36.3963 34.3517L37.8259 35.7503ZM58.0834 28.3657C55.4402 34.4268 51.7791 39.1301 45.3626 41.5966L46.0802 43.4634C53.1637 40.7405 57.1436 35.524 59.9166 29.1651L58.0834 28.3657ZM46.3386 43.3168C47.7733 42.1914 49.2057 41.178 50.4642 40.055L49.1326 38.5628C47.9341 39.6322 46.6503 40.5304 45.1042 41.7432L46.3386 43.3168ZM50.4636 40.0556C52.6466 38.111 54.2121 35.7338 54.994 32.871L53.0646 32.3441C52.3939 34.8002 51.0542 36.851 49.1332 38.5622L50.4636 40.0556ZM54.9939 32.8711C55.1262 32.3872 55.2248 31.6946 54.7698 31.1186C54.3643 30.6053 53.7257 30.4794 53.3484 30.4187L53.0308 32.3933C53.1086 32.4058 53.17 32.4181 53.2184 32.43C53.2671 32.442 53.2955 32.4518 53.3091 32.4572C53.3227 32.4626 53.3139 32.4606 53.2926 32.4461C53.2699 32.4307 53.2353 32.4025 53.2004 32.3583C53.1832 32.3366 53.1677 32.3132 53.1543 32.2886C53.1409 32.264 53.1307 32.2402 53.1231 32.2182C53.1079 32.1742 53.1053 32.144 53.1049 32.1366C53.1046 32.1302 53.1059 32.1427 53.1007 32.1797C53.0955 32.2165 53.0849 32.27 53.0647 32.3439L54.9939 32.8711ZM53.348 30.4186C50.274 29.9254 47.4176 30.5801 44.7585 31.7921L45.5881 33.6119C47.9996 32.5127 50.4514 31.9794 53.0312 32.3934L53.348 30.4186ZM44.7586 31.792C40.3293 33.8104 36.7535 36.9051 33.467 40.1861L34.88 41.6015C38.1113 38.3755 41.4853 35.4816 45.588 33.612L44.7586 31.792ZM33.4501 40.2033C33.3063 40.354 33.2291 40.5156 33.2033 40.5674C33.1672 40.64 33.1517 40.6749 33.114 40.7435L34.8658 41.7083C34.9307 41.5906 34.9786 41.4891 34.9945 41.4572C35.0049 41.4363 35.0004 41.4467 34.9882 41.4667C34.9731 41.4914 34.9435 41.5355 34.8969 41.5843L33.4501 40.2033ZM33.9519 42.2252C37.0233 42.3421 39.6629 43.0109 41.8597 44.3098C44.0485 45.6039 45.857 47.5582 47.2207 50.3459L49.0173 49.4669C47.5001 46.3659 45.4368 44.1014 42.8776 42.5882C40.3265 41.0798 37.342 40.3527 34.0279 40.2266L33.9519 42.2252ZM47.9021 48.9302C44.3241 49.7253 41.1983 49.5313 38.3188 47.6801L37.2372 49.3625C40.7081 51.5937 44.4295 51.7507 48.3359 50.8826L47.9021 48.9302ZM37.4845 49.4773C39.9089 50.2215 42.6205 50.8669 45.6045 49.4518L44.7475 47.6448C42.4935 48.7137 40.4315 48.2899 38.0715 47.5653L37.4845 49.4773ZM46.0335 48.0338C45.8849 47.7862 45.8212 47.6158 45.6292 47.2504C45.4736 46.9544 45.2423 46.5584 44.8645 46.2491L43.5975 47.7965C43.6517 47.8409 43.7309 47.9377 43.8588 48.181C43.9503 48.3549 44.1431 48.7704 44.3185 49.0628L46.0335 48.0338ZM44.8637 46.2484C43.6913 45.2906 42.4149 44.2582 40.9866 43.4734L40.0234 45.2262C41.2681 45.9102 42.4109 46.8272 43.5983 47.7972L44.8637 46.2484ZM40.9845 43.4723C38.7711 42.2628 36.3236 41.7107 33.7777 41.1886L33.3759 43.1478C35.9384 43.6733 38.1079 44.1796 40.0255 45.2273L40.9845 43.4723ZM32.7083 42.664C34.346 45.5328 34.9255 48.203 34.6411 50.7474C34.3555 53.3015 33.1882 55.8274 31.1628 58.3782L32.7292 59.6218C34.9185 56.8644 36.2905 53.9944 36.6287 50.9696C36.9679 47.9351 36.2555 44.8435 34.4453 41.6724L32.7083 42.664ZM32.7294 58.3784C30.8397 55.9968 29.577 53.5818 29.2314 50.7725L27.2464 51.0167C27.6514 54.3094 29.1319 57.0622 31.1626 59.6216L32.7294 58.3784ZM29.2314 50.7721C28.8877 47.9881 29.6602 45.4224 31.0736 42.8747L29.3247 41.9045C27.7842 44.6812 26.8341 47.6775 27.2464 51.0171L29.2314 50.7721ZM30.3574 41.4022C29.3303 41.2375 28.1125 41.4187 26.9159 41.7622C25.6987 42.1117 24.4105 42.6564 23.2045 43.307C21.999 43.9574 20.8488 44.728 19.9166 45.543C19.005 46.3399 18.2137 47.2553 17.855 48.2141L19.7282 48.9149C19.9149 48.4157 20.4139 47.7647 21.233 47.0487C22.0314 46.3507 23.0524 45.6616 24.1541 45.0672C25.2552 44.4732 26.4102 43.9882 27.4678 43.6846C28.5458 43.3751 29.4344 43.2798 30.0408 43.377L30.3574 41.4022ZM18.3402 49.4568C21.2702 50.939 24.0958 50.214 26.6786 49.2534L25.9814 47.3788C23.4828 48.3082 21.3728 48.7496 19.243 47.6722L18.3402 49.4568ZM25.6127 47.6194C24.9334 48.3187 23.4333 48.9382 21.5294 49.2141C19.6667 49.484 17.6562 49.3889 16.1239 48.8838L15.4977 50.7832C17.3971 51.4094 19.7336 51.4952 21.8162 51.1934C23.8578 50.8976 25.9014 50.1927 27.0473 49.0128L25.6127 47.6194ZM16.7085 50.274C18.0554 47.5292 19.8486 45.5942 22.044 44.3081C24.2482 43.0169 26.918 42.345 30.0635 42.2279L29.9891 40.2293C26.606 40.3552 23.5923 41.0832 21.0331 42.5824C18.4649 44.0869 16.4147 46.3328 14.9131 49.393L16.7085 50.274ZM30.7373 40.5255C30.2757 40.0586 29.8942 39.6578 29.5106 39.2882L28.1228 40.7282C28.4844 41.0768 28.8157 41.4266 29.3153 41.9317L30.7373 40.5255ZM29.5108 39.2883C26.7637 36.6396 23.7967 34.1615 20.2815 32.3479L19.3645 34.1253C22.6317 35.8109 25.4373 38.1388 28.1226 40.7281L29.5108 39.2883ZM20.2816 32.348C17.4111 30.8666 14.2829 29.877 10.8001 30.3981L11.0961 32.3761C14.0015 31.9414 16.6891 32.7446 19.3644 34.1252L20.2816 32.348ZM10.7998 30.3982C10.388 30.4599 9.94468 30.566 9.46564 30.6638L9.86556 32.6234C10.4449 32.5052 10.7522 32.4277 11.0964 32.376L10.7998 30.3982ZM8.68401 31.8346C9.73479 37.2345 13.3673 40.5279 17.5941 43.1923L18.6607 41.5005C14.6039 38.9431 11.5404 36.0427 10.6472 31.4526L8.68401 31.8346ZM18.2475 41.3536C17.3819 41.2489 16.2594 40.7693 14.9918 39.9335C13.742 39.1093 12.427 37.9891 11.1875 36.7057C8.68437 34.1137 6.62095 30.9947 5.96732 28.5011L4.03268 29.0082C4.81005 31.9739 7.13413 35.3875 9.74885 38.095C11.0683 39.4613 12.4947 40.6825 13.8908 41.6031C15.2691 42.512 16.6967 43.1806 18.0073 43.3392L18.2475 41.3536ZM5.15997 29.7417C13.3246 28.4186 20.4108 29.874 26.173 35.9217L27.621 34.5421C21.2872 27.8944 13.4694 26.369 4.84003 27.7675L5.15997 29.7417ZM27.8093 34.8223C25.3851 29.4232 24.8341 24.3801 25.7561 19.5859C26.6809 14.7778 29.1009 10.1516 32.7265 5.62517L31.1655 4.37483C27.4012 9.07433 24.7952 13.9927 23.7921 19.2082C22.7863 24.4378 23.4058 29.8977 25.9847 35.6415L27.8093 34.8223ZM31.1655 5.62512C34.7619 10.1157 37.1747 14.7077 38.1168 19.4869C39.0562 24.2523 38.5469 29.2701 36.1948 34.6504L38.0274 35.4515C40.5299 29.7268 41.1033 24.2956 40.0791 19.1001C39.0576 13.9183 36.4606 9.03731 32.7265 4.37488L31.1655 5.62512Z"
|
|
mask="url(#path-1-outside-1_16_6)" />
|
|
</svg>
|
|
<h4>FLOpay</h4>
|
|
</div>
|
|
<theme-toggle></theme-toggle>
|
|
</header>
|
|
<article id="landing" class="inner-page hidden">
|
|
<section class="grid justify-center gap-1">
|
|
<h1 class="h1">Send.request</h1>
|
|
<div class="flex gap-0-5">
|
|
<a href="#/sign_up" class="button">Sign up</a>
|
|
<a href="#/sign_in" class="button button--primary">Sign in</a>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
<article id="sign_in" class="inner-page hidden">
|
|
<svg class="illustration" width="673" height="417" viewBox="0 0 673 417" fill="none"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<g clip-path="url(#clip0_2_41)">
|
|
<rect width="673" height="417" fill="#4D77FF" />
|
|
<g filter="url(#filter0_d_2_41)">
|
|
<rect x="219.881" y="-158" width="622" height="150" transform="rotate(21.4614 219.881 -158)"
|
|
fill="#4D77FF" />
|
|
</g>
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M223 79C185.997 79 156 108.997 156 146V213C156 250.003 185.997 280 223 280H229C266.003 280 296 250.003 296 213V146C296 108.997 266.003 79 229 79H223ZM226 102C201.147 102 181 122.147 181 147V208C181 232.853 201.147 253 226 253C250.853 253 271 232.853 271 208V147C271 122.147 250.853 102 226 102Z"
|
|
fill="#64E899" />
|
|
<rect x="124" y="170" width="204" height="204" rx="32" fill="#93FFBE" />
|
|
<circle cx="225.5" cy="279.5" r="37.5" fill="#64E899" />
|
|
<path
|
|
d="M273.73 128.883H223.571C222.05 128.883 220.816 130.117 220.816 131.638C220.816 133.16 222.05 134.394 223.571 134.394H273.73C275.252 134.394 276.485 133.16 276.485 131.638C276.485 130.117 275.252 128.883 273.73 128.883Z"
|
|
fill="#F9BB8F" />
|
|
<path
|
|
d="M240.145 127.702H222.26C220.086 127.702 218.323 129.464 218.323 131.638C218.323 133.812 220.086 135.574 222.26 135.574H240.145C242.318 135.574 244.081 133.812 244.081 131.638C244.081 129.464 242.318 127.702 240.145 127.702Z"
|
|
fill="#FFE39A" />
|
|
<path
|
|
d="M210.212 169.048L222.376 131.038C222.84 129.588 222.041 128.038 220.591 127.574C219.142 127.11 217.591 127.909 217.127 129.358L204.964 167.369C204.5 168.818 205.299 170.369 206.748 170.833C208.197 171.297 209.748 170.498 210.212 169.048Z"
|
|
fill="#F9BB8F" />
|
|
<path
|
|
d="M219.949 142.497L223.9 130.148C224.563 128.078 223.422 125.862 221.351 125.2C219.281 124.537 217.065 125.678 216.403 127.749L212.451 140.097C211.788 142.168 212.93 144.383 215 145.046C217.071 145.708 219.286 144.567 219.949 142.497Z"
|
|
fill="#FFE39A" />
|
|
<path
|
|
d="M221.668 121.551L222.354 119.044C222.963 116.821 225.178 115.44 227.442 115.873C229.398 116.246 230.862 117.884 231.015 119.87L231.214 122.462C231.432 125.289 229.197 127.702 226.362 127.702C223.152 127.702 220.821 124.648 221.668 121.551Z"
|
|
fill="#F9BB8F" />
|
|
<path
|
|
d="M319.342 158.621L314.759 164.049C314.188 164.725 313.875 165.581 313.875 166.466C313.875 168.535 315.552 170.213 317.622 170.213H323C325.761 170.213 328 167.974 328 165.213V150.335C328 148.27 326.326 146.596 324.261 146.596C322.196 146.596 320.522 148.27 320.522 150.335V155.395C320.522 156.576 320.104 157.718 319.342 158.621Z"
|
|
fill="black" />
|
|
<path
|
|
d="M235.682 164.563L236.587 157.659C236.719 156.654 237.581 155.69 238.504 155.271C241.476 153.919 265.073 152.709 268.136 153.84L269.614 154.385C269.763 154.44 269.916 154.484 270.072 154.517L286.871 158.011L317.149 161.644C318.704 161.831 320.035 162.849 320.622 164.301C321.766 167.127 319.686 170.213 316.637 170.213H240.639C237.62 170.213 235.289 167.557 235.682 164.563Z"
|
|
fill="#EC8973" />
|
|
<path
|
|
d="M283.87 162.112L277.983 157.651C277.281 157.118 276.423 156.83 275.542 156.83C273.309 156.83 271.5 158.639 271.5 160.872L271.5 165.213C271.5 167.974 273.738 170.213 276.5 170.213L292.884 170.213C294.84 170.213 296.426 168.627 296.426 166.67C296.426 164.714 294.84 163.128 292.884 163.128L286.89 163.128C285.8 163.128 284.739 162.771 283.87 162.112Z"
|
|
fill="black" />
|
|
<path
|
|
d="M249.275 136.191L230.841 153.991C229.756 155.04 229.202 156.523 229.334 158.026L230.002 165.625C230.215 168.049 232.142 169.967 234.567 170.17L243.255 170.896C244.07 170.964 244.889 170.832 245.641 170.51L249.973 168.654C250.964 168.229 251.792 167.496 252.332 166.562L257.584 157.488C259.184 154.724 262.93 154.148 265.286 156.303L271.97 162.418C272.482 162.886 273.085 163.242 273.742 163.465L276.04 164.242C277.548 164.753 279.214 164.247 280.184 162.984C281.192 161.67 281.211 159.848 280.229 158.514L264.274 136.824C263.332 135.544 261.837 134.787 260.247 134.787H252.748C251.452 134.787 250.207 135.29 249.275 136.191Z"
|
|
fill="#FFB9AA" />
|
|
<ellipse cx="231.618" cy="110.383" rx="10.8015" ry="10.234" fill="#F9BB8F" />
|
|
<path
|
|
d="M239.129 106.289L235.134 107.929C233.258 108.699 232.033 110.526 232.033 112.554V114.919C232.033 116.735 231.048 118.409 229.461 119.29L225.719 121.369C222.833 122.971 219.214 121.388 218.433 118.181L215.708 106.991C215.272 105.202 215.855 103.317 217.226 102.087L221.468 98.2791C222.386 97.4555 223.575 97 224.808 97H237.297C238.936 97 240.463 97.8322 241.352 99.2097C242.987 101.745 241.92 105.143 239.129 106.289Z"
|
|
fill="black" />
|
|
<path
|
|
d="M226.17 164.347L216.593 134.679C216.365 133.973 216.297 133.225 216.394 132.489L216.932 128.405C217.255 125.962 219.31 124.118 221.774 124.061L232.009 123.824C233.592 123.788 235.099 124.503 236.071 125.753L245.86 138.34C246.058 138.595 246.231 138.869 246.377 139.157L253.752 153.767C254.683 155.612 254.383 157.841 252.996 159.374L244.791 168.443C243.582 169.78 241.747 170.362 239.989 169.968L229.834 167.689C228.107 167.302 226.714 166.03 226.17 164.347Z"
|
|
fill="#FFE39A" />
|
|
<path d="M51 374H622" stroke="black" />
|
|
<circle cx="418.5" cy="84.5" r="40.5" fill="#FFD2D2" />
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M487.5 142C488.446 142 489.385 141.97 490.317 141.912C503.976 141.059 519.314 142 533 142C552.33 142 568 126.33 568 107C568 87.67 552.33 72 533 72C527.947 72 522.903 70.0693 519.377 66.4497C511.294 58.1527 499.999 53 487.5 53C463.091 53 443.272 72.6522 443.003 96.9972C443.003 96.9988 443.002 97 443 97C429.745 97 419 107.745 419 121C419 134.255 429.745 145 443 145C455.923 145 471.766 141.1 484.663 141.911C485.601 141.97 486.547 142 487.5 142Z"
|
|
fill="#EEEEFF" />
|
|
<circle cx="96.9505" cy="365.951" r="128" transform="rotate(-59.8781 96.9505 365.951)"
|
|
fill="url(#paint0_linear_2_41)" />
|
|
</g>
|
|
<defs>
|
|
<filter id="filter0_d_2_41" x="149" y="-170" width="665.754" height="399.174"
|
|
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
|
result="hardAlpha" />
|
|
<feOffset dy="4" />
|
|
<feGaussianBlur stdDeviation="8" />
|
|
<feComposite in2="hardAlpha" operator="out" />
|
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" />
|
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2_41" />
|
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2_41" result="shape" />
|
|
</filter>
|
|
<linearGradient id="paint0_linear_2_41" x1="96.9505" y1="237.95" x2="96.9505" y2="493.95"
|
|
gradientUnits="userSpaceOnUse">
|
|
<stop stop-color="white" stop-opacity="0.2" />
|
|
<stop offset="1" stop-color="white" stop-opacity="0" />
|
|
</linearGradient>
|
|
<clipPath id="clip0_2_41">
|
|
<rect width="673" height="417" fill="white" />
|
|
</clipPath>
|
|
</defs>
|
|
</svg>
|
|
<section>
|
|
<h1 class="h2">Sign In</h1>
|
|
<p>Welcome back, glad to see you again</p>
|
|
<sm-form id="sign_in_form">
|
|
<sm-input id="private_key_field" type="password" placeholder="FLO private key"
|
|
class="password-field" error-text="Private key is invalid" data-private-key required>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" autocomplete="off" readonly
|
|
onchange="togglePrivateKeyVisibility(this)">
|
|
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px"
|
|
viewBox="0 0 24 24" width="24px" fill="#000000">
|
|
<title>Hide password</title>
|
|
<path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none" />
|
|
<path
|
|
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" />
|
|
</svg>
|
|
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px"
|
|
viewBox="0 0 24 24" width="24px" fill="#000000">
|
|
<title>Show password</title>
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path
|
|
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" />
|
|
</svg>
|
|
</label>
|
|
</sm-input>
|
|
<button id="sign_in_button" class="button button--primary" type="submit" disabled>Sign In</button>
|
|
</sm-form>
|
|
<p>
|
|
New here? <a href="#/sign_up">get your FLO login credentials</a>
|
|
</p>
|
|
</section>
|
|
</article>
|
|
<article id="sign_up" class="inner-page hidden">
|
|
<keys-generator id="keys_generator"></keys-generator>
|
|
</article>
|
|
<div id="loading" class="inner-page flex align-center justify-center">
|
|
<div class="grid gap-1 text-center justify-items-center">
|
|
<sm-spinner></sm-spinner>
|
|
<h4>Loading FLOpay</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="main_card" class="page hidden">
|
|
<header id="main_header">
|
|
<div class="flex align-center flex-1 app-name hide-on-mobile">
|
|
<svg class="icon flo-icon" width="64" height="64" viewBox="0 0 64 64" fill="none"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<mask id="path-1-outside-1_16_6" maskUnits="userSpaceOnUse" x="3" y="3" width="58" height="58">
|
|
<rect fill="white" x="3" y="3" width="58" height="58" />
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" />
|
|
</mask>
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M31.946 43.6019C28.8545 47.1308 28.7789 51.9719 31.9811 55.1957C35.0078 52.2797 35.0105 46.8446 31.946 43.6019ZM31.9487 10.6835C24.6452 19.1291 24.9206 29.1137 31.9433 37.4108C39.0929 28.9436 39.1118 19.0076 31.9487 10.6808V10.6835ZM37.1111 35.051C43.1861 28.841 50.5976 27.4208 59 28.7654C56.2919 34.9754 52.4714 39.9353 45.7214 42.53C47.2118 41.3609 48.5699 40.4051 49.7984 39.3089C51.8504 37.481 53.303 35.267 54.0293 32.6075C54.2588 31.7678 53.9618 31.5302 53.1896 31.406C50.3627 30.9524 47.7086 31.5464 45.1733 32.702C40.9073 34.646 37.4324 37.6403 34.1735 40.8938C34.1168 40.9532 34.0925 41.0396 33.9899 41.2259C40.3754 41.4689 45.2381 44.0177 48.119 49.9064C44.3768 50.738 40.9532 50.5625 37.778 48.5213C40.1702 49.2557 42.557 49.7903 45.176 48.5483C44.852 48.0083 44.663 47.3765 44.231 47.0228C43.0511 46.0589 41.8415 45.0842 40.505 44.3498C38.4395 43.2212 36.131 42.692 33.5768 42.1682C37.0247 48.2081 36.1607 53.6918 31.946 59C29.9858 56.5295 28.6142 53.9456 28.2389 50.8946C27.8609 47.8328 28.7222 45.0518 30.1991 42.3896C26.9321 41.8658 19.8824 45.6485 18.7916 48.5645C21.3215 49.8443 23.7893 49.2611 26.33 48.3161C24.5048 50.1953 19.2425 50.9648 15.8108 49.8335C18.6593 44.0285 23.4977 41.4716 30.0263 41.2286C29.5457 40.7426 29.1893 40.3673 28.8167 40.0082C26.1005 37.3892 23.2142 34.9862 19.823 33.2366C17.0501 31.8056 14.1422 30.9092 10.9481 31.3871C10.5701 31.4438 10.1948 31.5356 9.6656 31.6436C10.6376 36.6386 13.9856 39.7355 18.1274 42.3464C13.775 41.8199 6.431 34.214 5 28.7546C13.397 27.3938 20.849 28.8842 26.897 35.2319C21.8939 24.089 24.5561 14.2259 31.946 5C39.2765 14.153 41.9657 23.9459 37.1111 35.051" />
|
|
<path
|
|
d="M31.946 43.6019L32.6728 42.915L31.918 42.1163L31.1938 42.943L31.946 43.6019ZM31.9811 55.1957L31.2716 55.9004L31.9657 56.5992L32.6749 55.9159L31.9811 55.1957ZM31.9487 10.6835L32.7051 11.3376L32.9487 11.0559V10.6835H31.9487ZM31.9433 37.4108L31.18 38.0569L31.9442 38.9597L32.7074 38.056L31.9433 37.4108ZM31.9487 10.6808L32.7068 10.0287L30.9487 7.98495V10.6808H31.9487ZM59 28.7654L59.9166 29.1651L60.4326 27.9819L59.158 27.778L59 28.7654ZM45.7214 42.53L45.1042 41.7432L46.0802 43.4634L45.7214 42.53ZM49.7984 39.3089L49.1332 38.5622L49.1326 38.5628L49.7984 39.3089ZM54.0293 32.6075L53.0647 32.3439L53.0646 32.3441L54.0293 32.6075ZM53.1896 31.406L53.3484 30.4187L53.348 30.4186L53.1896 31.406ZM45.1733 32.702L45.588 33.612L45.5881 33.6119L45.1733 32.702ZM34.1735 40.8938L33.467 40.1861L33.4585 40.1946L33.4501 40.2033L34.1735 40.8938ZM33.9899 41.2259L33.114 40.7435L32.3319 42.1635L33.9519 42.2252L33.9899 41.2259ZM48.119 49.9064L48.3359 50.8826L49.5751 50.6072L49.0173 49.4669L48.119 49.9064ZM37.778 48.5213L38.0715 47.5653L37.2372 49.3625L37.778 48.5213ZM45.176 48.5483L45.6045 49.4518L46.6008 48.9794L46.0335 48.0338L45.176 48.5483ZM44.231 47.0228L44.8645 46.2491L44.8637 46.2484L44.231 47.0228ZM40.505 44.3498L40.9866 43.4734L40.9845 43.4723L40.505 44.3498ZM33.5768 42.1682L33.7777 41.1886L31.6127 40.7446L32.7083 42.664L33.5768 42.1682ZM31.946 59L31.1626 59.6216L31.9457 60.6085L32.7292 59.6218L31.946 59ZM28.2389 50.8946L29.2314 50.7725L29.2314 50.7721L28.2389 50.8946ZM30.1991 42.3896L31.0736 42.8747L31.7652 41.6279L30.3574 41.4022L30.1991 42.3896ZM18.7916 48.5645L17.855 48.2141L17.5413 49.0527L18.3402 49.4568L18.7916 48.5645ZM26.33 48.3161L27.0473 49.0128L25.9814 47.3788L26.33 48.3161ZM15.8108 49.8335L14.9131 49.393L14.4073 50.4237L15.4977 50.7832L15.8108 49.8335ZM30.0263 41.2286L30.0635 42.2279L32.3372 42.1433L30.7373 40.5255L30.0263 41.2286ZM28.8167 40.0082L28.1226 40.7281L28.1228 40.7282L28.8167 40.0082ZM19.823 33.2366L19.3644 34.1252L19.3645 34.1253L19.823 33.2366ZM10.9481 31.3871L10.8001 30.3981L10.7998 30.3982L10.9481 31.3871ZM9.6656 31.6436L9.46564 30.6638L8.49474 30.8619L8.68401 31.8346L9.6656 31.6436ZM18.1274 42.3464L18.0073 43.3392L18.6607 41.5005L18.1274 42.3464ZM5 28.7546L4.84003 27.7675L3.75363 27.9435L4.03268 29.0082L5 28.7546ZM26.897 35.2319L26.173 35.9217L27.8093 34.8223L26.897 35.2319ZM31.946 5L32.7265 4.37488L31.9461 3.40036L31.1655 4.37483L31.946 5ZM31.1938 42.943C27.7974 46.8199 27.6566 52.261 31.2716 55.9004L32.6906 54.491C29.9012 51.6828 29.9116 47.4417 32.6982 44.2609L31.1938 42.943ZM32.6749 55.9159C36.1304 52.5867 36.081 46.5214 32.6728 42.915L31.2192 44.2888C33.94 47.1678 33.8852 51.9727 31.2873 54.4755L32.6749 55.9159ZM31.1923 10.0294C27.4048 14.4092 25.5379 19.2455 25.5737 24.1102C25.6095 28.971 27.5438 33.7608 31.18 38.0569L32.7066 36.7647C29.3201 32.7637 27.6054 28.4127 27.5736 24.0955C27.5419 19.7822 29.1891 15.4034 32.7051 11.3376L31.1923 10.0294ZM32.7074 38.056C36.4083 33.6729 38.31 28.8504 38.3133 23.9938C38.3165 19.1359 36.42 14.3451 32.7068 10.0287L31.1906 11.3329C34.6405 15.3433 36.3161 19.6839 36.3133 23.9925C36.3104 28.3024 34.6279 32.6815 31.1792 36.7656L32.7074 38.056ZM30.9487 10.6808V10.6835H32.9487V10.6808H30.9487ZM37.8259 35.7503C43.6131 29.8345 50.6632 28.444 58.842 29.7528L59.158 27.778C50.532 26.3976 42.7591 27.8475 36.3963 34.3517L37.8259 35.7503ZM58.0834 28.3657C55.4402 34.4268 51.7791 39.1301 45.3626 41.5966L46.0802 43.4634C53.1637 40.7405 57.1436 35.524 59.9166 29.1651L58.0834 28.3657ZM46.3386 43.3168C47.7733 42.1914 49.2057 41.178 50.4642 40.055L49.1326 38.5628C47.9341 39.6322 46.6503 40.5304 45.1042 41.7432L46.3386 43.3168ZM50.4636 40.0556C52.6466 38.111 54.2121 35.7338 54.994 32.871L53.0646 32.3441C52.3939 34.8002 51.0542 36.851 49.1332 38.5622L50.4636 40.0556ZM54.9939 32.8711C55.1262 32.3872 55.2248 31.6946 54.7698 31.1186C54.3643 30.6053 53.7257 30.4794 53.3484 30.4187L53.0308 32.3933C53.1086 32.4058 53.17 32.4181 53.2184 32.43C53.2671 32.442 53.2955 32.4518 53.3091 32.4572C53.3227 32.4626 53.3139 32.4606 53.2926 32.4461C53.2699 32.4307 53.2353 32.4025 53.2004 32.3583C53.1832 32.3366 53.1677 32.3132 53.1543 32.2886C53.1409 32.264 53.1307 32.2402 53.1231 32.2182C53.1079 32.1742 53.1053 32.144 53.1049 32.1366C53.1046 32.1302 53.1059 32.1427 53.1007 32.1797C53.0955 32.2165 53.0849 32.27 53.0647 32.3439L54.9939 32.8711ZM53.348 30.4186C50.274 29.9254 47.4176 30.5801 44.7585 31.7921L45.5881 33.6119C47.9996 32.5127 50.4514 31.9794 53.0312 32.3934L53.348 30.4186ZM44.7586 31.792C40.3293 33.8104 36.7535 36.9051 33.467 40.1861L34.88 41.6015C38.1113 38.3755 41.4853 35.4816 45.588 33.612L44.7586 31.792ZM33.4501 40.2033C33.3063 40.354 33.2291 40.5156 33.2033 40.5674C33.1672 40.64 33.1517 40.6749 33.114 40.7435L34.8658 41.7083C34.9307 41.5906 34.9786 41.4891 34.9945 41.4572C35.0049 41.4363 35.0004 41.4467 34.9882 41.4667C34.9731 41.4914 34.9435 41.5355 34.8969 41.5843L33.4501 40.2033ZM33.9519 42.2252C37.0233 42.3421 39.6629 43.0109 41.8597 44.3098C44.0485 45.6039 45.857 47.5582 47.2207 50.3459L49.0173 49.4669C47.5001 46.3659 45.4368 44.1014 42.8776 42.5882C40.3265 41.0798 37.342 40.3527 34.0279 40.2266L33.9519 42.2252ZM47.9021 48.9302C44.3241 49.7253 41.1983 49.5313 38.3188 47.6801L37.2372 49.3625C40.7081 51.5937 44.4295 51.7507 48.3359 50.8826L47.9021 48.9302ZM37.4845 49.4773C39.9089 50.2215 42.6205 50.8669 45.6045 49.4518L44.7475 47.6448C42.4935 48.7137 40.4315 48.2899 38.0715 47.5653L37.4845 49.4773ZM46.0335 48.0338C45.8849 47.7862 45.8212 47.6158 45.6292 47.2504C45.4736 46.9544 45.2423 46.5584 44.8645 46.2491L43.5975 47.7965C43.6517 47.8409 43.7309 47.9377 43.8588 48.181C43.9503 48.3549 44.1431 48.7704 44.3185 49.0628L46.0335 48.0338ZM44.8637 46.2484C43.6913 45.2906 42.4149 44.2582 40.9866 43.4734L40.0234 45.2262C41.2681 45.9102 42.4109 46.8272 43.5983 47.7972L44.8637 46.2484ZM40.9845 43.4723C38.7711 42.2628 36.3236 41.7107 33.7777 41.1886L33.3759 43.1478C35.9384 43.6733 38.1079 44.1796 40.0255 45.2273L40.9845 43.4723ZM32.7083 42.664C34.346 45.5328 34.9255 48.203 34.6411 50.7474C34.3555 53.3015 33.1882 55.8274 31.1628 58.3782L32.7292 59.6218C34.9185 56.8644 36.2905 53.9944 36.6287 50.9696C36.9679 47.9351 36.2555 44.8435 34.4453 41.6724L32.7083 42.664ZM32.7294 58.3784C30.8397 55.9968 29.577 53.5818 29.2314 50.7725L27.2464 51.0167C27.6514 54.3094 29.1319 57.0622 31.1626 59.6216L32.7294 58.3784ZM29.2314 50.7721C28.8877 47.9881 29.6602 45.4224 31.0736 42.8747L29.3247 41.9045C27.7842 44.6812 26.8341 47.6775 27.2464 51.0171L29.2314 50.7721ZM30.3574 41.4022C29.3303 41.2375 28.1125 41.4187 26.9159 41.7622C25.6987 42.1117 24.4105 42.6564 23.2045 43.307C21.999 43.9574 20.8488 44.728 19.9166 45.543C19.005 46.3399 18.2137 47.2553 17.855 48.2141L19.7282 48.9149C19.9149 48.4157 20.4139 47.7647 21.233 47.0487C22.0314 46.3507 23.0524 45.6616 24.1541 45.0672C25.2552 44.4732 26.4102 43.9882 27.4678 43.6846C28.5458 43.3751 29.4344 43.2798 30.0408 43.377L30.3574 41.4022ZM18.3402 49.4568C21.2702 50.939 24.0958 50.214 26.6786 49.2534L25.9814 47.3788C23.4828 48.3082 21.3728 48.7496 19.243 47.6722L18.3402 49.4568ZM25.6127 47.6194C24.9334 48.3187 23.4333 48.9382 21.5294 49.2141C19.6667 49.484 17.6562 49.3889 16.1239 48.8838L15.4977 50.7832C17.3971 51.4094 19.7336 51.4952 21.8162 51.1934C23.8578 50.8976 25.9014 50.1927 27.0473 49.0128L25.6127 47.6194ZM16.7085 50.274C18.0554 47.5292 19.8486 45.5942 22.044 44.3081C24.2482 43.0169 26.918 42.345 30.0635 42.2279L29.9891 40.2293C26.606 40.3552 23.5923 41.0832 21.0331 42.5824C18.4649 44.0869 16.4147 46.3328 14.9131 49.393L16.7085 50.274ZM30.7373 40.5255C30.2757 40.0586 29.8942 39.6578 29.5106 39.2882L28.1228 40.7282C28.4844 41.0768 28.8157 41.4266 29.3153 41.9317L30.7373 40.5255ZM29.5108 39.2883C26.7637 36.6396 23.7967 34.1615 20.2815 32.3479L19.3645 34.1253C22.6317 35.8109 25.4373 38.1388 28.1226 40.7281L29.5108 39.2883ZM20.2816 32.348C17.4111 30.8666 14.2829 29.877 10.8001 30.3981L11.0961 32.3761C14.0015 31.9414 16.6891 32.7446 19.3644 34.1252L20.2816 32.348ZM10.7998 30.3982C10.388 30.4599 9.94468 30.566 9.46564 30.6638L9.86556 32.6234C10.4449 32.5052 10.7522 32.4277 11.0964 32.376L10.7998 30.3982ZM8.68401 31.8346C9.73479 37.2345 13.3673 40.5279 17.5941 43.1923L18.6607 41.5005C14.6039 38.9431 11.5404 36.0427 10.6472 31.4526L8.68401 31.8346ZM18.2475 41.3536C17.3819 41.2489 16.2594 40.7693 14.9918 39.9335C13.742 39.1093 12.427 37.9891 11.1875 36.7057C8.68437 34.1137 6.62095 30.9947 5.96732 28.5011L4.03268 29.0082C4.81005 31.9739 7.13413 35.3875 9.74885 38.095C11.0683 39.4613 12.4947 40.6825 13.8908 41.6031C15.2691 42.512 16.6967 43.1806 18.0073 43.3392L18.2475 41.3536ZM5.15997 29.7417C13.3246 28.4186 20.4108 29.874 26.173 35.9217L27.621 34.5421C21.2872 27.8944 13.4694 26.369 4.84003 27.7675L5.15997 29.7417ZM27.8093 34.8223C25.3851 29.4232 24.8341 24.3801 25.7561 19.5859C26.6809 14.7778 29.1009 10.1516 32.7265 5.62517L31.1655 4.37483C27.4012 9.07433 24.7952 13.9927 23.7921 19.2082C22.7863 24.4378 23.4058 29.8977 25.9847 35.6415L27.8093 34.8223ZM31.1655 5.62512C34.7619 10.1157 37.1747 14.7077 38.1168 19.4869C39.0562 24.2523 38.5469 29.2701 36.1948 34.6504L38.0274 35.4515C40.5299 29.7268 41.1033 24.2956 40.0791 19.1001C39.0576 13.9183 36.4606 9.03731 32.7265 4.37488L31.1655 5.62512Z"
|
|
mask="url(#path-1-outside-1_16_6)" />
|
|
</svg>
|
|
<h4>FLOpay</h4>
|
|
</div>
|
|
<button id="user_profile_button" class="button--small" onclick="openPopup('profile_popup')">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon margin-right-0-5" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
|
<path
|
|
d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z">
|
|
</path>
|
|
</svg>
|
|
<div id="user_profile_id" class="overflow-ellipsis"></div>
|
|
</button>
|
|
<div id="notifications_wrapper">
|
|
<button id="requests_notification_button" class="icon-only" onclick="toggleNotificationPanel()">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
|
fill="#000000">
|
|
<path
|
|
d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" />
|
|
</svg>
|
|
</button>
|
|
<div id="notifications_panel" class="flex flex-direction-column hidden">
|
|
<div class="page__header">
|
|
<button class="icon-only hide-on-desktop" onclick="closeNotificationPanel()">
|
|
<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 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 12z" />
|
|
</svg>
|
|
</button>
|
|
<div class="flex align-center space-between w-100">
|
|
<h3> Payment requests </h3>
|
|
<a class="button button--small" href="#/requests">See history</a>
|
|
</div>
|
|
</div>
|
|
<ul id="pending_payment_requests" class="grid gap-0-5 observe-empty-state"></ul>
|
|
<div class="empty-state">
|
|
<p>No pending requests</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<theme-toggle></theme-toggle>
|
|
</header>
|
|
<section id="home" class="inner-page hidden">
|
|
<div id="wallet_section">
|
|
<div class="grid gap-1">
|
|
<div class="flex align-center space-between">
|
|
<h4>My balance</h4>
|
|
<div class="multi-state-button">
|
|
<button class="button button--small" onclick="refreshBalance(this)">Refresh</button>
|
|
</div>
|
|
</div>
|
|
<div id="balance_wrapper" class="grid gap-0-5">
|
|
<div class="flex align-center space-between balance-card">
|
|
<span>Rupee</span>
|
|
<span id="rupee_balance"></span>
|
|
</div>
|
|
<div class="flex align-center space-between balance-card">
|
|
<span>FLO</span>
|
|
<span id="flo_balance"></span>
|
|
</div>
|
|
<div class="flex align-center space-between balance-card">
|
|
<span>BTC</span>
|
|
<span id="btc_balance"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<sm-chips id="wallet_section__asset_selector" class="user-element">
|
|
<sm-chip value="rupee" selected>Rupee</sm-chip>
|
|
<sm-chip value="btc">BTC</sm-chip>
|
|
</sm-chips>
|
|
<div id="asset_actions_wrapper" class="user-element">
|
|
<div class="grid gap-1-5">
|
|
<div class="actions-wrapper">
|
|
<button class="wallet-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="wallet-action" onclick="showTokenTransfer('request')">
|
|
<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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" />
|
|
</svg>
|
|
Request
|
|
</button>
|
|
<button class="wallet-action" onclick="openPopup('topup_wallet_popup', true)">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
viewBox="0 0 24 24" fill="none">
|
|
<path
|
|
d="M16.5 13.7619H18.3V16.4762H21V18.2857H18.3V21H16.5V18.2857H13.8V16.4762H16.5V13.7619ZM11.55 2L20.1 6.52381V8.33333H3V6.52381L11.55 2ZM15.6 10.1429H18.3V12.0248L17.4 11.9524C16.77 11.9524 16.167 12.061 15.6 12.26V10.1429ZM3 21V18.2857H12.072C12.243 19.3171 12.711 20.249 13.377 21H3ZM10.2 10.1429H12.9V14.3771C12.486 15.0014 12.198 15.7071 12.072 16.4762H10.2V10.1429ZM4.8 10.1429H7.5V16.4762H4.8V10.1429Z" />
|
|
</svg>
|
|
Top-up
|
|
</button>
|
|
<button class="wallet-action" onclick="openPopup('withdraw_wallet_popup')">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
viewBox="0 0 24 24" fill="none">
|
|
<path
|
|
d="M13.8 16.4762H21V18.2857H13.8V16.4762ZM11.55 2L20.1 6.52381V8.33333H3V6.52381L11.55 2ZM15.6 10.1429H18.3V12.0248L17.4 11.9524C16.77 11.9524 16.167 12.061 15.6 12.26V10.1429ZM3 21V18.2857H12.072C12.243 19.3171 12.711 20.249 13.377 21H3ZM10.2 10.1429H12.9V14.3771C12.486 15.0014 12.198 15.7071 12.072 16.4762H10.2V10.1429ZM4.8 10.1429H7.5V16.4762H4.8V10.1429Z" />
|
|
</svg>
|
|
Withdraw
|
|
</button>
|
|
<button class="wallet-action" onclick="initConversion('btc')">
|
|
<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"></rect>
|
|
</g>
|
|
<g>
|
|
<path
|
|
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
|
|
</path>
|
|
</g>
|
|
</svg>
|
|
Convert to BTC
|
|
</button>
|
|
</div>
|
|
<a id="wallet_history_button" class="integrated-action-button flex align-center user-element"
|
|
href="#/wallet">
|
|
<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="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.25 2.52.77-1.28-3.52-2.09V8z" />
|
|
</svg>
|
|
View top-up and withdrawal history
|
|
<svg class="icon justify-self-end" 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 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
|
|
</svg>
|
|
</a>
|
|
<details class="grid gap-1"
|
|
style="border-top: solid thin rgba(var(--text-color), 0.5); padding-top: 1rem; padding-bottom: 1rem;"
|
|
open>
|
|
<summary style="margin-bottom: 1.5rem;">
|
|
<h4 style="font-weight: 500;">Send Rupee tokens to other
|
|
RanchiMall products</h4>
|
|
<svg class="icon down-arrow" 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="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" />
|
|
</svg>
|
|
</summary>
|
|
<div class="actions-wrapper">
|
|
<button class="wallet-action" onclick="openExternalTransferPopup('exchange')">
|
|
<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 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
|
|
</svg>
|
|
Exchange market
|
|
</button>
|
|
<button class="wallet-action" onclick="openExternalTransferPopup('btc-bonds')">
|
|
<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 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
|
|
</svg>
|
|
Blockchain Bonds
|
|
</button>
|
|
<button class="wallet-action" onclick="openExternalTransferPopup('bobs-fund')">
|
|
<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 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
|
|
</svg>
|
|
Bob's fund
|
|
</button>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
<div class="actions-wrapper hidden">
|
|
<button class="wallet-action" onclick="openPopup('send_btc_popup')">
|
|
<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="wallet-action" onclick="initConversion('rupee')">
|
|
<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>
|
|
<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>
|
|
Convert to Rupee
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="grid hide-on-mobile user-element">
|
|
<div class="flex align-center space-between">
|
|
<h4>Recent transactions</h4>
|
|
<div class="flex gap-0-3">
|
|
<button class="button button--small icon-only" title="Refresh recent transactions"
|
|
onclick="render.recentTransactions()">
|
|
<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="M17.65 6.35c-1.63-1.63-3.94-2.57-6.48-2.31-3.67.37-6.69 3.35-7.1 7.02C3.52 15.91 7.27 20 12 20c3.19 0 5.93-1.87 7.21-4.56.32-.67-.16-1.44-.9-1.44-.37 0-.72.2-.88.53-1.13 2.43-3.84 3.97-6.8 3.31-2.22-.49-4.01-2.3-4.48-4.52C5.31 9.44 8.26 6 12 6c1.66 0 3.14.69 4.22 1.78l-1.51 1.51c-.63.63-.19 1.71.7 1.71H19c.55 0 1-.45 1-1V6.41c0-.89-1.08-1.34-1.71-.71l-.64.65z" />
|
|
</svg>
|
|
</button>
|
|
<a class="button button--small" href="#/history">See all</a>
|
|
</div>
|
|
</div>
|
|
<ul id="recent_transactions" class="observe-empty-state grid gap-2 margin-top-1-5"></ul>
|
|
<div class="empty-state">
|
|
<h4 class="empty-state__title">No transactions yet</h4>
|
|
<p class="empty-state__subtitle">Send or receive some assets to see them here</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid gap-1 hide-on-mobile user-element">
|
|
<div class="flex align-center space-between">
|
|
<h4>Contacts</h4>
|
|
<a class="button button--small" href="#/contacts">See all</a>
|
|
</div>
|
|
<button class="button interact margin-right-auto" onclick="openPopup('add_address_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 0h24v24H0z" fill="none" />
|
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
</svg>
|
|
Add FLO address
|
|
</button>
|
|
<ul id="recent_contacts" class="observe-empty-state grid gap-1"></ul>
|
|
<div class="empty-state">
|
|
<h4 class="empty-state__title">No contacts yet</h4>
|
|
<p class="empty-state__subtitle">Add some contacts to see them here</p>
|
|
</div>
|
|
</div>
|
|
<section id="cashier" class=" grid gap-1 hidden admin-element">
|
|
<div class="flex align-center space-between">
|
|
<h4>Requests</h4>
|
|
<sm-chips id="cashier_requests_selector">
|
|
<sm-chip value="pending" selected>Pending</sm-chip>
|
|
<sm-chip value="processed">Processed</sm-chip>
|
|
</sm-chips>
|
|
</div>
|
|
<div id="cashier_requests_wrapper">
|
|
<div>
|
|
<ul id="cashier_pending_request_list" class="observe-empty-state"></ul>
|
|
<div class="empty-state">
|
|
<p>No requests to process</p>
|
|
</div>
|
|
</div>
|
|
<div class="hidden">
|
|
<ul id="cashier_processed_request_list" class="observe-empty-state"></ul>
|
|
<div class="empty-state">
|
|
<p>No requests to process</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
<section id="history" class="inner-page hidden">
|
|
<div class="page__header">
|
|
<button class="button icon-only margin-right-0-5 hide-on-mobile" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<div class="flex align-center space-between w-100">
|
|
<h1>Payments history</h1>
|
|
<button class="button button--small align-self-end" onclick="openPopup('payments_filters_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 0h24v24H0z" fill="none" />
|
|
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" />
|
|
</svg>
|
|
Filters
|
|
</button>
|
|
</div>
|
|
<div id="history_applied_filters" class="flex gap-0-5"></div>
|
|
</div>
|
|
<ul id="payments_history" class="observe-empty-state"></ul>
|
|
<div class=" empty-state gap-1 justify-center text-center">
|
|
<p>No transactions</p>
|
|
</div>
|
|
</section>
|
|
<section id="wallet" class="inner-page hidden">
|
|
<div class="page__header">
|
|
<button class="button icon-only margin-right-0-5" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<h1>Wallet transactions</h1>
|
|
</div>
|
|
<div id="wallet_history_wrapper" class="grid gap-1-5">
|
|
<div class="hidden grid gap-1">
|
|
<h4>Pending</h4>
|
|
<ul id="pending_wallet_transactions" class="grid gap-0-5"></ul>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<h4>Processed</h4>
|
|
<ul id="wallet_history" class="observe-empty-state grid gap-0-5"></ul>
|
|
<div class=" empty-state gap-1 justify-center text-center">
|
|
<p>No transactions</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="contacts" class="inner-page hidden flex flex-direction-column">
|
|
<div class="page__header">
|
|
<button class="button icon-only margin-right-0-5 hide-on-mobile" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<div class="flex align-center space-between w-100">
|
|
<h1>Contacts</h1>
|
|
<button class="button interact" onclick="openPopup('add_address_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 0h24v24H0z" fill="none" />
|
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
</svg>
|
|
Add FLO address
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<ul id="saved_ids_list" class="observe-empty-state grid"></ul>
|
|
<div class="empty-state justify-center text-center align-center h-100" style="align-content: center;">
|
|
<svg class="justify-self-center" style="height: 12rem;" id="bb7dac0d-c86d-4eae-9345-05ead570be6d"
|
|
data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
|
<defs>
|
|
<style>
|
|
.e4b4c873-5e79-4c66-a530-269f7775150b {
|
|
fill: rgba(var(--text-color), 0.03);
|
|
}
|
|
|
|
.f8c35eef-c260-42fc-be6f-7c8afb0beeeb {
|
|
fill: rgba(var(--text-color), 0.2);
|
|
}
|
|
|
|
.ee8c2e6d-b8f3-4b81-80ab-31d470d121b9 {
|
|
fill: rgba(var(--text-color), 0.1);
|
|
}
|
|
</style>
|
|
</defs>
|
|
<rect class="e4b4c873-5e79-4c66-a530-269f7775150b" x="34.76" y="40.75" width="177.53" height="41.42"
|
|
rx="4" />
|
|
<circle class="f8c35eef-c260-42fc-be6f-7c8afb0beeeb" cx="57.21" cy="61.46" r="9.29" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="75.4" y="51.37" width="40.44" height="9.03"
|
|
rx="1.92" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="75.4" y="62.79" width="80.09" height="9.03"
|
|
rx="1.92" />
|
|
<rect class="e4b4c873-5e79-4c66-a530-269f7775150b" x="10" y="99.29" width="177.53" height="41.42"
|
|
rx="4" />
|
|
<circle class="f8c35eef-c260-42fc-be6f-7c8afb0beeeb" cx="32.45" cy="120" r="9.29" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="50.64" y="109.91" width="40.44" height="9.03"
|
|
rx="1.92" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="50.64" y="121.33" width="80.09" height="9.03"
|
|
rx="1.92" />
|
|
<rect class="e4b4c873-5e79-4c66-a530-269f7775150b" x="52.47" y="157.83" width="177.53"
|
|
height="41.42" rx="4" />
|
|
<circle class="f8c35eef-c260-42fc-be6f-7c8afb0beeeb" cx="74.93" cy="178.54" r="9.29" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="93.12" y="168.46" width="40.44" height="9.03"
|
|
rx="1.92" />
|
|
<rect class="ee8c2e6d-b8f3-4b81-80ab-31d470d121b9" x="93.12" y="179.87" width="80.09" height="9.03"
|
|
rx="1.92" />
|
|
</svg>
|
|
<div class="grid gap-0-5">
|
|
<b>No Saved FLO address</b>
|
|
<span>Use 'Add FLO address' to add a new FLO address</span>
|
|
</div>
|
|
</div>
|
|
<p id="saved_ids_tip" class="flex align-center justify-center" style="margin: 0 auto;">
|
|
<svg class="icon margin-right-0-5" style="fill: #ffc107;" 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 21c0 .5.4 1 1 1h4c.6 0 1-.5 1-1v-1H9v1zm3-19C8.1 2 5 5.1 5 9c0 2.4 1.2 4.5 3 5.7V17c0 .5.4 1 1 1h6c.6 0 1-.5 1-1v-2.3c1.8-1.3 3-3.4 3-5.7 0-3.9-3.1-7-7-7z" />
|
|
</svg>
|
|
<span>Tap on saved IDs to see transaction history</span>
|
|
</p>
|
|
</section>
|
|
<section id="contact" class="inner-page">
|
|
<div class="flex align-center">
|
|
<button class="button icon-only margin-right-0-5" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<h4 id="contact__title">FUwLtWesPB1RpfSaHi2D4xYER3GycNojJN</h4>
|
|
</div>
|
|
<ul id="contact__transactions" class="observe-empty-state"></ul>
|
|
<div class="empty-state grid gap-1 justify-center text-center" style="justify-items: center;">
|
|
<p>Nothing to see here</p>
|
|
</div>
|
|
<div class="flex gap-0-5">
|
|
<button class="button justify-right" onclick="showTokenTransfer('request')">Request</button>
|
|
<button class="button" onclick="showTokenTransfer('send')">Pay</button>
|
|
</div>
|
|
</section>
|
|
<section id="requests" class="inner-page">
|
|
<div class="page__header">
|
|
<button class="button icon-only margin-right-0-5" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<h1>Payment request history</h1>
|
|
</div>
|
|
<ul id="payment_request_history" class="observe-empty-state grid gap-0-5"></ul>
|
|
<div class=" empty-state gap-1 justify-center text-center">
|
|
<p>No requests</p>
|
|
</div>
|
|
</section>
|
|
<div id="transaction" class="inner-page grid gap-2">
|
|
<div class="grid gap-1 justify-start">
|
|
<button class="button icon-only margin-right-0-5" onclick="history.back()">
|
|
<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>
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path>
|
|
</svg>
|
|
</button>
|
|
<h4 id="transaction__type">Payment request</h4>
|
|
</div>
|
|
<div id="transaction_details" class="grid gap-2">
|
|
<div class="grid gap-1"></div>
|
|
</div>
|
|
</div>
|
|
<nav id="bottom_nav" class="hide-on-desktop user-element">
|
|
<ul>
|
|
<li>
|
|
<a href="#/home" class="nav-item nav-item--active">
|
|
<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>Home</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#/history" class="nav-item">
|
|
<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>History</span>
|
|
</a>
|
|
</li>
|
|
<li><a href="#/contacts" class="nav-item ">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none" />
|
|
<path
|
|
d="M20 0H4v2h16V0zM4 24h16v-2H4v2zM20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 2.75c1.24 0 2.25 1.01 2.25 2.25s-1.01 2.25-2.25 2.25S9.75 10.24 9.75 9 10.76 6.75 12 6.75zM17 17H7v-1.5c0-1.67 3.33-2.5 5-2.5s5 .83 5 2.5V17z" />
|
|
</svg>
|
|
<span>Contacts</span>
|
|
</a></li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
<!-- Popups -->
|
|
<sm-popup id="add_address_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Save FLO address</h3>
|
|
</header>
|
|
<sm-form>
|
|
<sm-input id="flo_id_to_save" placeholder="FLO address" error-text="Invalid FLO address" data-flo-id animate
|
|
required autofocus>
|
|
</sm-input>
|
|
<sm-input id="flo_id_title_to_save" placeholder="Title" animate required></sm-input>
|
|
<div class="multi-state-button">
|
|
<button id="save_flo_id_button" class="button button--primary cta" onclick="saveFloId()"
|
|
type="submit">Save</button>
|
|
</div>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<sm-popup id="edit_saved_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close">
|
|
<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>
|
|
<h3>Edit</h3>
|
|
</header>
|
|
<section class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<h5>FLO address</h5>
|
|
<sm-copy id="edit_saved_id"></sm-copy>
|
|
</div>
|
|
<sm-form>
|
|
<sm-input id="get_new_title" placeholder="Name" autofocus animate required></sm-input>
|
|
<div class="flex align-center space-between gap-0-5">
|
|
<button class="button button--danger icon-only" title="Delete this FLO address?"
|
|
onclick="deleteSavedId()">
|
|
<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="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
|
|
</svg>
|
|
</button>
|
|
<button class="button button--primary cta" style="width: auto;" type="submit"
|
|
onclick="saveIdChanges()">Save</button>
|
|
</div>
|
|
</sm-form>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="token_transfer_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3 id="token_transfer__title"></h3>
|
|
</header>
|
|
<section class="grid gap-2">
|
|
<sm-form>
|
|
<sm-input id="token_transfer__receiver" placeholder="FLO address" error-text="Invalid FLO address"
|
|
data-flo-id animate required autofocus>
|
|
<button slot="right" class="icon-only" onclick="openPopup('saved_ids_popup')"
|
|
title="Select from saved IDs">
|
|
<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="M21 5v14h2V5h-2zm-4 14h2V5h-2v14zM14 5H2c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zM8 7.75c1.24 0 2.25 1.01 2.25 2.25S9.24 12.25 8 12.25 5.75 11.24 5.75 10 6.76 7.75 8 7.75zM12.5 17h-9v-.75c0-1.5 3-2.25 4.5-2.25s4.5.75 4.5 2.25V17z" />
|
|
</svg>
|
|
</button>
|
|
</sm-input>
|
|
<sm-input id="token_transfer__amount" type="number" placeholder="0" required min="1" step="0.01">
|
|
<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="token_transfer__remark" pattern="[A-Za-z]" error-text="Only text allowed"
|
|
placeholder="Add a message" animate></sm-input>
|
|
<div class="multi-state-button">
|
|
<button id="token_transfer__button" class="button button--primary cta" onclick="executeUserAction()"
|
|
type="submit">Send</button>
|
|
</div>
|
|
</sm-form>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="saved_ids_popup">
|
|
<header slot="header" class="popup__header">
|
|
<div class="grid gap-1">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<sm-input id="search_saved_ids_picker" placeholder="Search"></sm-input>
|
|
</div>
|
|
</header>
|
|
<section class="grid gap-1">
|
|
<ul id="saved_ids_picker_list" class="observe-empty-state grid gap-0-5 h-100" style="overflow-y: auto;">
|
|
</ul>
|
|
<div class="empty-state">
|
|
<h4>No saved FLO IDs</h4>
|
|
</div>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="payments_filters_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Filters</h3>
|
|
</header>
|
|
<div id="history_filters_container" class="grid gap-2">
|
|
<fieldset class="grid gap-1">
|
|
<legend>
|
|
Asset
|
|
</legend>
|
|
<div class="flex gap-0-5">
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="asset" value="all" checked autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">All</span>
|
|
</label>
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="asset" value="rupee" autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">Rupee</span>
|
|
</label>
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="asset" value="btc" autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">BTC</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
<fieldset class="grid gap-1">
|
|
<legend>
|
|
Payments type
|
|
</legend>
|
|
<div class="flex gap-0-5">
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="type" value="all" checked autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">All</span>
|
|
</label>
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="type" value="sent" autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">Sent</span>
|
|
</label>
|
|
<label class="category-chip interact">
|
|
<input type="radio" name="type" value="received" autocomplete="off" />
|
|
<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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
|
|
</svg>
|
|
<span class="checkmark">Received</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
<div class="flex align-center space-between gap-0-5">
|
|
<button class="button" onclick="resetPaymentsFilters()">Reset</button>
|
|
<button class="button button--primary cta" onclick="applyPaymentsFilters()">Filter</button>
|
|
</div>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="topup_wallet_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<div id="topup_wallet_process">
|
|
<sm-form id="get_topup_amount_form">
|
|
<div class="grid gap-0-5">
|
|
<h4>Top-up wallet</h4>
|
|
<p>Add money to your wallet</p>
|
|
</div>
|
|
<sm-input id="request_cashier_amount" type="number" min="10" error-text="Amount should at least be ₹10"
|
|
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>
|
|
<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>
|
|
<strong id="low_user_flo_warning" class="hidden warning"></strong>
|
|
<button class="button button--primary cta" onclick="continueWalletTopup()"
|
|
type="submit">Continue</button>
|
|
</sm-form>
|
|
<sm-form id="confirm_topup_form" class="hidden">
|
|
<h4>Transfer money</h4>
|
|
<ol id="topup_steps" type="1">
|
|
<li>
|
|
<p>
|
|
Open your<strong> preferred UPI app </strong>
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<div class="grid gap-1">
|
|
<div class="grid gap-0-5">
|
|
<p>
|
|
Enter <strong> UPI ID </strong>below as recipient
|
|
</p>
|
|
<sm-copy id="topup_wallet__upi_id" style="font-weight: 700;"></sm-copy>
|
|
</div>
|
|
<details id="topup_wallet__qr_wrapper">
|
|
<summary class="interact">
|
|
<b style="font-size: 0.9rem;">Or get QR code to scan</b>
|
|
<svg class="icon down-arrow" 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 class="grid gap-0-5">
|
|
<div id="topup_wallet__qr_code"></div>
|
|
<strong> *All the required details for payment will be auto-filled.</strong>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<p>
|
|
After sending money, press <b>Confirm</b>.
|
|
</p>
|
|
</li>
|
|
</ol>
|
|
<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 hidden 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 hidden 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>
|
|
<div class="cashier-status hidden flex align-center">
|
|
<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="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
|
|
</svg>
|
|
<span>Cashier is currently offline. Please check back again after a while.</span>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="withdraw_wallet_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<div id="withdraw_wallet_process">
|
|
<sm-form>
|
|
<div class="grid gap-0-5">
|
|
<h4>Withdraw</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 at least be ₹1"
|
|
name="amount" step="0.01" 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>
|
|
<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-1">
|
|
<div class="grid gap-0-5 hidden">
|
|
<p>
|
|
<b>Select UPI ID to receive money</b>
|
|
</p>
|
|
<sm-select id="select_withdraw_upi_id" style="z-index: 2;"></sm-select>
|
|
</div>
|
|
<button class="button button--small justify-self-start" onclick="openPopup('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>
|
|
<div class="multi-state-button">
|
|
<button id="withdraw_rupee_button" class="button button--primary cta"
|
|
onclick="withdrawMoneyFromWallet()" type="submit">Withdraw</button>
|
|
</div>
|
|
</sm-form>
|
|
<div class="grid gap-0-5 hidden 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 transfer to bank request</h4>
|
|
<p>This may take upto 30 mins to complete</p>
|
|
<a href="" class="hidden" target="_blank" id="withdrawal_blockchain_link">Check transaction on
|
|
blockchain</a>
|
|
</div>
|
|
<div class="grid gap-0-5 hidden 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>
|
|
<div class="cashier-status hidden flex align-center">
|
|
<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="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
|
|
</svg>
|
|
<span>Cashier is currently offline. Please check back again after a while.</span>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="save_upi_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Save UPI ID</h3>
|
|
</header>
|
|
<sm-form>
|
|
<sm-input id="get_upi_id" placeholder="UPI ID" pattern="[a-zA-Z0-9.\-_]{2,49}@[a-zA-Z._]{2,49}"
|
|
error-text="Invalid UPI ID" autofocus animate required></sm-input>
|
|
<button class="button button--primary cta" onclick="saveUpiId()" type="submit">Save</button>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<sm-popup id="send_btc_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Send BTC</h3>
|
|
</header>
|
|
<sm-form id="send_tx" skip-submit>
|
|
<div>
|
|
<h4 class="margin-bottom-0-5">Receivers</h4>
|
|
<div id="receiver_container">
|
|
<fieldset class="receiver-card">
|
|
<sm-input class="receiver-input" placeholder="Receiver address" data-bc-address
|
|
error-text="Invalid address" animate required></sm-input>
|
|
<div class="flex align-start gap-0-5 remove-card-wrapper">
|
|
<sm-input type="number" class="amount-input w-100" placeholder="Amount" min="0.0000001"
|
|
step="0.00000001" error-text="Invalid amount" animate required>
|
|
<div class="currency-symbol flex" slot="icon">
|
|
<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"></rect>
|
|
</g>
|
|
<g>
|
|
<path
|
|
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
|
|
</path>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</sm-input>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<button id="add_receiver" class="button button--small">
|
|
<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 receiver</button>
|
|
</div>
|
|
<div id="fees_section" class="grid gap-0-5">
|
|
<div class="flex align-center space-between">
|
|
<h4>Fees</h4>
|
|
<sm-chips id="fees_selector">
|
|
<sm-chip value="suggested" selected>Suggested</sm-chip>
|
|
<sm-chip value="custom">Custom</sm-chip>
|
|
</sm-chips>
|
|
</div>
|
|
<p id="selected_fee_tip"></p>
|
|
<div id="send_fee_wrapper">
|
|
<sm-input type="number" id="send_fee" placeholder="Fee" min="0.00000001" step="0.00000001"
|
|
error-text="Please enter valid fees" readonly animate required>
|
|
<div class="currency-symbol flex" slot="icon">
|
|
<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"></rect>
|
|
</g>
|
|
<g>
|
|
<path
|
|
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
|
|
</path>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</sm-input>
|
|
<div id="send_fee_loader" class="hidden flex align-center gap-0-5">
|
|
<sm-spinner></sm-spinner>
|
|
<span>Calculating fees...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="error_section" class="hidden"></div>
|
|
<div class="multi-state-button">
|
|
<button id="send_transaction" type="submit" class="button button--primary cta w-100"
|
|
disabled>Send</button>
|
|
</div>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<sm-popup id="txid_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close" onclick="closePopup()">
|
|
<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>
|
|
<div class="grid gap-2">
|
|
<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>
|
|
<div class="grid gap-0-5 justify-center text-center">
|
|
<h4>Transaction sent</h4>
|
|
<p>Confirmation of transaction might take few hours. </p>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">Transaction ID</span>
|
|
<sm-copy id="txid"></sm-copy>
|
|
</div>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="convert_asset_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<sm-form id="conversion_form" class="hidden"></sm-form>
|
|
<div id="conversion_status" class="grid gap-1 justify-center text-center"> </div>
|
|
</sm-popup>
|
|
<sm-popup id="change_cashier_upi_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Change UPI ID</h3>
|
|
</header>
|
|
<sm-form>
|
|
<sm-input id="upi_id" placeholder="UPI ID" pattern="[a-zA-Z0-9.\-_]{2,49}@[a-zA-Z._]{2,49}"
|
|
error-text="Invalid UPI ID" autofocus animate required></sm-input>
|
|
<button class="button button--primary cta justify-self-start" type="submit" onclick="changeUpi()">
|
|
Change
|
|
</button>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<sm-popup id="external_transfer_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<div id="external_transfer_process"></div>
|
|
</sm-popup>
|
|
<sm-popup id="secure_pwd_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3 id="secure_pwd_title">Set password</h3>
|
|
</header>
|
|
<sm-form>
|
|
<sm-input id="secure_pwd_input" type="password" placeholder="Password" animate required autofocus>
|
|
</sm-input>
|
|
<button class="button button--primary cta secure-priv-key" type="submit" onclick="setSecurePassword()">
|
|
Set
|
|
</button>
|
|
</sm-form>
|
|
</sm-popup>
|
|
|
|
<!-- Cashier popups -->
|
|
<sm-popup id="confirm_topup_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start">
|
|
<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>
|
|
<h3>Confirm top-up</h3>
|
|
</header>
|
|
<div id="confirm_topup_wrapper">
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<p>
|
|
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">Transaction code</div>
|
|
<sm-copy id="top_up__code"></sm-copy>
|
|
</div>
|
|
<div class="flex justify-right gap-0-3">
|
|
<button class="button" onclick="showChildElement('confirm_topup_wrapper', 1)">Decline</button>
|
|
<div class="multi-state-button">
|
|
<button class="button" onclick="confirmTopUp(this)">Confirm</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-direction-column gap-1-5 hidden" style="height: 24rem;">
|
|
<div class="grid gap-0-3">
|
|
<p>Select reason</p>
|
|
<sm-select id="top_up__reason_selector">
|
|
<sm-option value="1003">Transaction code mismatch</sm-option>
|
|
<sm-option value="1002">Amount doesn't match</sm-option>
|
|
<sm-option value="1004">Didn't receive money</sm-option>
|
|
<sm-option value="other">Other</sm-option>
|
|
</sm-select>
|
|
</div>
|
|
<div class="grid gap-0-3 hidden">
|
|
<p>Describe reason</p>
|
|
<sm-textarea id="top_up__specified_reason" rows="4"></sm-textarea>
|
|
</div>
|
|
<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>
|
|
<sm-popup id="profile_popup">
|
|
<header slot="header" class="popup__header">
|
|
<button class="popup__header__close justify-self-start" onclick="closePopup()">
|
|
<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>
|
|
<h3>Profile</h3>
|
|
</header>
|
|
<div id="profile_popup__content" class="grid gap-3"></div>
|
|
</sm-popup>
|
|
|
|
<!-- templates -->
|
|
<template id="cashier_request_template">
|
|
<li class="cashier-request flex-wrap">
|
|
<div class="cashier-request__mode"></div>
|
|
<div class="cashier-request__details"></div>
|
|
<div class="flex flex-wrap align-center gap-0-5 flex-1">
|
|
<time class="cashier-request__time"></time>
|
|
<div class="bullet-point"></div>
|
|
<div class="cashier-request__requestor wrap-around"></div>
|
|
</div>
|
|
<div class="cashier-request__status"></div>
|
|
</li>
|
|
</template>
|
|
<template id="wallet_request_template">
|
|
<li>
|
|
<a href="" class="wallet-request interact">
|
|
<div class="wallet-request__icon"></div>
|
|
<div class="flex align-center space-between">
|
|
<div class="wallet-request__details"></div>
|
|
<div class="wallet-request__amount"></div>
|
|
</div>
|
|
<div class="flex align-center space-between">
|
|
<time class="wallet-request__time"></time>
|
|
<div class="wallet-request__status flex align-center"></div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
<template id="pending_payment_request_template">
|
|
<li class="payment-request payment-request--pending">
|
|
<div class="grid gap-0-5 flex-1">
|
|
<div class="payment-request__requestor wrap-around"></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">
|
|
<button class="button decline-payment">Decline</button>
|
|
<button class="button pay-requested">Pay</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<template id="processed_payment_request_template">
|
|
<li>
|
|
<a href="" class="payment-request payment-request--processed interact">
|
|
<div class="grid gap-0-5 flex-1">
|
|
<b class="payment-request__requestor wrap-around"></b>
|
|
<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__status"></div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
<template id="transaction_message_template">
|
|
<li class="transaction-message grid">
|
|
<b class="transaction-message__amount"></b>
|
|
<p class="transaction-message__remark"></p>
|
|
<time class="transaction-message__time"></time>
|
|
</li>
|
|
</template>
|
|
<template id="conditional_steps_template">
|
|
<li class="conditional">
|
|
<p>Enter <b class="amount"></b> as amount</p>
|
|
</li>
|
|
<li class="conditional">
|
|
<div class="grid gap-0-5">
|
|
<p>
|
|
Enter <strong>code</strong> below in the <strong> message/remark </strong> field
|
|
</p>
|
|
<sm-copy class="tx-code" style="font-weight: 700; "></sm-copy>
|
|
<strong class="warning">*This is very important step. If you skip or enter wrong code, transaction
|
|
can't
|
|
be verified.</strong>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<template id="receiver_template">
|
|
<fieldset class="receiver-card">
|
|
<sm-input class="receiver-input" placeholder="Receiver address" data-bc-address error-text="Invalid address"
|
|
animate required></sm-input>
|
|
<div class="flex align-start gap-0-5 remove-card-wrapper">
|
|
<sm-input type="number" class="amount-input w-100" placeholder="Amount" min="0.0000001"
|
|
step="0.00000001" error-text="Invalid amount" animate required>
|
|
<div class="currency-symbol flex" slot="icon">
|
|
<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"></rect>
|
|
</g>
|
|
<g>
|
|
<path
|
|
d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z">
|
|
</path>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</sm-input>
|
|
<button class="remove-card button--small">
|
|
<svg class="icon margin-right-0-3" 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 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
|
|
</svg>
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</fieldset>
|
|
</template>
|
|
<script id="onLoadStartUp">
|
|
let isShowingFloID = false;
|
|
async function onLoadStartUp() {
|
|
try {
|
|
routeTo('loading')
|
|
console.log("Starting the app! Please Wait!")
|
|
floDapps.setCustomPrivKeyInput(getSignedIn)
|
|
floDapps.setAppObjectStores({ savedIds: {}, savedUserData: {} })
|
|
await floExchangeAPI.init("FMxYC7gYZhouzqtHZukGnPiQ8nvG4CMzXM", "exchange")
|
|
console.log('Exchange API initialized!')
|
|
floDapps.launchStartUp().then(result => {
|
|
console.log(`Welcome ${floDapps.user.id}`);
|
|
floGlobals.myFloID = floCrypto.toFloID(floDapps.user.id);
|
|
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floGlobals.myFloID)
|
|
getRef('user_profile_id').textContent = floGlobals.myFloID
|
|
let showingFloID = true
|
|
// alternating between floID and btcID every 10 seconds
|
|
setInterval(() => {
|
|
getRef('user_profile_id').textContent = showingFloID ? floGlobals.myBtcID : floGlobals.myFloID
|
|
showingFloID = !showingFloID
|
|
}, 10000)
|
|
floGlobals.savedIds = {};
|
|
floGlobals.savedUserData = {
|
|
upiIds: {}
|
|
}
|
|
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(myFloID)
|
|
Promise.all([floExchangeAPI.getSink(floExchangeAPI.serviceList.EXCHANGE), floExchangeAPI.getSink(floExchangeAPI.serviceList.CONVERT)])
|
|
.then(([exchangeSink, convertSink]) => {
|
|
floGlobals.exchangeSink = exchangeSink
|
|
floGlobals.convertSink = convertSink
|
|
console.log('Exchange sink: ', exchangeSink)
|
|
console.log('Convert sink: ', convertSink)
|
|
}).catch(error => {
|
|
console.error(error)
|
|
})
|
|
refreshBalance()
|
|
if (floGlobals.isSubAdmin) {
|
|
cashierUI.renderRequests(Cashier.Requests);
|
|
Cashier.init().then(result => {
|
|
console.log(result);
|
|
document.querySelectorAll('.admin-element').forEach(elem => elem.classList.remove('hidden'))
|
|
document.querySelectorAll('.user-element').forEach(elem => elem.classList.add('hidden'))
|
|
getRef('home').classList.add('is-sub-admin')
|
|
routeTo(window.location.hash, { firstLoad: true })
|
|
}).catch(error => {
|
|
console.error(error)
|
|
detectAdBlocker(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('hidden'))
|
|
document.querySelectorAll('.user-element').forEach(elem => elem.classList.remove('hidden'))
|
|
routeTo(window.location.hash, { firstLoad: true })
|
|
floGlobals.loaded = true
|
|
}).catch(error => {
|
|
console.error(error)
|
|
detectAdBlocker(error)
|
|
})
|
|
}
|
|
}).catch(error => {
|
|
console.error(error)
|
|
})
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
function detectAdBlocker(error) {
|
|
if (error === 'Cloud offline') {
|
|
document.body.prepend(document.createElement('adblocker-warning'))
|
|
} else {
|
|
notify(error, "error")
|
|
}
|
|
}
|
|
</script>
|
|
<script>
|
|
/*jshint esversion: 9 */
|
|
// Global variables
|
|
const { html, render: renderElem, svg } = uhtml;
|
|
const domRefs = {};
|
|
floGlobals.connectionErrorNotifications = []
|
|
let transactionsHistoryLoader = null;
|
|
let walletHistoryLoader = null;
|
|
let contactHistoryLoader = null;
|
|
let paymentRequestsLoader = null;
|
|
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
floGlobals.connectionErrorNotifications.push(notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error'))
|
|
window.addEventListener('offline', () => {
|
|
floGlobals.connectionErrorNotifications.push(notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error'))
|
|
})
|
|
window.addEventListener('online', () => {
|
|
floGlobals.connectionErrorNotifications.forEach(notificationId => getRef('notification_drawer').remove(notificationId))
|
|
floGlobals.connectionErrorNotifications = []
|
|
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 = 50
|
|
// function required for popups or modals to appear
|
|
function openPopup(popupId, pinned) {
|
|
zIndex++
|
|
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
|
|
return getRef(popupId).show({ pinned })
|
|
}
|
|
|
|
// hides the popup or modal
|
|
function closePopup(options = {}) {
|
|
if (popupStack.peek() === undefined)
|
|
return;
|
|
popupStack.peek().popup.hide(options)
|
|
}
|
|
|
|
document.addEventListener('popupopened', async e => {
|
|
getRef('main_card').setAttribute('inert', '')
|
|
switch (e.target.id) {
|
|
case 'saved_ids_popup':
|
|
renderElem(getRef('saved_ids_picker_list'), html`${getArrayOfSavedIds().map(({ floID, title }) => render.savedIdPickerCard(floID, title))}`)
|
|
setTimeout(() => {
|
|
getRef('search_saved_ids_picker').focusIn()
|
|
}, 0);
|
|
break;
|
|
case 'withdraw_wallet_popup':
|
|
render.savedUpiIdOptions('select_withdraw_upi_id')
|
|
if (document.getElementById('select_withdraw_upi_id').children.length) {
|
|
getRef('select_withdraw_upi_id').parentNode.classList.remove('hidden')
|
|
}
|
|
break;
|
|
case 'send_btc_popup':
|
|
calculateFees()
|
|
break;
|
|
case 'profile_popup':
|
|
renderElem(getRef('profile_popup__content'), render.profile())
|
|
renderSavedUpiIds('saved_upi_ids_list')
|
|
break;
|
|
}
|
|
})
|
|
document.addEventListener('popupclosed', e => {
|
|
switch (e.target.id) {
|
|
case 'saved_ids_popup':
|
|
getRef('saved_ids_picker_list').innerHTML = ''
|
|
getRef('search_saved_ids_picker').value = ''
|
|
break;
|
|
case 'topup_wallet_popup':
|
|
showChildElement('topup_wallet_process', 0)
|
|
break;
|
|
case 'withdraw_wallet_popup':
|
|
getRef('select_withdraw_upi_id').parentNode.classList.add('hidden')
|
|
showChildElement('withdraw_wallet_process', 0)
|
|
break;
|
|
case 'convert_asset_popup':
|
|
break
|
|
case 'external_transfer_popup':
|
|
showChildElement('external_transfer_process', 0);
|
|
buttonLoader('external_transfer__button', false);
|
|
document.getElementById('external_transfer__amount').value = '';
|
|
break;
|
|
case 'confirm_topup_popup':
|
|
showChildElement('confirm_topup_wrapper', 0);
|
|
break;
|
|
case 'profile_popup':
|
|
renderElem(getRef('profile_popup__content'), html``)
|
|
break;
|
|
}
|
|
if (popupStack.items.length === 0) {
|
|
getRef('main_card').removeAttribute('inert')
|
|
}
|
|
zIndex--;
|
|
})
|
|
// 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', danger = false } = options
|
|
getRef('confirm_title').innerText = title;
|
|
getRef('confirm_message').innerText = message;
|
|
const cancelButton = getRef('confirmation_popup').querySelector('.cancel-button');
|
|
const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button')
|
|
confirmButton.textContent = confirmText
|
|
cancelButton.textContent = cancelText
|
|
if (danger)
|
|
confirmButton.classList.add('button--danger')
|
|
else
|
|
confirmButton.classList.remove('button--danger')
|
|
const { opened, closed } = openPopup('confirmation_popup')
|
|
confirmButton.onclick = () => {
|
|
closePopup({ payload: true })
|
|
}
|
|
cancelButton.onclick = () => {
|
|
closePopup()
|
|
}
|
|
closed.then((payload) => {
|
|
confirmButton.onclick = null
|
|
cancelButton.onclick = null
|
|
if (payload)
|
|
resolve(true)
|
|
else
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
// displays a popup for asking user input. Use this instead of JS prompt
|
|
function getPromptInput(title, message = '', options = {}) {
|
|
let { placeholder = '', isPassword = false, cancelText = 'Cancel', confirmText = 'OK' } = options
|
|
getRef('prompt_title').innerText = title;
|
|
getRef('prompt_message').innerText = message;
|
|
const cancelButton = getRef('prompt_popup').querySelector('.cancel-button');
|
|
const confirmButton = getRef('prompt_popup').querySelector('.confirm-button')
|
|
if (isPassword) {
|
|
placeholder = 'Password'
|
|
getRef('prompt_input').setAttribute("type", "password")
|
|
}
|
|
getRef('prompt_input').setAttribute("placeholder", placeholder)
|
|
getRef('prompt_input').focusIn()
|
|
cancelButton.textContent = cancelText;
|
|
confirmButton.textContent = confirmText;
|
|
openPopup('prompt_popup', true)
|
|
return new Promise((resolve, reject) => {
|
|
cancelButton.addEventListener('click', () => {
|
|
closePopup()
|
|
return null
|
|
}, { once: true })
|
|
confirmButton.addEventListener('click', () => {
|
|
closePopup()
|
|
resolve(getRef('prompt_input').value)
|
|
}, { once: true })
|
|
})
|
|
}
|
|
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, options = {}) {
|
|
let icon
|
|
switch (mode) {
|
|
case 'success':
|
|
icon = `<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>`
|
|
options.pinned = true
|
|
break;
|
|
}
|
|
getRef("notification_drawer").push(message, { icon, ...options });
|
|
if (mode === 'error') {
|
|
console.error(message)
|
|
}
|
|
}
|
|
|
|
function getFormattedTime(timestamp, format) {
|
|
try {
|
|
if (String(timestamp).length < 13)
|
|
timestamp *= 1000
|
|
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
|
|
minutes = new Date(timestamp).getMinutes(),
|
|
hours = new Date(timestamp).getHours(),
|
|
currentTime = new Date().toString().split(' ')
|
|
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
|
let finalHours = ``;
|
|
if (hours > 12)
|
|
finalHours = `${hours - 12}:${minutes}`
|
|
else if (hours === 0)
|
|
finalHours = `12:${minutes}`
|
|
else
|
|
finalHours = `${hours}:${minutes}`
|
|
|
|
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
|
switch (format) {
|
|
case 'date-only':
|
|
return `${month} ${date}, ${year}`;
|
|
case 'time-only':
|
|
return finalHours;
|
|
case 'relative':
|
|
// check if timestamp is older than a day
|
|
if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000)
|
|
return `${finalHours}`;
|
|
else
|
|
return relativeTime.from(timestamp)
|
|
default:
|
|
return `${month} ${date}, ${year} at ${finalHours}`;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
return timestamp;
|
|
}
|
|
}
|
|
// 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)
|
|
}
|
|
})
|
|
}
|
|
|
|
// detect browser version
|
|
function detectBrowser() {
|
|
let ua = navigator.userAgent,
|
|
tem,
|
|
M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
|
if (/trident/i.test(M[1])) {
|
|
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
|
return 'IE ' + (tem[1] || '');
|
|
}
|
|
if (M[1] === 'Chrome') {
|
|
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
|
|
if (tem != null) return tem.slice(1).join(' ').replace('OPR', 'Opera');
|
|
}
|
|
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
|
|
if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1]);
|
|
return M.join(' ');
|
|
}
|
|
window.addEventListener('hashchange', e => routeTo(window.location.hash))
|
|
window.addEventListener("load", () => {
|
|
const [browserName, browserVersion] = detectBrowser().split(' ');
|
|
const supportedVersions = {
|
|
Chrome: 85,
|
|
Firefox: 75,
|
|
Safari: 13,
|
|
}
|
|
if (browserName in supportedVersions) {
|
|
if (parseInt(browserVersion) < supportedVersions[browserName]) {
|
|
notify(`${browserName} ${browserVersion} is not fully supported, some features may not work properly. Please update to ${supportedVersions[browserName]} or higher.`, 'error')
|
|
}
|
|
} else {
|
|
notify('Browser is not fully compatible, some features may not work. for best experience please use Chrome, Edge, Firefox or Safari', 'error')
|
|
}
|
|
document.body.classList.remove('hidden')
|
|
handleMobileChange(mobileQuery)
|
|
document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = floCrypto.validateAddr)
|
|
getRef('receiver_container').querySelectorAll('sm-input[data-bc-address]').forEach(input => input.customValidation = btcOperator.validateAddress)
|
|
document.addEventListener('keyup', (e) => {
|
|
if (e.key === 'Escape') {
|
|
closePopup()
|
|
}
|
|
})
|
|
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"));
|
|
}
|
|
});
|
|
document.querySelectorAll('.popup__header__close, .close-popup-on-click').forEach(elem => {
|
|
elem.addEventListener('click', () => {
|
|
closePopup()
|
|
})
|
|
})
|
|
});
|
|
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(
|
|
[
|
|
{
|
|
opacity: 1,
|
|
transform: `scale(0)`
|
|
},
|
|
{
|
|
transform: "scale(4)",
|
|
opacity: 0,
|
|
},
|
|
],
|
|
{
|
|
duration: 600,
|
|
fill: "forwards",
|
|
easing: "ease-out",
|
|
}
|
|
);
|
|
target.append(circle);
|
|
rippleAnimation.onfinish = () => {
|
|
circle.remove();
|
|
};
|
|
}
|
|
|
|
const tempTransactions = [
|
|
{
|
|
"txid": "d4569bdfda5da0f8470d82d2fd56c135930dc78a88182bde439592f5596a11ef",
|
|
"time": 1676839514000,
|
|
"block": 777391,
|
|
"tx_senders": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00039959
|
|
},
|
|
"tx_input_value": 0.00039959,
|
|
"tx_receivers": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00038687
|
|
},
|
|
"tx_output_value": 0.00038687,
|
|
"tx_fee": 0.00001272,
|
|
"type": "self",
|
|
"amount": 0.00038687,
|
|
"address": "bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh",
|
|
"asset": "btc"
|
|
},
|
|
{
|
|
"txid": "19aeaa2e19ec38a2d253dbfd4897911ad05098ea026ee8acf9fd42dc699700d3",
|
|
"time": 1676747036000,
|
|
"block": 777219,
|
|
"tx_senders": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00040595
|
|
},
|
|
"tx_input_value": 0.00040595,
|
|
"tx_receivers": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00039959
|
|
},
|
|
"tx_output_value": 0.00039959,
|
|
"tx_fee": 0.00000636,
|
|
"type": "self",
|
|
"amount": 0.00039959,
|
|
"address": "bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh",
|
|
"asset": "btc"
|
|
},
|
|
{
|
|
"txid": "be4df5e551d5483792e2f14d0fa6a928a3b2d61cc3f506fee6fd51413c5374ee",
|
|
"time": 1676746166000,
|
|
"block": 777212,
|
|
"tx_senders": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00041027
|
|
},
|
|
"tx_input_value": 0.00041027,
|
|
"tx_receivers": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00040595
|
|
},
|
|
"tx_output_value": 0.00040595,
|
|
"tx_fee": 0.00000432,
|
|
"type": "self",
|
|
"amount": 0.00040595,
|
|
"address": "bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh",
|
|
"asset": "btc"
|
|
},
|
|
{
|
|
"txid": "53186ff4e865a5fd87e1dc83c764fe51bb3501bd18e7785893778af3c8ab8f09",
|
|
"time": 1676726891000,
|
|
"block": 777175,
|
|
"tx_senders": {
|
|
"bc1qkvcp7tfvu9r55ummjs4hrpqtnnq8rzyxr0yf94": 0.26179078
|
|
},
|
|
"tx_input_value": 0.26179078,
|
|
"tx_receivers": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00041027,
|
|
"bc1qkvcp7tfvu9r55ummjs4hrpqtnnq8rzyxr0yf94": 0.26137619
|
|
},
|
|
"tx_output_value": 0.26178646,
|
|
"tx_fee": 0.00000432,
|
|
"type": "in",
|
|
"amount": 0.00041027,
|
|
"sender": [
|
|
"bc1qkvcp7tfvu9r55ummjs4hrpqtnnq8rzyxr0yf94"
|
|
],
|
|
"asset": "btc"
|
|
},
|
|
{
|
|
"txid": "0c43a20d019e638527d2809d353d8d1283bed410682f64e5abbc7c1dac8d1f51",
|
|
"time": 1672163218000,
|
|
"block": 769137,
|
|
"tx_senders": {
|
|
"bc1q2pwy0j92wsaxzfe2qwpq9sts9a049wr77kclkx": 0.00718428
|
|
},
|
|
"tx_input_value": 0.00718428,
|
|
"tx_receivers": {
|
|
"bc1qgpxay3v490u5artsdvz46jfvc9e8e3cxpgcwgh": 0.00014637,
|
|
"bc1q2pwy0j92wsaxzfe2qwpq9sts9a049wr77kclkx": 0.00701487
|
|
},
|
|
"tx_output_value": 0.00716124,
|
|
"tx_fee": 0.00002304,
|
|
"type": "in",
|
|
"amount": 0.00014637,
|
|
"sender": [
|
|
"bc1q2pwy0j92wsaxzfe2qwpq9sts9a049wr77kclkx"
|
|
],
|
|
"asset": "btc"
|
|
}
|
|
]
|
|
|
|
const appState = {
|
|
params: {},
|
|
openedPages: new Set(),
|
|
}
|
|
const generalPages = ['sign_up', 'sign_in', 'loading', 'landing']
|
|
async function routeTo(targetPage, options = {}) {
|
|
console.log('route to', targetPage)
|
|
const { firstLoad, hashChange } = options
|
|
let pageId
|
|
let params = {}
|
|
let searchParams
|
|
if (targetPage === '') {
|
|
try {
|
|
if (floDapps.user.id)
|
|
pageId = 'home'
|
|
} catch (e) {
|
|
pageId = 'sign_in'
|
|
}
|
|
} else {
|
|
if (targetPage.includes('/')) {
|
|
let path;
|
|
[path, searchParams] = targetPage.split('?');
|
|
[, pageId] = path.split('/')
|
|
} else {
|
|
pageId = targetPage
|
|
}
|
|
}
|
|
try {
|
|
if (floDapps.user.id && (generalPages.includes(pageId))) {
|
|
history.replaceState(null, null, '#/home');
|
|
pageId = 'home'
|
|
}
|
|
} catch (e) {
|
|
if (!(generalPages.includes(pageId))) return
|
|
}
|
|
appState.currentPage = pageId
|
|
|
|
if (searchParams) {
|
|
const urlSearchParams = new URLSearchParams('?' + searchParams);
|
|
params = Object.fromEntries(urlSearchParams.entries());
|
|
}
|
|
switch (pageId) {
|
|
case 'sign_in':
|
|
setTimeout(() => {
|
|
getRef('private_key_field').focusIn()
|
|
}, 0);
|
|
break;
|
|
case 'sign_up':
|
|
getRef('keys_generator').generateKeys()
|
|
break;
|
|
case 'home':
|
|
if (!floGlobals.isMobileView && !floGlobals.isSubAdmin) {
|
|
render.recentTransactions()
|
|
render.savedIds()
|
|
}
|
|
break;
|
|
case 'contacts':
|
|
render.savedIds()
|
|
break;
|
|
case 'contact':
|
|
getRef('contact__title').textContent = getFloIdTitle(params.floId)
|
|
getRef('contact__transactions').innerHTML = '<sm-spinner></sm-spinner>'
|
|
Promise.all([
|
|
floTokenAPI.fetch(`api/v1.0/getTokenTransactions?token=rupee&senderFloAddress=${floGlobals.myFloID}&destFloAddress=${params.floId}`),
|
|
floTokenAPI.fetch(`api/v1.0/getTokenTransactions?token=rupee&senderFloAddress=${params.floId}&destFloAddress=${floGlobals.myFloID}`)])
|
|
.then(([sentTransactions, receivedTransactions]) => {
|
|
const allTransactions = Object.values({ ...sentTransactions.transactions, ...receivedTransactions.transactions }).sort((a, b) => b.transactionDetails.time - a.transactionDetails.time)
|
|
if (contactHistoryLoader) {
|
|
contactHistoryLoader.update(allTransactions)
|
|
} else {
|
|
contactHistoryLoader = new LazyLoader('#contact__transactions', allTransactions, render.transactionMessage, { bottomFirst: true });
|
|
}
|
|
contactHistoryLoader.init()
|
|
}).catch(err => {
|
|
console.error(err)
|
|
})
|
|
break;
|
|
case 'history':
|
|
render.transactionsHistory()
|
|
break;
|
|
case 'requests':
|
|
closeNotificationPanel()
|
|
const paymentRequests = [];
|
|
if (paymentRequestsLoader)
|
|
paymentRequestsLoader.clear();
|
|
for (const transactionId in User.moneyRequests) {
|
|
if (User.moneyRequests[transactionId].note) {
|
|
paymentRequests.unshift(User.moneyRequests[transactionId])
|
|
}
|
|
}
|
|
if (paymentRequestsLoader) {
|
|
paymentRequestsLoader.update(paymentRequests)
|
|
} else {
|
|
paymentRequestsLoader = new LazyLoader('#payment_request_history', paymentRequests, render.paymentRequestCard);
|
|
}
|
|
paymentRequestsLoader.init()
|
|
break;
|
|
case 'transaction':
|
|
let transactionDetails
|
|
let status
|
|
let shouldRender = {}
|
|
if (params.type === 'request') {
|
|
transactionDetails = User.moneyRequests[params.transactionId]
|
|
const { message: { remark }, note, tag } = transactionDetails
|
|
status = note ? note.split(':')[0] : 'PENDING';
|
|
getRef('transaction__type').textContent = 'Payment request'
|
|
if (status === 'PAID')
|
|
shouldRender['txLink'] = `${floBlockchainAPI.current_server}tx/${note.split(':')[1].trim()}`;
|
|
if (remark !== '')
|
|
shouldRender.txRemark = remark
|
|
} else if (params.type === 'wallet') {
|
|
transactionDetails = User.cashierRequests[params.transactionId]
|
|
console.log(transactionDetails)
|
|
const { message: { amount, mode, upi_id, upi_txid, token_txid, txCode }, note, tag } = transactionDetails
|
|
let upiID;
|
|
if (upi_id instanceof Object && "secret" in upi_id) {
|
|
try {
|
|
upiID = floDapps.user.decrypt(upi_id);
|
|
} catch (error) {
|
|
console.error("UPI ID is not encrypted with a proper key", error);
|
|
return notify("UPI ID is not encrypted with a proper key", 'error');
|
|
}
|
|
} else
|
|
upiID = upi_id;
|
|
status = tag ? tag : (note ? 'REJECTED' : "PENDING");
|
|
getRef('transaction__type').textContent = mode === 'cash-to-token' ? 'Wallet top-up' : 'Withdraw';
|
|
if (status === 'COMPLETED') {
|
|
shouldRender['txLink'] = `${floBlockchainAPI.current_server}tx/${mode === 'cash-to-token' ? note : token_txid}`
|
|
} else if (status === 'REJECTED') {
|
|
shouldRender.txNote = html`<svg class="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><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"></path></svg> ${note.split(':')[1]}`
|
|
}
|
|
if (mode === 'cash-to-token') {
|
|
if (status === 'COMPLETED') {
|
|
if (txCode) {
|
|
shouldRender.txNote = `Transaction code: ${txCode}`
|
|
} else if (upi_txid) {
|
|
shouldRender.txNote = `UPI Transaction ID: ${upi_txid}`
|
|
}
|
|
} else if (status === 'REJECTED') {
|
|
const reason = cashierRejectionErrors.hasOwnProperty(note.split(':')[1]) ? cashierRejectionErrors[note.split(':')[1]] : note.split(':')[1]
|
|
shouldRender.txNote = html` <svg class="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><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"></path></svg> ${reason} `
|
|
}
|
|
} else {
|
|
if (status === 'PENDING') {
|
|
shouldRender.txNote = `Pending transfer of ${formatAmount(amount)} to bank account linked to ${upiID}`
|
|
} else if (status === 'COMPLETED') {
|
|
shouldRender.txNote = `Transfer of ${formatAmount(amount)} to bank account linked to ${upiID} completed`
|
|
}
|
|
}
|
|
}
|
|
const { message: { amount }, time, note } = transactionDetails
|
|
let txAmount = formatAmount(amount)
|
|
if (status === 'COMPLETED' && note && note.includes('#')) {
|
|
const [txid, finalAmount] = note.split('#');
|
|
txAmount = formatAmount(parseFloat(finalAmount));
|
|
shouldRender.txNote = `Also received 1 FLO worth of ${formatAmount(amount - parseFloat(finalAmount))} due to low FLO balance`;
|
|
}
|
|
renderElem(getRef('transaction_details'), html`
|
|
<div class="grid gap-1">
|
|
<div id="transaction__amount">${txAmount}</div>
|
|
${shouldRender.remark ? html`<div id="transaction__remark">${shouldRender.remark}</div>` : ''}
|
|
<div class="flex align-center" style="font-size: 0.8rem;">
|
|
<time id="transaction__time">${getFormattedTime(time)}</time>
|
|
<div class="bullet-point"></div>
|
|
<span id="transaction__status">${status}</span>
|
|
</div>
|
|
</div>
|
|
${shouldRender.txLink ? html`<a id="transaction__link" href="${shouldRender.txLink}" target="_blank">See transaction
|
|
on blockchain</a>` : ''}
|
|
${shouldRender.txNote ? html`<div id="transaction__note" class="flex flex-direction-column gap-0-5">${shouldRender.txNote}</div>` : ''}
|
|
`)
|
|
break;
|
|
case 'wallet':
|
|
const walletTransactions = []
|
|
if (walletHistoryLoader)
|
|
walletHistoryLoader.clear()
|
|
const pendingWalletTransactions = document.createDocumentFragment()
|
|
let areTransactionsPending = false
|
|
for (const transactionId in User.cashierRequests) {
|
|
if (!User.cashierRequests[transactionId].note) {
|
|
areTransactionsPending = true
|
|
pendingWalletTransactions.prepend(render.walletRequestCard(User.cashierRequests[transactionId]))
|
|
} else {
|
|
walletTransactions.unshift(User.cashierRequests[transactionId])
|
|
}
|
|
}
|
|
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()
|
|
removeNotificationBadge('wallet_history_button')
|
|
break;
|
|
}
|
|
if (pageId !== 'history') {
|
|
if (transactionsHistoryLoader)
|
|
transactionsHistoryLoader.clear()
|
|
resetPaymentsFilters()
|
|
}
|
|
if (pageId !== 'wallet') {
|
|
if (walletHistoryLoader)
|
|
walletHistoryLoader.clear()
|
|
|
|
}
|
|
if (pageId !== 'contact') {
|
|
if (contactHistoryLoader)
|
|
contactHistoryLoader.clear()
|
|
}
|
|
|
|
if (appState.lastPage !== pageId) {
|
|
const animOptions = {
|
|
duration: 100,
|
|
fill: 'forwards',
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
}
|
|
if (document.querySelector('.nav-item--active'))
|
|
document.querySelector('.nav-item--active').classList.remove('nav-item--active');
|
|
const targetListItem = [...document.querySelectorAll(`a.nav-item`)].find(item => item.href.includes(pageId))
|
|
if (targetListItem) {
|
|
targetListItem.classList.add('nav-item--active')
|
|
if (!floGlobals.isSubAdmin)
|
|
getRef('bottom_nav').classList.remove('hidden')
|
|
} else {
|
|
getRef('bottom_nav').classList.add('hidden')
|
|
}
|
|
document.querySelectorAll('.page').forEach(page => page.classList.add('hidden'))
|
|
getRef(pageId).closest('.page').classList.remove('hidden')
|
|
document.querySelectorAll('.inner-page').forEach(page => page.classList.add('hidden'))
|
|
getRef(pageId).classList.remove('hidden')
|
|
getRef('main_card').style.overflowY = "hidden";
|
|
getRef(pageId).animate([
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
},
|
|
],
|
|
{
|
|
duration: 300,
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
}).onfinish = () => {
|
|
getRef('main_card').style.overflowY = "";
|
|
}
|
|
appState.lastPage = pageId
|
|
}
|
|
if (params)
|
|
appState.params = params
|
|
appState.openedPages.add(pageId)
|
|
|
|
}
|
|
|
|
// class based lazy loading
|
|
class LazyLoader {
|
|
constructor(container, elementsToRender, renderFn, options = {}) {
|
|
const { batchSize = 10, freshRender, bottomFirst = false, domUpdated } = options
|
|
|
|
this.elementsToRender = elementsToRender
|
|
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
|
this.renderFn = renderFn
|
|
this.intersectionObserver
|
|
|
|
this.batchSize = batchSize
|
|
this.freshRender = freshRender
|
|
this.domUpdated = domUpdated
|
|
this.bottomFirst = bottomFirst
|
|
|
|
this.shouldLazyLoad = false
|
|
this.lastScrollTop = 0
|
|
this.lastScrollHeight = 0
|
|
|
|
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)
|
|
}
|
|
get elements() {
|
|
return this.arrayOfElements
|
|
}
|
|
init() {
|
|
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
observer.disconnect()
|
|
this.render({ lazyLoad: true })
|
|
}
|
|
})
|
|
})
|
|
this.mutationObserver = new MutationObserver(mutationList => {
|
|
mutationList.forEach(mutation => {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.addedNodes.length) {
|
|
if (this.bottomFirst) {
|
|
if (this.lazyContainer.firstElementChild)
|
|
this.intersectionObserver.observe(this.lazyContainer.firstElementChild)
|
|
} else {
|
|
if (this.lazyContainer.lastElementChild)
|
|
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 || []
|
|
}
|
|
render(options = {}) {
|
|
let { lazyLoad = false } = options
|
|
this.shouldLazyLoad = lazyLoad
|
|
const frag = document.createDocumentFragment();
|
|
if (lazyLoad) {
|
|
if (this.bottomFirst) {
|
|
this.updateEndIndex = this.updateStartIndex
|
|
this.updateStartIndex = this.updateEndIndex - this.batchSize
|
|
} else {
|
|
this.updateStartIndex = this.updateEndIndex
|
|
this.updateEndIndex = this.updateEndIndex + this.batchSize
|
|
}
|
|
} else {
|
|
this.intersectionObserver.disconnect()
|
|
if (this.bottomFirst) {
|
|
this.updateEndIndex = this.arrayOfElements.length
|
|
this.updateStartIndex = this.updateEndIndex - this.batchSize - 1
|
|
} else {
|
|
this.updateStartIndex = 0
|
|
this.updateEndIndex = this.batchSize
|
|
}
|
|
this.lazyContainer.innerHTML = ``;
|
|
}
|
|
this.lastScrollHeight = this.lazyContainer.scrollHeight
|
|
this.lastScrollTop = this.lazyContainer.scrollTop
|
|
this.arrayOfElements.slice(this.updateStartIndex, this.updateEndIndex).forEach((element, index) => {
|
|
frag.append(this.renderFn(element))
|
|
})
|
|
if (this.bottomFirst) {
|
|
this.lazyContainer.prepend(frag)
|
|
// scroll anchoring for reverse scrolling
|
|
this.lastScrollTop += this.lazyContainer.scrollHeight - this.lastScrollHeight
|
|
this.lazyContainer.scrollTo({ top: this.lastScrollTop })
|
|
this.lastScrollHeight = this.lazyContainer.scrollHeight
|
|
} else {
|
|
this.lazyContainer.append(frag)
|
|
}
|
|
if (!lazyLoad && this.bottomFirst) {
|
|
this.lazyContainer.scrollTop = this.lazyContainer.scrollHeight
|
|
}
|
|
// 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
|
|
}
|
|
const slideInLeft = [
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateX(0)'
|
|
}
|
|
]
|
|
const slideOutLeft = [
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateX(0)'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(-1rem)'
|
|
},
|
|
]
|
|
const slideInRight = [
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(-1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateX(0)'
|
|
}
|
|
]
|
|
const slideOutRight = [
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateX(0)'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateX(1rem)'
|
|
},
|
|
]
|
|
const slideOutUp = [
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(-1rem)'
|
|
},
|
|
]
|
|
const slideInDown = [
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(-1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
}
|
|
]
|
|
|
|
const fadeIn = [
|
|
{
|
|
opacity: 0
|
|
},
|
|
{
|
|
opacity: 1
|
|
}
|
|
]
|
|
const fadeOut = [
|
|
{
|
|
opacity: 1
|
|
},
|
|
{
|
|
opacity: 0
|
|
}
|
|
]
|
|
|
|
|
|
function showChildElement(id, index, options = {}) {
|
|
return new Promise((resolve) => {
|
|
const { mobileView = false, entry, exit } = options
|
|
const animOptions = {
|
|
duration: 150,
|
|
easing: 'ease',
|
|
fill: 'forwards'
|
|
}
|
|
const parent = typeof id === 'string' ? document.getElementById(id) : id;
|
|
const visibleElement = [...parent.children].find(elem => !elem.classList.contains(mobileView ? 'hide-on-mobile' : 'hidden'));
|
|
if (visibleElement === parent.children[index]) return;
|
|
visibleElement.getAnimations().forEach(anim => anim.cancel())
|
|
parent.children[index].getAnimations().forEach(anim => anim.cancel())
|
|
if (visibleElement) {
|
|
if (exit) {
|
|
visibleElement.animate(exit, animOptions).onfinish = () => {
|
|
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden')
|
|
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
|
|
if (entry)
|
|
parent.children[index].animate(entry, animOptions).onfinish = () => resolve()
|
|
}
|
|
} else {
|
|
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden')
|
|
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
|
|
resolve()
|
|
}
|
|
} else {
|
|
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
|
|
parent.children[index].animate(entry, animOptions).onfinish = () => resolve()
|
|
}
|
|
})
|
|
}
|
|
|
|
// generate random string with numbers and capital and small letters
|
|
function randomString(length) {
|
|
let result = '';
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
const charactersLength = characters.length;
|
|
for (var i = 0; i < length; i++) {
|
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
document.addEventListener("visibilitychange", handleVisibilityChange, false);
|
|
function handleVisibilityChange() {
|
|
if (document.visibilityState === "hidden") {
|
|
// code if page is hidden
|
|
} else {
|
|
if (floGlobals.loaded && floGlobals.isSubAdmin)
|
|
startStatusInterval()
|
|
}
|
|
}
|
|
|
|
|
|
function conditionalClassToggle(el, className, condition) {
|
|
if (condition)
|
|
el.classList.add(className);
|
|
else
|
|
el.classList.remove(className);
|
|
}
|
|
function togglePrivateKeyVisibility(input) {
|
|
const target = input.closest('sm-input')
|
|
target.type = target.type === 'password' ? 'text' : 'password';
|
|
target.focusIn()
|
|
}
|
|
const mobileQuery = window.matchMedia('(max-width: 40rem)')
|
|
function handleMobileChange(e) {
|
|
floGlobals.isMobileView = e.matches
|
|
if (floGlobals.loaded)
|
|
routeTo(window.location.hash)
|
|
}
|
|
mobileQuery.addEventListener('change', handleMobileChange)
|
|
const reduceMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
reduceMotionQuery.addEventListener('change', () => {
|
|
floGlobals.prefersReducedMotion = reduceMotionQuery.matches
|
|
});
|
|
floGlobals.prefersReducedMotion = reduceMotionQuery.matches
|
|
</script>
|
|
<script>
|
|
/*jshint esversion: 8 */
|
|
/**
|
|
* @yaireo/relative-time - javascript function to transform timestamp or date to local relative-time
|
|
*
|
|
* @version v1.0.0
|
|
* @homepage https://github.com/yairEO/relative-time
|
|
*/
|
|
|
|
!function (e, t) { var o = o || {}; "function" == typeof o && o.amd ? o([], t) : "object" == typeof exports && "object" == typeof module ? module.exports = t() : "object" == typeof exports ? exports.RelativeTime = t() : e.RelativeTime = t() }(this, (function () { const e = { year: 31536e6, month: 2628e6, day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 }, t = "en", o = { numeric: "auto" }; function n(e) { e = { locale: (e = e || {}).locale || t, options: { ...o, ...e.options } }, this.rtf = new Intl.RelativeTimeFormat(e.locale, e.options) } return n.prototype = { from(t, o) { const n = t - (o || new Date); for (let t in e) if (Math.abs(n) > e[t] || "second" == t) return this.rtf.format(Math.round(n / e[t]), t) } }, n }));
|
|
|
|
const relativeTime = new RelativeTime({ style: 'narrow' });
|
|
|
|
// use floDapps.storeContact() to store contacts that can be used by other apps on same device
|
|
async function syncUserData(obsName, data) {
|
|
floDapps.user.private.then(privateKey => {
|
|
if (!privateKey) return;
|
|
const encryptedData = Crypto.AES.encrypt(JSON.stringify(data), privateKey);
|
|
return floCloudAPI.sendApplicationData(encryptedData, obsName, { receiverID: floGlobals.myFloID });
|
|
}).catch(error => {
|
|
console.log(error);
|
|
notify('Invalid password', 'error');
|
|
return false;
|
|
})
|
|
}
|
|
// store user data in separate IDB
|
|
async function organizeSyncedData(obsName) {
|
|
const fetchedData = await floCloudAPI.requestApplicationData(obsName, { mostRecent: true, senderIDs: [floGlobals.myFloID], receiverID: floGlobals.myFloID });
|
|
let vc = Object.keys(fetchedData).sort().pop();
|
|
if (vc && await compactIDB.readData(obsName, 'lastSyncTime') !== fetchedData[vc].time) {
|
|
await compactIDB.clearData(obsName);
|
|
const dataToDecrypt = fetchedData[vc].message;
|
|
floDapps.user.private.then(privateKey => {
|
|
if (!privateKey) return;
|
|
const decryptedData = JSON.parse(Crypto.AES.decrypt(dataToDecrypt, privateKey));
|
|
for (let key in decryptedData) {
|
|
floGlobals[obsName][key] = decryptedData[key];
|
|
compactIDB.addData(obsName, decryptedData[key], key);
|
|
}
|
|
compactIDB.addData(obsName, fetchedData[vc].time, 'lastSyncTime');
|
|
return true;
|
|
}).catch(error => {
|
|
console.log(error);
|
|
notify('Invalid password', 'error');
|
|
return false;
|
|
})
|
|
} else {
|
|
const idbData = await compactIDB.readAllData(obsName);
|
|
for (const key in idbData) {
|
|
if (key !== 'lastSyncTime')
|
|
floGlobals[obsName][key] = idbData[key];
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
const userUI = {};
|
|
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;
|
|
floGlobals.txCode = randomString(6);
|
|
// 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__upi_id').value = cashierUPI[cashier];
|
|
getRef('topup_wallet__qr_code').innerHTML = ''
|
|
getRef('topup_wallet__qr_code').append(new QRCode({
|
|
msg: `upi://pay?pn=FLOPay&pa=${cashierUPI[cashier]}&am=${amount}&tn=${floGlobals.txCode}`,
|
|
ecl: 'H'
|
|
}))
|
|
render.conditionalSteps()
|
|
showChildElement('topup_wallet_process', 1)
|
|
// getRef('topup_wallet__txid').focusIn();
|
|
}
|
|
function depositMoneyToWallet() {
|
|
let cashier = User.findCashier();
|
|
if (!cashier)
|
|
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 txCode = getRef('topup_wallet__code').value;
|
|
// 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, floGlobals.txCode/* , upiId */).then(result => {
|
|
console.log(result);
|
|
showChildElement('topup_wallet_process', 2);
|
|
refreshBalance()
|
|
}).catch(error => {
|
|
console.error(error)
|
|
getRef('topup_failed_reason').textContent = error;
|
|
showChildElement('topup_wallet_process', 3);
|
|
}).finally(() => {
|
|
buttonLoader('topup_wallet_button', false);
|
|
});
|
|
}
|
|
|
|
getRef('topup_wallet__qr_wrapper').addEventListener('toggle', e => render.conditionalSteps())
|
|
|
|
function withdrawMoneyFromWallet() {
|
|
let cashier = User.findCashier();
|
|
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_withdraw_upi_id').value;
|
|
if (!upiId)
|
|
return notify("Please add an UPI ID to continue", 'error');
|
|
buttonLoader('withdraw_rupee_button', true);
|
|
getRef('withdrawal_blockchain_link').classList.add('hidden');
|
|
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 => {
|
|
showChildElement('withdraw_wallet_process', 1);
|
|
refreshBalance();
|
|
getRef('withdrawal_blockchain_link').classList.remove('hidden');
|
|
getRef('withdrawal_blockchain_link').href = `${floBlockchainAPI.current_server}tx/${txid}`
|
|
console.log(result);
|
|
}).catch(error => {
|
|
getRef('withdrawal_failed_reason').textContent = error;
|
|
showChildElement('withdraw_wallet_process', 2);
|
|
console.error(error)
|
|
}).finally(() => {
|
|
buttonLoader('withdraw_rupee_button', false);
|
|
});
|
|
}).catch(error => {
|
|
getRef('withdrawal_failed_reason').textContent = error;
|
|
showChildElement('withdraw_wallet_process', 2);
|
|
buttonLoader('withdraw_rupee_button', false);
|
|
console.error(error)
|
|
})
|
|
}
|
|
function openExternalTransferPopup(type) {
|
|
let title = ``;
|
|
let description = ``;
|
|
let successTitle = '';
|
|
let successDescription = null;
|
|
switch (type) {
|
|
case 'exchange': {
|
|
title = 'Transfer to Exchange';
|
|
description = 'Deposit rupee tokens in RanchiMall Exchange';
|
|
successTitle = 'Rupees transfer initiated';
|
|
successDescription = html`<p>Amount may take upto 30 mins to show up in exchange</p>`;
|
|
} break;
|
|
case 'btc-bonds': {
|
|
title = 'Transfer to Blockchain Bonds';
|
|
description = 'Buy Blockchain Bonds with rupee tokens';
|
|
successTitle = 'Rupees transferred for Blockchain Bonds';
|
|
} break;
|
|
case 'bobs-fund': {
|
|
title = `Transfer to Bob's fund`;
|
|
description = `Buy Bob's fund with rupee tokens`;
|
|
successTitle = `Rupees transferred for Bob's fund`;
|
|
} break;
|
|
}
|
|
renderElem(getRef('external_transfer_process'), html`
|
|
<sm-form>
|
|
<div class="grid gap-0-5">
|
|
<h4>${title}</h4>
|
|
<p>${description}</p>
|
|
</div>
|
|
<sm-input id="external_transfer__amount" type="number" placeholder="Amount" min="1" step="0.01" error-text="minimum amount is ₹1" animate required autofocus> </sm-input>
|
|
<div class="multi-state-button">
|
|
<button id="external_transfer__button" class="button button--primary cta" onclick={externalTransfer()} data-type="${type}" type="submit">Transfer</button>
|
|
</div>
|
|
</sm-form>
|
|
<div class="grid gap-0-5 hidden 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>
|
|
<b id="external_transfer__success_message">${successTitle}</b>
|
|
${successDescription}
|
|
${(type === 'btc-bonds' || type === 'bobs-fund') ? html`<a id="external_transfer__link" target="_blank" class="margin-top-1">View transaction on blockchain</a>` : ''}
|
|
</div>
|
|
<div class="grid gap-0-5 hidden 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>
|
|
<b>Failed to transfer</b>
|
|
<p id="external_transfer__failed_reason"></p>
|
|
</div>
|
|
`)
|
|
openPopup('external_transfer_popup');
|
|
}
|
|
|
|
function externalTransfer() {
|
|
const type = document.getElementById('external_transfer__button').dataset.type;
|
|
let confirmationMessage = '';
|
|
let amount = parseFloat(document.getElementById('external_transfer__amount').value.trim());
|
|
let receiverFloID = '';
|
|
let remarks = '';
|
|
let name = '';
|
|
switch (type) {
|
|
case 'exchange': {
|
|
confirmationMessage = `You are depositing ${amount} rupee tokens to exchange`;
|
|
receiverFloID = floGlobals.exchangeSink;
|
|
name = 'RanchiMall Exchange';
|
|
} break;
|
|
case 'btc-bonds': {
|
|
receiverFloID = 'FBBstZ2GretgQqDP55yt8iVd4KNZkdvEzH';
|
|
confirmationMessage = `You are transferring ${amount} rupee tokens to blockchain bond at FLO address ${receiverFloID}`;
|
|
remarks = '|blockchain-bond';
|
|
name = 'Blockchain Bonds';
|
|
} break;
|
|
case 'bobs-fund': {
|
|
receiverFloID = 'FFXy5pJnfzu2fCDLhpUremyXQjGtFpgCDN';
|
|
confirmationMessage = `You are transferring ${amount} rupee tokens to Bob's fund at FLO address ${receiverFloID}`;
|
|
remarks = '|bobs-fund';
|
|
name = `Bob's Fund`;
|
|
} break;
|
|
}
|
|
getConfirmation('Continue?', { message: confirmationMessage, confirmText: 'Transfer' }).then(confirmation => {
|
|
if (confirmation) {
|
|
floDapps.user.private.then(async privateKey => {
|
|
if (!privateKey) return;
|
|
try {
|
|
buttonLoader('external_transfer__button', true);
|
|
let result
|
|
if (type === 'btc-bonds' || type === 'bobs-fund') {
|
|
result = await User.sendToken(receiverFloID, amount, remarks)
|
|
document.getElementById('external_transfer__link').href = `${floBlockchainAPI.current_server}tx/${result}`;
|
|
} else if (type === 'exchange') {
|
|
result = await floExchangeAPI.depositToken('rupee', amount, floGlobals.myFloID, receiverFloID, privateKey)
|
|
}
|
|
console.log(result);
|
|
showChildElement('external_transfer_process', 1);
|
|
document.getElementById('external_transfer__success_message').textContent = `Transferred ${formatAmount(amount)} to ${name}`;
|
|
} catch (error) {
|
|
let errorText = error;
|
|
console.log(error);
|
|
if (error.code) {
|
|
errorText = error.message;
|
|
}
|
|
if (error === 'Insufficient rupee# balance')
|
|
errorText = 'Insufficient rupee token balance in your wallet, please top-up your wallet.';
|
|
document.getElementById('external_transfer__failed_reason').textContent = errorText;
|
|
showChildElement('external_transfer_process', 2);
|
|
} finally {
|
|
buttonLoader('external_transfer__button', false);
|
|
}
|
|
}).catch(error => {
|
|
console.log(error);
|
|
notify('Invalid password', 'error');
|
|
closePopup();
|
|
return false;
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
getRef('wallet_section__asset_selector').addEventListener('change', (e) => {
|
|
if (e.target.value === 'rupee') {
|
|
showChildElement('asset_actions_wrapper', 0, { entry: slideInRight, exit: slideOutRight });
|
|
} else {
|
|
showChildElement('asset_actions_wrapper', 1, { entry: slideInLeft, exit: slideOutLeft });
|
|
}
|
|
})
|
|
|
|
async function renderSavedUpiIds(target) {
|
|
let savedUpiIds = []
|
|
for (const upiId in floGlobals.savedUserData.upiIds) {
|
|
savedUpiIds.push(render.savedUpiId(upiId))
|
|
}
|
|
renderElem(document.getElementById(target), html`${savedUpiIds}`)
|
|
}
|
|
function saveUpiId() {
|
|
const upiId = getRef('get_upi_id').value.trim();
|
|
if (upiId === '')
|
|
return notify("Please add an UPI ID to continue", 'error');
|
|
if (floGlobals.savedUserData.upiIds.hasOwnProperty(upiId))
|
|
return notify('This UPI ID is already saved', 'error');
|
|
floGlobals.savedUserData.upiIds[upiId] = {}
|
|
syncUserData('savedUserData', floGlobals.savedUserData).then(() => {
|
|
notify(`Saved ${upiId}`, 'success');
|
|
if (popupStack.items.find(item => item.popup.id === 'profile_popup')) {
|
|
renderSavedUpiIds('saved_upi_ids_list')
|
|
} else if (appState.currentPage === 'home') {
|
|
render.savedUpiIdOptions('select_withdraw_upi_id')
|
|
getRef('select_withdraw_upi_id').parentNode.classList.remove('hidden')
|
|
}
|
|
closePopup();
|
|
}).catch(error => {
|
|
notify(error, 'error');
|
|
})
|
|
}
|
|
function deleteSavedUpi(e) {
|
|
const upiId = e.target.closest('.saved-upi').dataset.upiId;
|
|
getConfirmation('Do you want delete this UPI ID?', {
|
|
confirmText: 'Delete',
|
|
}).then(res => {
|
|
if (res) {
|
|
delete floGlobals.savedUserData.upiIds[upiId];
|
|
renderSavedUpiIds('saved_upi_ids_list');
|
|
syncUserData('savedUserData', floGlobals.savedUserData).then(() => {
|
|
notify(`Deleted UPI ID`, 'success');
|
|
}).catch(error => {
|
|
notify(error, 'error');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
userUI.renderCashierRequests = function (requests, error = null) {
|
|
if (error)
|
|
return console.error(error);
|
|
else if (typeof requests !== "object" || requests === null)
|
|
return;
|
|
let processedRequests = 0;
|
|
for (let transactionID in requests) {
|
|
const { message: { amount, mode }, note, tag } = requests[transactionID];
|
|
let status = tag ? 'completed' : (note ? 'rejected' : "pending");
|
|
// console.log(requests[transactionID])
|
|
if (status !== 'pending') {
|
|
processedRequests++;
|
|
}
|
|
if (appState.lastPage === 'wallet') {
|
|
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]))
|
|
}
|
|
if (floGlobals.loaded && status !== 'pending') {
|
|
const { message: { amount, mode }, note, tag } = requests[transactionID];
|
|
notify(`Your ${mode === 'cash-to-token' ? 'top-up' : 'withdraw'} request of ${formatAmount(amount)} has been ${status}`, status === 'completed' ? 'success' : 'error', {
|
|
action: {
|
|
label: 'View',
|
|
callback: () => {
|
|
window.location.hash = `#/wallet`
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (appState.lastPage !== 'wallet') {
|
|
if (processedRequests === 0)
|
|
removeNotificationBadge('wallet_history_button');
|
|
else {
|
|
addNotificationBadge('wallet_history_button', processedRequests)
|
|
}
|
|
}
|
|
};
|
|
|
|
userUI.renderMoneyRequests = function (requests, error = null) {
|
|
if (error)
|
|
return console.error(error);
|
|
else if (typeof requests !== "object" || requests === null)
|
|
return;
|
|
if (appState.currentPage === 'requests') {
|
|
for (let r in requests) {
|
|
getRef('payment_request_history').querySelectorAll(`[data-vc="${r}"]`).forEach(card => card.remove());
|
|
getRef('payment_request_history').prepend(render.paymentRequestCard(requests[r]));
|
|
}
|
|
}
|
|
if (appState.currentPage === 'home') {
|
|
for (let r in requests) {
|
|
getRef('pending_payment_requests').querySelectorAll(`[data-vc="${r}"]`).forEach(card => card.remove());
|
|
}
|
|
}
|
|
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`
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
let totalRequests = 0;
|
|
for (const request in User.moneyRequests) {
|
|
if (!User.moneyRequests[request].note) totalRequests++;
|
|
}
|
|
if (totalRequests) {
|
|
addNotificationBadge('requests_notification_button', totalRequests)
|
|
} else {
|
|
removeNotificationBadge('requests_notification_button')
|
|
}
|
|
};
|
|
const pendingTransactionsObserver = new MutationObserver((mutations) => {
|
|
mutations.forEach(mutation => {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.target.children.length)
|
|
mutation.target.parentNode.classList.remove('hidden')
|
|
else
|
|
mutation.target.parentNode.classList.add('hidden')
|
|
}
|
|
})
|
|
});
|
|
|
|
function toggleNotificationPanel() {
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
if (getRef('notifications_panel').classList.contains('hidden')) {
|
|
const pendingPaymentRequests = document.createDocumentFragment();
|
|
for (const transactionId in User.moneyRequests) {
|
|
if (!User.moneyRequests[transactionId].note) {
|
|
pendingPaymentRequests.prepend(render.paymentRequestCard(User.moneyRequests[transactionId]))
|
|
}
|
|
}
|
|
getRef('pending_payment_requests').innerHTML = ''
|
|
getRef('pending_payment_requests').append(pendingPaymentRequests)
|
|
|
|
getRef('notifications_panel').classList.remove('hidden')
|
|
if (floGlobals.isMobileView) {
|
|
getRef('notifications_panel').animate(slideInLeft, animOptions)
|
|
} else {
|
|
getRef('notifications_panel').animate(slideInDown, animOptions).onfinish = () => {
|
|
document.addEventListener('click', detectOutsideClick)
|
|
}
|
|
}
|
|
document.body.style.overflow = 'hidden';
|
|
document.body.style.top = `-${window.scrollY}px`;
|
|
getRef('home').style.filter = `blur(0.5rem)`
|
|
getRef('home').inert = true;
|
|
} else {
|
|
closeNotificationPanel()
|
|
}
|
|
}
|
|
function detectOutsideClick(e) {
|
|
if (e.target.closest('#notifications_panel')) return;
|
|
closeNotificationPanel();
|
|
document.removeEventListener('click', detectOutsideClick);
|
|
|
|
}
|
|
function closeNotificationPanel() {
|
|
if (getRef('notifications_panel').classList.contains('hidden')) return;
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
if (floGlobals.isMobileView) {
|
|
getRef('notifications_panel').animate(slideOutRight, animOptions).onfinish = () => {
|
|
getRef('notifications_panel').classList.add('hidden')
|
|
}
|
|
} else {
|
|
getRef('notifications_panel').animate(slideOutUp, animOptions).onfinish = () => {
|
|
getRef('notifications_panel').classList.add('hidden')
|
|
}
|
|
}
|
|
const scrollY = document.body.style.top;
|
|
window.scrollTo(0, parseInt(scrollY || '0') * -1);
|
|
document.body.style.overflow = '';
|
|
document.body.style.top = 'initial';
|
|
document.removeEventListener('click', closeNotificationPanel)
|
|
getRef('home').style.filter = ``
|
|
getRef('home').inert = false;
|
|
}
|
|
|
|
function addNotificationBadge(elem, text) {
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
}
|
|
if (!getRef(elem).querySelector('.badge')) {
|
|
const badge = createElement('span', {
|
|
className: 'badge',
|
|
textContent: text
|
|
})
|
|
getRef(elem).append(badge)
|
|
badge.animate([
|
|
{
|
|
transform: 'scale(0) translateY(0.5rem)'
|
|
},
|
|
{
|
|
transform: 'scale(1) translateY(0)'
|
|
},
|
|
], animOptions)
|
|
} else {
|
|
const badge = getRef(elem).querySelector('.badge');
|
|
badge.textContent = text;
|
|
badge.animate([
|
|
{ transform: 'scale(1)' },
|
|
{ transform: `scale(1.5)` },
|
|
{ transform: 'scale(1)' }
|
|
], animOptions)
|
|
}
|
|
}
|
|
|
|
function removeNotificationBadge(elem) {
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
}
|
|
if (getRef(elem).querySelector('.badge')) {
|
|
const badge = getRef(elem).querySelector('.badge')
|
|
badge.animate([
|
|
{
|
|
transform: 'scale(1) translateY(0)'
|
|
},
|
|
{
|
|
transform: 'scale(0) translateY(0.5rem)'
|
|
},
|
|
], animOptions).onfinish = () => {
|
|
badge.remove()
|
|
}
|
|
}
|
|
}
|
|
|
|
userUI.payRequest = function (reqID) {
|
|
let { message: { amount, remark }, senderID } = User.moneyRequests[reqID];
|
|
getConfirmation('Pay?', { message: `Do you want to pay ${amount} to ${senderID}?`, confirmText: 'Pay' }).then(confirmation => {
|
|
if (confirmation) {
|
|
User.sendToken(senderID, amount, "|" + remark).then(txid => {
|
|
console.warn(`Sent ${amount} to ${senderID}`, txid);
|
|
notify(`Sent ${formatAmount(amount)} to ${getFloIdTitle(senderID)}. It may take a few mins to reflect in their wallet`, 'success');
|
|
User.decideRequest(User.moneyRequests[reqID], '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?', { confirmText: 'Decline' }).then(confirmation => {
|
|
if (confirmation) {
|
|
User.decideRequest(request, "DECLINED").then(result => {
|
|
console.log(result);
|
|
notify("Request declined", 'success');
|
|
}).catch(error => console.error(error))
|
|
}
|
|
})
|
|
}
|
|
|
|
delegate(getRef('pending_payment_requests'), 'click', '.pay-requested', e => {
|
|
const vectorClock = e.target.closest('.payment-request').dataset.vc;
|
|
userUI.payRequest(vectorClock);
|
|
})
|
|
delegate(getRef('pending_payment_requests'), 'click', '.decline-payment', e => {
|
|
const vectorClock = e.target.closest('.payment-request').dataset.vc;
|
|
userUI.declineRequest(vectorClock);
|
|
})
|
|
|
|
//Cashier
|
|
const cashierUI = {};
|
|
|
|
cashierUI.renderRequests = function (requests, error = null) {
|
|
if (error)
|
|
return console.error(error);
|
|
else if (typeof requests !== "object" || requests === null)
|
|
return;
|
|
for (let transactionID in requests) {
|
|
const { note, tag } = requests[transactionID];
|
|
let status = tag ? 'done' : (note ? 'failed' : "pending");
|
|
getRef('cashier_requests_wrapper').querySelectorAll(`[data-vc="${transactionID}"]`).forEach(card => card.remove());
|
|
getRef(status === 'pending' ? 'cashier_pending_request_list' : 'cashier_processed_request_list').prepend(render.cashierRequestCard(requests[transactionID]))
|
|
}
|
|
}
|
|
|
|
cashierUI.completeRequest = function (reqID) {
|
|
floGlobals.cashierProcessingRequest = Cashier.Requests[reqID];
|
|
const { message: { mode } } = floGlobals.cashierProcessingRequest;
|
|
if (mode === "cash-to-token")
|
|
completeCashToTokenRequest(floGlobals.cashierProcessingRequest);
|
|
else if (mode === "token-to-cash")
|
|
completeTokenToCashRequest(floGlobals.cashierProcessingRequest);
|
|
}
|
|
|
|
function completeCashToTokenRequest(request) {
|
|
const { message: { upi_txid, amount, upiID, txCode }, vectorClock } = request;
|
|
getRef('top_up_amount').textContent = formatAmount(amount);
|
|
getRef('top_up__code').value = txCode;
|
|
openPopup('confirm_topup_popup');
|
|
// 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;
|
|
// getRef('top_up__code').value = txCode;
|
|
// openPopup('confirm_topup_popup');
|
|
// }).catch(error => {
|
|
// notify(Array.isArray(error) ? error[1]: error, '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:', vectorClock);
|
|
// }).catch(error => console.error(error))
|
|
// })
|
|
}
|
|
|
|
function confirmTopUp(button) {
|
|
const { message: { amount }, vectorClock, senderID } = floGlobals.cashierProcessingRequest;
|
|
var tokenAmt = amount;
|
|
buttonLoader(button, true);
|
|
floBlockchainAPI.getBalance(senderID).then(async user_balance => {
|
|
let floAmt = floGlobals.sendAmt;
|
|
if (user_balance < floGlobals.settings.user_flo_threshold) {
|
|
let cur_rate = (await floExchangeAPI.getRates("FLO")).rate;
|
|
floAmt = floGlobals.settings.send_user_flo;
|
|
tokenAmt -= cur_rate * floAmt;
|
|
}
|
|
User.sendToken(senderID, tokenAmt, 'for cash-to-token', { sendAmt: floAmt }).then(txid => {
|
|
console.warn(`${amount} (${tokenAmt}|${floAmt}) cash-to-token for ${senderID}`, txid);
|
|
let note = txid + (tokenAmt === amount ? '' : `#${tokenAmt}`);
|
|
Cashier.finishRequest(floGlobals.cashierProcessingRequest, note).then(result => {
|
|
console.log(result);
|
|
console.info('Completed cash-to-token request:', vectorClock);
|
|
notify("Completed request", 'success');
|
|
closePopup()
|
|
}).catch(error => console.error(error))
|
|
.finally(() => buttonLoader(button, false));
|
|
}).catch(error => {
|
|
console.error(error)
|
|
buttonLoader(button, false);
|
|
})
|
|
}).catch(error => {
|
|
buttonLoader(button, false);
|
|
console.error(error)
|
|
})
|
|
}
|
|
|
|
getRef('top_up__reason_selector').addEventListener('change', e => {
|
|
console.log(e.target.value);
|
|
if (e.target.value === 'other') {
|
|
getRef('top_up__specified_reason').parentNode.classList.remove('hidden');
|
|
} else {
|
|
getRef('top_up__specified_reason').parentNode.classList.add('hidden');
|
|
}
|
|
})
|
|
function declineTopUp() {
|
|
const { vectorClock } = floGlobals.cashierProcessingRequest;
|
|
let reason = getRef('top_up__reason_selector').value;
|
|
if (reason === 'other') {
|
|
reason = getRef('top_up__specified_reason').value
|
|
}
|
|
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('Rejected top-up request', 'success');
|
|
closePopup()
|
|
}).catch(error => console.error(error)).finally(() => buttonLoader('decline_button', false));
|
|
}
|
|
|
|
|
|
async function completeTokenToCashRequest(request) {
|
|
const { vectorClock, senderID, message: { token_txid, amount, upi_id } } = request;
|
|
let upiID;
|
|
if (upi_id instanceof Object && "secret" in upi_id) {
|
|
try {
|
|
upiID = floDapps.user.decrypt(upi_id);
|
|
} catch (error) {
|
|
console.error("UPI ID is not encrypted with a proper key", error);
|
|
return notify("UPI ID is not encrypted with a proper key", 'error');
|
|
}
|
|
} else
|
|
upiID = upi_id;
|
|
Cashier.checkIfTokenTxIsValid(token_txid, senderID, amount).then(result => {
|
|
getPromptInput('Process', `Token transfer is verified!\n Send ${formatAmount(amount)}\n to ${upiID}\n Enter UPI transaction ID`, {
|
|
placeholder: 'UPI transaction ID',
|
|
}).then(upiTxID => {
|
|
if (!upiTxID || upiTxID.length < 10)
|
|
return notify("Invalid UPI txid", 'error');
|
|
Cashier.finishRequest(request, upiTxID).then(result => {
|
|
console.log(result);
|
|
console.info('Completed token-to-cash request:', vectorClock);
|
|
notify("Completed request", 'success');
|
|
}).catch(error => console.error(error))
|
|
})
|
|
}).catch(error => {
|
|
notify(error, '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:', vectorClock);
|
|
}).catch(error => console.error(error))
|
|
})
|
|
}
|
|
|
|
function getFloIdTitle(floID) {
|
|
return floGlobals.contacts[floID] || floID;
|
|
}
|
|
|
|
function formatAmount(amount = 0, currency = 'inr') {
|
|
if (!amount)
|
|
return '0';
|
|
const formattingOptions = {
|
|
currency,
|
|
style: 'currency'
|
|
}
|
|
if (currency === 'btc') {
|
|
formattingOptions.maximumFractionDigits = 8;
|
|
}
|
|
return amount.toLocaleString(currency === 'inr' ? `en-IN` : 'en-US', formattingOptions)
|
|
}
|
|
function copyToClipboard(textToCopy) {
|
|
navigator.clipboard.writeText(textToCopy)
|
|
.then(res => {
|
|
notify('Copied to clipboard', 'success');
|
|
})
|
|
.catch(err => console.error(err));
|
|
}
|
|
function copyTxId(e) {
|
|
copyToClipboard(e.target.closest('li').dataset.txid);
|
|
}
|
|
|
|
const cashierRejectionErrors = {
|
|
1001: `Your request was reject because of wrong transaction ID. If you have sent money, it'll be returned within 24 hrs.`,
|
|
1002: `Amount requested and amount sent via UPI doesn't match. your transferred money will be returned within 24hrs.`,
|
|
1003: `Your request was rejected because of wrong or missing remark/message code. If you have sent money, it'll be returned within 24 hrs.`,
|
|
1004: `Your request was rejected because specified amount wasn't received by the cashier.`,
|
|
}
|
|
|
|
const render = {
|
|
savedId(floID, title, ref) {
|
|
return html.for(ref, floID)`
|
|
<li class="saved-id grid interact" tabindex="0" data-flo-id="${floID}">
|
|
<div class="saved-id__initials">${title.charAt(0)}</div>
|
|
<div class="flex gap-1 space-between">
|
|
<div class="flex align-center flex-wrap gap-0-3">
|
|
<div class="saved-id__title">${title}</div>
|
|
<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 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
|
</div>
|
|
<button class="edit-saved icon-only" title="More options">
|
|
<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="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="grid align-center" style="grid-template-columns: 1fr auto;">
|
|
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
|
<button class="saved-id__copy margin-left-0-5" title="Copy FLO address"> copy </button>
|
|
</div>
|
|
</li>
|
|
`;
|
|
},
|
|
rupeeTxCard(transactionDetails) {
|
|
const { txid, time, sender, receiver, tokenAmount } = transactionDetails;
|
|
let transactionReceiver
|
|
let className
|
|
let icon
|
|
if (sender === floGlobals.myFloID) {
|
|
className = 'transaction grid sent'
|
|
transactionReceiver = `Sent to ${getFloIdTitle(receiver) || 'Myself'}`;
|
|
icon = svg`<svg class="icon icon--tx-type sent" 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="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
|
} else if (receiver === floGlobals.myFloID) {
|
|
className = 'transaction grid received'
|
|
transactionReceiver = `Received from ${getFloIdTitle(sender)}`;
|
|
icon = svg`<svg class="icon icon--tx-type" 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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
|
} else { //This should not happen unless API returns transaction that does not involve floGlobals.myFloID
|
|
return html`sender: ${sender} | receiver: ${receiver}`;
|
|
}
|
|
return html.node`
|
|
<li class="${className}" data-txid="${txid}">
|
|
<div class="transaction__icon">
|
|
<svg class="icon icon--rupee" 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>
|
|
${icon}
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<div class="flex align-center space-between gap-1">
|
|
<div class="grid gap-0-5">
|
|
<div class="transaction__receiver wrap-around">${transactionReceiver}</div>
|
|
<time class="transaction__time">${getFormattedTime(time * 1000)}</time>
|
|
</div>
|
|
<div class="transaction__amount">${formatAmount(tokenAmount)}</div>
|
|
</div>
|
|
<div class="flex align-center gap-0-5">
|
|
<a class="button button--small" target="_blank" href=${`${floBlockchainAPI.current_server}tx/${txid}`}>View details</a>
|
|
<button class="button button--small" onclick=${copyTxId}>Copy ID</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
`;
|
|
},
|
|
cashierRequestCard(details) {
|
|
const { time, senderID, message: { mode, amount = 0 }, note, tag, vectorClock } = details;
|
|
const clone = getRef('cashier_request_template').content.cloneNode(true).firstElementChild;
|
|
clone.dataset.vc = vectorClock;
|
|
const status = tag || note; //status tag for completed, note for rejected
|
|
clone.querySelector('.cashier-request__details').textContent = `${mode === 'cash-to-token' ? 'Top-up wallet with' : 'Withdraw'} ${formatAmount(amount)}`;
|
|
clone.querySelector('.cashier-request__requestor').textContent = senderID;
|
|
clone.querySelector('.cashier-request__time').textContent = getFormattedTime(time);
|
|
clone.querySelector('.cashier-request__mode').innerHTML = mode === 'token-to-cash' ? `<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"></rect> </g> <g> <g> <rect height="7" width="3" x="4" y="10"></rect> <rect height="7" width="3" x="10.5" y="10"></rect> <rect height="3" width="20" x="2" y="19"></rect> <rect height="7" width="3" x="17" y="10"></rect> <polygon points="12,1 2,6 2,8 22,8 22,6"></polygon> </g> </g> </svg>`
|
|
:
|
|
`<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><path d="M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></svg>`;
|
|
if (status)
|
|
clone.querySelector('.cashier-request__status').textContent = status.includes(':') ? status.split(':')[0] : status;
|
|
else
|
|
clone.querySelector('.cashier-request__status').innerHTML = `<button class="button process-cashier-request">Process</button>`;
|
|
return clone;
|
|
},
|
|
walletRequestCard(details) {
|
|
const { time, message: { mode, amount }, note, tag, vectorClock, senderID } = details;
|
|
// return empty text node if not my request
|
|
if (senderID !== floGlobals.myFloID) return document.createTextNode('');
|
|
const clone = getRef('wallet_request_template').content.cloneNode(true).firstElementChild.firstElementChild;
|
|
const type = mode === 'cash-to-token' ? 'Top-up' : 'Withdraw';
|
|
let status = tag ? tag : (note ? 'REJECTED' : "PENDING");
|
|
clone.classList.add(status.toLowerCase());
|
|
clone.classList.add(mode === 'cash-to-token' ? 'added' : 'withdrawn');
|
|
clone.dataset.vc = vectorClock;
|
|
clone.href = `#/transaction?transactionId=${vectorClock}&type=wallet`;
|
|
clone.querySelector('.wallet-request__icon').innerHTML = mode === 'cash-to-token' ?
|
|
`<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="M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" /></svg>`
|
|
:
|
|
`<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><g><rect height="7" width="3" x="4" y="10" /><rect height="7" width="3" x="10.5" y="10" /><rect height="3" width="20" x="2" y="19" /><rect height="7" width="3" x="17" y="10" /><polygon points="12,1 2,6 2,8 22,8 22,6" /></g></g></svg>`;
|
|
clone.querySelector('.wallet-request__details').textContent = type;
|
|
clone.querySelector('.wallet-request__amount').textContent = formatAmount(amount);
|
|
clone.querySelector('.wallet-request__time').textContent = getFormattedTime(time);
|
|
let icon = '';
|
|
if (status === 'REJECTED') {
|
|
icon = `<svg class="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>`
|
|
clone.querySelector('.wallet-request__status').innerHTML = `Failed ${icon}`;
|
|
clone.querySelector('.wallet-request__status').classList.add('capitalize')
|
|
}
|
|
if (status === 'COMPLETED' && note && note.includes('#')) {
|
|
const [txid, finalAmount] = note.split('#');
|
|
clone.querySelector('.wallet-request__amount').textContent = formatAmount(parseFloat(finalAmount));
|
|
clone.querySelector('.wallet-request__status').innerHTML = `<b style="color:var(--green)">+ 1 FLO</b> worth ${formatAmount(amount - parseFloat(finalAmount))}`;
|
|
}
|
|
return clone;
|
|
},
|
|
paymentRequestCard(details) {
|
|
const { time, senderID, message: { amount, remark }, note, vectorClock } = details;
|
|
const clone = getRef(`${note ? 'processed' : 'pending'}_payment_request_template`).content.cloneNode(true).firstElementChild;
|
|
clone.dataset.vc = vectorClock;
|
|
clone.querySelector('.payment-request__requestor').textContent = getFloIdTitle(senderID);
|
|
clone.querySelector('.payment-request__remark').textContent = remark;
|
|
clone.querySelector('.payment-request__time').textContent = getFormattedTime(time);
|
|
clone.querySelector('.payment-request__amount').textContent = amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' });
|
|
const status = note ? note.split(':')[0] : 'PENDING';
|
|
if (note) {
|
|
clone.firstElementChild.href = `#/transaction?transactionId=${vectorClock}&type=request`;
|
|
let icon
|
|
if (status === 'PAID')
|
|
icon = `<svg class="icon paid" 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="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M10.09,16.72l-3.8-3.81l1.48-1.48l2.32,2.33 l5.85-5.87l1.48,1.48L10.09,16.72z"/></g></svg>`
|
|
else
|
|
icon = `<svg class="icon declined" 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 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/></svg>`
|
|
clone.querySelector('.payment-request__status').innerHTML = `${status.toLowerCase()} ${icon}`;
|
|
}
|
|
return clone;
|
|
},
|
|
transactionMessage(details) {
|
|
const { tokenAmount, time, sender, receiver, flodata } = floTokenAPI.util.parseTxData(details)
|
|
let messageType = sender === receiver ? 'self' : sender === floGlobals.myFloID ? 'sent' : 'received';
|
|
const clone = getRef('transaction_message_template').content.cloneNode(true).firstElementChild;
|
|
clone.classList.add(messageType);
|
|
clone.querySelector('.transaction-message__amount').textContent = formatAmount(tokenAmount);
|
|
if (flodata.split('|')[1]) {
|
|
clone.querySelector('.transaction-message__remark').textContent = flodata.split('|')[1];
|
|
}
|
|
clone.querySelector('.transaction-message__time').textContent = getFormattedTime(time * 1000);
|
|
return clone;
|
|
},
|
|
savedUpiId(upiId) {
|
|
return html`
|
|
<li class="saved-upi" data-upi-id="${upiId}">
|
|
<div class="saved-upi__id">${upiId}</div>
|
|
<button class="delete-upi" title="Delete this UPI ID" onclick=${deleteSavedUpi}>
|
|
<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="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" /> </svg>
|
|
</button>
|
|
</li>
|
|
`;
|
|
},
|
|
savedIdPickerCard(floID, title) {
|
|
return html`
|
|
<li class="saved-id grid interact" tabindex="0" data-flo-id="${floID}">
|
|
<div class="saved-id__initials">${title[0]}</div>
|
|
<div class="grid gap-0-5">
|
|
<h4 class="saved-id__title">${title}</h4>
|
|
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
|
</div>
|
|
</li>`
|
|
},
|
|
savedUpiIdOptions(target) {
|
|
let savedIds = []
|
|
for (const upiId in floGlobals.savedUserData.upiIds) {
|
|
savedIds.push(html`<sm-option value=${upiId}>${upiId}</sm-option>`)
|
|
}
|
|
renderElem(document.getElementById(target), html`${savedIds}`)
|
|
},
|
|
btcTxCard(transactionDetails) {
|
|
let { amount, time, txid, sender, receiver, type, block } = transactionDetails;
|
|
let transactionReceiver
|
|
let icon
|
|
if (type === 'out') {
|
|
transactionReceiver = `Sent to ${receiver}`;
|
|
icon = svg`<svg class="icon icon--tx-type sent" 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="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
|
} else if (type === 'in') {
|
|
transactionReceiver = `Received from ${sender}`;
|
|
icon = svg`<svg class="icon icon--tx-type" 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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
|
} else if (type === 'self') {
|
|
transactionReceiver = `Sent to self`;
|
|
icon = svg`<svg class="icon icon--tx-type" 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="M18.65 8.35l-2.79 2.79c-.32.32-.1.86.35.86H18c0 3.31-2.69 6-6 6-.79 0-1.56-.15-2.25-.44-.36-.15-.77-.04-1.04.23-.51.51-.33 1.37.34 1.64.91.37 1.91.57 2.95.57 4.42 0 8-3.58 8-8h1.79c.45 0 .67-.54.35-.85l-2.79-2.79c-.19-.2-.51-.2-.7-.01zM6 12c0-3.31 2.69-6 6-6 .79 0 1.56.15 2.25.44.36.15.77.04 1.04-.23.51-.51.33-1.37-.34-1.64C14.04 4.2 13.04 4 12 4c-4.42 0-8 3.58-8 8H2.21c-.45 0-.67.54-.35.85l2.79 2.79c.2.2.51.2.71 0l2.79-2.79c.31-.31.09-.85-.36-.85H6z"/></svg>`;
|
|
}
|
|
const className = `btc-tx ${type} ${block === null ? 'unconfirmed-tx' : ''}`
|
|
return html.node`
|
|
<li class="${className}" data-txid="${txid}">
|
|
<div class="btc-tx__icon">
|
|
<svg class="icon icon--btc" 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="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"/></g></svg>
|
|
${icon}
|
|
</div>
|
|
<div class="grid gap-0-5 flex-1">
|
|
<div class="flex align-center space-between gap-1">
|
|
<div class="grid gap-0-5 flex-1">
|
|
<div class="btc-tx__receiver wrap-around">${transactionReceiver}</div>
|
|
<time class="btc-tx__time">${getFormattedTime(time)}</time>
|
|
</div>
|
|
<div class="btc-tx__amount amount-shown" data-btc-amount="${amount}">${formatAmount(amount, 'btc')}</div>
|
|
</div>
|
|
<div class="flex align-center gap-0-5">
|
|
<a class="button button--small" target="_blank" href=${`https://ranchimall.github.io/btcwallet/#/check_details?query=${txid}`}>View details</a>
|
|
<button class="button button--small" onclick=${copyTxId}>Copy ID</button>
|
|
</div>
|
|
${!block ? html`<p class="pending-badge">Confirmation pending</p>` : ''}
|
|
</div>
|
|
</li>
|
|
`;
|
|
},
|
|
async transactionsHistory() {
|
|
if (transactionsHistoryLoader)
|
|
transactionsHistoryLoader.clear()
|
|
getRef('payments_history').innerHTML = '<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';
|
|
getTransactionsHistory().then(transactions => {
|
|
if (transactions.length) {
|
|
if (transactionsHistoryLoader) {
|
|
transactionsHistoryLoader.update(transactions)
|
|
} else {
|
|
transactionsHistoryLoader = new LazyLoader('#payments_history', transactions, render.transactionCard)
|
|
}
|
|
transactionsHistoryLoader.init()
|
|
} else {
|
|
getRef('payments_history').textContent = 'No transactions found';
|
|
}
|
|
}).catch(error => {
|
|
getRef('payments_history').textContent = 'Unable to fetch transactions';
|
|
console.error(error)
|
|
})
|
|
},
|
|
recentTransactions() {
|
|
renderElem(getRef('recent_transactions'), html`<sm-spinner></sm-spinner>`)
|
|
getTransactionsHistory({ limit: 5 }).then(transactions => {
|
|
renderElem(getRef('recent_transactions'), html`${transactions.map(transaction => render.transactionCard(transaction))}`)
|
|
}).catch(e => {
|
|
console.error(e)
|
|
getRef('recent_transactions').parentNode.querySelector('a').classList.add('hidden')
|
|
renderElem(getRef('recent_transactions'), html`
|
|
<div class="grid gap-0-5">
|
|
<h4>Oops!</h4>
|
|
<p>Looks like we couldn't fetch your transactions at this time. Please try again later.</p>
|
|
</div>
|
|
`)
|
|
})
|
|
},
|
|
async savedIds() {
|
|
let target = 'saved_ids_list';
|
|
let savedIds = getArrayOfSavedIds();
|
|
if (appState.currentPage === 'home') {
|
|
target = 'recent_contacts';
|
|
savedIds = savedIds.slice(0, 5);
|
|
}
|
|
renderElem(getRef(target), html`${savedIds.map(({ floID, title }) => render.savedId(floID, title, getRef(target)))}`)
|
|
},
|
|
conditionalSteps() {
|
|
if (getRef('topup_wallet__qr_wrapper').open) {
|
|
getRef('topup_steps').querySelectorAll('.conditional').forEach(e => e.remove());
|
|
} else {
|
|
const amount = parseFloat(getRef('request_cashier_amount').value.trim());
|
|
if (getRef('topup_steps').children.length < 4) {
|
|
const conditionalSteps = getRef('conditional_steps_template').content.cloneNode(true);
|
|
conditionalSteps.querySelector('.amount').textContent = formatAmount(amount);
|
|
conditionalSteps.querySelector('.tx-code').setAttribute('value', floGlobals.txCode);
|
|
getRef('topup_steps').children[1].after(conditionalSteps)
|
|
} else {
|
|
getRef('topup_steps').querySelector('.amount').textContent = formatAmount(amount);
|
|
getRef('topup_steps').querySelector('.tx-code').setAttribute('value', floGlobals.txCode);
|
|
}
|
|
}
|
|
},
|
|
transactionCard(details) {
|
|
if (details.asset === 'rupee') {
|
|
return render.rupeeTxCard(details)
|
|
} else if (details.asset === 'btc') {
|
|
return render.btcTxCard(details)
|
|
}
|
|
},
|
|
appliedFilters() {
|
|
let appliedFiltersChips = []
|
|
for (const filterKey in floGlobals.appliedFilters) {
|
|
appliedFiltersChips.push(html`<button class="applied-filter" data-filter=${filterKey} data-value=${floGlobals.appliedFilters[filterKey]} title="Remove filter">
|
|
<span class="applied-filter__title">${floGlobals.appliedFilters[filterKey]}</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>`)
|
|
}
|
|
renderElem(getRef('history_applied_filters'), html`${appliedFiltersChips}`)
|
|
},
|
|
profile() {
|
|
return html`
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<h4>
|
|
BTC integrated with FLO
|
|
</h4>
|
|
<p>
|
|
You can use your FLO private key to perform transactions on the BTC network within our
|
|
app
|
|
ecosystem. The private key is the same for both.
|
|
</p>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<b>My FLO address</b>
|
|
<sm-copy class="user-flo-id" clip-text value=${floGlobals.myFloID}></sm-copy>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<b>My Bitcoin address</b>
|
|
<sm-copy class="user-btc-id" clip-text value=${floGlobals.myBtcID}></sm-copy>
|
|
</div>
|
|
<button class="button button--danger justify-self-start" onclick="signOut()">Sign out</button>
|
|
</div>
|
|
${!floGlobals.isPrivKeySecured ? html`
|
|
<div class="grid gap-1">
|
|
<div class="grid gap-0-5">
|
|
<h4>Secure private key</h4>
|
|
<p>
|
|
You can set a password to secure your private key and use the password instead of private key. This is applied to this browser only.
|
|
</p>
|
|
</div>
|
|
<button id="secure_pwd_button" class=${`button justify-self-start secure-priv-key`} onclick="openPopup('secure_pwd_popup')">Set password</button>
|
|
</div>
|
|
`: ''}
|
|
<div class="grid gap-1 user-element">
|
|
<h4>My UPI IDs</h4>
|
|
<ul id="saved_upi_ids_list" class="observe-empty-state"></ul>
|
|
<div class="empty-state" style="padding: 0">
|
|
<p>Add your UPI IDs for easier access during transactions.</p>
|
|
</div>
|
|
<button class="button justify-self-start" onclick="openPopup('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>
|
|
${floGlobals.isSubAdmin ? html`
|
|
<div class=" grid gap-1">
|
|
<div class="grid gap-0-5">
|
|
<h5>My UPI ID</h5>
|
|
${cashierUPI[myFloID] ? html`<sm-copy id="my_upi_id" value=${cashierUPI[myFloID]}></sm-copy>` : ''}
|
|
</div>
|
|
<button class="button button--small justify-self-start" onclick="openPopup('change_cashier_upi_popup')">${cashierUPI[myFloID] ? 'Change UPI ID' : 'Add UPI ID'}</button>
|
|
</div>
|
|
`: ''}
|
|
`;
|
|
}
|
|
};
|
|
|
|
function getTransactionsHistory(filters = floGlobals.appliedFilters || {}) {
|
|
const { asset = 'all', type = 'all', limit = 0 } = filters;
|
|
return new Promise((resolve, reject) => {
|
|
let promises = [];
|
|
if (asset === 'all')
|
|
promises = [getRupeeTransactions(type), getBtcTransactions(type)];
|
|
else if (asset === 'rupee')
|
|
promises = [getRupeeTransactions(type)];
|
|
else if (asset === 'btc')
|
|
promises = [getBtcTransactions(type)];
|
|
|
|
Promise.all(promises).then((transactions) => {
|
|
let allTransactions = [];
|
|
for (const txs of transactions) {
|
|
allTransactions = allTransactions.concat(txs)
|
|
}
|
|
allTransactions.sort((a, b) => b.time - a.time)
|
|
if (limit)
|
|
allTransactions = allTransactions.slice(0, limit)
|
|
resolve(allTransactions)
|
|
}).catch(error => {
|
|
reject(error)
|
|
})
|
|
})
|
|
}
|
|
function getRupeeTransactions(type = 'all') {
|
|
return new Promise((resolve, reject) => {
|
|
if (!floGlobals.myFloID) {
|
|
resolve([])
|
|
return;
|
|
}
|
|
floTokenAPI.getAllTxs(floGlobals.myFloID).then(({ transactions }) => {
|
|
let rupeeTransactions = [];
|
|
let shouldFilter = type !== 'all';
|
|
let propToCheck
|
|
if (shouldFilter)
|
|
propToCheck = type === 'sent' ? 'sender' : 'receiver';
|
|
for (const transactionId in transactions) {
|
|
const details = floTokenAPI.util.parseTxData(transactions[transactionId])
|
|
if (shouldFilter) {
|
|
if (details[propToCheck] !== floGlobals.myFloID)
|
|
continue;
|
|
}
|
|
rupeeTransactions.push({
|
|
...details,
|
|
txid: transactionId,
|
|
asset: 'rupee',
|
|
})
|
|
}
|
|
resolve(rupeeTransactions)
|
|
}).catch(e => {
|
|
reject(e)
|
|
})
|
|
})
|
|
}
|
|
function getBtcTransactions(type = 'all') {
|
|
return new Promise((resolve, reject) => {
|
|
if (!floGlobals.myBtcID) {
|
|
resolve([])
|
|
return;
|
|
}
|
|
btcOperator.getAddressData(floGlobals.myBtcID).then(({ txs }) => {
|
|
let propToCheck = false
|
|
if (type === 'sent')
|
|
propToCheck = 'out';
|
|
else if (type === 'received')
|
|
propToCheck = 'in';
|
|
const allTransactions = txs.filter(tx => {
|
|
tx.asset = 'btc';
|
|
if (propToCheck)
|
|
return (tx.type === propToCheck)
|
|
return true;
|
|
})
|
|
resolve(allTransactions)
|
|
}).catch(error => {
|
|
resolve([])
|
|
console.error(error)
|
|
})
|
|
// resolve(tempTransactions)
|
|
})
|
|
}
|
|
|
|
function buttonLoader(id, show) {
|
|
const button = typeof id === 'string' ? document.getElementById(id) : id;
|
|
button.disabled = show;
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
if (show) {
|
|
button.parentNode.append(createElement('sm-spinner'))
|
|
button.animate([
|
|
{
|
|
clipPath: 'circle(100%)',
|
|
},
|
|
{
|
|
clipPath: 'circle(0)',
|
|
},
|
|
], animOptions)
|
|
} else {
|
|
button.getAnimations().forEach(anim => anim.cancel())
|
|
const potentialTarget = button.parentNode.querySelector('sm-spinner')
|
|
if (potentialTarget) potentialTarget.remove();
|
|
}
|
|
}
|
|
async function refreshBalance(button) {
|
|
if (button)
|
|
buttonLoader(button, true)
|
|
floTokenAPI.getBalance(floGlobals.myFloID).then((balance = 0) => {
|
|
getRef('rupee_balance').textContent = formatAmount(balance)
|
|
if (button)
|
|
buttonLoader(button, false)
|
|
})
|
|
btcOperator.getBalance(floGlobals.myBtcID).then((btcBalance = 0) => {
|
|
getRef('btc_balance').textContent = formatAmount(btcBalance, 'btc')
|
|
})
|
|
try {
|
|
const [floBal, floRates] = await Promise.all([floBlockchainAPI.getBalance(floGlobals.myFloID), floExchangeAPI.getRates('FLO')])
|
|
getRef('flo_balance').textContent = formatAmount(floBal, 'flo')
|
|
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('hidden');
|
|
} else {
|
|
getRef('low_user_flo_warning').classList.add('hidden');
|
|
}
|
|
if (button)
|
|
buttonLoader(button, false)
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
|
|
function getArrayOfSavedIds() {
|
|
const arr = [];
|
|
for (const key in floGlobals.contacts) {
|
|
arr.push({
|
|
floID: key,
|
|
title: floGlobals.contacts[key]
|
|
});
|
|
}
|
|
return arr.sort((a, b) => a.title.localeCompare(b.title));
|
|
}
|
|
async function saveFloId() {
|
|
const floID = getRef('flo_id_to_save').value.trim();
|
|
if (floGlobals.contacts.hasOwnProperty(floID))
|
|
return notify('This FLO address is already saved', 'error');
|
|
const title = getRef('flo_id_title_to_save').value.trim();
|
|
buttonLoader('save_flo_id_button', true);
|
|
floDapps.storeContact(floID, title).then(() => {
|
|
render.savedIds()
|
|
notify(`Saved ${floID}`, 'success');
|
|
closePopup();
|
|
}).catch(error => {
|
|
notify(error, 'error');
|
|
}).finally(() => {
|
|
buttonLoader('save_flo_id_button', false);
|
|
})
|
|
}
|
|
delegate(getRef('saved_ids_list'), 'click', '.saved-id', handleSavedIdInteraction);
|
|
delegate(getRef('recent_contacts'), 'click', '.saved-id', handleSavedIdInteraction);
|
|
function handleSavedIdInteraction(e) {
|
|
if (e.target.closest('.edit-saved')) {
|
|
const target = e.target.closest('.saved-id');
|
|
getRef('edit_saved_id').setAttribute('value', target.dataset.floId);
|
|
getRef('get_new_title').value = getFloIdTitle(target.dataset.floId);
|
|
openPopup('edit_saved_popup');
|
|
} else if (e.target.closest('.saved-id__copy')) {
|
|
const target = e.target.closest('.saved-id');
|
|
navigator.clipboard.writeText(target.dataset.floId)
|
|
target.dispatchEvent(
|
|
new CustomEvent('copy', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
})
|
|
);
|
|
} else {
|
|
const target = e.target.closest('.saved-id');
|
|
window.location.hash = `#/contact?floId=${target.dataset.floId}`;
|
|
}
|
|
}
|
|
function saveIdChanges() {
|
|
const floID = getRef('edit_saved_id').value;
|
|
let title = getRef('get_new_title').value.trim();
|
|
if (title == '')
|
|
title = 'Unknown';
|
|
floDapps.storeContact(floID, title).then(() => {
|
|
render.savedIds()
|
|
closePopup();
|
|
}).catch(error => {
|
|
notify(error, 'error');
|
|
})
|
|
}
|
|
function deleteSavedId() {
|
|
getConfirmation('Do you want delete this FLO address?', {
|
|
confirmText: 'Delete',
|
|
}).then(res => {
|
|
if (res) {
|
|
const floID = getRef('edit_saved_id').value;
|
|
const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${floID}`);
|
|
if (toDelete)
|
|
toDelete.remove();
|
|
closePopup();
|
|
compactIDB.removeData('contacts', floID, floDapps.user.db_name).then(() => {
|
|
notify(`Deleted flo address`, 'success');
|
|
}).catch(error => {
|
|
notify(error, 'error');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
const savedIdsObserver = new MutationObserver((mutationList) => {
|
|
mutationList.forEach(mutation => {
|
|
conditionalClassToggle(getRef('saved_ids_tip'), 'hidden', !mutation.target.children.length);
|
|
})
|
|
})
|
|
|
|
savedIdsObserver.observe(getRef('saved_ids_list'), {
|
|
childList: true,
|
|
})
|
|
function insertElementAlphabetically(name, elementToInsert) {
|
|
const elementInserted = [...getRef('saved_ids_list').children].some(child => {
|
|
const floID = child.dataset.floId;
|
|
if (floGlobals.contacts[floID].localeCompare(name) > 0) {
|
|
child.before(elementToInsert)
|
|
return true
|
|
}
|
|
})
|
|
if (!elementInserted) {
|
|
getRef('saved_ids_list').append(elementToInsert)
|
|
}
|
|
}
|
|
|
|
getRef('search_saved_ids_picker').addEventListener('input', debounce(async e => {
|
|
const searchKey = e.target.value.trim();
|
|
let allSavedIds = getArrayOfSavedIds();
|
|
if (searchKey !== '') {
|
|
// search in all saved ids and filter.
|
|
allSavedIds = allSavedIds.filter(({ floID, title }) => {
|
|
return floID.toLowerCase().includes(searchKey.toLowerCase()) || title.toLowerCase().includes(searchKey.toLowerCase())
|
|
})
|
|
// sort by search key relevance and then by title.
|
|
allSavedIds = allSavedIds.sort((a, b) => {
|
|
const aRelevance = a.title.toLowerCase().includes(searchKey.toLowerCase()) ? 1 : 0;
|
|
const bRelevance = b.title.toLowerCase().includes(searchKey.toLowerCase()) ? 1 : 0;
|
|
if (aRelevance === bRelevance) {
|
|
return a.floID.localeCompare(b.floID)
|
|
}
|
|
return bRelevance - aRelevance;
|
|
})
|
|
}
|
|
renderElem(getRef('saved_ids_picker_list'), html`${allSavedIds.map(({ floID, title }) => render.savedIdPickerCard(floID, title))}`)
|
|
if (searchKey !== '') {
|
|
const potentialTarget = getRef('saved_ids_picker_list').firstElementChild
|
|
if (potentialTarget) {
|
|
potentialTarget.classList.add('highlight')
|
|
}
|
|
}
|
|
}, 100))
|
|
getRef('search_saved_ids_picker').addEventListener('keydown', e => {
|
|
if (e.key === 'Enter') {
|
|
const potentialTarget = getRef('saved_ids_picker_list').firstElementChild
|
|
if (potentialTarget) {
|
|
potentialTarget.click()
|
|
}
|
|
}
|
|
})
|
|
delegate(getRef('saved_ids_picker_list'), 'click', '.saved-id', e => {
|
|
getRef('token_transfer__receiver').value = e.delegateTarget.dataset.floId
|
|
getRef('token_transfer__receiver').focusIn()
|
|
closePopup()
|
|
})
|
|
|
|
let currentUserAction;
|
|
function showTokenTransfer(type) {
|
|
getRef('token_transfer__button').textContent = type;
|
|
currentUserAction = type;
|
|
if (type === 'send') {
|
|
getRef('token_transfer__title').textContent = 'Send rupee';
|
|
} else {
|
|
getRef('token_transfer__title').textContent = 'Request rupee';
|
|
}
|
|
if (appState.lastPage === 'contact') {
|
|
getRef('token_transfer__receiver').value = appState.params.floId;
|
|
getRef('token_transfer__receiver').readOnly = true;
|
|
getRef('token_transfer__receiver').querySelector('button').classList.add('hidden');
|
|
} else {
|
|
getRef('token_transfer__receiver').readOnly = false;
|
|
getRef('token_transfer__receiver').querySelector('button').classList.remove('hidden');
|
|
}
|
|
openPopup('token_transfer_popup');
|
|
if (appState.lastPage === 'contact') {
|
|
getRef('token_transfer__amount').focusIn();
|
|
}
|
|
}
|
|
|
|
|
|
userUI.sendMoneyToUser = function (floID, amount, remark) {
|
|
getConfirmation('Confirm', { message: `Do you want to send ${amount} to ${getFloIdTitle(floID)}?`, confirmText: 'send' }).then(confirmation => {
|
|
if (confirmation) {
|
|
buttonLoader('token_transfer__button', true);
|
|
User.sendToken(floID, amount, "|" + remark).then(txid => {
|
|
console.warn(`Sent ${amount} to ${floID}`, txid);
|
|
notify(`Sent ${amount} to ${getFloIdTitle(floID)}. It may take a few mins to reflect in their wallet`, 'success');
|
|
closePopup()
|
|
}).catch(error => notify(error, 'error'))
|
|
.finally(() => {
|
|
buttonLoader('token_transfer__button', false);
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
userUI.requestMoneyFromUser = function (floID, amount, remark) {
|
|
getConfirmation('Confirm', { message: `Do you want to request ${amount} from ${getFloIdTitle(floID)}?`, confirmText: 'request' }).then(confirmation => {
|
|
if (confirmation) {
|
|
buttonLoader('token_transfer__button', true);
|
|
User.requestToken(floID, amount, remark).then(result => {
|
|
console.log(`Requested ${amount} from ${floID}`, result);
|
|
notify(`Requested ${amount} from ${getFloIdTitle(floID)}`, 'success');
|
|
closePopup()
|
|
}).catch(error => notify(error, 'error'))
|
|
.finally(() => {
|
|
buttonLoader('token_transfer__button', false);
|
|
})
|
|
}
|
|
})
|
|
}
|
|
function executeUserAction() {
|
|
const floID = getRef('token_transfer__receiver').value.trim(),
|
|
amount = parseFloat(getRef('token_transfer__amount').value),
|
|
remark = getRef('token_transfer__remark').value.trim();
|
|
if (currentUserAction === 'send') {
|
|
userUI.sendMoneyToUser(floID, amount, remark);
|
|
} else {
|
|
userUI.requestMoneyFromUser(floID, amount, remark);
|
|
}
|
|
}
|
|
|
|
function toggleFilters() {
|
|
if (getRef('history_applied_filters').classList.contains('hidden') && getRef('history_applied_filters').children.length > 0) {
|
|
getRef('history_applied_filters').classList.remove('hidden')
|
|
} else if (!getRef('history_applied_filters').classList.contains('hidden') && getRef('history_applied_filters').children.length === 0) {
|
|
getRef('history_applied_filters').classList.add('hidden')
|
|
}
|
|
}
|
|
|
|
function applyPaymentsFilters() {
|
|
floGlobals.appliedFilters = {}
|
|
getRef('history_filters_container').querySelectorAll('input:checked').forEach(input => {
|
|
if (input.value !== 'all') {
|
|
floGlobals.appliedFilters[input.name] = input.value;
|
|
}
|
|
});
|
|
render.appliedFilters()
|
|
toggleFilters()
|
|
render.transactionsHistory()
|
|
closePopup()
|
|
}
|
|
function resetPaymentsFilters() {
|
|
getRef('history_filters_container').querySelectorAll('input[value="all"]').forEach(input => input.checked = true);
|
|
floGlobals.appliedFilters = {}
|
|
render.appliedFilters()
|
|
render.transactionsHistory()
|
|
closePopup()
|
|
toggleFilters()
|
|
}
|
|
|
|
delegate(getRef('history_applied_filters'), 'click', '.applied-filter', e => {
|
|
const filter = e.delegateTarget.dataset.filter
|
|
getRef('history_filters_container').querySelector(`input[name="${filter}"][value="all"]`).checked = true;
|
|
delete floGlobals.appliedFilters[filter]
|
|
render.appliedFilters()
|
|
render.transactionsHistory()
|
|
toggleFilters()
|
|
})
|
|
|
|
function changeUpi() {
|
|
const upiId = getRef('upi_id').value.trim();
|
|
Cashier.updateUPI(upiId).then(() => {
|
|
render.profile()
|
|
notify('UPI ID updated successfully', 'success');
|
|
closePopup()
|
|
}).catch(err => {
|
|
notify(err, 'error');
|
|
});
|
|
}
|
|
function getSignedIn(passwordType) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
console.log(floDapps.user.id)
|
|
getPromptInput('Enter password', '', {
|
|
isPassword: true,
|
|
}).then(password => {
|
|
if (password) {
|
|
resolve(password)
|
|
}
|
|
})
|
|
} catch (err) {
|
|
if (passwordType === 'PIN/Password') {
|
|
floGlobals.isPrivKeySecured = true;
|
|
getRef('private_key_field').removeAttribute('data-private-key');
|
|
getRef('private_key_field').setAttribute('placeholder', 'Password');
|
|
getRef('private_key_field').customValidation = null
|
|
} else {
|
|
floGlobals.isPrivKeySecured = false;
|
|
getRef('private_key_field').dataset.privateKey = ''
|
|
getRef('private_key_field').setAttribute('placeholder', 'FLO private key');
|
|
getRef('private_key_field').customValidation = floCrypto.getPubKeyHex;
|
|
}
|
|
if (window.location.hash.includes('sign_in') || window.location.hash.includes('sign_up')) {
|
|
routeTo(window.location.hash);
|
|
} else {
|
|
location.hash = `#/sign_in`;
|
|
}
|
|
getRef('sign_in_button').onclick = () => {
|
|
resolve(getRef('private_key_field').value.trim());
|
|
getRef('private_key_field').value = '';
|
|
routeTo('loading');
|
|
};
|
|
getRef('sign_up_button').onclick = () => {
|
|
resolve(getRef('keys_generator').keys.privKey);
|
|
getRef('keys_generator').clearKeys();
|
|
routeTo('loading');
|
|
};
|
|
}
|
|
});
|
|
}
|
|
function setSecurePassword() {
|
|
if (!floGlobals.isPrivKeySecured) {
|
|
const password = getRef('secure_pwd_input').value.trim();
|
|
floDapps.securePrivKey(password).then(() => {
|
|
floGlobals.isPrivKeySecured = true;
|
|
notify('Password set successfully', 'success');
|
|
closePopup();
|
|
}).catch(err => {
|
|
notify(err, 'error');
|
|
})
|
|
}
|
|
}
|
|
function signOut() {
|
|
getConfirmation('Sign out?', { message: 'You are about to sign out of the app, continue?', confirmText: 'Leave', cancelText: 'Stay' })
|
|
.then(async (res) => {
|
|
if (res) {
|
|
await floDapps.clearCredentials();
|
|
location.reload();
|
|
}
|
|
});
|
|
}
|
|
async function calculateBtcFees() {
|
|
const [senders, privKeys, receivers, amounts] = await getTransactionInputs().catch(e => {
|
|
console.error(e)
|
|
return
|
|
});
|
|
// if (!senders.length || !privKeys.length || !receivers.length || !amounts.length) return;
|
|
return btcOperator.createSignedTx(senders, privKeys, receivers, amounts)
|
|
}
|
|
const txParticipantsObserver = new MutationObserver(mutations => {
|
|
mutations.forEach(mutation => {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.addedNodes.length > 0 && mutation.target.children.length > 1) {
|
|
const removeButton = mutation.target.firstElementChild.querySelector('.remove-card')
|
|
if (!removeButton) {
|
|
const newRemoveButton = html.node`
|
|
<button class="remove-card button--small">
|
|
<svg class="icon margin-right-0-3" 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>
|
|
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
|
|
</svg>
|
|
Remove
|
|
</button>
|
|
`
|
|
mutation.target.firstElementChild.querySelector('.remove-card-wrapper').appendChild(newRemoveButton)
|
|
}
|
|
} else if (mutation.removedNodes.length > 0 && mutation.target.children.length === 1) {
|
|
const removeButton = mutation.target.firstElementChild.querySelector('.remove-card')
|
|
if (removeButton) {
|
|
removeButton.remove()
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
txParticipantsObserver.observe(getRef('receiver_container'), {
|
|
childList: true
|
|
})
|
|
let globalExchangeRate = {
|
|
btc: 1,
|
|
inr: 1
|
|
}
|
|
async function getExchangeRate() {
|
|
return new Promise((resolve, reject) => {
|
|
Promise.all(['usd', 'inr'].map(cur => fetch(`https://bitpay.com/api/rates/btc/${cur}`))).then(responses => {
|
|
Promise.all(responses.map(res => res.json())).then(rates => {
|
|
rates.forEach(rate => {
|
|
globalExchangeRate[rate.code.toLowerCase()] = rate.rate
|
|
})
|
|
globalExchangeRate.btc = 1
|
|
resolve(globalExchangeRate)
|
|
}).catch(err => console.log(err))
|
|
}).catch(err => console.log(err))
|
|
})
|
|
}
|
|
getRef('add_receiver').onclick = evt => {
|
|
let receiverCard = getRef('receiver_template').content.cloneNode(true)
|
|
if (!getRef('receiver_container').children.length) {
|
|
receiverCard.querySelector('.remove-card').remove()
|
|
}
|
|
receiverCard.querySelector('.currency-symbol').innerHTML = `<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"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg>`
|
|
getRef('receiver_container').appendChild(receiverCard);
|
|
getRef('receiver_container').querySelectorAll('sm-input[data-bc-address]').forEach(input => input.customValidation = btcOperator.validateAddress)
|
|
getRef('send_tx')._checkValidity()
|
|
}
|
|
delegate(getRef('receiver_container'), 'click', '.remove-card', e => {
|
|
e.target.closest('.receiver-card').remove()
|
|
getRef('send_tx')._checkValidity()
|
|
})
|
|
|
|
getRef('fees_selector').addEventListener('change', e => {
|
|
switch (e.target.value) {
|
|
case 'custom':
|
|
getRef('send_fee').readOnly = false;
|
|
getRef('send_fee').placeholder = 'Fee';
|
|
renderElem(getRef('selected_fee_tip'), html`Set custom fee`)
|
|
feeMemo.memoized = false
|
|
break;
|
|
case 'suggested':
|
|
calculateFees();
|
|
getRef('send_fee').readOnly = true;
|
|
break;
|
|
}
|
|
})
|
|
function calculateApproxFee() {
|
|
return new Promise((resolve, reject) => {
|
|
fetch('https://bitcoiner.live/api/fees/estimates/latest')
|
|
.then(res => {
|
|
res.json()
|
|
.then(data => {
|
|
const satPerByte = data.estimates['60'].sat_per_vbyte;
|
|
const legacyBytes = 200;
|
|
const segwitBytes = 77;
|
|
resolve((legacyBytes * satPerByte + (0.25 * satPerByte) * segwitBytes) / Math.pow(10, 8));
|
|
}).catch(e => {
|
|
reject(e)
|
|
})
|
|
}).catch(e => {
|
|
reject(e)
|
|
})
|
|
})
|
|
}
|
|
async function getTransactionInputs() {
|
|
const privateKey = await floDapps.user.private.catch(err => console.log(err));
|
|
const privKeys = btcOperator.convert.wif(privateKey);
|
|
const senders = floGlobals.myBtcID;
|
|
const receivers = [...getRef('receiver_container').querySelectorAll('.receiver-input')].filter(input => input.value.trim() !== '').map(input => input.value.trim());
|
|
const amounts = [...getRef('receiver_container').querySelectorAll('.amount-input')].filter(input => input.value.trim() !== '').map(input => {
|
|
return parseFloat(input.value.trim())
|
|
});
|
|
return [senders, privKeys, receivers, amounts]
|
|
}
|
|
|
|
getRef('receiver_container').addEventListener('input', debounce(calculateFees, 300))
|
|
let feeMemo = {
|
|
memoized: false,
|
|
memoizedFee: 0
|
|
};
|
|
function calculateFees() {
|
|
if (getRef('fees_selector').value === 'custom') return;
|
|
const allValid = [...getRef('receiver_container').querySelectorAll('sm-input')].every(input => input.isValid)
|
|
if (!allValid && feeMemo.memoized) return;
|
|
getRef('fees_selector').children[0].click();
|
|
getRef('fees_selector').classList.remove('hidden')
|
|
getRef('send_transaction').disabled = true;
|
|
getRef('send_fee').value = '';
|
|
getRef('send_fee_loader').classList.remove('hidden')
|
|
const animOptions = {
|
|
duration: 200,
|
|
easing: 'ease',
|
|
fill: 'forwards'
|
|
}
|
|
getRef('send_fee_loader').animate(fadeIn, animOptions)
|
|
getRef('fees_section').classList.remove('hidden')
|
|
getRef('error_section').classList.add('hidden')
|
|
if (allValid) {
|
|
getRef('send_fee').placeholder = 'Fee'
|
|
calculateBtcFees().then(({ fee }) => {
|
|
getRef('send_fee').value = fee.toFixed(8);
|
|
renderElem(getRef('selected_fee_tip'), html``)
|
|
getRef('send_transaction').disabled = false;
|
|
}).catch(e => {
|
|
getRef('fees_section').classList.add('hidden')
|
|
getRef('error_section').classList.remove('hidden')
|
|
renderElem(getRef('error_section'), html`
|
|
<p class="error flex align-center gap-0-5">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M11 15h2v2h-2v-2zm0-8h2v6h-2V7zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
|
|
${e}
|
|
</p>
|
|
`)
|
|
console.error(e)
|
|
}).finally(_ => {
|
|
getRef('send_fee_loader').animate(fadeOut, animOptions).onfinish = _ =>
|
|
getRef('send_fee_loader').classList.add('hidden')
|
|
|
|
})
|
|
feeMemo.memoized = false;
|
|
} else {
|
|
getRef('send_fee').placeholder = 'Approximate fee'
|
|
renderElem(getRef('selected_fee_tip'), html` <p style="opacity: 0.8;">*Fill out all fields for exact fee!</p> `)
|
|
if (feeMemo.memoized) {
|
|
getRef('send_fee').value = feeMemo.memoizedFee.toFixed(8);
|
|
return;
|
|
}
|
|
calculateApproxFee().then(fee => {
|
|
getRef('send_fee').value = fee.toFixed(8);
|
|
feeMemo.memoizedFee = fee;
|
|
}).catch(e => {
|
|
getRef('fees_selector').children[1].click();
|
|
getRef('fees_selector').classList.add('hidden')
|
|
}).finally(_ => {
|
|
getRef('send_fee_loader').animate(fadeOut, animOptions).onfinish = _ =>
|
|
getRef('send_fee_loader').classList.add('hidden')
|
|
})
|
|
feeMemo.memoized = true;
|
|
}
|
|
}
|
|
|
|
getRef('send_transaction').onclick = evt => {
|
|
buttonLoader('send_transaction', true)
|
|
floDapps.user.private.then(async privateKey => {
|
|
const [senders, privKeys, receivers, amounts] = await getTransactionInputs().catch(err => console.log(err));
|
|
const fee = parseFloat(getRef('send_fee').value.trim());
|
|
console.debug(senders, receivers, amounts, fee);
|
|
btcOperator.sendTx(senders, privKeys, receivers, amounts, fee).then(txid => {
|
|
console.log(txid);
|
|
closePopup();
|
|
getRef('txid').value = txid;
|
|
openPopup('txid_popup');
|
|
getRef('send_tx').reset()
|
|
if (!floGlobals.isMobileView && !floGlobals.isSubAdmin && appState.currentPage === 'home')
|
|
render.recentTransactions();
|
|
else if (!floGlobals.isSubAdmin && appState.currentPage === 'history')
|
|
render.transactionsHistory();
|
|
}).catch(error => {
|
|
notify(`Error sending transaction \n ${error}`, 'error');
|
|
}).finally(_ => {
|
|
buttonLoader('send_transaction', false)
|
|
})
|
|
}).catch(error => {
|
|
console.log(error);
|
|
notify('Invalid password', 'error');
|
|
closePopup();
|
|
return false;
|
|
})
|
|
}
|
|
|
|
const assetIcons = {
|
|
btc: ` <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"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg> `,
|
|
usd: `<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="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/></svg>`,
|
|
rupee: `<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><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>`
|
|
}
|
|
function convertCurrency(amount) {
|
|
let convertedAmount = 0;
|
|
if (amount && !Number.isNaN(amount)) {
|
|
if (convertingTo === 'rupee') {
|
|
convertedAmount = parseFloat((amount * globalExchangeRate.inr).toFixed(2));
|
|
} else {
|
|
convertedAmount = parseFloat((amount / globalExchangeRate.inr).toFixed(8))
|
|
}
|
|
}
|
|
return convertedAmount;
|
|
}
|
|
function handleConversionAmountInput(e) {
|
|
const fromAmount = parseFloat(e.target.value.trim());
|
|
const convertedAmount = convertCurrency(fromAmount);
|
|
getRef('converted_amount').textContent = formatAmount(convertedAmount, convertingTo === 'btc' ? 'btc' : 'inr');
|
|
if (e.target.validity.rangeUnderflow)
|
|
e.target.setAttribute('error-text', `Minimum amount is ${convertingTo === 'btc' ? '₹10' : '0.000001BTC'}`)
|
|
if (e.target.validity.rangeOverflow)
|
|
e.target.setAttribute('error-text', `You do not have enough ${convertingTo === 'btc' ? 'rupee tokens' : 'BTC'}`)
|
|
}
|
|
function handleConversionAmountSelection(e) {
|
|
const fromAmount = parseFloat(e.target.value.trim());
|
|
const convertedAmount = convertCurrency(fromAmount);
|
|
getRef('converted_amount').textContent = formatAmount(convertedAmount, convertingTo === 'btc' ? 'btc' : 'inr');
|
|
}
|
|
|
|
let convertingTo = 'btc';
|
|
async function initConversion(toAsset) {
|
|
try {
|
|
convertingTo = toAsset;
|
|
const fromAsset = convertingTo === 'btc' ? 'rupee' : 'btc';
|
|
renderElem(getRef('conversion_status'), html`
|
|
<sm-spinner class="justify-self-center"></sm-spinner>
|
|
<p class="text-center">Checking conversion capability...</p>
|
|
`)
|
|
getRef('conversion_status').classList.remove('hidden');
|
|
getRef('conversion_form').classList.add('hidden');
|
|
openPopup('convert_asset_popup');
|
|
// get possible conversion values
|
|
const {
|
|
[floExchangeAPI.processCode.CONVERT_MODE_GET]: permissibleBtc,
|
|
[floExchangeAPI.processCode.CONVERT_MODE_PUT]: permissibleRupee
|
|
} = await floExchangeAPI.getConvertValues()
|
|
// const {
|
|
// [floExchangeAPI.processCode.CONVERT_MODE_GET]: permissibleBtc,
|
|
// [floExchangeAPI.processCode.CONVERT_MODE_PUT]: permissibleRupee
|
|
// } = { 0: [100, 200, 300], 1: [100, 200, 300] }
|
|
if (convertingTo === 'btc' && !permissibleBtc || convertingTo === 'rupee' && !permissibleRupee) {
|
|
renderElem(getRef('conversion_status'), html`
|
|
<svg class="icon justify-self-center error-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="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"/></svg>
|
|
<strong>Conversion service not available</strong>
|
|
`)
|
|
return
|
|
}
|
|
let assetBalance = 0;
|
|
let title = ''
|
|
let description = ''
|
|
let isRange = true;
|
|
let leastAllowed = 0;
|
|
let maxAllowed = 0;
|
|
let step = 0.1;
|
|
switch (convertingTo) {
|
|
case 'btc':
|
|
assetBalance = await floTokenAPI.getBalance(floGlobals.myFloID)
|
|
if (Array.isArray(permissibleBtc)) {
|
|
description = `Select the amount of rupee tokens you want to convert to BTC`
|
|
isRange = false;
|
|
step = 0.00000001;
|
|
leastAllowed = Math.min(...permissibleBtc);
|
|
} else if (permissibleBtc.min) {
|
|
const { min, max } = permissibleBtc;
|
|
description = `Enter the amount of rupee tokens you want to convert to BTC. Minimum amount is ${min} and maximum amount is ${max}`
|
|
leastAllowed = min;
|
|
maxAllowed = Math.min(max, assetBalance);
|
|
}
|
|
title = `Convert Rupee tokens to BTC`;
|
|
break;
|
|
case 'rupee':
|
|
assetBalance = await btcOperator.getBalance(floGlobals.myBtcID)
|
|
if (Array.isArray(permissibleRupee)) {
|
|
description = `Select the amount of BTC you want to convert to rupee tokens`
|
|
isRange = false;
|
|
step = 0.01;
|
|
leastAllowed = Math.min(...permissibleRupee);
|
|
} else if (permissibleRupee.min) {
|
|
const { min, max } = permissibleRupee;
|
|
description = `Enter the amount of BTC you want to convert to rupee tokens. Minimum amount is ${min} and maximum amount is ${max}`
|
|
leastAllowed = min;
|
|
maxAllowed = Math.min(max, assetBalance);
|
|
}
|
|
title = `Convert BTC to Rupee tokens`;
|
|
break;
|
|
}
|
|
if (leastAllowed > assetBalance) {
|
|
renderElem(getRef('conversion_status'), html`
|
|
<svg class="icon justify-self-center error-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="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"/></svg>
|
|
<strong>
|
|
You do not have enough ${convertingTo === 'btc' ? 'Rupee tokens' : 'BTC'} to convert to ${convertingTo.toUpperCase()}. Minimum amount is ${leastAllowed}
|
|
</strong>
|
|
`)
|
|
return
|
|
}
|
|
|
|
getRef('conversion_status').classList.add('hidden');
|
|
getRef('conversion_form').classList.remove('hidden');
|
|
renderElem(getRef('conversion_form'), html`
|
|
<div class="grid gap-0-5">
|
|
<h3 id="convert_asset_title">${title}</h3>
|
|
<p id="convert_asset_description">${description}</p>
|
|
</div>
|
|
${isRange ? html`
|
|
<sm-input id="conversion_amount" oninput=${handleConversionAmountInput} min=${leastAllowed} max=${maxAllowed} step=${step} type="number" placeholder="Amount" autofocus animate required>
|
|
<div id="conversion_asset_icon" slot="icon">
|
|
${fromAsset === 'rupee' ?
|
|
html`<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><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>` :
|
|
html`<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"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg>`
|
|
}
|
|
</div>
|
|
</sm-input>
|
|
`: html`
|
|
<fieldset class="flex flex-wrap gap-0-3 align-center" onchange=${handleConversionAmountSelection}>
|
|
${(convertingTo === 'btc' ? permissibleBtc : permissibleRupee).map((amount) => html`
|
|
<label class="amount-option">
|
|
<input type="radio" name="amount" value="${amount}" required>
|
|
<span>${amount}</span>
|
|
</label>
|
|
`)
|
|
}
|
|
</fieldset>
|
|
`
|
|
}
|
|
<p class="flex align-center space-between">
|
|
You'll get:
|
|
<b id="converted_amount">0</b>
|
|
</p>
|
|
<p class="flex align-center space-between">
|
|
Exchange rate:
|
|
<b id="conversion_rate"></b>
|
|
</p>
|
|
<div class="multi-state-button">
|
|
<button id="convert_asset" type="submit" class="button button--primary cta w-100"
|
|
onclick="convertAsset()" disabled>Convert</button>
|
|
</div>
|
|
`)
|
|
getExchangeRate().then(rate => {
|
|
document.getElementById('conversion_rate').textContent = `1BTC = ${formatAmount(rate.inr)}`;
|
|
}).catch(error => {
|
|
notify('Could not get exchange rate', 'error')
|
|
console.error(error)
|
|
})
|
|
} catch (e) {
|
|
console.log(e)
|
|
notify(`Could not initialize conversion: ${e.message}`, 'error')
|
|
}
|
|
}
|
|
|
|
function convertAsset() {
|
|
getConfirmation('Are you sure you want to convert?', { confirmText: 'Convert' }).then(async (res) => {
|
|
if (!res) return;
|
|
buttonLoader('convert_asset', true)
|
|
const fromAsset = convertingTo === 'btc' ? 'rupee' : 'btc';
|
|
const fromAmount = parseFloat((document.getElementById('conversion_amount') || document.querySelector('.amount-option input:checked')).value.trim());
|
|
try {
|
|
let result
|
|
if (convertingTo === 'btc') {
|
|
result = floExchangeAPI.convertToBTC(fromAmount, floGlobals.myFloID, floGlobals.convertSink, await floDapps.user.private)
|
|
} else {
|
|
result = floExchangeAPI.convertFromBTC(fromAmount, floGlobals.myBtcID, floGlobals.convertSink, await floDapps.user.private)
|
|
}
|
|
console.log(result)
|
|
renderElem(getRef('conversion_status'), html`
|
|
<svg class="icon justify-self-center success-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 16.2L5.51 12.7 4.09 14.11 9 19l12-12-1.41-1.41z"/></svg>
|
|
<h3>Conversion successful</h3>
|
|
<p>Conversion of ${formatAmount(fromAmount)} ${fromAsset} to ${convertingTo} is in progress. You can check the status of your conversion in the <a href="/transactions">Transactions</a> tab.</p>
|
|
`)
|
|
} catch (e) {
|
|
getRef('conversion_status').classList.remove('hidden');
|
|
getRef('conversion_form').classList.add('hidden');
|
|
renderElem(getRef('conversion_status'), html`
|
|
<svg class="icon justify-self-center error-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="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"/></svg>
|
|
<strong>
|
|
<h3>Conversion failed</h3>
|
|
<p>${e.message}</p>
|
|
</strong>
|
|
`)
|
|
console.log(e)
|
|
} finally {
|
|
buttonLoader('convert_asset', false)
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |