2786 lines
159 KiB
HTML
2786 lines
159 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<title>FLO Wallet</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
<meta charset="UTF-8">
|
|
<meta name="description"
|
|
content="This webapp allows monitoring FLO addresses and performing transactions based on FLO blockchain.">
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
|
rel="stylesheet">
|
|
<link rel="stylesheet" href="css/main.min.css">
|
|
<script>
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
|
|
const floGlobals = {
|
|
blockchain: "FLO",
|
|
tokenApiUrl: 'https://ranchimallflo.duckdns.org',
|
|
// tokenURL: 'https://ranchimallflo-testing.duckdns.org/'
|
|
}
|
|
</script>
|
|
<script src="scripts/components.js" defer></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
|
|
<script src="scripts/lib.js"></script>
|
|
<script src="scripts/floCrypto.js"></script>
|
|
<script src="scripts/floBlockchainAPI.js"></script>
|
|
<script src="scripts/floTokenAPI.js"></script>
|
|
<script src="scripts/compactIDB.js"></script>
|
|
<script src="scripts/flo-webwallet.js"></script>
|
|
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
|
<script>
|
|
function onLoadStartUp() {
|
|
var IDBObjects = {
|
|
lastSync: {},
|
|
labels: {},
|
|
pinnedIds: {},
|
|
transactions: {}
|
|
}
|
|
loader()
|
|
compactIDB.initDB("FLOwebWallet", IDBObjects).then(async result => {
|
|
render.savedIds();
|
|
if (!floGlobals.tokens || !floGlobals.smartContracts) {
|
|
fetchJSON(`${floGlobals.tokenApiUrl}/api/v2/tokenSmartContractList`).then(({ tokens, smartContracts }) => {
|
|
floGlobals.scMap = new Map()
|
|
floGlobals.tokens = tokens.sort((a, b) => a.localeCompare(b))
|
|
floGlobals.smartContracts = smartContracts
|
|
.filter(sc => sc.status === 'active')
|
|
.sort((a, b) => a.contractName.localeCompare(b.contractName))
|
|
floGlobals.smartContracts.forEach((sc, index) => {
|
|
floGlobals.scMap.set(`${sc.contractName}_${sc.contractAddress}`, index)
|
|
})
|
|
routeTo(window.location.hash, { firstLoad: true })
|
|
}).catch(e => {
|
|
console.error(e)
|
|
notify('Error fetching tokens', 'error')
|
|
}).finally(() => {
|
|
loader(false)
|
|
})
|
|
}
|
|
console.log(result)
|
|
}).catch(error => console.error(error))
|
|
}
|
|
</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>
|
|
<div id="loader" class="hidden">
|
|
<sm-spinner></sm-spinner>
|
|
<p>
|
|
Loading FLO Wallet
|
|
</p>
|
|
</div>
|
|
<div id="main_card">
|
|
<header id="main_header" class="flex align-center space-between">
|
|
<div class="flex align-center">
|
|
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108"
|
|
style="enable-background:new 0 0 90.5 106.3;" xml:space="preserve">
|
|
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
|
|
c1.3-3.6,2.6-5.2,12.9-15.1c6.2-5.9,9.3-10.3,11.1-15.5c0.7-2.1,0.8-7.6,0.2-9.4C66.5,12,61.7,6.7,53.7,1.6c-3-1.9-4.3-2.1-4.3-0.8
|
|
c0,0.3-0.5,1.4-1,2.4l-1,1.8l-2.8-1.9c-1.5-1.1-3.4-2.2-4.1-2.6c-1.3-0.7-2.4-0.6-2.4,0.2c0,0.3-1.4,3.4-2,4.4
|
|
c0,0.1-0.4-0.1-0.9-0.4c-6.1-4.4-8.7-5.5-8.7-3.9c0,0.7-1.8,4.2-4,7.9C16,19.5,9.4,24.9,2.6,24.9c-3,0-2.9-0.1-2,3.4
|
|
c0.7,2.8,1.1,3.1,3.6,2.3c2.3-0.7,3.9-1.5,5.8-2.9c0.8-0.6,1.5-0.9,1.6-0.9c0.1,0.1,0.5,1,0.7,2.1s0.7,2,0.9,2.1
|
|
c0.8,0.3,5.1-1.3,7.5-2.9l2.3-1.5l0.5,1.8c0.6,2.4,1,2.7,3.3,2.1c3.9-1,7.7-3.7,11.5-8.2l2-2.4l-0.2,2.1c-0.6,5.4-4.3,11.4-11.3,18
|
|
c-1.8,1.7-4.7,4.5-6.5,6.2c-10.7,10.2-10,18.6,2,26.5c2.7,1.8,10.3,5.8,15.3,8c0.9,0.4,3.3,1.7,5.3,2.9c11,6.5,16.4,13.1,16.4,19.7
|
|
c0,1.3,0.1,2.4,0.2,2.6l0,0c0.3,0.3,0.1,0.3,3-0.5c1.4-0.4,2.6-0.9,2.8-1.1c0.4-0.6-0.6-3.7-1.8-6.1c-1.3-2.5-5.6-7-8.9-9.4
|
|
c-3.8-2.8-9.3-5.9-17-9.7c-8.5-4.2-11.8-6.2-14.7-9.1c-2.6-2.6-3.9-5.3-3.9-8.2c0-4.6,2.3-8.6,8.3-14.1c9.4-8.7,13-13,15.5-18.8
|
|
c1.3-3,1.4-3.4,1.4-6.7c0-3.1-0.1-3.8-1.1-6l-1.1-2.4l1-1.6c0.5-0.9,1.2-2.1,1.5-2.6l0.5-1l1.5,2.1c1.8,2.6,3.2,6.8,3.2,9.3
|
|
c0,1.7-0.6,4.7-1.4,6.4c-0.2,0.4-0.4,1-0.5,1.3c-0.1,0.3-1.1,2-2.2,3.7c-2,3-5.2,6.4-13.4,14.2c-5.7,5.4-7.6,8.6-7.8,13.1
|
|
c-0.2,3.7,0.7,5.9,3.7,9.2c3.2,3.4,6.9,5.8,17.4,11c12.1,6,17.3,9.6,21.3,14.5c2.5,3.2,3.7,5.8,3.9,9.3c0.1,1.6,0.3,3,0.5,3
|
|
c0.1,0.1,0.8,0,1.4-0.2s1.9-0.5,2.7-0.7l1.5-0.4l-0.2-1.5c-0.7-5.1-5.4-10.8-13.1-16c-4.4-2.9-5.8-3.7-17.3-9.4
|
|
c-5.7-2.8-9.2-5.1-11.8-7.6c-4.3-4.2-5.1-8.8-2.7-13.9c1.4-2.8,2.7-4.4,12.5-13.8c8-7.7,11.4-13.7,11.4-20.1c0-5.1-2.3-9.9-6.9-14.3
|
|
c-1.1-1-2-2-2.1-2.2c-0.2-0.4,1.5-3.9,1.9-3.9c1.2,0,7.8,6.3,9.7,9.2c2,3.3,2.5,5,2.5,8.9c0,3.9-0.6,5.9-2.9,9.8
|
|
c-2.4,4.1-4.2,6-14.2,15.5c-3.4,3.2-5.7,6.1-6.9,8.7c-0.9,2-1.1,2.7-1.1,5.1c0,2.3,0.2,3.2,1,4.9c1.9,4,7.4,8.5,15.4,12.4
|
|
c12.5,6.1,15.1,7.6,19.4,10.7c7.2,5.3,10.6,10.5,10.6,16c0,1.3,0.1,2.4,0.3,2.5c0.4,0.3,4.8-0.8,5.5-1.3
|
|
C90.7,104.4,90.7,104.3,90.2,102.5z M20.3,23.3L20.3,23.3c-2,1-3.3,1.4-4.8,1.5L13.3,25l2.3-2.8c3.7-4.5,6.4-8.9,10-16
|
|
c0.9-1.8,1.8-3.5,2-3.6c0.4-0.4,2.6,1.1,5.1,3.4l2.1,1.9l-1.9,2.8C28.2,17.5,24.5,21.2,20.3,23.3z M39.3,17.4
|
|
c-1.2,1.7-6.5,5.7-8.6,6.5v0c-1.1,0.4-2.8,0.8-3.9,0.9L24.9,25l2.1-2.6c2.5-3.1,5.1-7,7-10.4c0.7-1.4,1.4-2.5,1.5-2.6
|
|
c0.3-0.4,1.7,1.4,3,4.1l1.5,3L39.3,17.4z M44.6,10c-0.7,1.2-1.4,2.1-1.5,2.1c-0.1,0-1.5-1.4-3-3l-2.8-3l0.6-1.5
|
|
c1.1-2.6,1.3-2.7,3.4-1c1.9,1.5,4.5,3.8,4.5,4.1C45.8,7.8,45.3,8.9,44.6,10z" />
|
|
</svg>
|
|
<div>
|
|
<span style="font-size: 0.8rem;">
|
|
RanchiMall
|
|
</span>
|
|
<h4>FLO Wallet</h4>
|
|
</div>
|
|
</div>
|
|
<theme-toggle></theme-toggle>
|
|
</header>
|
|
<div id="pages_container">
|
|
<div id="home" class="page flex h-100">
|
|
<div class="flex direction-column gap-2 h-100">
|
|
<section id="primary_actions_wrapper">
|
|
<button id="gen_new_addr_btn" class="button primary-action interact">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
|
<g>
|
|
<rect fill="none" height="24" width="24" />
|
|
</g>
|
|
<g>
|
|
<g>
|
|
<path
|
|
d="M18.32,4.26C16.84,3.05,15.01,2.25,13,2.05v2.02c1.46,0.18,2.79,0.76,3.9,1.62L18.32,4.26z M19.93,11h2.02 c-0.2-2.01-1-3.84-2.21-5.32L18.31,7.1C19.17,8.21,19.75,9.54,19.93,11z M18.31,16.9l1.43,1.43c1.21-1.48,2.01-3.32,2.21-5.32 h-2.02C19.75,14.46,19.17,15.79,18.31,16.9z M13,19.93v2.02c2.01-0.2,3.84-1,5.32-2.21l-1.43-1.43 C15.79,19.17,14.46,19.75,13,19.93z M13,12V7h-2v5H7l5,5l5-5H13z M11,19.93v2.02c-5.05-0.5-9-4.76-9-9.95s3.95-9.45,9-9.95v2.02 C7.05,4.56,4,7.92,4,12S7.05,19.44,11,19.93z" />
|
|
</g>
|
|
</g>
|
|
</svg>
|
|
Generate FLO address
|
|
</button>
|
|
<button id="retrieve_addr_btn" class="button primary-action interact"
|
|
onclick="openPopup('retrieve_flo_id_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 0h24v24H0V0z" fill="none" />
|
|
<path
|
|
d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z" />
|
|
</svg>
|
|
Retrieve FLO address
|
|
</button>
|
|
<button class="button primary-action interact" onclick="openPopup('check_balance_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="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>
|
|
Check balance
|
|
</button>
|
|
</section>
|
|
<section class="flex direction-column h-100">
|
|
<div class="grid align-center gap-0-5">
|
|
<div class="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="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>
|
|
<h4>Saved FLO addresses</h4>
|
|
</div>
|
|
<p class="flex align-center">
|
|
<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 id="saved_ids_tip">Click 'Add FLO address' to add a new FLO address.</span>
|
|
</p>
|
|
</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>
|
|
<h4>No Saved FLO address</h4>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<button id="add_address_button" class="button interact fab" 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 0h24v24H0V0z" 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 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" />
|
|
</svg>
|
|
Add FLO address
|
|
</button>
|
|
</div>
|
|
<div id="transactions" class="hidden page">
|
|
<header class="grid">
|
|
<div class="grid align-center w-100">
|
|
<a href="#/home" class="flex interact icon-only">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<button class="icon-only" style="margin-left:auto" onclick="render.transactions()"
|
|
title="Refresh">
|
|
<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="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>
|
|
</div>
|
|
<div id="queried_flo_address"></div>
|
|
</header>
|
|
<section id="transactions_scroller" class="grid gap-2" style="overflow-y: auto;padding: 0 1rem;">
|
|
<div id="transactions_hero_section" class="grid gap-2"
|
|
style="background-color: rgba(var(--text-color), 0.03); padding: 1.5rem 1rem; border-radius: 0.5rem;">
|
|
<div class="flex align-center">
|
|
<svg class="icon margin-right-0-5" viewBox="0 0 48 48"
|
|
style="enable-background:new 0 0 107.65 47.07;" xml:space="preserve">
|
|
<path class="flo-logo"
|
|
d="M34.2,32.4c0,0,3.75-0.18,7.41-3.86c2.96-2.98,3.65-6.66,3.99-8.52c-11.04-0.63-12.36,0.99-13.71,1.68
|
|
c-1.19,0.61-5.33,4.55-5.33,4.55s3.06-3.13,3.2-9.94c0.09-4.54-1.02-7.39-2.72-10.64C25.29,2.33,22.79,0,22.79,0l0.01,4.97
|
|
c0,0,4.35,2.84,4.35,11.84c0,6.52-4.35,11.02-4.35,11.02s-4.35-4.5-4.35-11.02c0-9.01,4.35-11.84,4.35-11.84L22.79,0
|
|
c0,0-2.48,2.33-4.23,5.67c-1.7,3.25-2.81,6.1-2.72,10.64c0.13,6.81,3.2,9.94,3.2,9.94s-4.14-3.95-5.33-4.55
|
|
c-1.35-0.69-2.67-2.31-13.71-1.68c0.34,1.86,1.03,5.54,3.99,8.52c3.66,3.68,7.41,3.86,7.41,3.86s-5.05-2.03-7.15-9.45
|
|
c0,0,5.76-0.7,9.63,1.87c2.52,1.67,4.86,4.26,6.79,6.01c0,0-2.58-0.04-6.81,1.88c-2.54,1.15-3.92,2.84-4.44,4.38
|
|
c-0.36,1.06-0.2,2.27-0.2,2.27s3.31,0.31,5.94,0c1.99-0.23,3.42-2.16,3.42-2.16s-2,0.78-3.95,0.78c-2.06,0-2.67-0.66-2.67-0.66
|
|
c0.98-3.64,8.68-5.19,8.68-5.19s-1.34,2.6-1.42,6.5c-0.1,4.79,3.57,8.52,3.57,8.45c0,0.07,3.67-3.66,3.57-8.45
|
|
c-0.08-3.9-1.42-6.5-1.42-6.5s7.71,1.55,8.68,5.19c0,0-0.61,0.66-2.67,0.66c-1.95,0-3.95-0.78-3.95-0.78s1.43,1.93,3.42,2.16
|
|
c2.63,0.31,5.94,0,5.94,0s0.16-1.21-0.2-2.27c-0.52-1.54-1.9-3.23-4.44-4.38c-4.23-1.92-6.81-1.88-6.81-1.88
|
|
c1.93-1.76,4.27-4.34,6.79-6.01c3.87-2.57,9.63-1.87,9.63-1.87C39.26,30.38,34.2,32.4,34.2,32.4z M22.8,43.06
|
|
c-0.95-1.37-1.47-2.13-1.47-4.26c0-2.4,1.12-4.61,1.47-5.14c0.35,0.52,1.47,2.74,1.47,5.14C24.27,40.92,23.75,41.69,22.8,43.06z">
|
|
</path>
|
|
</svg>
|
|
<div class="grid gap-0-5">
|
|
<h5>FLO balance</h5>
|
|
<span id="flo_balance"></span>
|
|
</div>
|
|
</div>
|
|
<div id="tokens" class="grid gap-0-5 hidden">
|
|
<div class="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="M0 0h24v24H0V0z" fill="none" />
|
|
<path
|
|
d="M15 4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z" />
|
|
<path
|
|
d="M3 12c0-2.61 1.67-4.83 4-5.65V4.26C3.55 5.15 1 8.27 1 12s2.55 6.85 6 7.74v-2.09c-2.33-.82-4-3.04-4-5.65z" />
|
|
</svg>
|
|
<h5>Tokens owned</h5>
|
|
</div>
|
|
<ul id="token_list"></ul>
|
|
</div>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<div id="transactions_header" class="flex align-center space-between sticky top-0"
|
|
style="background-color: rgba(var(--foreground-color),1); padding-bottom: 0.5rem;">
|
|
<div class="flex">
|
|
<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="M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z" />
|
|
</svg>
|
|
<h5>Transactions</h5>
|
|
</div>
|
|
<sm-chips id="filter_selector">
|
|
<sm-chip value="sent" selected>Sent</sm-chip>
|
|
<sm-chip value="received">Received</sm-chip>
|
|
<sm-chip value="all">All</sm-chip>
|
|
</sm-chips>
|
|
</div>
|
|
<ul id="transactions_list" class="flex"></ul>
|
|
</div>
|
|
</section>
|
|
<div class="empty-state justify-center align-center h-100">
|
|
<div class="grid justify-center text-center gap-1">
|
|
<sm-spinner class="justify-self-center"></sm-spinner>
|
|
<span>Loading transactions</span>
|
|
</div>
|
|
</div>
|
|
<button id="scroll_to_top" class="fab hidden" onclick="backToTop()">
|
|
<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="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div id="send" class="page hidden gap-1">
|
|
<div class="grid full-bleed">
|
|
<h3>Send</h3>
|
|
<p>Perform FLO blockchain transactions</p>
|
|
</div>
|
|
<sm-form id="send_form">
|
|
<div class="grid gap-1">
|
|
<div id="balance_card">
|
|
<div class="flex align-center space-between">
|
|
<h5>Balance</h5>
|
|
</div>
|
|
<p style="line-height: 1.2; opacity: 0.7;">
|
|
Sender balance will be shown once you enter sender private key
|
|
</p>
|
|
</div>
|
|
<sm-input id="get_private_key_field" placeholder="Sender's private key" class="password-field"
|
|
type="password" error-text="Invalid private key" data-private-key required autofocus>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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>
|
|
<sm-input id="receiver" class="w-100" placeholder="Receiver's FLO address"
|
|
error-text="Invalid FLO address" data-flo-address="" animate required>
|
|
<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>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<sm-input id="tx_flo_amount" type="number" placeholder="FLO amount" step="0.00000001"
|
|
min="0.00000001" error-text="Invalid amount" animate required>
|
|
</sm-input>
|
|
<sm-input id="tx_token_amount" type="number" class="hidden" placeholder="Token amount"
|
|
step="0.00000001" min="0.00000001" error-text="Invalid amount" animate required disabled>
|
|
</sm-input>
|
|
<div id="flo_data_wrapper" class="grid gap-0-5">
|
|
<sm-textarea id="flo_data_textarea" placeholder="FLO data" rows="8" maxlength="1040"
|
|
animate>
|
|
</sm-textarea>
|
|
<div id="show_character_count">1040/1040</div>
|
|
<p id="flo_data_status"></p>
|
|
</div>
|
|
<button class="button button--primary hidden" id="fix_invalid_button"
|
|
onclick="removeInvalid()">Fix</button>
|
|
<div class="multi-state-button">
|
|
<button id="send_button" class="button button--primary cta" type="submit"
|
|
onclick="sendMessage()">Send</button>
|
|
</div>
|
|
</div>
|
|
</sm-form>
|
|
</div>
|
|
<div id="smartcontracts" class="page hidden">
|
|
<div class="grid gap-2">
|
|
<div class="grid full-bleed">
|
|
<h3>Smart Contracts</h3>
|
|
<p>
|
|
Create, participate and manage smart contracts on FLO blockchain.
|
|
</p>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<h4>Actions</h4>
|
|
<ul id="smart_contract_actions" class="flex align-center flex-wrap gap-0-5">
|
|
<li>
|
|
<a class="button smart-contract-action" href="#/smartcontracts/deposit">
|
|
<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="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z" />
|
|
</svg>
|
|
<span>Deposit</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="button smart-contract-action" href="#/smartcontracts/participate">
|
|
<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="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
|
|
</svg>
|
|
<span>Participate</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="button smart-contract-action" href="#/smartcontracts/updateprice">
|
|
<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="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12.06,19v-2.01c-0.02,0-0.04,0-0.06,0 c-1.28,0-2.56-0.49-3.54-1.46c-1.71-1.71-1.92-4.35-0.64-6.29l1.1,1.1c-0.71,1.33-0.53,3.01,0.59,4.13c0.7,0.7,1.62,1.03,2.54,1.01 v-2.14l2.83,2.83L12.06,19z M16.17,14.76l-1.1-1.1c0.71-1.33,0.53-3.01-0.59-4.13C13.79,8.84,12.9,8.5,12,8.5c-0.02,0-0.04,0-0.06,0 v2.15L9.11,7.83L11.94,5v2.02c1.3-0.02,2.61,0.45,3.6,1.45C17.24,10.17,17.45,12.82,16.17,14.76z" />
|
|
</svg>
|
|
<span>Update price</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="button smart-contract-action" href="#/smartcontracts/trigger">
|
|
<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="M14.59,7.41L18.17,11H6v2h12.17l-3.59,3.59L16,18l6-6l-6-6L14.59,7.41z M2,6v12h2V6H2z" />
|
|
</svg>
|
|
<span>Trigger</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<h4>Creation templates</h4>
|
|
<ul id="smart_contract_creation_templates">
|
|
<li>
|
|
<a class="button smart-contract-template"
|
|
href="#/smartcontracts/create?type=one-time-event&subtype=time-trigger">
|
|
<h4>
|
|
Time trigger (One time event)
|
|
</h4>
|
|
<p>
|
|
Suitable for time-specific events like crowdfunding.
|
|
</p>
|
|
<svg class="icon margin-left-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="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="button smart-contract-template"
|
|
href="#/smartcontracts/create?type=one-time-event&subtype=external-trigger">
|
|
<h4>
|
|
External trigger (One time event)
|
|
</h4>
|
|
<p>
|
|
Suitable for externally triggered events like voting.
|
|
</p>
|
|
<svg class="icon margin-left-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="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="button smart-contract-template"
|
|
href="#/smartcontracts/create?type=continuous-event&subtype=tokenswap">
|
|
<h4>
|
|
Continuous event
|
|
</h4>
|
|
<p>
|
|
Suitable for ongoing processes involving multiple participants, such as
|
|
token swaps.
|
|
</p>
|
|
<svg class="icon margin-left-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="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="grid hidden gap-1">
|
|
<div class="flex gap-0-5 align-center">
|
|
<a class="button icon-only" href="#/smartcontracts">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<h4>Create smart contract</h4>
|
|
</div>
|
|
<sm-form id="smart_contract_creation_form"></sm-form>
|
|
</div>
|
|
<div class="grid hidden gap-1">
|
|
<div class="flex gap-0-5 align-center">
|
|
<a class="button icon-only" href="#/smartcontracts">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<h4>Deposit</h4>
|
|
</div>
|
|
<sm-form id="smart_contract_deposit_form"></sm-form>
|
|
</div>
|
|
<div class="grid hidden gap-1">
|
|
<div class="flex gap-0-5 align-center">
|
|
<a class="button icon-only" href="#/smartcontracts">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<h4>Participate</h4>
|
|
</div>
|
|
<sm-form id="smart_contract_participate_form"></sm-form>
|
|
</div>
|
|
<div class="grid hidden gap-1">
|
|
<div class="flex gap-0-5 align-center">
|
|
<a class="button icon-only" href="#/smartcontracts">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<h4>Update price</h4>
|
|
</div>
|
|
<sm-form id="smart_contract_update_form"></sm-form>
|
|
</div>
|
|
<div class="grid hidden gap-1">
|
|
<div class="flex gap-0-5 align-center">
|
|
<a class="button icon-only" href="#/smartcontracts">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</a>
|
|
<h4>Trigger</h4>
|
|
</div>
|
|
<sm-form id="smart_contract_trigger_form"></sm-form>
|
|
</div>
|
|
</div>
|
|
<div id="settings" class="page hidden gap-2">
|
|
<h3>Settings</h3>
|
|
<section class="grid gap-1">
|
|
<h4>Accent color</h4>
|
|
<color-grid id="accent_color_selector"></color-grid>
|
|
</section>
|
|
<section id="deleteDB" class="grid gap-0-5">
|
|
<h4>Clear all local data</h4>
|
|
<p>This will delete all local Web Wallet data like added addresses and locally stored
|
|
transactions.After clearing local data you may experience slow loading of newly added address,
|
|
please proceed cautiously! </p>
|
|
<button class="button button--danger cta justify-self-start" onclick="deleteDB()">Clear
|
|
data</button>
|
|
</section>
|
|
<div class="flex">
|
|
<b>
|
|
Powered by
|
|
<a class="border-card" href="https://flo.cash/" target="_blank" rel="noopener noreferrer">FLO
|
|
blockchain</a>
|
|
</b>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<nav id="main_navbar">
|
|
<ul>
|
|
<li>
|
|
<a href="#/home" class="nav-item interact">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
width="24px" fill="#000000">
|
|
<path d="M0 0h24v24H0z" fill="none" />
|
|
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
|
|
</svg>
|
|
<span class="nav-item__title">Home</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#/send" class="nav-item interact" title='Send FLO data'>
|
|
<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>
|
|
<span class="nav-item__title">Send</span>
|
|
</a>
|
|
</li>
|
|
<li class="hidden">
|
|
<a href="#/smartcontracts" class="nav-item interact" title='Send FLO data'>
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
|
<path d="M0,0h24v24H0V0z" fill="none" />
|
|
<g>
|
|
<path
|
|
d="M19.5,3.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2v14H3v3c0,1.66,1.34,3,3,3h12c1.66,0,3-1.34,3-3V2 L19.5,3.5z M19,19c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H8V5h11V19z" />
|
|
<rect height="2" width="6" x="9" y="7" />
|
|
<rect height="2" width="2" x="16" y="7" />
|
|
<rect height="2" width="6" x="9" y="10" />
|
|
<rect height="2" width="2" x="16" y="10" />
|
|
</g>
|
|
</svg>
|
|
<span class="nav-item__title">Smart Contracts</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#/settings" class="nav-item interact">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
|
<g>
|
|
<path d="M0,0h24v24H0V0z" fill="none" />
|
|
<path
|
|
d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
|
|
</g>
|
|
</svg>
|
|
<span class="nav-item__title">Settings</span>
|
|
</button>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
<sm-popup id="add_address_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>
|
|
<h3>Add address</h3>
|
|
</header>
|
|
<sm-form>
|
|
<sm-input id="flo_addr_to_save" placeholder="FLO address" error-text="Invalid FLO address" autofocus
|
|
data-flo-address animate required>
|
|
</sm-input>
|
|
<sm-input id="label_to_save" placeholder="Name" animate></sm-input>
|
|
<button class="button button--primary cta" type="submit" onclick="saveFloId()" disabled>Add</button>
|
|
</sm-form>
|
|
</sm-popup>
|
|
<sm-popup id="edit_saved_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>
|
|
<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="new_addr_label" placeholder="Name" autofocus animate required></sm-input>
|
|
<div class="flex align-center space-between">
|
|
<button class="button icon-only" title="Delete this FLO address?" onclick="deleteSaved()">
|
|
<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" type="submit" onclick="saveChanges()">Save</button>
|
|
</div>
|
|
</sm-form>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="create_flo_id_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">
|
|
<div id="flo_id_warning" class="grid justify-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="M0 0h24v24H0z" fill="none" />
|
|
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
|
|
</svg>
|
|
<h3>Keep your keys safe!</h3>
|
|
<strong>Don't share with anyone. Once lost private key can't be
|
|
recovered.</strong>
|
|
</div>
|
|
<div id="created_flo_id"></div>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="retrieve_flo_id_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>
|
|
<section class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<h4>Forgot FLO address?</h4>
|
|
<p>In case of FLO address being lost and you have the private key associated with it. You can recover
|
|
it.</p>
|
|
</div>
|
|
<sm-form>
|
|
<div id="recovered_flo_id_wrapper" class="hidden">
|
|
<h5>Recovered FLO address</h5>
|
|
<sm-copy id="recovered_flo_id"></sm-copy>
|
|
</div>
|
|
<sm-input id="retrieve_flo_id_field" type="password" error-text="Invalid private key"
|
|
placeholder="Private key" class="password-field" data-private-key required autofocus>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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 class="button button--primary cta" type="submit" onclick="retrieveFloId()">Recover</button>
|
|
</sm-form>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="check_balance_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>Check balance</h3>
|
|
</header>
|
|
<sm-form id="check_balance_form">
|
|
<sm-input id="check_balance_field" type="text" error-text="Invalid FLO address" placeholder="FLO address"
|
|
data-flo-address="" required autofocus>
|
|
</sm-input>
|
|
<div class="multi-state-button">
|
|
<button id="check_balance_button" class="button button--primary cta" type="submit"
|
|
onclick="checkBalance()" disabled>Check</button>
|
|
</div>
|
|
</sm-form>
|
|
<div id="balance_wrapper" class="hidden grid gap-0-5" style="margin-top: 2rem">
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="saved_ids_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>Select an address</h3>
|
|
</header>
|
|
<section class="grid gap-1">
|
|
<sm-input id="search_saved_ids_picker" placeholder="Search"></sm-input>
|
|
<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 addresses</h4>
|
|
</div>
|
|
</section>
|
|
</sm-popup>
|
|
<sm-popup id="transaction_result_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>
|
|
</header>
|
|
<div id="transaction_result"></div>
|
|
</sm-popup>
|
|
<template id="saved_id_template">
|
|
<li class="saved-id grid interact" tabindex="0">
|
|
<button class="interact edit-saved icon-only" title="Edit name">
|
|
<div class="saved-id__initial"></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="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
|
</svg>
|
|
</button>
|
|
<h4 class="saved-id__label"></h4>
|
|
<div class="grid align-center" style="grid-template-columns: 1fr auto;">
|
|
<div class="saved-id__flo-id overflow-ellipsis"></div>
|
|
<button class="copy-saved-id icon-only margin-left-0-5" title="Copy FLO address">
|
|
<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="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<template id="transaction_template">
|
|
<li class="transaction grid">
|
|
<div class="flex align-center">
|
|
<div class="transaction__icon"></div>
|
|
<div class="transaction__receiver"></div>
|
|
</div>
|
|
<p class="transaction__flo-data breakable"></p>
|
|
<div class="flex align-center space-between">
|
|
<a href="" class="transaction__link flex align-center" target="_blank" rel="noopener noreferrer">
|
|
<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 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>
|
|
See transaction
|
|
</a>
|
|
<time class="transaction__time"></time>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<script>
|
|
// Global variables
|
|
const { html, render: renderElem } = uhtml;
|
|
const domRefs = {};
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
notify(
|
|
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
|
"error"
|
|
);
|
|
window.addEventListener("offline", () => {
|
|
notify(
|
|
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
|
"error",
|
|
{ pinned: true }
|
|
);
|
|
});
|
|
window.addEventListener("online", () => {
|
|
getRef("notification_drawer").clearAll();
|
|
notify("We are back online.", "success");
|
|
});
|
|
|
|
// Use instead of document.getElementById
|
|
function getRef(elementId) {
|
|
if (!domRefs.hasOwnProperty(elementId)) {
|
|
domRefs[elementId] = {
|
|
count: 1,
|
|
ref: null,
|
|
};
|
|
return document.getElementById(elementId);
|
|
} else {
|
|
if (domRefs[elementId].count < 3) {
|
|
domRefs[elementId].count = domRefs[elementId].count + 1;
|
|
return document.getElementById(elementId);
|
|
} else {
|
|
if (!domRefs[elementId].ref)
|
|
domRefs[elementId].ref = document.getElementById(elementId);
|
|
return domRefs[elementId].ref;
|
|
}
|
|
}
|
|
}
|
|
function $(selector) {
|
|
return document.querySelector(selector);
|
|
}
|
|
|
|
// returns dom with specified element
|
|
function createElement(tagName, options = {}) {
|
|
const { className, textContent, innerHTML, attributes = {} } = options
|
|
const elem = document.createElement(tagName)
|
|
for (let attribute in attributes) {
|
|
elem.setAttribute(attribute, attributes[attribute])
|
|
}
|
|
if (className)
|
|
elem.className = className
|
|
if (textContent)
|
|
elem.textContent = textContent
|
|
if (innerHTML)
|
|
elem.innerHTML = innerHTML
|
|
return elem
|
|
}
|
|
|
|
// Use when a function needs to be executed after user finishes changes
|
|
const debounce = (callback, wait) => {
|
|
let timeoutId = null;
|
|
return (...args) => {
|
|
window.clearTimeout(timeoutId);
|
|
timeoutId = window.setTimeout(() => {
|
|
callback.apply(null, args);
|
|
}, wait);
|
|
};
|
|
}
|
|
|
|
let zIndex = 10
|
|
// function required for popups or modals to appear
|
|
function openPopup(popupId, pinned) {
|
|
zIndex++
|
|
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
|
|
getRef(popupId).show({ pinned })
|
|
return getRef(popupId);
|
|
}
|
|
|
|
// hides the popup or modal
|
|
function closePopup() {
|
|
if (popupStack.peek() === undefined)
|
|
return;
|
|
popupStack.peek().popup.hide()
|
|
}
|
|
|
|
document.addEventListener('popupopened', async e => {
|
|
switch (e.target.id) {
|
|
case 'saved_ids_popup':
|
|
const frag = document.createDocumentFragment()
|
|
const allSavedIds = await getArrayOfSavedIds()
|
|
allSavedIds.forEach(({ floID, name }) => {
|
|
frag.append(render.savedIdPickerCard(floID, name))
|
|
})
|
|
getRef('saved_ids_picker_list').innerHTML = ''
|
|
getRef('saved_ids_picker_list').append(frag)
|
|
getRef('search_saved_ids_picker').focusIn()
|
|
break;
|
|
}
|
|
})
|
|
document.addEventListener('popupclosed', e => {
|
|
zIndex--
|
|
switch (e.target.id) {
|
|
case 'saved_ids_popup':
|
|
getRef('saved_ids_picker_list').innerHTML = ''
|
|
getRef('search_saved_ids_picker').value = ''
|
|
break;
|
|
case 'transaction_result_popup':
|
|
renderElem(getRef('transaction_result'), html``)
|
|
break;
|
|
case 'retrieve_flo_id_popup':
|
|
getRef('recovered_flo_id_wrapper').classList.add('hidden')
|
|
break;
|
|
case 'check_balance_popup':
|
|
getRef('balance_wrapper').classList.add('hidden')
|
|
getRef('check_balance_form')._checkValidity()
|
|
break;
|
|
}
|
|
})
|
|
|
|
// displays a popup for asking permission. Use this instead of JS confirm
|
|
const getConfirmation = (title, options = {}) => {
|
|
return new Promise(resolve => {
|
|
const { message = '', cancelText = 'Cancel', confirmText = 'OK', danger = false } = options
|
|
openPopup('confirmation_popup', true)
|
|
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')
|
|
confirmButton.onclick = () => {
|
|
closePopup()
|
|
resolve(true);
|
|
}
|
|
cancelButton.onclick = () => {
|
|
closePopup()
|
|
resolve(false);
|
|
}
|
|
})
|
|
}
|
|
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, options = {}) {
|
|
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;
|
|
}
|
|
if (mode === 'error') {
|
|
console.error(message)
|
|
}
|
|
return getRef("notification_drawer").push(message, { icon, ...options });
|
|
}
|
|
|
|
function getFormattedTime(time, format) {
|
|
try {
|
|
if (String(time).indexOf('_'))
|
|
time = String(time).split('_')[0]
|
|
const intTime = parseInt(time)
|
|
if (String(intTime).length < 13)
|
|
time *= 1000
|
|
let [day, month, date, year] = new Date(intTime).toString().split(' '),
|
|
minutes = new Date(intTime).getMinutes(),
|
|
hours = new Date(intTime).getHours(),
|
|
currentTime = new Date().toString().split(' ')
|
|
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
|
let finalHours = ``;
|
|
if (hours > 12)
|
|
finalHours = `${hours - 12}:${minutes}`
|
|
else if (hours === 0)
|
|
finalHours = `12:${minutes}`
|
|
else
|
|
finalHours = `${hours}:${minutes}`
|
|
|
|
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
|
switch (format) {
|
|
case 'date-only':
|
|
return `${month} ${date}, ${year}`;
|
|
break;
|
|
default:
|
|
return `${month} ${date} ${year}, ${finalHours}`;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
return time;
|
|
}
|
|
}
|
|
// implement event delegation
|
|
function delegate(el, event, selector, fn) {
|
|
el.addEventListener(event, function (e) {
|
|
const potentialTarget = e.target.closest(selector)
|
|
if (potentialTarget) {
|
|
e.delegateTarget = potentialTarget
|
|
fn.call(this, e)
|
|
}
|
|
})
|
|
}
|
|
|
|
function buttonLoader(id, show) {
|
|
const button = typeof id === 'string' ? getRef(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();
|
|
}
|
|
}
|
|
// 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')
|
|
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]), .interact")) {
|
|
createRipple(e, e.target.closest("button, .interact"));
|
|
}
|
|
});
|
|
getRef('accent_color_selector').colors = selectedColors
|
|
if (localStorage.getItem(`accent-color`)) {
|
|
const color = localStorage.getItem(`accent-color`)
|
|
getRef('accent_color_selector').selectedColor = color
|
|
document.body.style.setProperty('--accent-color', `var(${color})`);
|
|
}
|
|
else {
|
|
getRef('accent_color_selector').selectedColor = '--nice-blue'
|
|
}
|
|
document.addEventListener('colorselected', e => {
|
|
const color = e.detail.value
|
|
localStorage.setItem(`accent-color`, color);
|
|
document.body.style.setProperty('--accent-color', `var(${color})`);
|
|
})
|
|
|
|
});
|
|
function createRipple(event, target) {
|
|
const circle = document.createElement("span");
|
|
const diameter = Math.max(target.clientWidth, target.clientHeight);
|
|
const radius = diameter / 2;
|
|
const targetDimensions = target.getBoundingClientRect();
|
|
circle.style.width = circle.style.height = `${diameter}px`;
|
|
circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`;
|
|
circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`;
|
|
circle.classList.add("ripple");
|
|
const rippleAnimation = circle.animate(
|
|
[
|
|
{
|
|
transform: "scale(4)",
|
|
opacity: 0,
|
|
},
|
|
],
|
|
{
|
|
duration: 600,
|
|
fill: "forwards",
|
|
easing: "ease-out",
|
|
}
|
|
);
|
|
target.append(circle);
|
|
rippleAnimation.onfinish = () => {
|
|
circle.remove();
|
|
};
|
|
}
|
|
|
|
const pagesData = {
|
|
params: {}
|
|
}
|
|
|
|
let tempData
|
|
async function routeTo(targetPage, options = {}) {
|
|
const { firstLoad, hashChange } = options
|
|
let pageId
|
|
let params = {}
|
|
let searchParams
|
|
let wildcards
|
|
if (targetPage === '') {
|
|
pageId = 'home'
|
|
} else {
|
|
if (targetPage.includes('/')) {
|
|
let path;
|
|
[path, searchParams] = targetPage.split('?');
|
|
[, pageId, ...wildcards] = path.split('/')
|
|
} else {
|
|
pageId = targetPage
|
|
}
|
|
}
|
|
if (searchParams) {
|
|
const urlSearchParams = new URLSearchParams('?' + searchParams);
|
|
params = Object.fromEntries(urlSearchParams.entries());
|
|
}
|
|
if (pagesData.lastPage !== pageId) {
|
|
pagesData.wildcards = wildcards
|
|
}
|
|
if (params)
|
|
pagesData.params = params
|
|
switch (pageId) {
|
|
case 'transactions':
|
|
const floId = wildcards[0]
|
|
if (floId && floCrypto.validateFloID(floId)) {
|
|
render.transactions(floId).then(() => {
|
|
scrollToTopObserver.observe(getRef('transactions_list').firstElementChild)
|
|
})
|
|
} else {
|
|
notify('Invalid Flo ID', 'error')
|
|
}
|
|
break;
|
|
case 'smartcontracts':
|
|
const [subpage] = wildcards
|
|
const { type, subtype, scName, scAddress } = params
|
|
if (subpage) {
|
|
switch (subpage) {
|
|
case 'create':
|
|
if (!type && !subtype)
|
|
getRef('smart_contract_creation_form').classList.add('split-layout')
|
|
else
|
|
getRef('smart_contract_creation_form').classList.remove('split-layout')
|
|
renderElem(getRef('smart_contract_creation_form'), render.contractCreationForm(type, subtype))
|
|
showChildElement('smartcontracts', 1, { entry: slideInLeft, exit: slideOutLeft })
|
|
break;
|
|
case 'deposit': {
|
|
const filteredSmartContracts = filterSmartContracts({ type: 'continuos-event' })
|
|
if (filteredSmartContracts.length) {
|
|
renderElem(getRef('smart_contract_deposit_form'), html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">FLO private key</span>
|
|
<sm-input class="password-field" type="password" error-text="Invalid private key" data-private-key required>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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> <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"></path> </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> <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"></path> </svg>
|
|
</label>
|
|
</sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Select smart contract</span>
|
|
<sm-select>
|
|
${render.availableSmartContractOptions(filteredSmartContracts)}
|
|
</sm-select>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Amount</span>
|
|
<sm-input type="number" required></sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Expiration (Time after which unspent amount will be returned)</span>
|
|
<input type="datetime-local" required>
|
|
</div>
|
|
<div class="multi-state-button">
|
|
<button class="button button--primary" type="submit" disabled>Deposit</button>
|
|
</div>
|
|
`)
|
|
} else {
|
|
renderElem(getRef('smart_contract_deposit_form'), html`
|
|
<div class="grid justify-items-center gap-0-5">
|
|
<svg class="icon icon--big" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><circle cx="15.5" cy="9.5" r="1.5"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="M11.99 2C6.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 8zm0-3.5c.73 0 1.39.19 1.97.53.12-.14.86-.98 1.01-1.14-.85-.56-1.87-.89-2.98-.89-1.11 0-2.13.33-2.99.88.97 1.09.01.02 1.01 1.14.59-.33 1.25-.52 1.98-.52z"/></svg>
|
|
<strong class="justify-self-center">No smart contracts found</strong>
|
|
</div>
|
|
`)
|
|
}
|
|
showChildElement('smartcontracts', 2, { entry: slideInLeft, exit: slideOutLeft })
|
|
break;
|
|
}
|
|
case 'participate': {
|
|
const filteredSmartContracts = filterSmartContracts()
|
|
if (filteredSmartContracts.length) {
|
|
let selectedSmartContract = filteredSmartContracts[0] || {}
|
|
if (scName && scAddress)
|
|
selectedSmartContract = getScDetails(scName, scAddress)
|
|
const { contractName, contractAddress, acceptingToken, tokenIdentification, userChoices } = selectedSmartContract
|
|
history.replaceState(null, null, `#/smartcontracts/participate?scName=${contractName}&scAddress=${contractAddress}`)
|
|
renderElem(getRef('smart_contract_participate_form'), html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Select smart contract</span>
|
|
<sm-select id="selected_smart_contract" onchange="handleSmartContractSelection('participate')">
|
|
${render.availableSmartContractOptions(filteredSmartContracts, `${contractName}_${contractAddress}`)}
|
|
</sm-select>
|
|
</div>
|
|
${userChoices ? html`
|
|
<fieldset>
|
|
<legend>Choices</legend>
|
|
<div class="grid gap-0-5">
|
|
${userChoices.map(choice => html`
|
|
<label class="flex align-center">
|
|
<input type="radio" name="choice" value="${choice}" required>
|
|
<span class="capitalize">${choice}</span>
|
|
</label>
|
|
`)}
|
|
</div>
|
|
`: ''}
|
|
<div class="grid gap-0-5">
|
|
<span id="participation_amount_label" class="label">Participation amount (${acceptingToken || tokenIdentification})</span>
|
|
<sm-input id="participation_amount" type="number" step="0.00000001" min="0.00000001" required></sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">FLO private key</span>
|
|
<sm-input id="participant_private_key" class="password-field" type="password" error-text="Invalid private key" data-private-key required>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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> <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"></path> </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> <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"></path> </svg>
|
|
</label>
|
|
</sm-input>
|
|
</div>
|
|
<div class="multi-state-button">
|
|
<button id="participate_button" class="button button--primary" onclick=${participate} type="submit" disabled>Participate</button>
|
|
</div>
|
|
`)
|
|
} else {
|
|
renderElem(getRef('smart_contract_participate_form'), html`
|
|
<div class="grid justify-items-center gap-0-5">
|
|
<svg class="icon icon--big" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><circle cx="15.5" cy="9.5" r="1.5"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="M11.99 2C6.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 8zm0-3.5c.73 0 1.39.19 1.97.53.12-.14.86-.98 1.01-1.14-.85-.56-1.87-.89-2.98-.89-1.11 0-2.13.33-2.99.88.97 1.09.01.02 1.01 1.14.59-.33 1.25-.52 1.98-.52z"/></svg>
|
|
<strong class="justify-self-center">No smart contracts found</strong>
|
|
</div>
|
|
`)
|
|
}
|
|
showChildElement('smartcontracts', 3, { entry: slideInLeft, exit: slideOutLeft })
|
|
break;
|
|
}
|
|
case 'updateprice': {
|
|
const filteredSmartContracts = filterSmartContracts({ type: 'continuos-event', dynamic: true })
|
|
if (filteredSmartContracts.length) {
|
|
let selectedSmartContract = filteredSmartContracts[0] || {}
|
|
if (scName && scAddress)
|
|
selectedSmartContract = getScDetails(scName, scAddress)
|
|
const { contractName, contractAddress, oracle_address, price, acceptingToken } = selectedSmartContract
|
|
history.replaceState(null, null, `#/smartcontracts/participate?scName=${contractName}&scAddress=${contractAddress}`)
|
|
renderElem(getRef('smart_contract_update_form'), html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Oracle FLO Address</span>
|
|
<sm-copy id="oracle_address" value=${oracle_address}></sm-copy>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Oracle FLO private key</span>
|
|
<sm-input id="oracle_private_key" class="password-field" type="password" error-text="Invalid private key" data-private-key="" required="">
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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> <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"></path> </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> <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"></path> </svg>
|
|
</label>
|
|
</sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Select smart contract</span>
|
|
<sm-select id="selected_smart_contract" onchange="handleSmartContractSelection('updateprice')">
|
|
${render.availableSmartContractOptions(filteredSmartContracts, `${contractName}_${contractAddress}`)}
|
|
</sm-select>
|
|
</div>
|
|
<p>
|
|
<span class="label">Current price: </span>
|
|
<span id="current_price" class="label">${price} ${acceptingToken}</span>
|
|
</p>
|
|
<div class="grid gap-0-5">
|
|
<span id="updated_price_label" class="label">Updated price (${acceptingToken})</span>
|
|
<sm-input id="updated_price" type="number" step="0.00000001" min="0.00000001" error-text="Minimum 0.00000001 required" required></sm-input>
|
|
</div>
|
|
<div class="multi-state-button">
|
|
<button id="update_price_button" class="button button--primary" type="submit" onclick=${updatePrice} disabled>Update</button>
|
|
</div>
|
|
`)
|
|
} else {
|
|
renderElem(getRef('smart_contract_update_form'), html`
|
|
<div class="grid justify-items-center gap-0-5">
|
|
<svg class="icon icon--big" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><circle cx="15.5" cy="9.5" r="1.5"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="M11.99 2C6.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 8zm0-3.5c.73 0 1.39.19 1.97.53.12-.14.86-.98 1.01-1.14-.85-.56-1.87-.89-2.98-.89-1.11 0-2.13.33-2.99.88.97 1.09.01.02 1.01 1.14.59-.33 1.25-.52 1.98-.52z"/></svg>
|
|
<strong class="justify-self-center">No smart contracts found</strong>
|
|
</div>
|
|
`)
|
|
}
|
|
showChildElement('smartcontracts', 4, !firstLoad ? { entry: slideInLeft, exit: slideOutLeft } : {})
|
|
break;
|
|
}
|
|
case 'trigger': {
|
|
const filteredSmartContracts = filterSmartContracts({ type: 'one-time-event', subType: 'external-event' })
|
|
if (filteredSmartContracts.length) {
|
|
renderElem(getRef('smart_contract_trigger_form'), html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">FLO private key</span>
|
|
<sm-input class="password-field" type="password" error-text="Invalid private key" data-private-key="" required="">
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" 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> <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"></path> </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> <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"></path> </svg>
|
|
</label>
|
|
</sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Select smart contract</span>
|
|
<sm-select>
|
|
${render.availableSmartContractOptions(filteredSmartContracts)}
|
|
</sm-select>
|
|
</div>
|
|
<fieldset>
|
|
<legend>Select outcome</legend>
|
|
<div class="grid gap-0-5">
|
|
<label>
|
|
<input type="radio" name="outcome" value="1" checked>
|
|
<span>Outcome 1</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="outcome" value="2">
|
|
<span>Outcome 2</span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
<div class="multi-state-button">
|
|
<button class="button button--primary" type="submit" disabled>Trigger</button>
|
|
</div>
|
|
`)
|
|
} else {
|
|
renderElem(getRef('smart_contract_trigger_form'), html`
|
|
<div class="grid justify-items-center gap-0-5">
|
|
<svg class="icon icon--big" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><circle cx="15.5" cy="9.5" r="1.5"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="M11.99 2C6.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 8zm0-3.5c.73 0 1.39.19 1.97.53.12-.14.86-.98 1.01-1.14-.85-.56-1.87-.89-2.98-.89-1.11 0-2.13.33-2.99.88.97 1.09.01.02 1.01 1.14.59-.33 1.25-.52 1.98-.52z"/></svg>
|
|
<strong class="justify-self-center">No smart contracts found</strong>
|
|
</div>
|
|
`)
|
|
}
|
|
showChildElement('smartcontracts', 5, { entry: slideInLeft, exit: slideOutLeft })
|
|
break;
|
|
}
|
|
default:
|
|
notify('Invalid page', 'error')
|
|
}
|
|
} else {
|
|
showChildElement('smartcontracts', 0, { entry: slideInRight, exit: slideOutRight })
|
|
renderElem(getRef('smart_contract_creation_form'), html``)
|
|
}
|
|
break
|
|
default:
|
|
getRef('transactions_list').innerHTML = ''
|
|
scrollToTopObserver.disconnect()
|
|
}
|
|
const animOptions = {
|
|
duration: 100,
|
|
fill: 'forwards',
|
|
}
|
|
let previousActiveElement = getRef('main_navbar').querySelector('.nav-item--active')
|
|
const currentActiveElement = document.querySelector(`.nav-item[href="#/${pageId}"]`)
|
|
if (currentActiveElement) {
|
|
if (getRef('main_navbar').classList.contains('hidden')) {
|
|
getRef('main_navbar').classList.remove('hide-away')
|
|
getRef('main_navbar').classList.remove('hidden')
|
|
getRef('main_navbar').animate([
|
|
{
|
|
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
|
|
opacity: 0,
|
|
},
|
|
{
|
|
transform: `none`,
|
|
opacity: 1,
|
|
},
|
|
], {
|
|
duration: 100,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
})
|
|
}
|
|
getRef('main_header').classList.remove('hidden')
|
|
const previousActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(previousActiveElement)
|
|
const currentActiveElementIndex = [...getRef('main_navbar').querySelectorAll('.nav-item')].indexOf(currentActiveElement)
|
|
const isOnTop = previousActiveElementIndex < currentActiveElementIndex
|
|
const currentIndicator = createElement('div', { className: 'nav-item__indicator' });
|
|
let previousIndicator = getRef('main_navbar').querySelector('.nav-item__indicator')
|
|
if (!previousIndicator) {
|
|
previousIndicator = currentIndicator.cloneNode(true)
|
|
previousActiveElement = currentActiveElement
|
|
previousActiveElement.append(previousIndicator)
|
|
} else if (currentActiveElementIndex !== previousActiveElementIndex) {
|
|
const indicatorDimensions = previousIndicator.getBoundingClientRect()
|
|
const currentActiveElementDimensions = currentActiveElement.getBoundingClientRect()
|
|
let moveBy
|
|
if (isMobileView) {
|
|
moveBy = ((currentActiveElementDimensions.width - indicatorDimensions.width) / 2) + indicatorDimensions.width
|
|
} else {
|
|
moveBy = ((currentActiveElementDimensions.height - indicatorDimensions.height) / 2) + indicatorDimensions.height
|
|
}
|
|
indicatorObserver.observe(previousIndicator)
|
|
previousIndicator.animate([
|
|
{
|
|
transform: 'none',
|
|
opacity: 1,
|
|
},
|
|
{
|
|
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `${moveBy}px` : `-${moveBy}px`})`,
|
|
opacity: 0,
|
|
},
|
|
], { ...animOptions, easing: 'ease-in' }).onfinish = () => {
|
|
previousIndicator.remove()
|
|
}
|
|
tempData = {
|
|
currentActiveElement,
|
|
currentIndicator,
|
|
isOnTop,
|
|
animOptions,
|
|
moveBy
|
|
}
|
|
}
|
|
previousActiveElement.classList.remove('nav-item--active');
|
|
currentActiveElement.classList.add('nav-item--active')
|
|
} else {
|
|
if (!getRef('main_navbar').classList.contains('hidden')) {
|
|
getRef('main_navbar').classList.add('hide-away')
|
|
getRef('main_navbar').animate([
|
|
{
|
|
transform: `none`,
|
|
opacity: 1,
|
|
},
|
|
{
|
|
transform: isMobileView ? `translateY(100%)` : `translateX(-100%)`,
|
|
opacity: 0,
|
|
},
|
|
], {
|
|
duration: 200,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}).onfinish = () => {
|
|
getRef('main_navbar').classList.add('hidden')
|
|
}
|
|
getRef('main_header').classList.add('hidden')
|
|
}
|
|
}
|
|
if (pagesData.lastPage !== pageId) {
|
|
document.querySelectorAll('.page').forEach(page => page.classList.add('hidden'))
|
|
getRef(pageId).classList.remove('hidden')
|
|
getRef(pageId).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards', easing: 'ease' })
|
|
pagesData.lastPage = pageId
|
|
}
|
|
document.querySelectorAll('sm-input[data-flo-address]').forEach(input => input.customValidation = floCrypto.validateFloID)
|
|
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
|
|
}
|
|
|
|
const indicatorObserver = new IntersectionObserver(entries => {
|
|
entries.forEach(entry => {
|
|
if (!entry.isIntersecting) {
|
|
const { currentActiveElement, currentIndicator, isOnTop, animOptions, moveBy } = tempData
|
|
currentActiveElement.append(currentIndicator)
|
|
currentIndicator.animate([
|
|
{
|
|
transform: `translate${isMobileView ? 'X' : 'Y'}(${isOnTop ? `-${moveBy}px` : `${moveBy}px`})`,
|
|
opacity: 0,
|
|
},
|
|
{
|
|
transform: 'none',
|
|
opacity: 1
|
|
},
|
|
], { ...animOptions, easing: 'ease-out' })
|
|
}
|
|
})
|
|
}, {
|
|
threshold: 1
|
|
})
|
|
|
|
// class based lazy loading
|
|
class LazyLoader {
|
|
constructor(container, elementsToRender, renderFn, options = {}) {
|
|
const { batchSize = 10, freshRender } = options
|
|
|
|
this.elementsToRender = elementsToRender
|
|
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
|
this.renderFn = renderFn
|
|
this.intersectionObserver
|
|
|
|
this.batchSize = batchSize
|
|
this.freshRender = freshRender
|
|
|
|
this.lazyContainer = document.querySelector(container)
|
|
|
|
this.update = this.update.bind(this)
|
|
this.render = this.render.bind(this)
|
|
this.init = this.init.bind(this)
|
|
this.clear = this.clear.bind(this)
|
|
}
|
|
init() {
|
|
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
observer.disconnect()
|
|
this.render({ lazyLoad: true })
|
|
}
|
|
})
|
|
}, {
|
|
threshold: 0.3
|
|
})
|
|
this.mutationObserver = new MutationObserver(mutationList => {
|
|
mutationList.forEach(mutation => {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.addedNodes.length) {
|
|
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
this.mutationObserver.observe(this.lazyContainer, {
|
|
childList: true,
|
|
})
|
|
this.render()
|
|
}
|
|
update(elementsToRender) {
|
|
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
|
this.render()
|
|
}
|
|
render(options = {}) {
|
|
let { lazyLoad = false } = options
|
|
const frag = document.createDocumentFragment();
|
|
if (lazyLoad) {
|
|
this.updateStartIndex = this.updateEndIndex
|
|
this.updateEndIndex = this.arrayOfElements.length > this.updateEndIndex + this.batchSize ? this.updateEndIndex + this.batchSize : this.arrayOfElements.length
|
|
} else {
|
|
this.intersectionObserver.disconnect()
|
|
this.lazyContainer.innerHTML = ``;
|
|
this.updateStartIndex = 0
|
|
this.updateEndIndex = this.arrayOfElements.length > this.batchSize ? this.batchSize : this.arrayOfElements.length
|
|
}
|
|
for (let index = this.updateStartIndex; index < this.updateEndIndex; index++) {
|
|
frag.append(this.renderFn(this.arrayOfElements[index]))
|
|
}
|
|
this.lazyContainer.append(frag)
|
|
// Callback to be called if elements are updated or rendered for first time
|
|
if (!lazyLoad && this.freshRender)
|
|
this.freshRender()
|
|
}
|
|
clear() {
|
|
this.intersectionObserver.disconnect()
|
|
this.mutationObserver.disconnect()
|
|
this.lazyContainer.innerHTML = ``;
|
|
}
|
|
reset() {
|
|
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
|
|
this.render()
|
|
}
|
|
}
|
|
function animateTo(element, keyframes, options) {
|
|
const anime = element.animate(keyframes, { ...options, fill: 'both' })
|
|
anime.finished.then(() => {
|
|
anime.commitStyles()
|
|
anime.cancel()
|
|
})
|
|
return anime
|
|
}
|
|
let isMobileView = false
|
|
const mobileQuery = window.matchMedia('(max-width: 40rem)')
|
|
function handleMobileChange(e) {
|
|
isMobileView = e.matches
|
|
}
|
|
mobileQuery.addEventListener('change', handleMobileChange)
|
|
|
|
handleMobileChange(mobileQuery)
|
|
|
|
// fetch data and return json
|
|
async function fetchJSON(url, options = {}) {
|
|
const response = await fetch(url, options)
|
|
const json = await response.json()
|
|
if (response.ok) {
|
|
return json
|
|
} else {
|
|
throw new Error(json.description)
|
|
}
|
|
}
|
|
|
|
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 slideOutDown = [
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(1rem)'
|
|
},
|
|
]
|
|
const slideInUp = [
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(1rem)'
|
|
},
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
},
|
|
]
|
|
const slideOutUp = [
|
|
{
|
|
opacity: 1,
|
|
transform: 'translateY(0)'
|
|
},
|
|
{
|
|
opacity: 0,
|
|
transform: 'translateY(-1rem)'
|
|
},
|
|
]
|
|
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()
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
<script>
|
|
//UI for webWallet
|
|
const render = {
|
|
savedIdCard(floID, name) {
|
|
const clone = getRef('saved_id_template').content.cloneNode(true).firstElementChild;
|
|
clone.dataset.floAddress = floID;
|
|
clone.dataset.name = name;
|
|
clone.querySelector('.saved-id__initial').textContent = name[0];
|
|
clone.querySelector('.saved-id__label').textContent = name;
|
|
clone.querySelector('.saved-id__flo-id').textContent = floID;
|
|
return clone
|
|
},
|
|
async savedIds() {
|
|
const frag = document.createDocumentFragment()
|
|
try {
|
|
const allSavedIds = await getArrayOfSavedIds();
|
|
allSavedIds.forEach(({ floID, name }) => {
|
|
frag.append(render.savedIdCard(floID, name))
|
|
})
|
|
getRef('saved_ids_list').innerHTML = '';
|
|
getRef('saved_ids_list').append(frag)
|
|
} catch (err) {
|
|
notify(err, 'error')
|
|
}
|
|
},
|
|
savedIdPickerCard(floID, name) {
|
|
return createElement('li', {
|
|
className: 'saved-id grid interact',
|
|
attributes: { 'tabindex': '0', 'data-flo-address': floID },
|
|
innerHTML: `
|
|
<div class="saved-id__initial">${name[0]}</div>
|
|
<div class="grid gap-0-5">
|
|
<h4 class="saved-id__label">${name}</h4>
|
|
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
|
</div>
|
|
`
|
|
})
|
|
},
|
|
transactionCard(details) {
|
|
const { sender, receiver, floData, time, txid } = details
|
|
const queriedFloId = pagesData.wildcards[0]
|
|
const clone = getRef('transaction_template').content.cloneNode(true).firstElementChild;
|
|
if (sender === receiver) {
|
|
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><title>sender and receiver is same</title><path d="M.01 0h24v24h-24V0z" fill="none"/><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>`;
|
|
} else if (queriedFloId === sender) {
|
|
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>`;
|
|
} else {
|
|
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>`;
|
|
}
|
|
clone.querySelector('.transaction__receiver').textContent = queriedFloId === sender ? receiver : sender
|
|
clone.querySelector('.transaction__flo-data').textContent = floData
|
|
clone.querySelector('.transaction__link').href = `https://flosight.ranchimall.net/tx/${txid}`
|
|
clone.querySelector('.transaction__time').textContent = getFormattedTime(time * 1000)
|
|
return clone
|
|
},
|
|
async transactions(queriedFloId = pagesData.wildcards[0]) {
|
|
try {
|
|
scrollToTopObserver.disconnect()
|
|
// retrieve tokens and render them
|
|
getRef('tokens').classList.add('hidden')
|
|
fetchJSON(`https://ranchimallflo.duckdns.org/api/v1.0/getFloAddressBalance?floAddress=${queriedFloId}`).then(({ floAddressBalances }) => {
|
|
let ownedTokens = []
|
|
for (const token in floAddressBalances) {
|
|
ownedTokens.push(html`
|
|
<li class="token-item">
|
|
<span>${token}: </span><span>${parseFloat(floAddressBalances[token].balance.toFixed(8))}</span>
|
|
</li>
|
|
`)
|
|
}
|
|
if (ownedTokens.length) {
|
|
renderElem(getRef('token_list'), html`${ownedTokens}`)
|
|
getRef('tokens').classList.remove('hidden')
|
|
}
|
|
}).catch(e => {
|
|
console.error(e)
|
|
})
|
|
// retrieve FLO balance
|
|
getRef('flo_balance').innerHTML = `<sm-spinner></sm-spinner>`;
|
|
floWebWallet.getBalance(queriedFloId).then((retrievedBal) => {
|
|
getRef('flo_balance').textContent = `${parseFloat(retrievedBal.toFixed(3))} FLO`;
|
|
}).catch((error) => {
|
|
notify(error, 'error');
|
|
})
|
|
floWebWallet.getLabels().then(allLabels => {
|
|
if (allLabels[queriedFloId]) {
|
|
renderElem(getRef('queried_flo_address'), html`<h4>${allLabels[queriedFloId]}</h4> <sm-copy clip-text value=${queriedFloId}></sm-copy>`)
|
|
} else {
|
|
renderElem(getRef('queried_flo_address'), html`<h4><sm-copy clip-text value=${queriedFloId}></sm-copy></h4>`)
|
|
}
|
|
})
|
|
// show spinner
|
|
getRef('transactions_scroller').nextElementSibling.classList.remove('hidden')
|
|
getRef('transactions_scroller').classList.add('hidden')
|
|
getRef('transactions_list').innerHTML = '';
|
|
await floWebWallet.syncTransactions(queriedFloId);
|
|
let allTransactions = (await floWebWallet.readTransactions(queriedFloId)).reverse();
|
|
const filter = getRef('filter_selector').value;
|
|
if (filter !== 'all') {
|
|
allTransactions = allTransactions.filter(t => filter === 'sent' ? t.sender === queriedFloId : t.receiver === queriedFloId)
|
|
}
|
|
// render transactions
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.update(allTransactions)
|
|
} else {
|
|
transactionsLazyLoader = new LazyLoader('#transactions_list', allTransactions, render.transactionCard)
|
|
transactionsLazyLoader.init()
|
|
}
|
|
getRef('transactions_scroller').classList.remove('hidden')
|
|
getRef('transactions_scroller').nextElementSibling.classList.add('hidden')
|
|
getRef('transactions_scroller').scroll({
|
|
top: 0
|
|
})
|
|
} catch (err) {
|
|
notify(err, 'error');
|
|
}
|
|
},
|
|
availableAssetOptions() {
|
|
return (floGlobals.tokens || []).map(token => html` <sm-option value=${token}>${token}</sm-option> `)
|
|
},
|
|
availableSmartContractOptions(smartContracts = [], selected) {
|
|
return smartContracts
|
|
.map(({ contractName, contractAddress }) => html`
|
|
<sm-option class="breakable" value=${`${contractName}_${contractAddress}`} ?selected=${`${contractName}_${contractAddress}` === `${selected}`}>${`${replaceDash(contractName)} (${contractAddress})`}</sm-option>
|
|
`)
|
|
},
|
|
contractCreationForm(type, subtype) {
|
|
return html`
|
|
${!type && !subtype ? html`
|
|
<div class="grid gap-1-5">
|
|
<fieldset class="grid gap-0-5">
|
|
<legend>Type</legend>
|
|
<label>
|
|
<input type="radio" name="contract-type" value="one-time-event" ?checked=${type === 'one-time-event'} ?disabled=${type !== 'one-time-event'}>
|
|
<span>One time event</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="contract-type" value="continuous-event" ?checked=${type === 'continuous-event'} ?disabled=${type !== 'continuous-event'}>
|
|
<span>Continuous event</span>
|
|
</label>
|
|
</fieldset>
|
|
<fieldset class="grid gap-0-5">
|
|
<legend>Subtype</legend>
|
|
<label>
|
|
<input type="radio" name="contract-subtype" value="time-trigger" ?checked=${subtype === 'time-trigger'} ?disabled=${subtype !== 'time-trigger'}>
|
|
<span>Time trigger</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="contract-subtype" value="external-trigger" ?checked=${subtype === 'external-trigger'} ?disabled=${subtype !== 'external-trigger'}>
|
|
<span>External trigger</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="contract-subtype" value="tokenswap" ?checked=${subtype === 'tokenswap'} ?disabled=${subtype !== 'tokenswap'}>
|
|
<span>Token Swap</span>
|
|
</label>
|
|
</fieldset>
|
|
</div>
|
|
` : ''}
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Contract name</span>
|
|
<sm-input required> </sm-input>
|
|
</div>
|
|
${type === 'one-time-event' ? html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Asset</span>
|
|
<sm-select>
|
|
${render.availableAssetOptions()}
|
|
</sm-select>
|
|
</div>
|
|
${subtype === 'time-trigger' ? html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Payee FLO addresses</span>
|
|
<div class="flex gap-0-5">
|
|
<sm-input class="flex w-100" placeholder="FLO address" data-flo-address required> </sm-input>
|
|
<sm-input placeholder="Share (%)" required> </sm-input>
|
|
</div>
|
|
</div>
|
|
` : html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">User choices</span>
|
|
<div class="grid gap-0-3">
|
|
<sm-input class="user-choice" placeholder="Choice 1" required> </sm-input>
|
|
<sm-input class="user-choice" placeholder="Choice 2" required> </sm-input>
|
|
</div>
|
|
<button class="justify-start 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="M0 0h24v24H0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
Add choice
|
|
</button>
|
|
</div>
|
|
`}
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Expiration</span>
|
|
<input type="datetime-local" required>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Participation amount (optional)</span>
|
|
<sm-input type="number"> </sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Min. subscription amount (optional)</span>
|
|
<sm-input type="number"> </sm-input>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Max. subscription amount (optional)</span>
|
|
<sm-input type="number"> </sm-input>
|
|
</div>
|
|
` : html`
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Price</span>
|
|
<sm-input type="number"> </sm-input>
|
|
</div>
|
|
<fieldset class="grid gap-0-5" onchange=${handlePriceTypeChange}>
|
|
<legend>Price type</legend>
|
|
<label>
|
|
<input type="radio" name="price-type" value="static" checked>
|
|
<span>Static</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="price-type" value="dynamic">
|
|
<span>Dynamic</span>
|
|
</label>
|
|
</fieldset>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Input token</span>
|
|
<sm-select>
|
|
${render.availableAssetOptions()}
|
|
</sm-select>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<span class="label">Output token</span>
|
|
<sm-select>
|
|
${render.availableAssetOptions()}
|
|
</sm-select>
|
|
</div>
|
|
`}
|
|
<button class="button button--primary" type="submit" disabled>Create</button>
|
|
</div>
|
|
`
|
|
}
|
|
}
|
|
let transactionsLazyLoader
|
|
delegate(document, 'focusin', '.textarea', e => {
|
|
e.target.parentNode.classList.add('label-active');
|
|
});
|
|
delegate(document, 'focusout', '.textarea', e => {
|
|
if (e.target.value == '')
|
|
e.target.parentNode.classList.remove('label-active');
|
|
});
|
|
delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => {
|
|
if (e.target.closest('.edit-saved')) {
|
|
const target = e.target.closest('.saved-id');
|
|
getRef('edit_saved_id').setAttribute('value', target.dataset.floAddress)
|
|
getRef('new_addr_label').value = target.dataset.name
|
|
openPopup('edit_saved_popup')
|
|
} else if (e.target.closest('.copy-saved-id')) {
|
|
const target = e.target.closest('.saved-id');
|
|
navigator.clipboard.writeText(target.dataset.floAddress)
|
|
target.dispatchEvent(
|
|
new CustomEvent('copy', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
})
|
|
);
|
|
} else {
|
|
const target = e.target.closest('.saved-id');
|
|
window.location.hash = `#/transactions/${target.dataset.floAddress}`
|
|
}
|
|
})
|
|
delegate(getRef('saved_ids_picker_list'), 'click', '.saved-id', e => {
|
|
const target = e.target.closest('.saved-id');
|
|
getRef('receiver').value = target.dataset.floAddress
|
|
getRef('receiver').focusIn()
|
|
closePopup()
|
|
})
|
|
|
|
getRef('filter_selector').addEventListener('change', async e => {
|
|
const queriedFloId = pagesData.wildcards[0]
|
|
const filter = e.target.value
|
|
let allTransactions = (await floWebWallet.readTransactions(queriedFloId)).reverse()
|
|
if (filter !== 'all') {
|
|
allTransactions = allTransactions.filter(t => filter === 'sent' ? t.sender === queriedFloId : t.receiver === queriedFloId)
|
|
}
|
|
transactionsLazyLoader.update(allTransactions)
|
|
})
|
|
const scrollToTopObserver = new IntersectionObserver(entries => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
getRef('scroll_to_top').animate([
|
|
{
|
|
transform: 'scale(1)',
|
|
},
|
|
{
|
|
transform: 'scale(0)',
|
|
},
|
|
], {
|
|
duration: 150,
|
|
easing: 'ease',
|
|
}).onfinish = () => {
|
|
getRef('scroll_to_top').classList.add('hidden')
|
|
}
|
|
} else {
|
|
getRef('scroll_to_top').classList.remove('hidden')
|
|
getRef('scroll_to_top').animate([
|
|
{
|
|
transform: 'scale(0)',
|
|
},
|
|
{
|
|
transform: 'scale(1)',
|
|
},
|
|
], {
|
|
duration: 150,
|
|
easing: 'ease'
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
getRef('gen_new_addr_btn').addEventListener('click', () => {
|
|
const { floID, privKey } = floCrypto.generateNewID(),
|
|
card = createElement('div', {
|
|
className: 'generated-id-card',
|
|
innerHTML: `
|
|
<div>
|
|
<h5>FLO Address</h5>
|
|
<sm-copy value="${floID}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${privKey}"></sm-copy>
|
|
</div>
|
|
`
|
|
});
|
|
getRef('created_flo_id').innerHTML = '';
|
|
getRef('created_flo_id').append(card);
|
|
openPopup('create_flo_id_popup');
|
|
});
|
|
|
|
// Create array of objects from object of objects
|
|
async function getArrayOfSavedIds() {
|
|
const savedIds = await floWebWallet.getLabels()
|
|
const arr = []
|
|
for (const key in savedIds) {
|
|
arr.push({
|
|
floID: key,
|
|
name: savedIds[key]
|
|
})
|
|
}
|
|
return arr.sort((a, b) => a.name.localeCompare(b.name))
|
|
}
|
|
|
|
getRef('search_saved_ids_picker').addEventListener('input', debounce(async e => {
|
|
const frag = document.createDocumentFragment()
|
|
const searchKey = e.target.value.trim();
|
|
let allSavedIds = await getArrayOfSavedIds();
|
|
if (searchKey !== '') {
|
|
const fuse = new Fuse(allSavedIds, { keys: ['floID', 'name'] })
|
|
allSavedIds = fuse.search(searchKey).map(v => v.item)
|
|
}
|
|
allSavedIds.forEach(({ floID, name }) => {
|
|
frag.append(render.savedIdPickerCard(floID, name))
|
|
})
|
|
getRef('saved_ids_picker_list').innerHTML = '';
|
|
getRef('saved_ids_picker_list').append(frag);
|
|
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()
|
|
}
|
|
}
|
|
})
|
|
|
|
const savedIdsObserver = new MutationObserver((mutationList) => {
|
|
mutationList.forEach(mutation => {
|
|
getRef('saved_ids_tip').textContent = mutation.target.children.length === 0 ? `Click 'Add FLO address' to add a new FLO address.` : `Tap on saved IDs to see transactions and balance.`
|
|
})
|
|
})
|
|
|
|
savedIdsObserver.observe(getRef('saved_ids_list'), {
|
|
childList: true,
|
|
})
|
|
|
|
|
|
function saveFloId() {
|
|
const floID = getRef('flo_addr_to_save').value.trim()
|
|
let name = getRef('label_to_save').value.trim();
|
|
if (name == '')
|
|
name = 'Unknown'
|
|
compactIDB.addData('labels', name, floID).then((resolve) => {
|
|
insertElementAlphabetically(name, render.savedIdCard(floID, name))
|
|
closePopup()
|
|
}).catch((error) => {
|
|
notify('FLO address already saved', 'error');
|
|
})
|
|
}
|
|
|
|
function insertElementAlphabetically(name, elementToInsert) {
|
|
const elementInserted = [...getRef('saved_ids_list').children].some(child => {
|
|
if (child.dataset.name.localeCompare(name) > 0) {
|
|
child.before(elementToInsert)
|
|
return true
|
|
}
|
|
})
|
|
if (!elementInserted) {
|
|
getRef('saved_ids_list').append(elementToInsert)
|
|
}
|
|
}
|
|
|
|
getRef('get_private_key_field').addEventListener('input', debounce(e => {
|
|
if (e.target.isValid) {
|
|
checkSenderBalance()
|
|
} else {
|
|
resetBalance()
|
|
}
|
|
}, 100))
|
|
|
|
function checkSenderBalance() {
|
|
renderBalance(0, true)
|
|
const senderPrivateKey = getRef('get_private_key_field').value.trim()
|
|
const senderFloAddr = floCrypto.getFloID(senderPrivateKey)
|
|
Promise.all([
|
|
floWebWallet.getBalance(senderFloAddr),
|
|
fetchJSON(`https://ranchimallflo.duckdns.org/api/v1.0/getFloAddressBalance?floAddress=${senderFloAddr}`)
|
|
]).then(([retrievedBal, { floAddressBalances }]) => {
|
|
renderBalance(parseFloat(retrievedBal))
|
|
let ownedTokens = []
|
|
for (const token in floAddressBalances) {
|
|
ownedTokens.push(html`
|
|
<label class="token-balance">
|
|
<input type="radio" name="sender-token" data-balance=${floAddressBalances[token].balance.toFixed(8)} value=${token}/>
|
|
<span>${token} </span><b class="margin-left-auto">${parseFloat(floAddressBalances[token].balance.toFixed(8))}</b>
|
|
</label>
|
|
`)
|
|
}
|
|
if (ownedTokens.length) {
|
|
ownedTokens.push(html`
|
|
<label class="token-balance">
|
|
<input type="radio" name="sender-token" value="none" checked/>
|
|
<span>Don't send a token</span>
|
|
</label>
|
|
`)
|
|
renderElem(document.getElementById('sender_tokens_wrapper'), html.for(document.getElementById('sender_tokens_wrapper'), senderFloAddr)`
|
|
<h5>Tokens</h5>
|
|
<p>Select a token, if you want to send a token.</p>
|
|
<form onsubmit="event.preventDefault()" onchange=${handleTokenSelection} class="grid gap-1">
|
|
<div class="grid gap-0-5">
|
|
${ownedTokens}
|
|
</div>
|
|
</form>
|
|
`)
|
|
document.getElementById('sender_tokens_wrapper').classList.remove('hidden')
|
|
handleTokenSelection()
|
|
} else {
|
|
document.getElementById('sender_tokens_wrapper').classList.add('hidden')
|
|
clearSelection()
|
|
}
|
|
}).catch((error) => {
|
|
notify(error, 'error');
|
|
resetBalance()
|
|
})
|
|
}
|
|
function clearSelection() {
|
|
getRef('tx_token_amount').disabled = true
|
|
getRef('tx_token_amount').classList.add('hidden')
|
|
getRef('tx_token_amount').value = ''
|
|
getRef('flo_data_wrapper').classList.remove('hidden')
|
|
getRef('flo_data_textarea').value = ''
|
|
getRef('tx_flo_amount').value = ''
|
|
}
|
|
function handleTokenSelection() {
|
|
const selectedToken = document.getElementById('sender_tokens_wrapper').querySelector('input[type="radio"]:checked')
|
|
if (selectedToken.value === 'none')
|
|
return clearSelection(e)
|
|
const tokenName = selectedToken.value
|
|
const tokenBalance = parseFloat(selectedToken.dataset.balance)
|
|
getRef('flo_data_wrapper').classList.add('hidden')
|
|
getRef('tx_token_amount').disabled = false
|
|
getRef('tx_token_amount').classList.remove('hidden')
|
|
getRef('tx_token_amount').placeholder = `${tokenName.charAt(0).toUpperCase() + tokenName.slice(1)} amount`
|
|
getRef('tx_token_amount').setAttribute('max', tokenBalance)
|
|
getRef('tx_flo_amount').value = '0.001'
|
|
}
|
|
getRef('tx_token_amount').addEventListener('input', e => {
|
|
const tokenAmount = parseFloat(e.target.value.trim())
|
|
const selectedToken = document.getElementById('sender_tokens_wrapper').querySelector('input[type="radio"]:checked')
|
|
const tokenName = selectedToken.value
|
|
const tokenBalance = parseFloat(selectedToken.dataset.balance)
|
|
const { rangeOverflow, rangeUnderflow } = e.target.validity;
|
|
if (rangeUnderflow)
|
|
e.target.setAttribute('error-text', `Minimum 0.00000001 ${tokenName} allowed`)
|
|
if (rangeOverflow)
|
|
e.target.setAttribute('error-text', `You can send ${tokenName} upto ${tokenBalance} only`)
|
|
getRef('flo_data_textarea').value = `send ${tokenAmount} ${tokenName}#`
|
|
})
|
|
getRef('tx_flo_amount').addEventListener('input', e => {
|
|
const floAmount = parseFloat(e.target.value.trim())
|
|
const { rangeOverflow, rangeUnderflow } = e.target.validity;
|
|
if (rangeUnderflow)
|
|
e.target.setAttribute('error-text', `Minimum 0.00000001 FLO allowed`)
|
|
if (rangeOverflow)
|
|
e.target.setAttribute('error-text', `You can send FLO upto ${getRef('sender_balance').textContent} only`)
|
|
})
|
|
|
|
function renderBalance(balance = 0, loading = false) {
|
|
getRef('tx_flo_amount').setAttribute('max', balance)
|
|
renderElem(getRef('balance_card'), html`
|
|
<div class="grid">
|
|
<div class="flex align-center space-between">
|
|
<h5>Balance</h5>
|
|
<button id="refresh_balance_button" class="button button--small button--colored hidden"
|
|
onclick="checkSenderBalance()">Refresh</button>
|
|
</div>
|
|
<div class="flex align-end gap-0-3">
|
|
<b id="sender_balance" style="font-size: 2.5rem;line-height: 1;">${loading ? html`<sm-spinner></sm-spinner>` : balance}</b>
|
|
<span>FLO</span>
|
|
</div>
|
|
${!loading && balance == 0 ? html`
|
|
<p style="margin-top: 1.5rem; color: var(--danger-color)">
|
|
You don't have any FLO in your wallet. Please add some FLO to your wallet to send FLO.
|
|
</p>
|
|
` : ''}
|
|
</div>
|
|
<div id="sender_tokens_wrapper" class="grid hidden"></div>
|
|
`)
|
|
}
|
|
|
|
function resetBalance() {
|
|
clearSelection()
|
|
renderElem(getRef('balance_card'), html`
|
|
<div class="flex align-center space-between">
|
|
<h5>Balance</h5>
|
|
</div>
|
|
<p style="line-height: 1.2; opacity: 0.7;">
|
|
Sender balance will be shown once you enter a valid address
|
|
</p>
|
|
`)
|
|
}
|
|
|
|
|
|
function saveChanges() {
|
|
let name = getRef('new_addr_label').value.trim();
|
|
if (name == '')
|
|
name = 'Unknown';
|
|
compactIDB.writeData('labels', name, getRef('edit_saved_id').value).then((resolve) => {
|
|
const potentialTarget = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-address="${getRef('edit_saved_id').value}"]`)
|
|
if (potentialTarget) {
|
|
potentialTarget.dataset.name = name;
|
|
potentialTarget.querySelector('.saved-id__label').textContent = name;
|
|
potentialTarget.querySelector('.saved-id__initial').textContent = name.charAt(0).toUpperCase();
|
|
// place the renamed card in alphabetically correct position
|
|
const clone = potentialTarget.cloneNode(true);
|
|
potentialTarget.remove();
|
|
insertElementAlphabetically(name, clone)
|
|
}
|
|
closePopup()
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
})
|
|
}
|
|
|
|
function deleteSaved() {
|
|
getConfirmation('Do you want delete this FLO address?', {
|
|
confirmText: 'Delete',
|
|
}).then(res => {
|
|
if (res) {
|
|
const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-address="${getRef('edit_saved_id').value}"]`)
|
|
if (toDelete)
|
|
toDelete.remove();
|
|
compactIDB.removeData('labels', getRef('edit_saved_id').value);
|
|
closePopup()
|
|
}
|
|
})
|
|
}
|
|
|
|
function togglePrivateKeyVisibility(input) {
|
|
const target = input.closest('sm-input')
|
|
target.type = target.type === 'password' ? 'text' : 'password';
|
|
target.focusIn()
|
|
}
|
|
|
|
function retrieveFloId() {
|
|
const privKey = getRef('retrieve_flo_id_field').value.trim()
|
|
floWebWallet.recoverAddr(privKey).then(({ floID }) => {
|
|
getRef('recovered_flo_id_wrapper').classList.remove('hidden')
|
|
getRef('recovered_flo_id').value = floID
|
|
})
|
|
}
|
|
|
|
function checkBalance() {
|
|
getRef('balance_wrapper').classList.add('hidden')
|
|
const floID = getRef('check_balance_field').value.trim()
|
|
buttonLoader(getRef('check_balance_button'), true)
|
|
Promise.all([
|
|
floWebWallet.getBalance(floID),
|
|
fetchJSON(`https://ranchimallflo.duckdns.org/api/v1.0/getFloAddressBalance?floAddress=${floID}`)
|
|
]).then(([retrievedBal, { floAddressBalances }]) => {
|
|
renderElem(getRef('balance_wrapper'), html`
|
|
<h4>Balance</h4>
|
|
<div class="flex gap-0-5 align-center space-between">
|
|
<span>FLO:</span>
|
|
<strong style="font-size: 1.1rem">${retrievedBal}</strong>
|
|
</div>
|
|
${floAddressBalances ? Object.keys(floAddressBalances).map(token => html`
|
|
<div class="flex gap-0-5 align-center space-between">
|
|
<span>${token}:</span>
|
|
<strong style="font-size: 1.1rem">${parseFloat(floAddressBalances[token].balance.toFixed(8))}</strong>
|
|
</div>
|
|
`) : ''}
|
|
`)
|
|
getRef('balance_wrapper').classList.remove('hidden')
|
|
}).catch(err => {
|
|
notify(err, 'error')
|
|
}).finally(() => {
|
|
buttonLoader(getRef('check_balance_button'), false)
|
|
})
|
|
}
|
|
|
|
function sendMessage() {
|
|
const privKey = getRef('get_private_key_field').value.trim();
|
|
const sender = floCrypto.getFloID(privKey)
|
|
const floAmount = parseFloat(getRef('tx_flo_amount').value.trim());
|
|
const receiver = getRef('receiver').value.trim();
|
|
const floData = getRef('flo_data_textarea').value.trim();
|
|
const selectedToken = document.getElementById('sender_tokens_wrapper').querySelector('input[type="radio"]:checked')
|
|
const tokenAmount = parseFloat(getRef('tx_token_amount').value.trim())
|
|
getConfirmation(`Confirm transaction`, {
|
|
message: `
|
|
Sending ${floAmount} FLO ${selectedToken && selectedToken.value !== 'none' ? ` and ${tokenAmount} ${selectedToken.value}` : ''} to ${receiver}
|
|
`,
|
|
confirmText: 'Send',
|
|
}).then(res => {
|
|
if (res) {
|
|
buttonLoader('send_button', true)
|
|
floWebWallet.sendTransaction(sender, receiver, floAmount, floData, privKey).then((transactionId) => {
|
|
showTransactionResult(true, transactionId);
|
|
getRef('send_form').reset();
|
|
getRef('send_button').disabled = true;
|
|
resetBalance()
|
|
}).catch((error) => {
|
|
showTransactionResult(false, error);
|
|
}).finally(() => {
|
|
buttonLoader('send_button', false)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
function showTransactionResult(success, result) {
|
|
renderElem(getRef('transaction_result'), html`
|
|
${success ? html`
|
|
<svg class="icon icon--success" 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.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" /> </svg>
|
|
` : ''}
|
|
${!success ? html`
|
|
<svg class="icon 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>
|
|
` : ''}
|
|
<h3 id="transaction_result__title">${success ? 'Transaction request sent' : 'Transaction failed'}</h3>
|
|
<p id="transaction_result__description"> ${success ? 'This might take upto 30 mins to complete and reflect on blockchain.' : result} </p>
|
|
${success ? html`<a id="transaction_link" href=${`https://flosight.ranchimall.net/tx/${result}`} style="margin-top: 1.5rem;" target="_blank">See transaction on blockchain</a>` : ''}
|
|
`)
|
|
openPopup('transaction_result_popup')
|
|
}
|
|
|
|
|
|
function deleteDB() {
|
|
getConfirmation('Clear all local data?', {
|
|
confirmText: 'Clear',
|
|
}, () => {
|
|
compactIDB.deleteDB().then((message) => {
|
|
notify(message, 'success');
|
|
onLoadStartUp()
|
|
}).catch((error) => {
|
|
notify(error, 'error');
|
|
})
|
|
})
|
|
}
|
|
|
|
function backToTop() {
|
|
getRef('transactions_scroller').scroll({
|
|
top: 0,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
|
|
getRef('flo_data_textarea').addEventListener('keydown', e => {
|
|
if (e.target.value.length > 1040)
|
|
e.preventDefault()
|
|
})
|
|
|
|
getRef('flo_data_textarea').addEventListener('input', function (e) {
|
|
if (this.value.trim() !== '') {
|
|
getRef('send_button').classList.add('hidden')
|
|
getRef('fix_invalid_button').classList.remove('hidden')
|
|
}
|
|
else {
|
|
getRef('send_button').classList.remove('hidden')
|
|
getRef('fix_invalid_button').classList.add('hidden')
|
|
}
|
|
if (1040 - this.value.length) {
|
|
getRef('show_character_count').textContent = `${1040 - this.value.length} /1040`
|
|
}
|
|
else
|
|
getRef('show_character_count').textContent = `You can only add FLO data upto 1040 characters.`
|
|
checkFloData()
|
|
})
|
|
function checkFloData() {
|
|
const floDataText = getRef('flo_data_textarea').value;
|
|
if (/^[\x20-\x7E]*$/.test(floDataText)) {
|
|
getRef('send_button').classList.remove('hidden')
|
|
getRef('fix_invalid_button').classList.add('hidden')
|
|
getRef('flo_data_status').textContent = ''
|
|
}
|
|
else {
|
|
getRef('flo_data_status').textContent = 'FLO data contains invalid characters. Use "Fix" to remove invalid characters.'
|
|
}
|
|
}
|
|
function removeInvalid() {
|
|
const floDataText = getRef('flo_data_textarea').value;
|
|
getRef('flo_data_textarea').value = floDataText.replace(/\s/g, " ").replace(/[^\x20-\x7E]/g, '');
|
|
checkFloData()
|
|
}
|
|
|
|
const selectedColors = [
|
|
'--dark-red',
|
|
'--red',
|
|
'--kinda-pink',
|
|
'--purple',
|
|
'--shady-blue',
|
|
'--nice-blue',
|
|
'--maybe-cyan',
|
|
'--teal',
|
|
'--mint-green',
|
|
'--greenish-yellow',
|
|
'--yellowish-green',
|
|
'--dark-teal',
|
|
'--orange',
|
|
'--tangerine',
|
|
'--redish-orange',
|
|
]
|
|
|
|
// async function getContractInfo(name, address) {
|
|
// return new Promise((resolve, reject) => {
|
|
// if (!name) {
|
|
// name = floGlobals.smartContracts[0].contractName
|
|
// address = floGlobals.smartContracts[0].contractAddress
|
|
// }
|
|
// fetchJSON(`${floGlobals.tokenApiUrl}/api/v2/smartContractInfo?contractName=${name}&contractAddress=${address}`)
|
|
// .then(info => {
|
|
// console.log(info)
|
|
// const {
|
|
// contractInfo: {
|
|
// contractType,
|
|
// numberOfDeposits,
|
|
// numberOfParticipants,
|
|
// priceType,
|
|
// oracle_address,
|
|
// contractSubtype,
|
|
// status,
|
|
// expiryTime,
|
|
// payeeAddress,
|
|
// userChoices,
|
|
// tokenIdentification,
|
|
// acceptingToken,
|
|
// sellingToken,
|
|
// contractAmount,
|
|
// minimumsubscriptionamount,
|
|
// maximumsubscriptionamount,
|
|
// totalHonorAmount,
|
|
// totalParticipationAmount,
|
|
// price
|
|
// }, contractAddress,
|
|
// contractName
|
|
// } = info
|
|
// resolve({
|
|
// contractName,
|
|
// contractAddress,
|
|
// contractType,
|
|
// contractSubtype,
|
|
// status,
|
|
// expiryTime,
|
|
// payeeAddress,
|
|
// userChoices,
|
|
// token: tokenIdentification,
|
|
// acceptingToken,
|
|
// sellingToken,
|
|
// participationFees: contractAmount,
|
|
// minimumsubscriptionamount,
|
|
// maximumsubscriptionamount,
|
|
// numberOfDeposits,
|
|
// numberOfParticipants,
|
|
// priceType,
|
|
// oracle_address,
|
|
// totalHonorAmount,
|
|
// totalParticipationAmount,
|
|
// price
|
|
// })
|
|
// }).catch(error => {
|
|
// reject(error)
|
|
// })
|
|
// })
|
|
// }
|
|
|
|
function getScDetails(name, address) {
|
|
return floGlobals.smartContracts[floGlobals.scMap.get(`${name}_${address}`)] || {}
|
|
}
|
|
|
|
function filterSmartContracts(options) {
|
|
const { type, subType, dynamic = false } = options || {}
|
|
let filteredSmartContracts = (floGlobals.smartContracts || []);
|
|
if (type) {
|
|
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.contractType === type)
|
|
if (type === 'continuos-event' && dynamic)
|
|
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.oracle_address)
|
|
}
|
|
if (subType)
|
|
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.contractSubType === subType)
|
|
return filteredSmartContracts
|
|
}
|
|
|
|
function handleSmartContractSelection(actionType) {
|
|
const selectedSmartContract = document.getElementById('selected_smart_contract').value
|
|
const [contractName, contractAddress] = selectedSmartContract.split('_')
|
|
window.location.hash = `#/smartcontracts/${actionType}?scName=${contractName}&scAddress=${contractAddress}`
|
|
}
|
|
|
|
function participate(e) {
|
|
const selectedSmartContract = document.getElementById('selected_smart_contract').value
|
|
const [contractName, contractAddress] = (selectedSmartContract).split('_')
|
|
const participationAmount = parseFloat(document.getElementById('participation_amount').value.trim())
|
|
const { contractType, contractSubType, acceptingToken, tokenIdentification } = getScDetails(contractName, contractAddress)
|
|
const participantPrivateKey = document.getElementById('participant_private_key').value.trim()
|
|
const participantAddress = floCrypto.getFloID(participantPrivateKey)
|
|
let floData
|
|
switch (contractType) {
|
|
case 'one-time-event': {
|
|
// check min,max subscription and participation amount
|
|
switch (contractSubType) {
|
|
case 'time-trigger':
|
|
floData = `send ${participationAmount} ${tokenIdentification}# to ${contractName}@`
|
|
break
|
|
case 'external-trigger':
|
|
const userChoice = getRef('smart_contract_participate_form').querySelector('input[name="choice"]:checked').value
|
|
if (!userChoice)
|
|
return notify('Please select a choice', 'error')
|
|
floData = `send ${participationAmount} ${tokenIdentification}# to ${contractName}@ to FLO address ${contractAddress} with the userchoice: ${userChoice}`
|
|
break
|
|
}
|
|
}
|
|
break;
|
|
case 'continuos-event': {
|
|
switch (contractSubType) {
|
|
case 'tokenswap':
|
|
floData = `send ${participationAmount} ${acceptingToken}# to ${contractName}@`
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break
|
|
}
|
|
console.log(floData)
|
|
buttonLoader('participate_button', true)
|
|
floTokenAPI.getBalance(participantAddress, acceptingToken).then(balance => {
|
|
if (balance < participationAmount) {
|
|
buttonLoader('participate_button', false)
|
|
return notify(`Insufficient balance. You have ${balance} ${acceptingToken || tokenIdentification}`, 'error')
|
|
}
|
|
getConfirmation('Participate', {
|
|
message: `Are you sure you want to participate in ${contractName} with ${participationAmount} ${acceptingToken || tokenIdentification}?`,
|
|
confirmText: 'Participate',
|
|
}).then(res => {
|
|
if (!res) return
|
|
floBlockchainAPI.sendTx(participantAddress, contractAddress, floGlobals.sendAmt, participantPrivateKey, floData).then(txid => {
|
|
showTransactionResult(true, txid)
|
|
getRef('smart_contract_participate_form').reset()
|
|
document.getElementById('participate_button').disabled = true
|
|
}).catch(error => {
|
|
showTransactionResult(false, error)
|
|
}).finally(() => {
|
|
buttonLoader('participate_button', false)
|
|
})
|
|
})
|
|
}).catch(error => {
|
|
notify(`Error getting balance. ${error}`, 'error')
|
|
buttonLoader('participate_button', false)
|
|
})
|
|
}
|
|
|
|
function updatePrice(e) {
|
|
const selectedSmartContract = document.getElementById('selected_smart_contract').value
|
|
const [contractName, contractAddress] = (selectedSmartContract).split('_')
|
|
const oraclePrivateKey = document.getElementById('oracle_private_key').value
|
|
const oracleAddress = document.getElementById('oracle_address').value
|
|
if (!floCrypto.verifyPrivKey(oraclePrivateKey, oracleAddress)) {
|
|
return notify(`Private key doesn't match with Oracle address`, 'error')
|
|
}
|
|
const updatedPrice = parseFloat(document.getElementById('updated_price').value.trim())
|
|
const floData = ` {"price-update":{"contract-name": "${contractName}", "contract-address": "${contractAddress}", "price": ${updatedPrice}}} `
|
|
console.log(floData)
|
|
getConfirmation('Update price', {
|
|
message: `Are you sure you want to update the price of ${contractName} to ${updatedPrice} FLO?`,
|
|
confirmText: 'Update',
|
|
cancelText: 'Cancel'
|
|
}).then((res) => {
|
|
if (!res) return
|
|
buttonLoader(e.target.closest('button'), true)
|
|
floBlockchainAPI.writeData(oracleAddress, floData, oraclePrivateKey, contractAddress).then((txid) => {
|
|
showTransactionResult(true, txid)
|
|
getRef('smart_contract_update_form').reset()
|
|
document.getElementById('update_price_button').disabled = true
|
|
}).catch((error) => {
|
|
showTransactionResult(false, error)
|
|
}).finally(() => {
|
|
buttonLoader(e.target.closest('button'), false)
|
|
})
|
|
})
|
|
}
|
|
|
|
function handlePriceTypeChange(e) {
|
|
switch (e.target.value) {
|
|
case 'static':
|
|
e.target.closest('sm-form').querySelector('.oracle-address-wrapper')?.remove()
|
|
break;
|
|
case 'dynamic':
|
|
e.target.closest('sm-form').querySelector('.oracle-address-wrapper')?.remove()
|
|
e.target.closest('fieldset').after(html.node`
|
|
<div class="grid gap-0-5 oracle-address-wrapper">
|
|
<span class="label">Oracle FLO address</span>
|
|
<sm-input data-flo-address required> </sm-input>
|
|
</div>
|
|
`)
|
|
break;
|
|
}
|
|
}
|
|
|
|
function loader(show = true) {
|
|
if (show) {
|
|
getRef('loader').classList.remove('hidden')
|
|
} else {
|
|
getRef('loader').animate([
|
|
{ transform: 'translateY(0)' },
|
|
{ transform: 'translateY(-100%)' }
|
|
], {
|
|
duration: 300,
|
|
easing: 'ease-in-out'
|
|
}).onfinish = () =>
|
|
getRef('loader').classList.add('hidden')
|
|
}
|
|
}
|
|
function replaceDash(str) {
|
|
return str.replace(/-/g, ' ')
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |