exchangemarket/public/home.html
2021-10-19 16:42:09 +05:30

1844 lines
87 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RanchiMall market</title>
<script src="components.js" defer></script>
<link rel="stylesheet" href="css/main.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet">
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
const floGlobals = {
//Required for all
blockchain: "FLO",
//Required for blockchain API operators
apiURL: {
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
},
tokenURL: "https://ranchimallflo.duckdns.org/",
token: "rupee",
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf",
sendAmt: 0.001,
fee: 0.0005,
}
</script>
<script src="https://sairajzero.github.io/Standard_Operations/cdn/floCrypto.js"></script>
<script src="https://github.com/sairajzero/Standard_Operations/releases/download/test/floBlockchainAPI.js"></script>
<script src="fn.js"></script>
</head>
<body class="hide-completely">
<sm-notifications id="notification_drawer"></sm-notifications>
<audio id="notification_sound">
<source src="https://rmservices.duckdns.org/files/notification-sound.mp3" type="audio/mpeg">
<source src="https://rmservices.duckdns.org/files/notification-sound.ogg" type="audio/ogg">
</audio>
<sm-popup id="confirmation_popup">
<h4 id="confirm_title"></h4>
<p id="confirm_message"></p>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
</div>
</sm-popup>
<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">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn" type="submit">OK</sm-button>
</div>
</sm-form>
</sm-popup>
<article id="loading" class="page page-layout hide-completely">
<sm-spinner></sm-spinner>
<h4>Loading RanchiMall Market</h4>
</article>
<article id="home" class="page">
<header id="main_header">
<div class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
<title>RanchiMall</title>
<path
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
</svg>
<div class="grid">
<h4>RanchiMall Market</h4>
</div>
</div>
<theme-toggle></theme-toggle>
<div id="user_dropdown" class="dropdown-wrapper grid user-content hide-completely">
<button onclick="toggleDropdown('user_dropdown')">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<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" />
</svg>
</button>
<div class="dropdown grid gap-2 hide-completely">
<h3>Profile</h3>
<div class="grid">
<div class="label">My FLO ID</div>
<sm-copy id="user_id"></sm-copy>
</div>
<div class="grid gap-0-5">
<h4>Add password lock</h4>
<p>
Adding password lock allows to authenticate transactions with custom password instead of
private key.
</p>
<sm-button onclick="proxy.lock();">Add password</sm-button>
</div>
<sm-button class="danger" onclick="UI_evt.logout();">Log out</sm-button>
</div>
</div>
</header>
<section id="dashboard" class="card mobile-page hide-on-mobile">
<sm-form id="login_form" class="hide-">
<div class="grid gap-0-5">
<h4>Login</h4>
<p>Please login for trading and accessing your account.</p>
</div>
<sm-input type="password" id="login_form__priv_key" placeholder="Private key" variant="outlined" animate
required hiderequired></sm-input>
<sm-checkbox id="remember_me" checked>
<span class="button__icon--right">
Remember me
</span>
</sm-checkbox>
<input type="text" id="sign_in_id" style="display: none;" hidden />
<sm-button variant="primary" onclick="UI_evt.login();">Log in</sm-button>
<sm-button onclick="showPopup('registration_popup')">Not registered? click here!</sm-button>
</sm-form>
<sm-form id="trade_form" class="user-content hide-completely">
<div class="flex align-center space-between">
<p>FLO/INR rate</p>
<span id="flo_rate"></span>
</div>
<div class="flex space-between align-center">
<h4>Trade FLO</h4>
<strip-select id="trade_type_selector" class="tab">
<strip-option value="buy" selected>Buy</strip-option>
<strip-option value="sell">Sell</strip-option>
</strip-select>
</div>
<sm-input id="get_price" variant="outlined" placeholder="Max price (₹)" type="number" step="0.00001"
required hiderequired animate>
</sm-input>
<sm-input id="get_quantity" variant="outlined" placeholder="Quantity (FLO)" type="number"
step="0.00000001" required hiderequired animate></sm-input>
<sm-input id="get_total" variant="outlined" placeholder="Total (₹)" type="number" min="1" step="0.01"
required hiderequired animate>
</sm-input>
<div id="quantity_selector" class="flex align-center">
<span id="quantity_type">Rupee</span>
<button class="button" value="25">25%</button>
<button class="button" value="50">50%</button>
<button class="button" value="75">75%</button>
<button class="button" value="100">100%</button>
</div>
<div id="trade_button_wrapper" class="stateful-button-wrapper flex align-center justify-center">
<sm-button id="trade_button" class="uppercase w-100" variant="primary" onclick="tradeFlo()">BUY FLO
</sm-button>
</div>
</sm-form>
</section>
<section id="orders_section" class="grid gap-1">
<section id="my_orders_section" class="grid gap-1 card user-content mobile-page hide-on-mobile">
<div id="my_orders_section__header" class="orders_section__header flex">
<div id="orders_section__header--primary" class="flex w-100 align-center space-between">
<h4>
My orders
</h4>
<strip-select id="my_orders_category_selector" class="tab">
<strip-option value="open" selected>Open</strip-option>
<strip-option value="completed">History</strip-option>
</strip-select>
</div>
<div id="orders_section__header--secondary"
class="flex w-100 align-center space-between hide-completely">
<button class="" onclick="clearSelection()" title="Clear all selection">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button__icon--left" height="24px"
viewBox="0 0 24 24" width="24px">
<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>
<span id="selected_orders"></span>
</button>
<div class="options">
<button class="button" title="Cancel selected orders" onclick="cancelAll()">
Cancel orders
</button>
</div>
</div>
</div>
<ul id="orders_list" class="observe-empty-state"></ul>
<p class="empty-state">
No orders placed
</p>
</section>
<section id="market_orders_section" class="grid gap-1 card mobile-page hide-on-mobile">
<div class="orders_section__header flex align-center space-between">
<h4 class="flex align-center">
Market orders
<button onclick="refresh();" title="Refresh" style="margin-left: 1rem;">
<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.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
</svg>
</button>
</h4>
<strip-select id="market_orders_category_selector" class="tab">
<strip-option value="open" selected>Open</strip-option>
<strip-option value="completed">History</strip-option>
</strip-select>
</div>
<ul id="market_orders_list" class="observe-empty-state"></ul>
<p class="empty-state">
No orders placed
</p>
</section>
</section>
<section id="user_section" class="grid card user-content mobile-page hide-completely hide-on-mobile">
<h4 class="flex align-center user_section__header">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button__icon--left" height="24px"
viewBox="0 0 24 24" width="24px">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" />
<circle cx="16" cy="12" r="1.5" />
</svg>
My wallet
</h4>
<div id="wallet_actions">
<p class="label">Select asset</p>
<sm-select id="wallet_asset_selector">
<sm-option value="FLO">FLO</sm-option>
<sm-option value="Rupee">Rupee</sm-option>
</sm-select>
<div class="flex wallet_actions__wrapper">
<button class="button" value="deposit">
Deposit
</button>
<button class="button" value="withdraw">
Withdraw
</button>
</div>
</div>
<div class="grid gap-0-5">
<h4>Balance</h4>
<div class="balance-card">
<div class="balance-card__icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M16.36,15.39c1.83,0,4.26-2.49,4.36-4.74-5.65-.19-4.91.47-7.28,2.39,2.19-2.4,1.42-7.79-1.43-10V6.17c2.33,1.49,2.21,5.14,0,7.15-2.23-2-2.27-5.69,0-7.15V3c-2.83,2.26-3.62,7.66-1.44,10-2.36-1.93-1.63-2.58-7.28-2.39.1,2.26,2.55,4.73,4.36,4.74,0,0-1.93.22-2.74-2.62,2.38-.37,4.29-.14,6.28,2-.79-.11-4.89,1.13-4.38,3.26.53.06,3,.3,3.58-.83-.17.18-1.25.5-1.53.05.38-1.39,2.32-2,2.32-2-1,1.82-.48,4.63.82,5.72,1.31-1.08,1.8-3.95.82-5.72,0,0,1.95.6,2.32,2-.29.46-1.36.12-1.53-.05.58,1.14,3.06.88,3.58.83.49-2.17-3.58-3.36-4.38-3.26,2-2.17,3.92-2.39,6.28-2C18.3,15.62,16.36,15.39,16.36,15.39ZM12,19.46c-.91-.79-.5-3,0-3.59C12.5,16.45,12.91,18.66,12,19.46Z" />
</svg>
</div>
<div class="balance-card__token">FLO</div>
<div id="flo_balance"></div>
</div>
<div class="balance-card">
<div class="balance-card__icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M15.3,4.91a4.78,4.78,0,0,0-.39-.5l3.14,0L18.75,2H6L5.25,4.6H9a4.22,4.22,0,0,1,3.06,1,3.16,3.16,0,0,1,.75,1.24H5.93L5.25,9.22h7.62a3.15,3.15,0,0,1-.34.82,3,3,0,0,1-1.37,1.17,5.34,5.34,0,0,1-2.2.4H5.5l0,1.9,7,8.49h3.56v-.17L9.68,14l.09,0a8.07,8.07,0,0,0,3.65-1,5,5,0,0,0,2-2.09A6.29,6.29,0,0,0,16,9.22h2.1l.69-2.42H15.93A5.93,5.93,0,0,0,15.3,4.91Z" />
</svg>
</div>
<div class="balance-card__token">Rupee</div>
<div id="rupee_balance"></div>
</div>
</div>
</section>
</article>
<footer id="bottom_nav" class="flex align-center hide-on-desktop">
<a href="#/dashboard" class="bottom_nav__item bottom_nav__item--active">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<rect fill="none" height="24" width="24" />
<path
d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M5,19V5h6v14H5z M19,19h-6v-7h6V19z M19,10h-6V5h6V10z" />
</svg>
<div class="item__title">Dashboard</div>
</a>
<a href="#/my_orders" class="bottom_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 M15,20H6c-0.55,0-1-0.45-1-1v-1h10V20z 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>
<div class="item__title">My orders</div>
</a>
<a href="#/market" class="bottom_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">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<g />
<g>
<path
d="M21.9,8.89l-1.05-4.37c-0.22-0.9-1-1.52-1.91-1.52H5.05C4.15,3,3.36,3.63,3.15,4.52L2.1,8.89 c-0.24,1.02-0.02,2.06,0.62,2.88C2.8,11.88,2.91,11.96,3,12.06V19c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-6.94 c0.09-0.09,0.2-0.18,0.28-0.28C21.92,10.96,22.15,9.91,21.9,8.89z M18.91,4.99l1.05,4.37c0.1,0.42,0.01,0.84-0.25,1.17 C19.57,10.71,19.27,11,18.77,11c-0.61,0-1.14-0.49-1.21-1.14L16.98,5L18.91,4.99z M13,5h1.96l0.54,4.52 c0.05,0.39-0.07,0.78-0.33,1.07C14.95,10.85,14.63,11,14.22,11C13.55,11,13,10.41,13,9.69V5z M8.49,9.52L9.04,5H11v4.69 C11,10.41,10.45,11,9.71,11c-0.34,0-0.65-0.15-0.89-0.41C8.57,10.3,8.45,9.91,8.49,9.52z M4.04,9.36L5.05,5h1.97L6.44,9.86 C6.36,10.51,5.84,11,5.23,11c-0.49,0-0.8-0.29-0.93-0.47C4.03,10.21,3.94,9.78,4.04,9.36z M5,19v-6.03C5.08,12.98,5.15,13,5.23,13 c0.87,0,1.66-0.36,2.24-0.95c0.6,0.6,1.4,0.95,2.31,0.95c0.87,0,1.65-0.36,2.23-0.93c0.59,0.57,1.39,0.93,2.29,0.93 c0.84,0,1.64-0.35,2.24-0.95c0.58,0.59,1.37,0.95,2.24,0.95c0.08,0,0.15-0.02,0.23-0.03V19H5z" />
</g>
</g>
</svg>
<div class="item__title">Market</div>
</a>
<a href="#/wallet" class="bottom_nav__item user-content">
<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="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" />
<circle cx="16" cy="12" r="1.5" />
</svg>
<div class="item__title">Wallet</div>
</a>
</footer>
<sm-popup id="registration_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
<svg class="icon" 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 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
</svg>
</button>
<h4>Register</h4>
</header>
<div class="grid gap-1-5">
<sm-form id="register_section">
<p>Enter the private key of FLO ID you want to register.</p>
<sm-input id="get_registration_key" variant="outlined" placeholder="Private key" type="password"
required hiderequired></sm-input>
<sm-button variant="primary" onclick="UI_evt.signup()">Register</sm-button>
</sm-form>
<div class="grid gap-0-5">
<p>Don't have FLO credentials?</p>
<sm-button onclick="showPopup('sign_up_popup')">Generate FLO credentials</sm-button>
</div>
</div>
</sm-popup>
<sm-popup id="sign_up_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
<svg class="icon" 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 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
</svg>
</button>
<h4>Get credentials</h4>
</header>
<section id="sign_up_section" class="grid gap-1-5">
<div class="grid gap-1-5">
<div class="grid gap-0-5">
<h5>FLO ID</h5>
<sm-copy id="generated_flo_id"></sm-copy>
</div>
<div class="grid gap-0-5">
<h5>Private key</h5>
<sm-copy id="generated_private_key"></sm-copy>
</div>
</div>
<sm-button id="sign_up_button" variant="primary" onclick="registerID()">Register these credentials
</sm-button>
<strong class="warning">
Keep your private key secure and don't share with anyone.
Once lost there is no way to recover private key.
</strong>
</section>
</sm-popup>
<sm-popup id="wallet_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
<svg class="icon" 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 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
</svg>
</button>
<h4 id="wallet_popup__title" class="capitalize"></h4>
</header>
<sm-form id="wallet_form">
<sm-input id="get_user_amount" variant="outlined" placeholder="Quantity" type="number" min="1"
step="0.00001" required hiderequired animate>
</sm-input>
<sm-input id="get_private_key" variant="outlined" placeholder="FLO private key" type="password" required
error-text="Invalid private key" hiderequired animate>
</sm-input>
<div id="wallet_popup__cta_wrapper" class="stateful-button-wrapper">
<sm-button id="wallet_popup__cta" class="uppercase" variant="primary"></sm-button>
</div>
</sm-form>
<div id="wallet_result" class="grid gap-2 hide-completely">
<div id="wallet_result__icon"></div>
<div class="grid gap-0-5">
<h4 id="wallet_result__title"></h4>
<p id="wallet_result__description"></p>
</div>
<button class="button" onclick="hideWalletResult()">Go back</button>
</div>
</sm-popup>
<sm-popup id="transaction_info_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
<svg class="icon" 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 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
</svg>
</button>
<h4>Transaction details</h4>
</header>
<div class="grid gap-1-5">
<div id="transaction__buyer_wrapper" class="grid">
<h5 class="label capitalize">Buyer</h5>
<sm-copy id="transaction__buyer"></sm-copy>
</div>
<div id="transaction__seller_wrapper" class="grid">
<h5 class="label capitalize">Seller</h5>
<sm-copy id="transaction__seller"></sm-copy>
</div>
<div class="grid">
<h5 id="transaction_time__label" class="label">Completed</h5>
<h4 id="transaction_time"></h4>
</div>
</div>
</sm-popup>
<template id="net_balance_template">
<span class="available-balance"></span>
</template>
<template id="locked_balance_template">
<div>
<span class="label">Locked</span>
<span class="locked-balance"></span>
</div>
<div>
<span class="label">Available</span>
<span class="available-balance"></span>
</div>
</template>
<template id="order_template">
<li class="list__item order-card">
<sm-checkbox></sm-checkbox>
<div class="grid">
<div class="order-card__type capitalize"></div>
<div class="order-card__quantity"></div>
</div>
<div class="grid">
<span class="label order-card__price-type">Unit price</span>
<div class="order-card__price"></div>
</div>
<time class="order-card__time"></time>
<button class="cancel-order" title="Cancel this order">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z" />
</svg>
<span>Cancel</span>
</button>
</li>
</template>
<template id="market_order_template">
<li class="list__item transaction-card align-center">
<div class="grid">
<div class="transaction-card__type capitalize"></div>
<div class="transaction-card__quantity"></div>
</div>
<div class="grid">
<span class="label transaction-card__price-type">Unit price</span>
<div class="transaction-card__price"></div>
</div>
<div class="grid">
<span class="label transaction-card__price-type">Total</span>
<div class="transaction-card__total"></div>
</div>
<button class="more-info" title="View order details">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-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>
</button>
</li>
</template>
<template id="transaction_template">
<li class="list__item transaction-card align-center">
<div class="grid">
<div class="transaction-card__type capitalize"></div>
<div class="transaction-card__quantity"></div>
</div>
<div class="grid">
<span class="label transaction-card__price-type">Unit price</span>
<div class="transaction-card__price"></div>
</div>
<div class="grid">
<span class="label transaction-card__price-type">Total</span>
<div class="transaction-card__total"></div>
</div>
<button class="more-info" title="View transaction details">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-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>
</button>
</li>
</template>
<template id="market_transaction_template">
<li class="list__item transaction-card align-center">
<div class="transaction-card__quantity"></div>
<div class="transaction-card__price"></div>
<div class="transaction-card__total"></div>
<button class="more-info" title="View transaction details">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" height="24px" viewBox="0 0 24 24" width="24px">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-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>
</button>
</li>
</template>
<template id="market_order_list_template">
<h4 class="grid list__header">
<div>Quantity</div>
<div>Buy price</div>
<div>Sell price</div>
<div>Quantity</div>
</h4>
<section id="buyers_list"></section>
<section id="sellers_list"></section>
</template>
<template id="market_transaction_list_template">
<h4 class="grid list__header">
<div>Quantity</div>
<div>Unit price</div>
<div>Total</div>
<div></div>
</h4>
</template>
<template id="success_template">
<div class="stateful-result stateful-result--success">
<div class="result__background result__background--success"></div>
<div class="icon-wrapper flex align-center">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button__icon--left" 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>Success</span>
</div>
</div>
</template>
<template id="failure_template">
<div class="stateful-result stateful-result--failure">
<div class="result__background"></div>
<div class="icon-wrapper flex align-center">
<svg xmlns="http://www.w3.org/2000/svg" class="icon button__icon--left" 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>
<span>Failed</span>
</div>
</div>
</template>
<script id="ui_utils">
// Global variables
const domRefs = {};
let timerId;
const currentYear = new Date().getFullYear();
//Checks for internet connection status
if (!navigator.onLine)
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error",
{ sound: true }
);
window.addEventListener("offline", () => {
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error",
{ pinned: true, sound: true }
);
});
window.addEventListener("online", () => {
getRef("notification_drawer").clearAll();
notify("We are back online.", "success");
});
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
}
}
// returns dom with specified element
function createElement(tagName, options = {}) {
const { className, textContent, innerHTML, attributes = {} } = options
const elem = document.createElement(tagName)
for (let attribute in attributes) {
elem.setAttribute(attribute, attributes[attribute])
}
if (className)
elem.className = className
if (textContent)
elem.textContent = textContent
if (innerHTML)
elem.innerHTML = innerHTML
return elem
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
// Limits the rate of function execution
function throttle(func, delay) {
// If setTimeout is already scheduled, no need to do anything
if (timerId) {
return;
}
// Schedule a setTimeout after delay seconds
timerId = setTimeout(function () {
func();
// Once setTimeout function execution is finished, timerId = undefined so that in
// the next scroll event function execution can be scheduled by the setTimeout
timerId = undefined;
}, delay);
}
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
if (this.items.length == 0)
return "Underflow";
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
}
let popupStack = new Stack()
let zIndex = 10
// function required for popups or modals to appear
function showPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
popupStack = getRef(popupId).show({ pinned, popupStack })
return getRef(popupId);
}
// hides the popup or modal
function hidePopup() {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide()
}
// displays a popup for asking permission. Use this instead of JS confirm
const getConfirmation = (title, options = {}) => {
return new Promise(resolve => {
const { message, cancelText = 'Cancel', confirmText = 'OK' } = options
showPopup('confirmation_popup', true)
getRef('confirm_title').textContent = title;
getRef('confirm_message').textContent = message;
let cancelButton = getRef('confirmation_popup').children[2].children[0],
submitButton = getRef('confirmation_popup').children[2].children[1]
submitButton.textContent = confirmText
cancelButton.textContent = cancelText
submitButton.onclick = () => {
hidePopup()
resolve(true);
}
cancelButton.onclick = () => {
hidePopup()
resolve(false);
}
})
}
// displays a popup for asking user input. Use this instead of JS prompt
async function getPromptInput(title, message = '', options = {}) {
const { isPassword = true, cancelText = 'Cancel', confirmText = 'OK' } = options
showPopup('prompt_popup', true)
getRef('prompt_title').textContent = title;
getRef('prompt_message').textContent = message;
let buttons = getRef('prompt_popup').querySelectorAll("sm-button");
if (isPassword)
getRef('prompt_input').setAttribute("type", "password")
getRef('prompt_input').focusIn()
buttons[0].textContent = cancelText;
buttons[1].textContent = confirmText;
return new Promise((resolve, reject) => {
buttons[0].onclick = () => {
hidePopup()
return;
}
buttons[1].onclick = () => {
const value = getRef('prompt_input').value;
hidePopup()
resolve(value)
}
})
}
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
function notify(message, mode, options = {}) {
const { pinned = false, sound = false } = options
let icon
switch (mode) {
case 'success':
icon = `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
break;
case 'error':
icon = `<svg class="icon icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>`
break;
}
getRef("notification_drawer").push(message, { pinned, icon });
if (navigator.onLine && sound) {
getRef("notification_sound").currentTime = 0;
getRef("notification_sound").play();
}
if (mode === 'error') {
console.error(message)
}
}
function getFormattedTime(time, relative) {
try {
if (String(time).indexOf('_'))
time = String(time).split('_')[0]
const intTime = parseInt(time)
if (String(intTime).length < 13)
time *= 1000
let timeFrag = new Date(intTime).toString().split(' '),
day = timeFrag[0],
month = timeFrag[1],
date = timeFrag[2],
year = timeFrag[3],
minutes = new Date(intTime).getMinutes(),
hours = new Date(intTime).getHours(),
currentTime = new Date().toString().split(' ')
minutes = minutes < 10 ? `0${minutes}` : minutes
let finalHours = ``;
if (hours > 12)
finalHours = `${hours - 12}:${minutes}`
else if (hours === 0)
finalHours = `12:${minutes}`
else
finalHours = `${hours}:${minutes}`
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
if (relative) {
if (year == currentYear) {
if (currentTime[1] === month) {
const dateDiff = (parseInt(currentTime[2]) - parseInt(date))
if (dateDiff === 0)
return `${finalHours}`;
else if (dateDiff === 1)
return `Yesterday`;
else if (dateDiff > 1 && dateDiff < 8)
return currentTime[0];
else
return ` ${date} ${month}`;
}
else
return ` ${date} ${month}`;
}
else
return `${month} ${year}`;
}
else
return `${finalHours}, ${month} ${date} ${year}`;
} catch (e) {
console.error(e);
return time;
}
}
window.addEventListener('hashchange', e => showPage(window.location.hash))
window.addEventListener("load", () => {
showPage(window.location.hash)
document.body.classList.remove('hide-completely')
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => {
if (e.code === 'Escape') {
hidePopup()
}
})
document.addEventListener('copy', () => {
notify('copied', 'success')
})
});
const pagesData = {
openedPages: [],
params: {}
}
async function showPage(targetPage, options = {}) {
const { firstLoad, hashChange } = options
let pageId
let params
if (targetPage === '') {
if (typeof myFloID === "undefined") {
pageId = 'landing'
} else {
pageId = 'home'
}
} else {
if (targetPage.includes('/')) {
const pages = targetPage.split('/')
pageId = pages[1]
} else {
pageId = targetPage
}
}
if (pagesData.lastPage !== pageId) {
let target
switch (pageId) {
case 'dashboard':
target = 'dashboard'
break;
case 'my_orders':
target = 'my_orders_section'
break;
case 'market':
target = 'market_orders_section'
break;
case 'wallet':
target = 'user_section'
break;
}
if (target) {
document.querySelectorAll('.mobile-page').forEach(elem => elem.classList.add('hide-on-mobile'))
getRef(target).classList.remove('hide-on-mobile')
document.querySelectorAll('.bottom_nav__item').forEach(elem => elem.classList.remove('bottom_nav__item--active'))
document.querySelector(`.bottom_nav__item[href="#/${pageId}"]`).classList.add('bottom_nav__item--active')
getRef(target)?.animate([
{
transform: 'translateY(1rem)',
opacity: 0,
},
{
transform: 'none',
opacity: 1,
},
],
{
duration: 300,
easing: 'ease'
})
pagesData.lastPage = target
if (!pagesData.openedPages.includes(target)) {
pagesData.openedPages.push(target)
}
}
}
}
// class based lazy loading
class LazyLoader {
constructor(container, elementsToRender, renderFn, options = {}) {
const { batchSize = 10 } = options
this.elementsToRender = elementsToRender
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.renderFn = renderFn
this.intersectionObserver
this.batchSize = batchSize
this.lazyContainer = document.querySelector(container)
this.update = this.update.bind(this)
this.render = this.render.bind(this)
this.init = this.init.bind(this)
this.clear = this.clear.bind(this)
}
init() {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.disconnect()
this.render({ lazyLoad: true })
}
})
}, {
threshold: 0.3
})
this.mutationObserver = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
if (mutation.type === 'childList') {
if (mutation.addedNodes.length) {
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
}
}
})
})
this.mutationObserver.observe(this.lazyContainer, {
childList: true,
})
this.render()
}
update(elementsToRender) {
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
this.render()
}
render(options = {}) {
let { lazyLoad = false } = options
const frag = document.createDocumentFragment();
if (lazyLoad) {
this.updateStartIndex = this.updateEndIndex
this.updateEndIndex = this.arrayOfElements.length > this.updateEndIndex + this.batchSize ? this.updateEndIndex + this.batchSize : this.arrayOfElements.length
} else {
this.intersectionObserver.disconnect()
this.lazyContainer.innerHTML = ``;
this.updateStartIndex = 0
this.updateEndIndex = this.arrayOfElements.length > this.batchSize ? this.batchSize : this.arrayOfElements.length
}
for (let index = this.updateStartIndex; index < this.updateEndIndex; index++) {
frag.append(this.renderFn(this.arrayOfElements[index]))
}
this.lazyContainer.append(frag)
}
clear() {
this.intersectionObserver.disconnect()
this.mutationObserver.disconnect()
this.lazyContainer.innerHTML = ``;
}
reset() {
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
this.render()
}
}
</script>
<script>
function formatAmount(amount) {
return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' })
}
const render = {
orderCard(orderDetails = {}) {
const { id, quantity, price, time, type } = orderDetails
const card = getRef('order_template').content.cloneNode(true).firstElementChild
card.dataset.id = id
card.dataset.type = type
card.querySelector('.order-card__type').textContent = type
card.querySelector('.order-card__quantity').textContent = `${quantity} FLO`
card.querySelector('.order-card__price-type').textContent = type === 'buy' ? 'Max price' : 'Min price'
card.querySelector('.order-card__price').textContent = formatAmount(price)
card.querySelector('.order-card__time').textContent = getFormattedTime(time, true)
return card
},
transactionCard(transactionDetails = {}) {
const { buyer, seller, type, other, quantity, unitValue, time } = transactionDetails
const card = getRef('transaction_template').content.cloneNode(true).firstElementChild
card.dataset.type = type
card.querySelector('.transaction-card__type').textContent = type
card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO`
card.querySelector('.transaction-card__price').textContent = formatAmount(unitValue)
card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2)))
card.querySelector('.more-info').dataset.buyer = buyer
card.querySelector('.more-info').dataset.seller = seller
card.querySelector('.more-info').dataset.other = other
card.querySelector('.more-info').dataset.time = time
return card
},
marketOrderCard(orderDetails = {}) {
const { floID, quantity, unitValue, time, type } = orderDetails
const card = getRef('market_order_template').content.cloneNode(true).firstElementChild
card.dataset.type = type
card.classList.add(`transaction-card--${type}`)
card.querySelector('.transaction-card__type').textContent = type
card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO`
card.querySelector('.transaction-card__price').textContent = formatAmount(unitValue)
card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2)))
card.querySelector('.more-info').dataset.time = time
card.querySelector('.more-info').dataset.buyer = type === 'buy' ? floID : undefined
card.querySelector('.more-info').dataset.seller = type === 'sell' ? floID : undefined
card.querySelector('.more-info').dataset.pending = true
return card
},
marketTransactionCard(transactionDetails = {}) {
const { buyer, seller, quantity, unitValue, time } = transactionDetails
const card = getRef('market_transaction_template').content.cloneNode(true).firstElementChild
card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO`
card.querySelector('.transaction-card__price').textContent = `${unitValue}`
card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2)))
card.querySelector('.more-info').dataset.time = time
card.querySelector('.more-info').dataset.buyer = buyer
card.querySelector('.more-info').dataset.seller = seller
return card
},
}
function showProcess(id) {
getRef(id).children[0].classList.add('clip')
getRef(id).append(document.createElement('sm-spinner'))
}
function hideProcess(id) {
getRef(id).children[0].classList.remove('clip')
getRef(id).querySelector('sm-spinner')?.remove()
}
let tradeType = 'buy'
getRef('trade_type_selector').addEventListener('change', e => {
tradeType = e.detail.value
getRef('get_price').setAttribute('placeholder', tradeType === 'buy' ? 'Max price' : 'Min price')
getRef('trade_button').textContent = `${tradeType} FLO`
getRef('quantity_type').textContent = tradeType === 'buy' ? `Rupee` : `FLO`
})
async function tradeFlo() {
const quantity = parseFloat(getRef('get_quantity').value)
const price = parseFloat(getRef('get_price').value)
showProcess('trade_button_wrapper')
try {
if (tradeType === 'buy') {
await buy(quantity, price, proxy.secret)
} else {
await sell(quantity, price, proxy.secret)
}
getRef('trade_button_wrapper').append(getRef('success_template').content.cloneNode(true))
notify(`Placed ${tradeType} order`, 'success')
}
catch (err) {
getRef('trade_button_wrapper').append(getRef('failure_template').content.cloneNode(true))
notify(err.data, 'error')
}
finally {
updateRate()
getRef('get_quantity').value = 0
getRef('get_total').value = 0
setTimeout(() => {
hideProcess('trade_button_wrapper')
setTimeout(() => {
getRef('trade_button_wrapper').querySelector('.stateful-result').remove()
}, 100);
}, 4000);
}
}
getRef('quantity_selector').addEventListener('click', e => {
// Get latest balance and exchange rate
if (e.target.closest('button')) {
const target = e.target.closest('button')
const unitValue = parseFloat(getRef('get_price').value)
const fraction = parseInt(target.value) / 100
if (tradeType === 'buy') {
getRef('get_total').value = parseFloat((fraction * balance.rupee).toFixed(2))
getRef('get_quantity').value = parseFloat(((balance.rupee * fraction) / unitValue).toFixed(5))
} else {
getRef('get_total').value = parseFloat(((fraction * balance.flo) * rate.flo).toFixed(2))
getRef('get_quantity').value = parseFloat((balance.flo * fraction).toFixed(5))
}
}
})
getRef('get_price').addEventListener('keyup', e => {
const unitValue = parseFloat(getRef('get_price').value) || rate.flo
const quantity = parseFloat(getRef('get_quantity').value) || 0
getRef('get_total').value = parseFloat((quantity * unitValue).toFixed(2)) || 0
})
getRef('get_quantity').addEventListener('keyup', e => {
const unitValue = parseFloat(getRef('get_price').value)
getRef('get_total').value = parseFloat((parseFloat(e.target.value) * unitValue).toFixed(2)) || 0
})
getRef('get_total').addEventListener('keyup', e => {
const unitValue = parseFloat(getRef('get_price').value)
getRef('get_quantity').value = parseFloat((parseFloat(e.target.value) / unitValue).toFixed(5)) || 0
})
getRef('wallet_actions').addEventListener('click', e => {
if (e.target.closest('.button')) {
const target = e.target.closest('.button')
showPopup('wallet_popup')
const type = target.value
const asset = getRef('wallet_asset_selector').value
getRef('wallet_popup__cta').textContent = `${type} ${asset}`
getRef('wallet_popup__cta').setAttribute('value', type)
getRef('wallet_popup__title').textContent = `${type} ${asset}`
if (type === 'withdraw') {
getRef('get_private_key').classList.add('hide-completely')
getRef('get_private_key').removeAttribute('required')
getRef('get_private_key').removeAttribute('hiderequired')
} else {
getRef('get_private_key').setAttribute('required', '')
getRef('get_private_key').setAttribute('hiderequired', '')
getRef('get_private_key').classList.remove('hide-completely')
}
getRef('wallet_form').elementsChanged()
}
})
function showWalletResult(status, title, description) {
const animOptions = {
duration: 150,
easing: 'ease',
fill: 'forwards'
}
let icon
if (status === 'success') {
icon = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon--success" 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>`
} else {
icon = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon--failure" 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>`
}
getRef('wallet_result__icon').innerHTML = icon
getRef('wallet_result__title').textContent = title
getRef('wallet_result__description').textContent = description
getRef('wallet_form').animate(slideOutLeft, animOptions)
.onfinish = () => {
getRef('wallet_form').classList.add('hide-completely')
getRef('wallet_result').classList.remove('hide-completely')
getRef('wallet_result').animate(slideInLeft, animOptions)
}
}
function hideWalletResult() {
const animOptions = {
duration: 150,
easing: 'ease',
fill: 'forwards'
}
getRef('wallet_result').animate(slideOutRight, animOptions)
.onfinish = () => {
getRef('wallet_result').classList.add('hide-completely')
getRef('wallet_form').classList.remove('hide-completely')
getRef('wallet_form').animate(slideInRight, animOptions)
}
}
getRef('wallet_popup__cta').addEventListener('click', async e => {
const asset = getRef('wallet_asset_selector').value
const type = e.target.getAttribute('value')
const quantity = parseFloat(getRef('get_user_amount').value)
try {
showProcess('wallet_popup__cta_wrapper')
if (type === 'deposit') {
const privKey = getRef('get_private_key').value;
if (asset === 'FLO') {
await depositFLO(quantity, userID, privKey, proxy.secret)
} else {
await depositRupee(quantity, userID, privKey, proxy.secret)
}
showWalletResult('success', `Sent ${asset} deposit request`, 'This may take upto 30 mins to reflect in your wallet.')
} else {
if (asset === 'FLO') {
await withdrawFLO(quantity, proxy.secret)
} else {
await withdrawRupee(quantity, proxy.secret)
}
showWalletResult('success', `Sent ${asset} withdraw request`, 'This may take upto 30 mins to reflect in your wallet.')
}
}
catch (err) {
showWalletResult('error', `Failed`, err.data || err)
}
finally {
hideProcess('wallet_popup__cta_wrapper')
}
})
const selectedOrders = new Map()
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 slideInDown = [
{
opacity: 0,
transform: 'translateY(-1rem)'
},
{
opacity: 1,
transform: 'translateY(0)'
},
]
const slideOutUp = [
{
opacity: 1,
transform: 'translateY(0)'
},
{
opacity: 0,
transform: 'translateY(-1rem)'
},
]
getRef('orders_list').addEventListener('change', e => {
const animOptions = {
duration: 150,
easing: 'ease',
fill: 'forwards'
}
const target = e.target.parentNode;
target.classList.toggle('order-card--selected')
if (e.target.checked) {
selectedOrders.set(target.dataset.id, target.dataset.type)
} else {
selectedOrders.delete(target.dataset.id)
}
getRef('selected_orders').textContent = `${selectedOrders.size} selected`
if (selectedOrders.size === 1 && !getRef('my_orders_section__header').children[0].classList.contains('hide-completely')) {
getRef('my_orders_section__header').children[0].animate(slideOutLeft, animOptions)
.onfinish = () => {
getRef('my_orders_section__header').children[0].classList.add('hide-completely')
getRef('my_orders_section__header').children[1].classList.remove('hide-completely')
getRef('my_orders_section__header').children[1].animate(slideInLeft, animOptions)
}
} else if (selectedOrders.size === 0 && getRef('my_orders_section__header').children[0].classList.contains('hide-completely')) {
getRef('my_orders_section__header').children[1].animate(slideOutRight, animOptions)
.onfinish = () => {
getRef('my_orders_section__header').children[1].classList.add('hide-completely')
getRef('my_orders_section__header').children[0].classList.remove('hide-completely')
getRef('my_orders_section__header').children[0].animate(slideInRight, animOptions)
}
}
})
function clearSelection() {
getRef('orders_list').querySelectorAll('sm-checkbox[checked]').forEach(elem => elem.checked = false)
}
function showMoreDetails(e) {
const target = e.target.closest('.more-info')
showPopup('transaction_info_popup')
if (target.dataset.buyer !== 'undefined') {
getRef('transaction__buyer_wrapper').classList.remove('hide-completely')
getRef('transaction__buyer').value = target.dataset.buyer
} else {
getRef('transaction__buyer_wrapper').classList.add('hide-completely')
}
if (target.dataset.seller !== 'undefined') {
getRef('transaction__seller_wrapper').classList.remove('hide-completely')
getRef('transaction__seller').value = target.dataset.seller
} else {
getRef('transaction__seller_wrapper').classList.add('hide-completely')
}
getRef('transaction_time__label').textContent = target.dataset.pending === 'true' ? 'Placed on' : 'Completed on'
getRef('transaction_time').textContent = getFormattedTime(target.dataset.time)
}
getRef('orders_list').addEventListener('click', e => {
if (e.target.closest('.cancel-order')) {
getConfirmation('Cancel this order?').then(res => {
if (res) {
const target = e.target.closest('.order-card')
const id = target.dataset.id
const type = target.dataset.type
cancelOrder(type, id, proxy.secret)
.then(() => {
notify('Order cancelled', 'success')
target.animate([
{
opacity: 1
},
{
opacity: 0
},
], {
duration: 100,
fill: 'forwards'
})
.onfinish = () => {
const height = target.getBoundingClientRect().height
const siblings = Array.from(target.parentNode.children)
const nextSiblings = siblings.slice(siblings.indexOf(target) + 1)
nextSiblings.forEach(elem => {
elem.animate([
{
transform: 'translateY(0)'
},
{
transform: `translateY(-${height}px)`
},
], {
duration: 200,
fill: 'forwards'
})
.onfinish = (e) => {
e.target.cancel()
}
})
setTimeout(() => {
target.remove()
}, 200);
}
})
.catch(err => notify(err.data, 'error'))
}
})
} else if (e.target.closest('.more-info')) {
showMoreDetails(e)
}
})
function cancelAll() {
getConfirmation('Cancel all selected orders?').then(async res => {
if (res) {
try {
await Promise.all(
selectedOrders.map((type, id) => cancelOrder(type, id, proxy.secret))
)
selectedOrders.forEach((type, id) => {
getRef('orders_list').querySelector(`[data-id="${id}"]`).remove()
})
notify('All selected orders cancelled', 'success')
selectedOrders.clear()
}
catch (err) {
notify(err, 'error')
}
}
})
}
function renderUserOrders() {
const { buyOrders, sellOrders, transactions } = accountDetails
getRef('orders_list').innerHTML = '';
const frag = document.createDocumentFragment()
const ordersType = getRef('my_orders_category_selector').value
if (ordersType === 'open') {
const allOpenOrders = [...(buyOrders || myBuyOrders), ...(sellOrders || mySellOrders)].sort((a, b) => b.time_placed - a.time_placed)
allOpenOrders.forEach(order => {
const { id, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order
const orderDetails = {
id,
quantity,
type: minPrice ? 'sell' : 'buy',
price: minPrice || maxPrice,
time: time_placed
}
frag.append(render.orderCard(orderDetails))
})
} else {
(transactions || myTransactions).forEach(transaction => {
const { floID, quantity, unitValue, tx_time, buyer, seller } = transaction
let type, other;
if (seller === floID) {
type = 'Sold';
other = buyer === floID ? 'MySelf' : buyer;
} else if (buyer === floID) {
type = 'Bought';
other = seller;
} else
return;
const transactionDetails = {
buyer,
seller,
type,
other,
quantity,
unitValue,
time: tx_time
}
frag.append(render.transactionCard(transactionDetails))
});
}
getRef('orders_list').append(frag)
}
getRef('my_orders_category_selector').addEventListener('change', renderUserOrders)
getRef('market_orders_category_selector').addEventListener('change', renderMarketOrders)
async function renderMarketOrders() {
const frag = document.createDocumentFragment()
getRef('market_orders_list').innerHTML = '';
const ordersType = getRef('market_orders_category_selector').value
if (ordersType === 'open') {
try {
const [buyOrders, sellOrders] = await Promise.all([getBuyList(), getSellList()])
const allOpenOrders = [...(buyOrders), ...(sellOrders)].sort((a, b) => b.time_placed - a.time_placed)
allOpenOrders.forEach(order => {
const { floID, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order
const orderDetails = {
floID,
quantity,
type: minPrice ? 'sell' : 'buy',
unitValue: minPrice || maxPrice,
time: time_placed
}
frag.append(render.marketOrderCard(orderDetails))
})
}
catch (err) {
notify(err, 'error')
}
} else {
try {
// const marketTransactions = await getTransactionList()
const marketTransactions = myTransactions
marketTransactions.forEach(transaction => {
const { seller, buyer, quantity, unitValue, tx_time } = transaction
const transactionDetails = {
buyer,
seller,
quantity,
unitValue,
time: tx_time
}
frag.append(render.marketTransactionCard(transactionDetails))
})
const marketTransactionList = getRef('market_transaction_list_template').content.cloneNode(true)
}
catch (err) {
notify(err, 'error')
}
}
getRef('market_orders_list').append(frag)
}
getRef('market_orders_list').addEventListener('click', e => {
if (e.target.closest('.more-info')) {
showMoreDetails(e)
}
})
const openDropdowns = new Set()
function toggleDropdown(id) {
const animOptions = {
duration: 300,
fill: 'forwards',
easing: 'ease'
}
if (!openDropdowns.has(id)) {
getRef(id).querySelector('.dropdown').classList.remove('hide-completely')
getRef(id).querySelector('.dropdown').animate(slideInDown, animOptions)
.onfinish = () => {
getRef(id).classList.toggle('open')
openDropdowns.add(id)
document.addEventListener('click', handleFocusOut)
}
} else {
getRef(id).querySelector('.dropdown').animate(slideOutUp, animOptions)
.onfinish = () => {
getRef(id).querySelector('.dropdown').classList.add('hide-completely')
getRef(id).classList.toggle('open')
openDropdowns.delete(id)
}
}
}
function handleFocusOut(e) {
if (!e.target.closest('.dropdown-wrapper')) {
openDropdowns.forEach(id => toggleDropdown(id))
openDropdowns.clear()
} else {
document.addEventListener('click', handleFocusOut)
}
}
document.addEventListener('popupopened', e => {
switch (e.target.id) {
case 'registration_popup':
getRef('get_registration_key').focusIn()
break;
case 'sign_up_popup':
const { floID, privKey } = floCrypto.generateNewID()
getRef('generated_flo_id').value = floID
getRef('generated_private_key').value = privKey
break;
}
})
document.addEventListener('popupclosed', e => {
switch (e.target.id) {
case 'wallet_popup':
hideWalletResult()
break;
}
})
</script>
<script>
let userID; //container for user ID and proxy private-key
const proxy = {
private: null,
public: null,
async lock() {
if (!this.private)
throw "No proxy key found!";
let pwd = await getPromptInput("Add password", 'This password applies to this browser only!', { isPassword: true, confirmText: "Add password" });
if (!pwd)
notify("Password cannot be empty", 'error');
else if (pwd.length < 4)
notify("Password minimum length is 4", 'error');
else {
let tmp = Crypto.AES.encrypt(this.private, pwd);
localStorage.setItem("proxy_secret", "?" + tmp);
notify("Successfully locked with Password", 'success');
}
},
clear() {
localStorage.removeItem("proxy_secret");
this.private = null;
this.public = null;
},
set secret(key) {
localStorage.setItem("proxy_secret", key);
this.private = key;
this.public = floCrypto.getPubKeyHex(key);
},
get secret() {
if (this.private)
return this.private;
try {
let tmp = localStorage.getItem("proxy_secret");
if (typeof tmp === "string" && tmp.startsWith("?")) {
getPromptInput("Enter password", '', { isPassword: true }).then(pwd => {
if (!pwd)
throw "Password Required for making transactions";
else {
try {
tmp = Crypto.AES.decrypt(tmp.substring(1), pwd);
} catch (error) {
throw "Incorrect Password! Password Required for making transactions";
}
}
});
}
this.private = tmp;
this.public = floCrypto.getPubKeyHex(tmp);
return this.private;
} catch (error) {
alert(error);
console.error(error);
throw "Unable to fetch Proxy secret";
}
}
}
function updateRate() {
getRate().then(rate => {
rate.flo = rate
getRef('flo_rate').textContent = formatAmount(parseFloat(rate))
getRef('get_price').value = parseFloat(parseFloat(rate).toFixed(2))
}).catch(error => console.error(error))
}
function refresh(init = false) {
if (init)
console.info("init");
else
console.info("refresh");
updateRate()
renderMarketOrders()
account();
}
function showBalance(containerId, availableBalance = 0, lockedBalance = 0) {
getRef(containerId).innerHTML = ''
const templateToClone = lockedBalance ? 'locked_balance_template' : 'net_balance_template';
const card = getRef(templateToClone).content.cloneNode(true)
card.querySelector('.available-balance').textContent = availableBalance
if (lockedBalance) {
card.querySelector('.locked-balance').textContent = lockedBalance
}
getRef(containerId).className = lockedBalance ? 'grid balance-card__amount-wrapper' : ''
getRef(containerId).parentNode.className = `balance-card ${lockedBalance ? 'is-locked' : ''}`
getRef(containerId).append(card)
}
const myBuyOrders = [
{
id: 'dfs5g16sdg1',
time_placed: generateRandomDate(),
quantity: 14.5,
maxPrice: 1.36,
},
{
id: 'f4gd1d56fg1',
time_placed: generateRandomDate(),
quantity: 78.3,
maxPrice: 1.26,
},
{
id: 's4dg5s4d1g98',
time_placed: generateRandomDate(),
quantity: 14.5,
maxPrice: 1.28,
},
]
const mySellOrders = [
{
id: 'sd8g45g419s6',
time_placed: generateRandomDate(),
quantity: 15,
minPrice: 1.16,
},
{
id: 's59d1g9ws18d',
time_placed: generateRandomDate(),
quantity: 8.3,
minPrice: 1.86,
},
{
id: 'w899e1g4d1g98',
time_placed: generateRandomDate(),
quantity: 18.5,
minPrice: 1.64,
},
]
const myTransactions = [
{
id: 'sd8g45g419s6',
tx_time: generateRandomDate(),
quantity: 15,
unitValue: 1.16,
buyer: 'w8eg4w8eg4w98werg9'
},
{
id: 's59d1g9ws18d',
tx_time: generateRandomDate(),
quantity: 8.3,
unitValue: 1.86,
buyer: 'w8eg4w8eg4w98werg9'
},
{
id: 'w899e1g4d1g98',
tx_time: generateRandomDate(),
quantity: 18.5,
unitValue: 1.64,
buyer: 'w8eg4w8eg4w98werg9'
},
]
const market = {
buyOrders: [
{
floID: 'dfs5g16sdg1',
time_placed: generateRandomDate(),
quantity: 14.5,
maxPrice: 1.36,
},
{
floID: 'f4gd1d56fg1',
time_placed: generateRandomDate(),
quantity: 78.3,
maxPrice: 1.26,
},
{
floID: 's4dg5s4d1g98',
time_placed: generateRandomDate(),
quantity: 14.5,
maxPrice: 1.28,
},
],
sellOrders: [
{
floID: 'sd8g45g419s6',
time_placed: generateRandomDate(),
quantity: 15,
minPrice: 1.16,
},
{
floID: 's59d1g9ws18d',
time_placed: generateRandomDate(),
quantity: 8.3,
minPrice: 1.86,
},
{
floID: 'w899e1g4d1g98',
time_placed: generateRandomDate(),
quantity: 18.5,
minPrice: 1.64,
},
]
}
function generateRandomDate() {
return new Date() - Math.floor(Math.random() * 10000000000);
}
const balance = {}
let accountDetails = {}
function account() {
getAccount().then(acc => {
getRef("login_form").classList.add('hide-completely')
getRef('home').classList.add('signed-in')
accountDetails = acc
console.debug(acc);
//Element display
document.querySelectorAll(".user-content").forEach(elem => elem.classList.remove('hide-completely'))
getRef('trade_form').classList.remove('hide-completely')
getRef("user_id").value = acc.floID;
userID = acc.floID;
//FLO Balance
let flo_total = acc.coins.reduce((a, x) => a + x.quantity, 0);
let flo_locked = acc.sellOrders.reduce((a, x) => a + x.quantity, 0);
let flo_net = flo_total - flo_locked;
console.debug("FLO", flo_total, flo_locked, flo_net);
balance.flo = flo_net
showBalance("flo_balance", flo_net, flo_locked)
//Rupee Balance
let rupee_total = acc.rupee_total;
let rupee_locked = acc.buyOrders.reduce((a, x) => a + x.quantity * x.maxPrice, 0);
let rupee_net = rupee_total - rupee_locked;
console.debug("RUPEE", rupee_total, rupee_locked, rupee_net);
balance.rupee = rupee_net
showBalance("rupee_balance", rupee_net, rupee_locked)
//My orders
renderUserOrders()
try {
proxy.secret;
} catch (error) {
console.warn(error);
}
}).catch(error => {
getRef('home').classList.remove('signed-in')
if (error instanceof ResponseError) {
let response = JSON.parse(error.data)
console.log(error);
console.log(response);
getRef("login_form").classList.remove('hide-completely')
document.querySelectorAll(".user-content").forEach(elem => elem.classList.add('hide-completely'))
getRef('sign_in_id').value = response.sid;
proxy.clear();
} else
console.error(error);
})
};
const UI_evt = {
signup(privKey) {
let sid = getRef('sign_in_id').value;
if (!privKey)
privKey = getRef('get_registration_key').value.trim()
if (privKey !== '') {
signUp(privKey, sid).then(result => {
console.info(result);
notify("Account registered!", 'success')
hidePopup()
}).catch(error => {
notify(error, 'error');
});
}
},
logout() {
getConfirmation('Log out?', { cancelText: 'Stay', confirmText: 'Log out' }).then(res => {
if (res) {
logout().then(result => {
console.warn(result);
proxy.clear();
location.reload();
}).catch(error => console.error(error));
}
})
},
login() {
let privKey = getRef('login_form__priv_key').value;
let sid = getRef('sign_in_id').value;
let rememberMe = getRef('remember_me').checked;
let tmpKey = floCrypto.generateNewID();
login(privKey, tmpKey.pubKey, sid, rememberMe).then(result => {
console.log(result);
proxy.secret = tmpKey.privKey;
account();
}).catch(error => console.error(error));
}
};
function registerID() {
UI_evt.signup(getRef('generated_private_key').value)
hidePopup()
}
window.addEventListener('load', e => {
refresh(true);
})
</script>
</body>
</html>