Added wallet deposit/withdraw UI

This commit is contained in:
sairaj mote 2021-10-04 19:12:31 +05:30
parent 75879ca2f5
commit 5a67beb249
6 changed files with 885 additions and 223 deletions

View File

@ -1,11 +0,0 @@
{
"secret": "Secret_For_Session__Enter_A_Strong_String(Text/Password)",
"blockchain_id": "FLO_ID_of_the_admin",
"blockchain_private": "Private_Key_of_the_Admin",
"port": "8080",
"sql_user": "mySQL_user",
"sql_pwd": "mySQL_password",
"sql_db": "supernode",
"sql_host": "localhost"
}

View File

@ -418,7 +418,7 @@ input{
color: var(--accent-color)
}
:host([variant="outlined"]) .input {
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 0.4) inset;
box-shadow: 0 0 0 0.1rem var(--border-color, rgba(var(--text-color), 0.4)) inset;
background: rgba(var(--background-color), 1);
}
:host([variant="outlined"]) .label {
@ -2207,3 +2207,405 @@ class SlideButton extends HTMLElement {
window.customElements.define('slide-button', SlideButton);
const smSelect = document.createElement('template')
smSelect.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
--accent-color: #4d2588;
--text-color: 17, 17, 17;
--background-color: 255, 255, 255;
--max-height: auto;
--min-width: 100%;
}
:host([disabled]) .select{
opacity: 0.6;
cursor: not-allowed;
}
.hide{
display: none !important;
}
.select{
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
cursor: pointer;
width: 100%;
-webkit-tap-highlight-color: transparent;
}
.icon {
height: 1.5rem;
width: 1.5rem;
fill: rgba(var(--text-color), 0.7);
}
.selected-option-text{
font-size: 0.9rem;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
}
.selection{
border-radius: 0.3rem;
display: -ms-grid;
display: grid;
-ms-grid-columns: 1fr auto;
grid-template-columns: 1fr auto;
grid-template-areas: 'heading heading' '. .';
padding: 0.4rem 1rem;
background: rgba(var(--text-color), 0.06);
border: solid 1px rgba(var(--text-color), 0.2);
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
outline: none;
}
.selection:focus{
-webkit-box-shadow: 0 0 0 0.1rem var(--accent-color);
box-shadow: 0 0 0 0.1rem var(--accent-color)
}
.icon{
margin-left: 1rem;
}
:host([align-select="left"]) .options{
left: 0;
}
:host([align-select="right"]) .options{
right: 0;
}
.options{
top: 100%;
margin-top: 0.2rem;
overflow: hidden auto;
position: absolute;
grid-area: options;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
min-width: var(--min-width);
max-height: var(--max-height);
background: rgba(var(--background-color), 1);
border: solid 1px rgba(var(--text-color), 0.2);
border-radius: 0.3rem;
z-index: 2;
-webkit-box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
}
.rotate{
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg)
}
@media (any-hover: hover){
::-webkit-scrollbar{
width: 0.5rem;
height: 0.5rem;
}
::-webkit-scrollbar-thumb{
background: rgba(var(--text-color), 0.3);
border-radius: 1rem;
&:hover{
background: rgba(var(--text-color), 0.5);
}
}
}
</style>
<div class="select" >
<div class="selection">
<div class="selected-option-text"></div>
<svg class="icon toggle" 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 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
</div>
<div part="options" class="options hide">
<slot></slot>
</div>
</div>`;
customElements.define('sm-select', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).append(smSelect.content.cloneNode(true))
this.reset = this.reset.bind(this)
this.open = this.open.bind(this)
this.collapse = this.collapse.bind(this)
this.toggle = this.toggle.bind(this)
this.handleOptionsNavigation = this.handleOptionsNavigation.bind(this)
this.handleOptionSelection = this.handleOptionSelection.bind(this)
this.handleKeydown = this.handleKeydown.bind(this)
this.handleClickOutside = this.handleClickOutside.bind(this)
this.availableOptions
this.previousOption
this.isOpen = false;
this.slideDown = [{
transform: `translateY(-0.5rem)`,
opacity: 0
},
{
transform: `translateY(0)`,
opacity: 1
}
]
this.slideUp = [{
transform: `translateY(0)`,
opacity: 1
},
{
transform: `translateY(-0.5rem)`,
opacity: 0
}
]
this.animationOptions = {
duration: 300,
fill: "forwards",
easing: 'ease'
}
this.optionList = this.shadowRoot.querySelector('.options')
this.chevron = this.shadowRoot.querySelector('.toggle')
this.selection = this.shadowRoot.querySelector('.selection')
this.selectedOptionText = this.shadowRoot.querySelector('.selected-option-text')
}
static get observedAttributes() {
return ['value', 'disabled']
}
get value() {
return this.getAttribute('value')
}
set value(val) {
this.setAttribute('value', val)
}
reset(fire = true) {
if (this.availableOptions[0] && this.previousOption !== this.availableOptions[0]) {
const firstElement = this.availableOptions[0];
if (this.previousOption) {
this.previousOption.classList.remove('check-selected')
}
firstElement.classList.add('check-selected')
this.value = firstElement.getAttribute('value')
this.selectedOptionText.textContent = firstElement.textContent
this.previousOption = firstElement;
if (fire) {
this.fireEvent()
}
}
}
open() {
this.optionList.classList.remove('hide')
this.optionList.animate(this.slideDown, this.animationOptions)
this.chevron.classList.add('rotate')
this.isOpen = true
}
collapse() {
this.chevron.classList.remove('rotate')
this.optionList.animate(this.slideUp, this.animationOptions)
.onfinish = () => {
this.optionList.classList.add('hide')
this.isOpen = false
}
}
toggle() {
if (!this.isOpen && !this.hasAttribute('disabled')) {
this.open()
} else {
this.collapse()
}
}
fireEvent() {
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true,
detail: {
value: this.value
}
}))
}
handleOptionsNavigation(e) {
if (e.code === 'ArrowUp') {
e.preventDefault()
if (document.activeElement.previousElementSibling) {
document.activeElement.previousElementSibling.focus()
} else {
this.availableOptions[this.availableOptions.length - 1].focus()
}
}
else if (e.code === 'ArrowDown') {
e.preventDefault()
if (document.activeElement.nextElementSibling) {
document.activeElement.nextElementSibling.focus()
} else {
this.availableOptions[0].focus()
}
}
}
handleOptionSelection(e) {
if (this.previousOption !== document.activeElement) {
this.value = document.activeElement.getAttribute('value')
this.selectedOptionText.textContent = document.activeElement.textContent;
this.fireEvent()
if (this.previousOption) {
this.previousOption.classList.remove('check-selected')
}
document.activeElement.classList.add('check-selected')
this.previousOption = document.activeElement
}
}
handleClick(e) {
if (e.target === this) {
this.toggle()
}
else {
this.handleOptionSelection()
this.collapse()
}
}
handleKeydown(e) {
if (e.target === this) {
if (this.isOpen && e.code === 'ArrowDown') {
e.preventDefault()
this.availableOptions[0].focus()
this.handleOptionSelection(e)
}
else if (e.code === 'Enter' || e.code === 'Space') {
e.preventDefault()
this.toggle()
}
}
else {
this.handleOptionsNavigation(e)
this.handleOptionSelection(e)
if (e.code === 'Enter' || e.code === 'Space') {
e.preventDefault()
this.collapse()
}
}
}
handleClickOutside(e) {
if (this.isOpen && !this.contains(e.target)) {
this.collapse()
}
}
connectedCallback() {
this.setAttribute('role', 'listbox')
if (!this.hasAttribute('disabled')) {
this.selection.setAttribute('tabindex', '0')
}
let slot = this.shadowRoot.querySelector('slot')
slot.addEventListener('slotchange', e => {
this.availableOptions = slot.assignedElements()
this.reset(false)
});
this.addEventListener('click', this.handleClick)
this.addEventListener('keydown', this.handleKeydown)
document.addEventListener('mousedown', this.handleClickOutside)
}
disconnectedCallback() {
this.removeEventListener('click', this.toggle)
this.removeEventListener('keydown', this.handleKeydown)
document.removeEventListener('mousedown', this.handleClickOutside)
}
attributeChangedCallback(name) {
if (name === "disabled") {
if (this.hasAttribute('disabled')) {
this.selection.removeAttribute('tabindex')
} else {
this.selection.setAttribute('tabindex', '0')
}
}
}
})
// option
const smOption = document.createElement('template')
smOption.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.option{
display: grid;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
min-width: 100%;
gap: 0.5rem;
grid-template-columns: max-content minmax(0, 1fr);
padding: 0.8rem 1.2rem;
cursor: pointer;
overflow-wrap: break-word;
outline: none;
user-select: none;
}
:host(:focus){
outline: none;
background: rgba(var(--text-color), 0.1);
}
.icon {
opacity: 0;
height: 1.2rem;
width: 1.2rem;
fill: rgba(var(--text-color), 0.8);
}
:host(:focus) .option .icon{
opacity: 0.4
}
:host(.check-selected) .icon{
opacity: 1
}
@media (hover: hover){
.option:hover{
background: rgba(var(--text-color), 0.1);
}
:host(:not(.check-selected):hover) .icon{
opacity: 0.4
}
}
</style>
<div class="option">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>
<slot></slot>
</div>`;
customElements.define('sm-option', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).append(smOption.content.cloneNode(true))
}
connectedCallback() {
this.setAttribute('role', 'option')
this.setAttribute('tabindex', '0')
}
})

View File

@ -376,24 +376,29 @@ ul {
fill: var(--accent-color);
}
#confirmation_popup {
#confirmation_popup,
#prompt_popup {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
#confirmation_popup h4 {
#confirmation_popup h4,
#prompt_popup h4 {
font-weight: 500;
margin-bottom: 0.5rem;
}
#confirmation_popup sm-button {
#confirmation_popup sm-button,
#prompt_popup sm-button {
margin: 0;
}
#confirmation_popup .flex {
#confirmation_popup .flex,
#prompt_popup .flex {
padding: 0;
margin-top: 1rem;
}
#confirmation_popup .flex sm-button:first-of-type {
#confirmation_popup .flex sm-button:first-of-type,
#prompt_popup .flex sm-button:first-of-type {
margin-right: 0.6rem;
margin-left: auto;
}
@ -405,6 +410,22 @@ button:active,
transform: scale(0.96);
}
.popup__header {
display: grid;
gap: 0.5rem;
width: 100%;
padding: 0 1.5rem 0 0.5rem;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
grid-template-columns: auto 1fr auto;
}
.popup__header__close {
padding: 0.5rem;
cursor: pointer;
}
#main_page {
padding: 1.5rem;
}
@ -571,12 +592,11 @@ strip-option:last-of-type {
#home {
height: 100%;
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: minmax(0, 1fr);
}
#main_header {
padding: 1.5rem;
padding: 1.8rem 1.5rem;
display: grid;
gap: 1rem;
-webkit-box-align: center;
@ -589,7 +609,7 @@ strip-option:last-of-type {
--width: min(24rem, 100%);
-ms-flex-item-align: start;
align-self: flex-start;
padding: 0 1.5rem;
padding: 1rem 1.5rem;
}
#quantity_selector .button {
@ -611,22 +631,48 @@ strip-option:last-of-type {
min-width: 8ch;
}
#orders_section {
padding: 1.5rem;
}
#user_section {
gap: 1.5rem;
padding: 0 1.5rem;
padding: 1.5rem;
-ms-flex-line-pack: start;
align-content: flex-start;
}
.wallet_actions__wrapper {
grid-column: span 3;
gap: 0.5rem;
margin-top: 0.5rem;
}
.wallet_actions__wrapper .button {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.balance-card {
display: grid;
grid-template-columns: auto 1fr auto;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
gap: 0.3rem 1rem;
padding: 0.5rem 0;
border-radius: 0.5rem;
}
.balance-card.is-locked {
grid-template-columns: auto 1fr;
gap: 1rem;
}
.balance-card.is-locked .label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
.balance-card:not(.is-locked) {
grid-template-columns: auto 1fr auto;
}
.balance-card__icon {
display: -webkit-box;
display: -ms-flexbox;
@ -649,27 +695,58 @@ strip-option:last-of-type {
font-size: 0.9rem;
font-weight: 500;
}
.balance-card__amount {
font-size: 1rem;
font-weight: 700;
.balance-card__amount-wrapper {
grid-column: span 2;
gap: 0.3rem 1rem;
grid-template-columns: 1fr 1fr;
}
.balance-card__actions {
grid-column: span 3;
gap: 0.5rem;
margin-top: 0.5rem;
}
.balance-card__actions .button {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
.balance-card__amount-wrapper > :nth-child(even) {
text-align: right;
}
@media screen and (max-width: 640px) {
.loader-button-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: relative;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.loader-button-wrapper sm-button,
.loader-button-wrapper slide-button {
width: 100%;
z-index: 1;
-webkit-transition: -webkit-clip-path 0.3s;
transition: -webkit-clip-path 0.3s;
transition: clip-path 0.3s;
transition: clip-path 0.3s, -webkit-clip-path 0.3s;
-webkit-clip-path: circle(100%);
clip-path: circle(100%);
}
.loader-button-wrapper sm-button.clip,
.loader-button-wrapper slide-button.clip {
pointer-events: none;
-webkit-clip-path: circle(0);
clip-path: circle(0);
}
.loader-button-wrapper sm-spinner {
position: absolute;
}
@media screen and (max-width: 40rem) {
sm-button {
--padding: 0.9rem 1.6rem;
}
}
@media screen and (min-width: 640px) {
@media screen and (min-width: 40rem) {
sm-popup {
--width: 24rem;
}
.h1 {
font-size: 2rem;
}
@ -686,6 +763,10 @@ strip-option:last-of-type {
font-size: 1rem;
}
.popup__header {
padding: 1rem 1.5rem 0 0.5rem;
}
#confirmation_popup {
--width: 24rem;
}
@ -694,25 +775,30 @@ strip-option:last-of-type {
grid-template-columns: 1fr 90vw 1fr;
}
}
@media screen and (min-width: 1024px) {
@media screen and (min-width: 64rem) {
.page-layout {
grid-template-columns: 1fr 80vw 1fr;
}
#home {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
padding: 1.5vmax 3vmax;
grid-template-columns: 24rem minmax(0, 1fr) 20rem;
grid-template-rows: auto 1fr;
gap: 1rem;
}
#main_header {
grid-column: span 3;
#home > * {
border-radius: 0.5rem;
background-color: var(--foreground-color);
border: solid thin rgba(var(--text-color), 0.1);
}
.hide-on-desktop {
display: none;
}
}
@media screen and (min-width: 1920px) {
@media screen and (min-width: 120rem) {
.page-layout {
grid-template-columns: 1fr 70vw 1fr;
}

File diff suppressed because one or more lines are too long

View File

@ -341,22 +341,19 @@ ul {
fill: var(--accent-color);
}
}
#confirmation_popup {
#confirmation_popup,
#prompt_popup {
flex-direction: column;
h4 {
font-weight: 500;
margin-bottom: 0.5rem;
}
sm-button {
margin: 0;
}
.flex {
padding: 0;
margin-top: 1rem;
sm-button:first-of-type {
margin-right: 0.6rem;
margin-left: auto;
@ -370,6 +367,20 @@ button:active,
transform: scale(0.96);
}
.popup__header {
display: grid;
gap: 0.5rem;
width: 100%;
padding: 0 1.5rem 0 0.5rem;
align-items: center;
grid-template-columns: auto 1fr auto;
}
.popup__header__close {
padding: 0.5rem;
cursor: pointer;
}
#main_page {
padding: 1.5rem;
@ -517,12 +528,11 @@ strip-option {
#home {
height: 100%;
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: minmax(0, 1fr);
}
#main_header {
padding: 1.5rem;
padding: 1.8rem 1.5rem;
display: grid;
gap: 1rem;
align-items: center;
@ -532,7 +542,7 @@ strip-option {
#trade_form {
--width: min(24rem, 100%);
align-self: flex-start;
padding: 0 1.5rem;
padding: 1rem 1.5rem;
}
#quantity_selector {
.button {
@ -551,19 +561,41 @@ strip-option {
font-weight: 500;
min-width: 8ch;
}
#orders_section {
padding: 1.5rem;
}
#user_section {
gap: 1.5rem;
padding: 0 1.5rem;
padding: 1.5rem;
align-content: flex-start;
}
.user_section__header {
}
.wallet_actions__wrapper {
grid-column: span 3;
gap: 0.5rem;
margin-top: 0.5rem;
.button {
flex: 1;
}
}
.balance-card {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 0.3rem 1rem;
padding: 0.5rem 0;
border-radius: 0.5rem;
&.is-locked {
grid-template-columns: auto 1fr;
gap: 1rem;
.label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
}
&:not(.is-locked) {
grid-template-columns: auto 1fr auto;
}
&__icon {
display: flex;
align-content: center;
@ -581,25 +613,44 @@ strip-option {
font-size: 0.9rem;
font-weight: 500;
}
&__amount {
font-size: 1rem;
font-weight: 700;
}
&__actions {
grid-column: span 3;
gap: 0.5rem;
margin-top: 0.5rem;
.button {
flex: 1;
&__amount-wrapper {
grid-column: span 2;
gap: 0.3rem 1rem;
grid-template-columns: 1fr 1fr;
& > :nth-child(even) {
text-align: right;
}
}
}
@media screen and (max-width: 640px) {
.loader-button-wrapper {
display: flex;
position: relative;
justify-content: center;
align-items: center;
sm-button,
slide-button {
width: 100%;
z-index: 1;
transition: clip-path 0.3s;
clip-path: circle(100%);
&.clip {
pointer-events: none;
clip-path: circle(0);
}
}
sm-spinner {
position: absolute;
}
}
@media screen and (max-width: 40rem) {
sm-button {
--padding: 0.9rem 1.6rem;
}
}
@media screen and (min-width: 640px) {
@media screen and (min-width: 40rem) {
sm-popup {
--width: 24rem;
}
.h1 {
font-size: 2rem;
}
@ -615,6 +666,9 @@ strip-option {
.h4 {
font-size: 1rem;
}
.popup__header {
padding: 1rem 1.5rem 0 0.5rem;
}
#confirmation_popup {
--width: 24rem;
}
@ -622,24 +676,28 @@ strip-option {
grid-template-columns: 1fr 90vw 1fr;
}
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: 64rem) {
}
@media screen and (min-width: 1024px) {
@media screen and (min-width: 64rem) {
.page-layout {
grid-template-columns: 1fr 80vw 1fr;
}
#home {
align-items: flex-start;
padding: 1.5vmax 3vmax;
grid-template-columns: 24rem minmax(0, 1fr) 20rem;
grid-template-rows: auto 1fr;
gap: 1rem;
& > * {
border-radius: 0.5rem;
background-color: var(--foreground-color);
border: solid thin rgba(var(--text-color), 0.1);
}
#main_header {
grid-column: span 3;
}
.hide-on-desktop {
display: none;
}
}
@media screen and (min-width: 1920px) {
@media screen and (min-width: 120rem) {
.page-layout {
grid-template-columns: 1fr 70vw 1fr;
}

View File

@ -50,6 +50,15 @@
<sm-button variant="no-outline" class="submit-btn">OK</sm-button>
</div>
</sm-popup>
<sm-popup id="prompt_popup">
<h4 id="prompt_title"></h4>
<p id="prompt_message"></p>
<sm-input id="prompt_input"></sm-input>
<div class="flex align-center">
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
<sm-button variant="no-outline" class="submit-btn" type="submit">OK</sm-button>
</div>
</sm-popup>
<article id="landing" class="page page-layout hide-completely">
<header class="flex space-between">
<div class="logo">
@ -139,6 +148,7 @@
<h4>Loading RanchiMall Market</h4>
</article>
<article id="home" class="page">
<section>
<header id="main_header">
<div class="logo">
<svg class="main-logo" viewBox="0 0 27.25 32">
@ -160,9 +170,11 @@
<strip-option value="sell">Sell</strip-option>
</strip-select>
</div>
<sm-input id="get_price" placeholder="Max price" type="number" step="0.00000001" required hiderequired>
<sm-input id="get_price" variant="outlined" placeholder="Max price" type="number" step="0.00000001"
required hiderequired animate>
</sm-input>
<sm-input id="get_quantity" placeholder="Quantity" type="number" step="0.0001" required hiderequired>
<sm-input id="get_quantity" variant="outlined" placeholder="Quantity" type="number" step="0.00000001"
required hiderequired animate>
</sm-input>
<div id="quantity_selector" class="flex align-center">
<span id="quantity_type">Rupee</span>
@ -173,14 +185,21 @@
</div>
<sm-button id="trade_button" class="uppercase" variant="primary" onclick="tradeFlo()">BUY</sm-button>
</sm-form>
<section>
</section>
<section id="orders_section" class="grid gap-1-5">
<div class="flex space-between align-center">
<h4>My orders</h4>
<strip-select id="orders_scope_selector">
<strip-option value="active" selected>Open</strip-option>
<strip-option value="completed">Completed</strip-option>
</strip-select>
</div>
<div class="table" style="--table-columns: 4;">
<div class="table__header table__row">
<div></div>
<div>Quantity</div>
<div>At price</div>
<div>Order Placed</div>
<div>Order placed</div>
</div>
</div>
</section>
@ -195,6 +214,23 @@
</svg>
My wallet
</h4>
<div id="wallet_actions">
<p>Select asset</p>
<sm-select id="wallet_asset_selector">
<sm-option value="FLO">FLO</sm-option>
<sm-option value="Rupee">Rupee</sm-option>
</sm-select>
<div class="flex wallet_actions__wrapper">
<button class="button" value="deposit">
Deposit
</button>
<button class="button" value="withdraw">
Withdraw
</button>
</div>
</div>
<div class="grid gap-0-5">
<h4>Balance</h4>
<div class="balance-card">
<div class="balance-card__icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
@ -203,15 +239,7 @@
</svg>
</div>
<div class="balance-card__token">FLO</div>
<div id="flo_balance" class="balance-card__amount"></div>
<div class="flex balance-card__actions">
<button class="button" onclick="UI_evt.depositFLO();">
Deposit
</button>
<button class="button" onclick="UI_evt.withdrawFLO()">
Withdraw
</button>
</div>
<div id="flo_balance"></div>
</div>
<div class="balance-card">
<div class="balance-card__icon">
@ -221,18 +249,34 @@
</svg>
</div>
<div class="balance-card__token">Rupee</div>
<div id="rupee_balance" class="balance-card__amount">₹15402</div>
<div class="flex balance-card__actions">
<button class="button" onclick="UI_evt.depositRupee()">
Deposit
</button>
<button class="button" onclick="UI_evt.withdrawRupee()">
Withdraw
</button>
<div id="rupee_balance"></div>
</div>
</div>
</section>
</article>
<sm-popup id="wallet_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z" />
</svg>
</button>
<h4 id="wallet_popup__title" class="capitalize"></h4>
</header>
<sm-form id="wallet_form">
<sm-input id="get_user_amount" variant="outlined" placeholder="Quantity" type="number" step="0.00000001"
required hiderequired animate>
</sm-input>
<sm-input id="get_private_key" variant="outlined" placeholder="FLO private key" type="password" required
error-text="Invalid private key" hiderequired animate>
</sm-input>
<div id="wallet_popup__cta_wrapper" class="loader-button-wrapper">
<sm-button id="wallet_popup__cta" variant="primary"></sm-button>
</div>
</sm-form>
</sm-popup>
<form id="login-form">
<fieldset>
<legend>Login</legend>
@ -247,8 +291,6 @@
<fieldset>
<legend>Profile</legend>
<span id="user_id"></span><br />
FLO: <span id="flo_bal"></span><br />
Rupee: <span id="rupee_bal"></span><br />
<button onclick="proxy.lock();">Add password lock</button><br />
<button onclick="UI_evt.logout();">logout</button>
<button onclick="toggle_view('my-profile');">Toggle</button>
@ -278,7 +320,7 @@
<th>Select</th>
<th>Quantity</th>
<th>Min Price</th>
<th>Order Placed</th>
<th>Order placed</th>
</tr>
</thead>
<tbody data-type="sell"></tbody>
@ -360,6 +402,19 @@
</fieldset>
</div>
</div>
<template id="net_balance_template">
<span class="available-balance"></span>
</template>
<template id="locked_balance_template">
<div>
<span class="label">Locked</span>
<span class="locked-balance"></span>
</div>
<div>
<span class="label">Available</span>
<span class="available-balance"></span>
</div>
</template>
<script id="ui_utils">
// Global variables
const domRefs = {};
@ -503,6 +558,34 @@
})
}
// displays a popup for asking user input. Use this instead of JS prompt
async function getPromptInput(title, message = '', options = {}) {
const { isPassword = true, cancelText = 'Cancel', confirmText = 'OK' } = options
showPopup('prompt_popup', true)
getRef('prompt_title').textContent = title;
let input = getRef('prompt_input');
input.setAttribute("placeholder", message)
let buttons = getRef('prompt_popup').querySelectorAll("sm-button");
if (isPassword)
input.setAttribute("type", "text")
else
input.setAttribute("type", "password")
input.focusIn()
buttons[0].textContent = cancelText;
buttons[1].textContent = confirmText;
return new Promise((resolve, reject) => {
buttons[0].onclick = () => {
hidePopup()
return;
}
buttons[1].onclick = () => {
let value = input.value;
hidePopup()
resolve(value)
}
})
}
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
function notify(message, mode, options = {}) {
const { pinned = false, sound = false } = options
@ -742,23 +825,23 @@
}
</script>
<script>
var user_id; //container for user ID and proxy private-key
let user_id; //container for user ID and proxy private-key
const proxy = {
private: null,
public: null,
lock() {
async lock() {
if (!this.private)
throw "No proxy key found!";
let pwd = prompt("Enter password: ");
let pwd = await getPromptInput("Enter password", '', { isPassword: true });
if (!pwd)
alert("Password cannot be empty");
notify("Password cannot be empty", 'error');
else if (pwd.length < 4)
alert("Password minimum length is 4");
notify("Password minimum length is 4", 'error');
else {
let tmp = Crypto.AES.encrypt(this.private, pwd);
localStorage.setItem("proxy_secret", "?" + tmp);
alert("Successfully locked with Password");
notify("Successfully locked with Password", 'success');
}
},
clear() {
@ -777,7 +860,7 @@
try {
let tmp = localStorage.getItem("proxy_secret");
if (typeof tmp === "string" && tmp.startsWith("?")) {
let pwd = prompt("Enter password: ");
getPromptInput("Enter password", '', { isPassword: true }).then(pwd => {
if (!pwd)
throw "Password Required for making transactions";
else {
@ -788,6 +871,7 @@
}
}
});
}
this.private = tmp;
this.public = floCrypto.getPubKeyHex(tmp);
@ -865,6 +949,19 @@
account();
}
function showBalance(containerId, availableBalance = 0, lockedBalance = 0) {
getRef(containerId).innerHTML = ''
const templateToClone = lockedBalance ? 'locked_balance_template' : 'net_balance_template';
const card = getRef(templateToClone).content.cloneNode(true).firstElementChild
card.querySelector('.available-balance').textContent = availableBalance
if (lockedBalance) {
card.querySelector('.locked-balance').textContent = lockedBalance
}
getRef(containerId).className = lockedBalance ? 'grid balance-card__amount-wrapper' : ''
getRef(containerId).parentNode.className = `balance-card ${lockedBalance ? 'is-locked' : ''}`
getRef(containerId).append(card)
}
function account() {
getAccount().then(acc => {
console.debug(acc);
@ -878,13 +975,14 @@
let flo_locked = acc.sellOrders.reduce((a, x) => a + x.quantity, 0);
let flo_net = flo_total - flo_locked;
console.debug("FLO", flo_total, flo_locked, flo_net);
document.getElementById("flo_bal").textContent = flo_net + "(+" + flo_locked + ")";
showBalance("flo_balance", flo_net, flo_locked)
//Rupee Balance
let rupee_total = acc.rupee_total;
let rupee_locked = acc.buyOrders.reduce((a, x) => a + x.quantity * x.maxPrice, 0);
let rupee_net = rupee_total - rupee_locked;
console.debug("RUPEE", rupee_total, rupee_locked, rupee_net);
document.getElementById("rupee_bal").textContent = rupee_net + "(+" + rupee_locked + ")";
showBalance("rupee_balance", rupee_net, rupee_locked)
//My buy orders
let container = document.getElementById("my-buy-orders").getElementsByTagName("tbody")[0];
container.innerHTML = '';
@ -948,16 +1046,17 @@
const UI_evt = {};
UI_evt.signup = function () {
UI_evt.signup = async function () {
let sid = document.forms['login-form']['sid'].value;
let privKey = prompt("Enter Private Key of floID to register: ");
let privKey = await getPromptInput("Register private key", "Enter the private key of floID you want to register", { isPassword: true });
if (privKey) {
signUp(privKey, sid).then(result => {
console.info(result);
alert("Account registered!")
notify("Account registered!", 'success')
}).catch(error => {
console.error(error)
alert(error);
notify(error, 'error');
});
}
};
UI_evt.logout = function () {
@ -981,83 +1080,40 @@
}).catch(error => console.error(error));
};
UI_evt.sell = function () {
let formInputs = document.forms['sell-form'];
sell(parseFloat(formInputs["quantity"].value), parseFloat(formInputs["min-price"].value), proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
};
UI_evt.buy = function () {
let formInputs = document.forms['buy-form'];
buy(parseFloat(formInputs["quantity"].value), parseFloat(formInputs["max-price"].value), proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
};
UI_evt.cancelOrders = function () {
let container = document.getElementById('my-orders');
let cancel = [];
let inputs = container.getElementsByTagName('input')
let inputs = container.querySelectorAll('input[checked]')
for (let i = 0; i < inputs.length; i++) {
if (inputs[i].type === "checkbox" && inputs[i].checked) {
let row = inputs[i].parentElement.parentElement
let id = row.dataset['id'];
let type = row.parentElement.dataset['type'];
cancel.push([type, id]);
}
}
cancel.forEach(o => cancelOrder(o[0], o[1], proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(o, error)))
};
UI_evt.depositFLO = function () {
let formInputs = document.forms['deposit-withdraw-form'];
let privKey = prompt("Enter private key");
depositFLO(parseFloat(formInputs["quantity"].value), user_id, privKey, proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
}
UI_evt.depositRupee = function () {
let formInputs = document.forms['deposit-withdraw-form'];
let privKey = prompt("Enter private key");
depositRupee(parseFloat(formInputs["quantity"].value), user_id, privKey, proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
}
UI_evt.withdrawFLO = function () {
let formInputs = document.forms['deposit-withdraw-form'];
withdrawFLO(parseFloat(formInputs["quantity"].value), proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
}
UI_evt.withdrawRupee = function () {
let formInputs = document.forms['deposit-withdraw-form'];
withdrawRupee(parseFloat(formInputs["quantity"].value), proxy.secret)
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(_ => formInputs.reset());
}
refresh(true);
</script>
<script>
function showProcess(id) {
getRef(id).children[0].classList.add('clip')
getRef(id).append(document.createElement('sm-spinner'))
}
function hideProcess(id) {
getRef(id).children[0].classList.remove('clip')
getRef(id).querySelector('sm-spinner')?.remove()
}
let tradeType = 'buy'
getRef('trade_type_selector').addEventListener('change', e => {
getRef('get_price').setAttribute('placeholder', e.detail.value === 'buy' ? 'Max price' : 'Min price')
getRef('trade_button').textContent = e.detail.value
getRef('quantity_type').textContent = e.detail.value === 'buy' ? `Rupee` : `FLO`
tradeType = e.detail.value
getRef('get_price').setAttribute('placeholder', tradeType === 'buy' ? 'Max price' : 'Min price')
getRef('trade_button').textContent = tradeType
getRef('quantity_type').textContent = tradeType === 'buy' ? `Rupee` : `FLO`
})
async function tradeFlo() {
const tradeType = getRef('trade_type_selector').value
const quantity = parseFloat(getRef('get_quantity').value)
const price = parseFloat(getRef('get_price').value)
try {
@ -1075,6 +1131,77 @@
getRef('trade_form').reset()
}
}
const balance = {
flo: 5.1245,
rupee: 457.2
}
const rate = {
flo: 1.487
}
getRef('quantity_selector').addEventListener('click', e => {
// Get latest balance and exchange rate
if (e.target.closest('button')) {
const target = e.target.closest('button')
const fraction = parseInt(target.value) / 100
if (tradeType === 'buy') {
getRef('get_quantity').value = parseFloat(((balance.rupee * fraction) / rate.flo).toFixed(8))
} else {
getRef('get_quantity').value = parseFloat((balance.flo * fraction).toFixed(8))
}
}
})
getRef('wallet_actions').addEventListener('click', e => {
if (e.target.closest('.button')) {
const target = e.target.closest('.button')
showPopup('wallet_popup')
const type = target.value
const asset = getRef('wallet_asset_selector').value
getRef('wallet_popup__cta').textContent = `${type}`
getRef('wallet_popup__cta').setAttribute('value', type)
getRef('wallet_popup__title').textContent = `${type} ${asset}`
if (type === 'withdraw') {
getRef('get_private_key').classList.add('hide-completely')
getRef('get_private_key').removeAttribute('required')
getRef('get_private_key').removeAttribute('hiderequired')
} else {
getRef('get_private_key').setAttribute('required', '')
getRef('get_private_key').setAttribute('hiderequired', '')
getRef('get_private_key').classList.remove('hide-completely')
}
getRef('wallet_form').elementsChanged()
}
})
getRef('wallet_popup__cta').addEventListener('click', async e => {
const asset = getRef('wallet_asset_selector').value
const type = e.target.getAttribute('value')
const quantity = parseFloat(getRef('get_quantity').value)
console.log(type, asset)
try {
showProcess('wallet_popup__cta_wrapper')
if (type === 'deposit') {
const privKey = getRef('get_private_key').value;
if (asset === 'FLO') {
await depositFLO(quantity, user_id, privKey, proxy.secret)
} else {
await depositRupee(quantity, user_id, privKey, proxy.secret)
}
notify(`Deposited ${asset} successfully`)
} else {
if (asset === 'FLO') {
await withdrawFLO(quantity, proxy.secret)
} else {
await withdrawRupee(quantity, proxy.secret)
}
notify(`Withdrawn ${asset} successfully`)
}
}
catch (err) {
notify(err, 'error')
}
finally {
hideProcess('wallet_popup__cta_wrapper')
}
})
</script>
</body>