feature and UI update
-- added BTC as asset -- added option to send btc -- added option to view btc transactions -- implemented different design for homepage
This commit is contained in:
parent
766128d2eb
commit
0f97744191
1
css/bg-art-5.svg
Normal file
1
css/bg-art-5.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 240 240"><defs><style>.a{fill:url(#a);}.b{fill:url(#b);}.c{fill:url(#c);}</style><linearGradient id="a" x1="144.49" y1="221.31" x2="241.6" y2="157.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="b" x1="90.96" y1="190.69" x2="180.52" y2="132.19" gradientTransform="matrix(-0.36, 0.73, -0.7, -0.35, 326.09, 97.01)" xlink:href="#a"/><linearGradient id="c" x1="32.7" y1="395.41" x2="125.65" y2="334.7" gradientTransform="matrix(0.86, -0.58, 0.56, 0.83, -241, -210.66)" xlink:href="#a"/></defs><circle class="a" cx="192.35" cy="190.05" r="57.99"/><circle class="b" cx="163.17" cy="139.1" r="43.16"/><circle class="c" cx="30.93" cy="46.87" r="57.09"/></svg>
|
||||
|
After Width: | Height: | Size: 860 B |
391
css/main.css
391
css/main.css
@ -96,14 +96,12 @@ a.button {
|
||||
button {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
@ -112,6 +110,7 @@ button {
|
||||
padding: 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: var(--accent-color) solid medium;
|
||||
@ -121,8 +120,12 @@ button:not(:disabled) {
|
||||
}
|
||||
|
||||
.button {
|
||||
color: var(--accent-color);
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
}
|
||||
.button .icon {
|
||||
fill: var(--accent-color);
|
||||
}
|
||||
.button--primary, .button--danger {
|
||||
color: rgba(var(--background-color), 1) !important;
|
||||
}
|
||||
@ -179,7 +182,6 @@ details summary {
|
||||
display: flex;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
@ -249,7 +251,6 @@ strip-option {
|
||||
--border-radius: 0.2rem;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@ -305,12 +306,11 @@ ol li::before {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.breakable {
|
||||
.wrap-around {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
@ -504,6 +504,10 @@ ol li::before {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.margin-right-0-3 {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
.margin-right-0-5 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
@ -531,26 +535,15 @@ ol li::before {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
min-height: 8rem;
|
||||
min-height: 5rem;
|
||||
}
|
||||
.page__header .grid {
|
||||
margin-top: auto;
|
||||
}
|
||||
.page__header h1 {
|
||||
margin-top: auto;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.page-layout {
|
||||
display: grid;
|
||||
gap: 1.5rem 0;
|
||||
grid-template-columns: 1.5rem minmax(0, 1fr) 1.5rem;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.page-layout > * {
|
||||
grid-column: 2/3;
|
||||
}
|
||||
|
||||
#confirmation_popup,
|
||||
#prompt_popup {
|
||||
flex-direction: column;
|
||||
@ -622,19 +615,34 @@ ol li::before {
|
||||
|
||||
#sign_in,
|
||||
#sign_up {
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
#sign_in section,
|
||||
#sign_up section {
|
||||
margin-top: -8rem;
|
||||
width: min(24rem, 100%);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#sign_in sm-form,
|
||||
#sign_up sm-form {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
#sign_in {
|
||||
display: grid;
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
#sign_in .illustration {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-color: #4d77ff;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
#sign_in section {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: -2.3rem;
|
||||
}
|
||||
|
||||
#sign_up .h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@ -660,27 +668,44 @@ ol li::before {
|
||||
}
|
||||
|
||||
#main_header {
|
||||
padding: 1.5rem;
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
padding: 1rem 1rem;
|
||||
align-items: center;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.logged-in-user-id {
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
max-width: -webkit-fit-content;
|
||||
max-width: -moz-fit-content;
|
||||
max-width: fit-content;
|
||||
padding: 0.4rem 0.8rem 0.4rem 0.5rem;
|
||||
border-radius: 2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#main_card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto 1fr;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transition: background-color 0.3s;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
background-color: rgba(var(--background-color), 1);
|
||||
}
|
||||
|
||||
#main_navbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
background: rgba(var(--text-color), 0.03);
|
||||
}
|
||||
#main_navbar.hide-away {
|
||||
position: absolute;
|
||||
justify-self: center;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
#main_navbar ul {
|
||||
border-radius: 1rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
box-shadow: 0 0.8rem 3rem rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -700,7 +725,7 @@ ol li::before {
|
||||
padding: 0.5rem 0.3rem;
|
||||
color: var(--text-color);
|
||||
font-size: 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
border-radius: 0.7rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.nav-item .icon {
|
||||
@ -750,10 +775,12 @@ ol li::before {
|
||||
}
|
||||
|
||||
.inner-page {
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 1rem;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
align-content: start;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.password-field label {
|
||||
@ -799,55 +826,46 @@ ol li::before {
|
||||
}
|
||||
|
||||
#wallet_cards_wrapper {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-ms-scroll-snap-type: x proximity;
|
||||
scroll-snap-type: x proximity;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
scroll-snap-align: start;
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
width: min(16rem, 90%);
|
||||
flex-shrink: 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
.balance-card:not(:last-of-type) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.balance-card:nth-of-type(1) {
|
||||
background: url("bg-art2.svg") no-repeat bottom right, #c2ffd7;
|
||||
background-size: contain;
|
||||
}
|
||||
.balance-card:nth-of-type(1) .icon {
|
||||
background-color: rgba(102, 255, 156, 0.5);
|
||||
}
|
||||
.balance-card:nth-of-type(2) {
|
||||
background: url("back.svg") no-repeat bottom right, #fcffa8;
|
||||
background-size: contain;
|
||||
}
|
||||
.balance-card:nth-of-type(2) .icon {
|
||||
background-color: rgba(255, 234, 0, 0.5);
|
||||
}
|
||||
.balance-card > .flex {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.balance-card > .flex .icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
.balance-card .asset-icon {
|
||||
position: relative;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 1.1rem;
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
margin-right: 0.5rem;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
.balance-card.rupee-card .asset-icon {
|
||||
background-color: hsl(141deg, 100%, 70%);
|
||||
}
|
||||
.balance-card.flo-card .asset-icon {
|
||||
fill: white;
|
||||
background-color: #4d77ff;
|
||||
}
|
||||
.balance-card.btc-card .asset-icon {
|
||||
background-color: rgb(255, 173, 8);
|
||||
}
|
||||
|
||||
#rupee_balance span:first-of-type,
|
||||
#flo_balance span:first-of-type {
|
||||
font-size: 2rem;
|
||||
#flo_balance span:first-of-type,
|
||||
#btc_balance span:first-of-type {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
#rupee_balance span:last-of-type,
|
||||
#flo_balance span:last-of-type {
|
||||
#rupee_balance span:nth-of-type(2),
|
||||
#flo_balance span:nth-of-type(2),
|
||||
#btc_balance span:nth-of-type(2) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@ -865,9 +883,9 @@ ol li::before {
|
||||
}
|
||||
.wallet-action .icon:first-of-type,
|
||||
.integrated-action-button .icon:first-of-type {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
padding: 0.6rem;
|
||||
height: 2.3rem;
|
||||
width: 2.3rem;
|
||||
padding: 0.5rem;
|
||||
fill: rgba(var(--foreground-color), 1);
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 2rem;
|
||||
@ -885,14 +903,15 @@ ol li::before {
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
white-space: initial;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
.wallet-action .icon {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.integrated-action-button {
|
||||
padding: 0.8rem 0;
|
||||
justify-content: initial;
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.integrated-action-button .icon:first-of-type {
|
||||
margin-right: 1rem;
|
||||
@ -936,29 +955,40 @@ ol li::before {
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.remove-card-wrapper {
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
.receiver-card {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
border: none;
|
||||
}
|
||||
.receiver-card:not(:last-of-type) {
|
||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
||||
}
|
||||
|
||||
#contacts {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
#contacts .scrolling-wrapper {
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
#saved_ids_list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding-bottom: 5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.saved-id {
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.8rem 0;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
.saved-id.highlight {
|
||||
box-shadow: 0 0 0.1rem 0.1rem var(--accent-color) inset;
|
||||
@ -1002,6 +1032,12 @@ ol li::before {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#saved_ids_tip {
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 2rem;
|
||||
padding: 0.5rem 0.8rem 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
border-radius: 0.5rem;
|
||||
@ -1084,7 +1120,7 @@ ol li::before {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 0.2rem 0 0.5rem 0;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
background-color: rgba(var(--background-color), 1);
|
||||
z-index: 1;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
@ -1153,7 +1189,7 @@ ol li::before {
|
||||
#payments_history {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
padding-bottom: 4rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.transaction {
|
||||
@ -1205,6 +1241,76 @@ ol li::before {
|
||||
grid-area: 1/3/3/4;
|
||||
}
|
||||
|
||||
.btc-tx {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 0.5rem 1rem;
|
||||
align-items: center;
|
||||
grid-template-areas: "icon time amount" "icon receiver receiver" "icon txid txid";
|
||||
}
|
||||
.btc-tx:not(:last-of-type) {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.btc-tx__amount {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btc-tx.out .icon {
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
.btc-tx.out .btc-tx__amount {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
.btc-tx.out .btc-tx__amount::before {
|
||||
content: "- ";
|
||||
}
|
||||
.btc-tx.in .icon {
|
||||
fill: var(--green);
|
||||
}
|
||||
.btc-tx.in .btc-tx__amount {
|
||||
color: var(--green);
|
||||
}
|
||||
.btc-tx.in .btc-tx__amount::before {
|
||||
content: "+ ";
|
||||
}
|
||||
.btc-tx.unconfirmed-tx {
|
||||
grid-template-areas: "icon time amount" "icon receiver receiver" "icon txid txid" "icon unconfirmed unconfirmed";
|
||||
}
|
||||
.btc-tx.unconfirmed-tx .icon {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
.btc-tx__icon {
|
||||
grid-area: icon;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 2rem;
|
||||
}
|
||||
.btc-tx__receiver {
|
||||
grid-area: receiver;
|
||||
font-weight: 500;
|
||||
}
|
||||
.btc-tx__time {
|
||||
grid-area: time;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
.btc-tx__amount {
|
||||
grid-area: amount;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.btc-tx__id {
|
||||
grid-area: txid;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
.btc-tx .unconfirmed-wrapper {
|
||||
grid-area: unconfirmed;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@ -1221,11 +1327,8 @@ ol li::before {
|
||||
|
||||
#add_address_button {
|
||||
border-radius: 0.5rem;
|
||||
color: rgba(var(--background-color), 1);
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
#add_address_button .icon {
|
||||
fill: rgba(var(--background-color), 1);
|
||||
background-color: transparent;
|
||||
border: solid 0.1rem var(--accent-color);
|
||||
}
|
||||
|
||||
.user-action-result__icon {
|
||||
@ -1291,7 +1394,7 @@ ol li::before {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
padding: 0.8rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 0.3rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
@ -1463,12 +1566,31 @@ ol li::before {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 40rem) {
|
||||
.inner-page {
|
||||
padding-bottom: 7rem;
|
||||
}
|
||||
#main_navbar {
|
||||
bottom: 0.5rem;
|
||||
justify-self: center;
|
||||
margin: 0 auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: -webkit-max-content;
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
background-color: rgba(var(--foreground-color), 0.9);
|
||||
-webkit-backdrop-filter: blur(0.5rem);
|
||||
backdrop-filter: blur(0.5rem);
|
||||
}
|
||||
#main_navbar.hide-away {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
aspect-ratio: 1/1;
|
||||
width: 4.5rem;
|
||||
}
|
||||
.integrated-action-button .icon:last-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
@ -1477,58 +1599,32 @@ ol li::before {
|
||||
sm-popup {
|
||||
--width: 24rem;
|
||||
}
|
||||
|
||||
.page-layout {
|
||||
grid-template-columns: 1fr 90vw 1fr;
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
grid-column: 1/-1;
|
||||
padding: 1rem 1.5rem 0 1.5rem;
|
||||
}
|
||||
|
||||
body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#main_card {
|
||||
height: calc(100vh - 2rem);
|
||||
width: calc(100vw - 2rem);
|
||||
grid-template-areas: "header" ".";
|
||||
position: relative;
|
||||
border-radius: 0.8rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.05), 0 1rem 3rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
#main_card:not(.nav-hidden) {
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas: "nav header" "nav .";
|
||||
}
|
||||
|
||||
#main_header {
|
||||
grid-area: header;
|
||||
padding: 1rem 4vw;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
|
||||
#main_navbar {
|
||||
grid-area: nav;
|
||||
border-top: none;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 1rem);
|
||||
margin: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: rgba(37, 110, 255, 0.03);
|
||||
margin: 1rem;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
#main_navbar ul {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.3rem;
|
||||
align-self: center;
|
||||
height: auto;
|
||||
}
|
||||
#main_navbar ul li:last-of-type {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
@ -1539,24 +1635,24 @@ ol li::before {
|
||||
border-radius: 0 1rem 1rem 0;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
#contact > * {
|
||||
padding: 1rem 12vw;
|
||||
}
|
||||
#contact > :last-child {
|
||||
padding: 0.5rem 12vw;
|
||||
}
|
||||
#saved_ids_list {
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
}
|
||||
|
||||
body[data-theme=dark] #main_navbar {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 56rem) {
|
||||
#main_card {
|
||||
width: 56rem;
|
||||
height: min(90vh, 48rem);
|
||||
.inner-page {
|
||||
padding: 1.5rem 18vw;
|
||||
}
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
@ -1564,7 +1660,6 @@ ol li::before {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(var(--text-color), 0.3);
|
||||
border-radius: 1rem;
|
||||
@ -1572,14 +1667,12 @@ ol li::before {
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
|
||||
.interact:not([disabled]) {
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.interact:not([disabled]):hover {
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
}
|
||||
|
||||
button:not([disabled]),
|
||||
.button:not([disabled]) {
|
||||
transition: background-color 0.3s, filter 0.3s;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
359
css/main.scss
359
css/main.scss
@ -94,7 +94,6 @@ button {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
@ -103,6 +102,7 @@ button {
|
||||
padding: 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
&:focus-visible {
|
||||
outline: var(--accent-color) solid medium;
|
||||
}
|
||||
@ -111,7 +111,11 @@ button {
|
||||
}
|
||||
}
|
||||
.button {
|
||||
color: var(--accent-color);
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
.icon {
|
||||
fill: var(--accent-color);
|
||||
}
|
||||
&--primary,
|
||||
&--danger {
|
||||
color: rgba(var(--background-color), 1) !important;
|
||||
@ -278,7 +282,7 @@ ol {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.breakable {
|
||||
.wrap-around {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
@ -470,6 +474,9 @@ ol {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
.margin-right-0-3 {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
.margin-right-0-5 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
@ -493,25 +500,15 @@ ol {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
min-height: 8rem;
|
||||
min-height: 5rem;
|
||||
.grid {
|
||||
margin-top: auto;
|
||||
}
|
||||
h1 {
|
||||
margin-top: auto;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-layout {
|
||||
display: grid;
|
||||
gap: 1.5rem 0;
|
||||
grid-template-columns: 1.5rem minmax(0, 1fr) 1.5rem;
|
||||
align-content: flex-start;
|
||||
& > * {
|
||||
grid-column: 2/3;
|
||||
}
|
||||
}
|
||||
#confirmation_popup,
|
||||
#prompt_popup {
|
||||
flex-direction: column;
|
||||
@ -577,16 +574,31 @@ ol {
|
||||
|
||||
#sign_in,
|
||||
#sign_up {
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
section {
|
||||
margin-top: -8rem;
|
||||
width: min(24rem, 100%);
|
||||
}
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
sm-form {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
#sign_in {
|
||||
display: grid;
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
padding-bottom: 0;
|
||||
.illustration {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-color: #4d77ff;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
section {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: -2.3rem;
|
||||
}
|
||||
}
|
||||
#sign_up {
|
||||
.h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
@ -612,25 +624,39 @@ ol {
|
||||
}
|
||||
}
|
||||
#main_header {
|
||||
padding: 1.5rem;
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
padding: 1rem 1rem;
|
||||
align-items: center;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
min-height: 4rem;
|
||||
}
|
||||
.logged-in-user-id {
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
max-width: fit-content;
|
||||
padding: 0.4rem 0.8rem 0.4rem 0.5rem;
|
||||
border-radius: 2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
#main_card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto 1fr;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transition: background-color 0.3s;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
background-color: rgba(var(--background-color), 1);
|
||||
}
|
||||
|
||||
#main_navbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
background: rgba(var(--text-color), 0.03);
|
||||
&.hide-away {
|
||||
position: absolute;
|
||||
}
|
||||
justify-self: center;
|
||||
margin: 0.5rem;
|
||||
ul {
|
||||
border-radius: 1rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
box-shadow: 0 0.8rem 3rem rgba(0 0 0/ 0.15);
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -650,7 +676,7 @@ ol {
|
||||
padding: 0.5rem 0.3rem;
|
||||
color: var(--text-color);
|
||||
font-size: 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
border-radius: 0.7rem;
|
||||
font-weight: 500;
|
||||
.icon {
|
||||
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
@ -699,10 +725,12 @@ ol {
|
||||
margin: 0.3rem;
|
||||
}
|
||||
.inner-page {
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 1rem;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
align-content: start;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.password-field {
|
||||
@ -745,52 +773,49 @@ ol {
|
||||
align-content: flex-start;
|
||||
}
|
||||
#wallet_cards_wrapper {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x proximity;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||
}
|
||||
.balance-card {
|
||||
scroll-snap-align: start;
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
width: min(16rem, 90%);
|
||||
flex-shrink: 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 0.5rem;
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
.asset-icon {
|
||||
position: relative;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 1.1rem;
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
&:nth-of-type(1) {
|
||||
background: url("bg-art2.svg") no-repeat bottom right, hsl(141, 100%, 88%);
|
||||
background-size: contain;
|
||||
.icon {
|
||||
background-color: hsla(141, 100%, 70%, 0.5);
|
||||
&.rupee-card {
|
||||
.asset-icon {
|
||||
background-color: hsla(141, 100%, 70%);
|
||||
}
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
background: url("back.svg") no-repeat bottom right, hsl(62, 100%, 83%);
|
||||
background-size: contain;
|
||||
.icon {
|
||||
background-color: hsla(55, 100%, 50%, 0.5);
|
||||
&.flo-card {
|
||||
.asset-icon {
|
||||
fill: white;
|
||||
background-color: #4d77ff;
|
||||
}
|
||||
}
|
||||
& > .flex {
|
||||
margin-bottom: 0.3rem;
|
||||
.icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
margin-right: 0.5rem;
|
||||
&.btc-card {
|
||||
.asset-icon {
|
||||
background-color: rgba(255, 173, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
#rupee_balance,
|
||||
#flo_balance {
|
||||
#flo_balance,
|
||||
#btc_balance {
|
||||
span:first-of-type {
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
span:last-of-type {
|
||||
span:nth-of-type(2) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@ -805,9 +830,9 @@ ol {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
.icon:first-of-type {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
padding: 0.6rem;
|
||||
height: 2.3rem;
|
||||
width: 2.3rem;
|
||||
padding: 0.5rem;
|
||||
fill: rgba(var(--foreground-color), 1);
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 2rem;
|
||||
@ -824,13 +849,14 @@ ol {
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
white-space: initial;
|
||||
padding: 0.8rem;
|
||||
.icon {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
.integrated-action-button {
|
||||
padding: 0.8rem 0;
|
||||
justify-content: initial;
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
.icon:first-of-type {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@ -870,25 +896,35 @@ ol {
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
.remove-card-wrapper {
|
||||
min-height: 2rem;
|
||||
}
|
||||
.receiver-card {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
border: none;
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: solid thin rgba(var(--text-color), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
#contacts {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
.scrolling-wrapper {
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
}
|
||||
#saved_ids_list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding-bottom: 5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.saved-id {
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0 0.8rem;
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.8rem 0;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
user-select: none;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
&.highlight {
|
||||
box-shadow: 0 0 0.1rem 0.1rem var(--accent-color) inset;
|
||||
}
|
||||
@ -931,6 +967,11 @@ ol {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
#saved_ids_tip {
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 2rem;
|
||||
padding: 0.5rem 0.8rem 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
@ -1012,7 +1053,7 @@ ol {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 0.2rem 0 0.5rem 0;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
background-color: rgba(var(--background-color), 1);
|
||||
z-index: 1;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
@ -1078,7 +1119,7 @@ ol {
|
||||
#payments_history {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
padding-bottom: 4rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.transaction {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
@ -1132,6 +1173,79 @@ ol {
|
||||
grid-area: 1/3/3/4;
|
||||
}
|
||||
}
|
||||
.btc-tx {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 0.5rem 1rem;
|
||||
align-items: center;
|
||||
grid-template-areas: "icon time amount" "icon receiver receiver" "icon txid txid";
|
||||
&:not(:last-of-type) {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
&__amount {
|
||||
white-space: nowrap;
|
||||
}
|
||||
&.out {
|
||||
.icon {
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
.btc-tx__amount {
|
||||
color: var(--danger-color);
|
||||
&::before {
|
||||
content: "- ";
|
||||
}
|
||||
}
|
||||
}
|
||||
&.in {
|
||||
.icon {
|
||||
fill: var(--green);
|
||||
}
|
||||
.btc-tx__amount {
|
||||
color: var(--green);
|
||||
&::before {
|
||||
content: "+ ";
|
||||
}
|
||||
}
|
||||
}
|
||||
&.unconfirmed-tx {
|
||||
grid-template-areas: "icon time amount" "icon receiver receiver" "icon txid txid" "icon unconfirmed unconfirmed";
|
||||
.icon {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
}
|
||||
&__icon {
|
||||
grid-area: icon;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 2rem;
|
||||
}
|
||||
&__receiver {
|
||||
grid-area: receiver;
|
||||
font-weight: 500;
|
||||
}
|
||||
&__time {
|
||||
grid-area: time;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
&__amount {
|
||||
grid-area: amount;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
&__id {
|
||||
grid-area: txid;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
.unconfirmed-wrapper {
|
||||
grid-area: unconfirmed;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
.fab {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@ -1146,11 +1260,8 @@ ol {
|
||||
}
|
||||
#add_address_button {
|
||||
border-radius: 0.5rem;
|
||||
color: rgba(var(--background-color), 1);
|
||||
background-color: var(--accent-color);
|
||||
.icon {
|
||||
fill: rgba(var(--background-color), 1);
|
||||
}
|
||||
background-color: transparent;
|
||||
border: solid 0.1rem var(--accent-color);
|
||||
}
|
||||
.user-action-result__icon {
|
||||
justify-self: center;
|
||||
@ -1194,7 +1305,7 @@ ol {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
padding: 0.8rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 0.3rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
&:not(:last-of-type) {
|
||||
@ -1366,13 +1477,28 @@ ol {
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
}
|
||||
@media screen and (max-width: 40rem) {
|
||||
.inner-page {
|
||||
padding-bottom: 7rem;
|
||||
}
|
||||
#main_navbar {
|
||||
bottom: 0.5rem;
|
||||
justify-self: center;
|
||||
margin: 0 auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: max-content;
|
||||
background-color: rgba(var(--foreground-color), 0.9);
|
||||
backdrop-filter: blur(0.5rem);
|
||||
&.hide-away {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.nav-item {
|
||||
aspect-ratio: 1/1;
|
||||
width: 4.5rem;
|
||||
}
|
||||
.integrated-action-button {
|
||||
.icon:last-of-type {
|
||||
margin-left: auto;
|
||||
@ -1383,9 +1509,6 @@ ol {
|
||||
sm-popup {
|
||||
--width: 24rem;
|
||||
}
|
||||
.page-layout {
|
||||
grid-template-columns: 1fr 90vw 1fr;
|
||||
}
|
||||
.popup__header {
|
||||
grid-column: 1/-1;
|
||||
padding: 1rem 1.5rem 0 1.5rem;
|
||||
@ -1394,40 +1517,22 @@ ol {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#main_card {
|
||||
height: calc(100vh - 2rem);
|
||||
width: calc(100vw - 2rem);
|
||||
grid-template-areas: "header" ".";
|
||||
&:not(.nav-hidden) {
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas: "nav header" "nav .";
|
||||
}
|
||||
position: relative;
|
||||
border-radius: 0.8rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.05),
|
||||
0 1rem 3rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
#main_header {
|
||||
grid-area: header;
|
||||
padding: 1rem 4vw;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
#main_navbar {
|
||||
grid-area: nav;
|
||||
border-top: none;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 1rem);
|
||||
margin: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: rgba(37 110 255/ 0.03);
|
||||
margin: 1rem;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
ul {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.3rem;
|
||||
li:last-of-type {
|
||||
margin-top: auto;
|
||||
}
|
||||
align-self: center;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.nav-item {
|
||||
@ -1443,20 +1548,23 @@ ol {
|
||||
.card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
#saved_ids_list {
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
.balance-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
body[data-theme="dark"] {
|
||||
#main_navbar {
|
||||
background-color: rgba(0 0 0/ 0.2);
|
||||
#contact {
|
||||
& > * {
|
||||
padding: 1rem 12vw;
|
||||
}
|
||||
& > :last-child {
|
||||
padding: 0.5rem 12vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 56rem) {
|
||||
#main_card {
|
||||
width: 56rem;
|
||||
height: min(90vh, 48rem);
|
||||
#saved_ids_list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
}
|
||||
.inner-page {
|
||||
padding: 1.5rem 18vw;
|
||||
}
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
@ -1468,7 +1576,6 @@ ol {
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(var(--text-color), 0.3);
|
||||
border-radius: 1rem;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
|
||||
792
index.html
792
index.html
File diff suppressed because one or more lines are too long
@ -140,26 +140,26 @@ customElements.define('sm-button',
|
||||
})
|
||||
const smForm = document.createElement('template');
|
||||
smForm.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
form{
|
||||
display: grid;
|
||||
gap: var(--gap, 1.5rem);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<form part="form" onsubmit="return false">
|
||||
<slot></slot>
|
||||
</form>
|
||||
`;
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host{
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
form{
|
||||
display: inherit;
|
||||
gap: var(--gap, 1.5rem);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<form part="form" onsubmit="return false">
|
||||
<slot></slot>
|
||||
</form>
|
||||
`;
|
||||
|
||||
customElements.define('sm-form', class extends HTMLElement {
|
||||
constructor() {
|
||||
@ -173,7 +173,7 @@ customElements.define('sm-form', class extends HTMLElement {
|
||||
this.requiredElements
|
||||
this.submitButton
|
||||
this.resetButton
|
||||
this.allRequiredValid = false;
|
||||
this.invalidFields = false;
|
||||
|
||||
this.debounce = this.debounce.bind(this)
|
||||
this._checkValidity = this._checkValidity.bind(this)
|
||||
@ -191,18 +191,13 @@ customElements.define('sm-form', class extends HTMLElement {
|
||||
};
|
||||
}
|
||||
_checkValidity() {
|
||||
this.allRequiredValid = this.requiredElements.every(elem => elem.isValid)
|
||||
if (!this.submitButton) return;
|
||||
if (this.allRequiredValid) {
|
||||
this.submitButton.disabled = false;
|
||||
}
|
||||
else {
|
||||
this.submitButton.disabled = true;
|
||||
}
|
||||
this.invalidFields = this.requiredElements.filter(elem => !elem.isValid)
|
||||
this.submitButton.disabled = this.invalidFields.length;
|
||||
}
|
||||
handleKeydown(e) {
|
||||
if (e.key === 'Enter' && !e.target.tagName.includes('TEXTAREA')) {
|
||||
if (this.allRequiredValid) {
|
||||
if (!this.invalidFields.length) {
|
||||
if (this.submitButton) {
|
||||
this.submitButton.click()
|
||||
}
|
||||
@ -210,9 +205,8 @@ customElements.define('sm-form', class extends HTMLElement {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}))
|
||||
}
|
||||
else {
|
||||
this.requiredElements.find(elem => !elem.isValid).vibrate()
|
||||
} else {
|
||||
this.requiredElements.forEach(elem => { if (!elem.isValid) elem.vibrate() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,220 +224,230 @@ customElements.define('sm-form', class extends HTMLElement {
|
||||
this._checkValidity()
|
||||
}
|
||||
connectedCallback() {
|
||||
const slot = this.shadowRoot.querySelector('slot')
|
||||
slot.addEventListener('slotchange', this.elementsChanged)
|
||||
this.shadowRoot.querySelector('slot').addEventListener('slotchange', this.elementsChanged)
|
||||
this.addEventListener('input', this.debounce(this._checkValidity, 100));
|
||||
this.addEventListener('keydown', this.debounce(this.handleKeydown, 100));
|
||||
const mutationObserver = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
this.elementsChanged()
|
||||
}
|
||||
})
|
||||
})
|
||||
mutationObserver.observe(this, { childList: true, subtree: true })
|
||||
}
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('input', this.debounce(this._checkValidity, 100));
|
||||
this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100));
|
||||
mutationObserver.disconnect()
|
||||
}
|
||||
})
|
||||
|
||||
//Input
|
||||
const smInput = document.createElement('template')
|
||||
smInput.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration { display: none; }
|
||||
input[type=number] {
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input::-ms-reveal,
|
||||
input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
input:invalid{
|
||||
outline: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
::-moz-focus-inner{
|
||||
border: none;
|
||||
}
|
||||
:host{
|
||||
display: flex;
|
||||
--success-color: #00C853;
|
||||
--danger-color: red;
|
||||
--width: 100%;
|
||||
--icon-gap: 0.5rem;
|
||||
--background: rgba(var(--text-color, (17,17,17)), 0.06);
|
||||
}
|
||||
.hide{
|
||||
display: none !important;
|
||||
}
|
||||
button{
|
||||
display: flex;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-radius: 1rem;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:focus{
|
||||
outline: var(--accent-color, teal) solid medium;
|
||||
}
|
||||
.icon {
|
||||
height: 1.4rem;
|
||||
width: 1.4rem;
|
||||
fill: rgba(var(--text-color, (17,17,17)), 0.6);
|
||||
}
|
||||
|
||||
:host(.round) .input{
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.input {
|
||||
display: flex;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: var(--icon-gap);
|
||||
padding: var(--padding, 0.6rem 0.8rem);
|
||||
border-radius: var(--border-radius,0.3rem);
|
||||
transition: opacity 0.3s, box-shadow 0.2s;
|
||||
background: var(--background);
|
||||
width: 100%;
|
||||
outline: none;
|
||||
min-height: 3.5rem;
|
||||
}
|
||||
.input.readonly .clear{
|
||||
opacity: 0 !important;
|
||||
margin-right: -2rem;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
.readonly{
|
||||
pointer-events: none;
|
||||
}
|
||||
.input:focus-within:not(.readonly){
|
||||
box-shadow: 0 0 0 0.1rem var(--accent-color,teal) inset !important;
|
||||
}
|
||||
.disabled{
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.label {
|
||||
grid-area: 1/1/2/2;
|
||||
font-size: inherit;
|
||||
opacity: .7;
|
||||
font-weight: 400;
|
||||
transition: -webkit-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
transition: transform 0.3s, -webkit-transform 0.3s, color .03;
|
||||
transform-origin: left;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
will-change: transform;
|
||||
}
|
||||
.outer-container{
|
||||
position: relative;
|
||||
width: var(--width);
|
||||
}
|
||||
.container{
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
}
|
||||
input{
|
||||
grid-area: 1/1/2/2;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
caret-color: var(--accent-color, teal);
|
||||
}
|
||||
:host([animate]) .input:focus-within .container input,
|
||||
.animate-placeholder .container input {
|
||||
-webkit-transform: translateY(0.6rem);
|
||||
-ms-transform: translateY(0.6rem);
|
||||
transform: translateY(0.6rem);
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration { display: none; }
|
||||
input[type=number] {
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
|
||||
:host([animate]) .input:focus-within .label,
|
||||
.animate-placeholder .label {
|
||||
-webkit-transform: translateY(-0.7em) scale(0.8);
|
||||
-ms-transform: translateY(-0.7em) scale(0.8);
|
||||
transform: translateY(-0.7em) scale(0.8);
|
||||
opacity: 1;
|
||||
color: var(--accent-color,teal)
|
||||
}
|
||||
:host([variant="outlined"]) .input {
|
||||
box-shadow: 0 0 0 1px var(--border-color, rgba(var(--text-color, (17,17,17)), 0.3)) inset;
|
||||
background: rgba(var(--background-color, (255,255,255)), 1);
|
||||
}
|
||||
.animate-placeholder:focus-within:not(.readonly) .label{
|
||||
color: var(--accent-color,teal)
|
||||
}
|
||||
.feedback-text:not(:empty){
|
||||
display: flex;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 0.9rem;
|
||||
align-items: center;
|
||||
padding: 0.8rem 0;
|
||||
color: rgba(var(--text-color, (17,17,17)), 0.8);
|
||||
}
|
||||
.success{
|
||||
color: var(--success-color);
|
||||
}
|
||||
.error{
|
||||
color: var(--danger-color);
|
||||
}
|
||||
.status-icon{
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
.status-icon--error{
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
.status-icon--success{
|
||||
fill: var(--success-color);
|
||||
}
|
||||
@media (any-hover: hover){
|
||||
.icon:hover{
|
||||
background: rgba(var(--text-color, (17,17,17)), 0.1);
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="outer-container">
|
||||
<label part="input" class="input">
|
||||
<slot name="icon"></slot>
|
||||
<div class="container">
|
||||
<input type="text"/>
|
||||
<div part="placeholder" class="label"></div>
|
||||
<button class="clear hide" title="Clear" tabindex="-1">
|
||||
<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 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</label>
|
||||
<p class="feedback-text"></p>
|
||||
</div>
|
||||
`;
|
||||
input::-ms-reveal,
|
||||
input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
input:invalid{
|
||||
outline: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
::-moz-focus-inner{
|
||||
border: none;
|
||||
}
|
||||
:host{
|
||||
display: flex;
|
||||
--success-color: #00C853;
|
||||
--danger-color: red;
|
||||
--width: 100%;
|
||||
--icon-gap: 0.5rem;
|
||||
--min-height: 3.2rem;
|
||||
--background: rgba(var(--text-color, (17,17,17)), 0.06);
|
||||
}
|
||||
.hide{
|
||||
display: none !important;
|
||||
}
|
||||
button{
|
||||
display: flex;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-radius: 1rem;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:focus{
|
||||
outline: var(--accent-color, teal) solid medium;
|
||||
}
|
||||
.icon {
|
||||
height: 1.2rem;
|
||||
width: 1.2rem;
|
||||
fill: rgba(var(--text-color, (17,17,17)), 0.6);
|
||||
}
|
||||
|
||||
:host(.round) .input{
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.input {
|
||||
display: flex;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: var(--icon-gap);
|
||||
padding: var(--padding, 0.6rem 0.8rem);
|
||||
border-radius: var(--border-radius,0.3rem);
|
||||
transition: opacity 0.3s, box-shadow 0.2s;
|
||||
background: var(--background);
|
||||
width: 100%;
|
||||
outline: none;
|
||||
min-height: var(--min-height);
|
||||
}
|
||||
.input.readonly .clear{
|
||||
opacity: 0 !important;
|
||||
margin-right: -2rem;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
.readonly{
|
||||
pointer-events: none;
|
||||
}
|
||||
.input:focus-within:not(.readonly){
|
||||
box-shadow: 0 0 0 0.1rem var(--accent-color,teal) inset !important;
|
||||
}
|
||||
.disabled{
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.label {
|
||||
grid-area: 1/1/2/2;
|
||||
font-size: inherit;
|
||||
opacity: .7;
|
||||
font-weight: 400;
|
||||
transition: -webkit-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
transition: transform 0.3s, -webkit-transform 0.3s, color .03;
|
||||
transform-origin: left;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
will-change: transform;
|
||||
}
|
||||
.outer-container{
|
||||
position: relative;
|
||||
width: var(--width);
|
||||
}
|
||||
.container{
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
}
|
||||
input{
|
||||
grid-area: 1/1/2/2;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
caret-color: var(--accent-color, teal);
|
||||
}
|
||||
:host([animate]) .input:focus-within .container input,
|
||||
.animate-placeholder .container input {
|
||||
-webkit-transform: translateY(0.6rem);
|
||||
-ms-transform: translateY(0.6rem);
|
||||
transform: translateY(0.6rem);
|
||||
}
|
||||
|
||||
:host([animate]) .input:focus-within .label,
|
||||
.animate-placeholder .label {
|
||||
-webkit-transform: translateY(-0.7em) scale(0.8);
|
||||
-ms-transform: translateY(-0.7em) scale(0.8);
|
||||
transform: translateY(-0.7em) scale(0.8);
|
||||
opacity: 1;
|
||||
color: var(--accent-color,teal)
|
||||
}
|
||||
:host([variant="outlined"]) .input {
|
||||
box-shadow: 0 0 0 1px var(--border-color, rgba(var(--text-color, (17,17,17)), 0.3)) inset;
|
||||
background: rgba(var(--background-color, (255,255,255)), 1);
|
||||
}
|
||||
.animate-placeholder:focus-within:not(.readonly) .label{
|
||||
color: var(--accent-color,teal)
|
||||
}
|
||||
.feedback-text:not(:empty){
|
||||
display: flex;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 0.9rem;
|
||||
align-items: center;
|
||||
padding: 0.8rem 0;
|
||||
color: rgba(var(--text-color, (17,17,17)), 0.8);
|
||||
}
|
||||
.success{
|
||||
color: var(--success-color);
|
||||
}
|
||||
.error{
|
||||
color: var(--danger-color);
|
||||
}
|
||||
.status-icon{
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
.status-icon--error{
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
.status-icon--success{
|
||||
fill: var(--success-color);
|
||||
}
|
||||
@media (any-hover: hover){
|
||||
.icon:hover{
|
||||
background: rgba(var(--text-color, (17,17,17)), 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="outer-container">
|
||||
<label part="input" class="input">
|
||||
<slot name="icon"></slot>
|
||||
<div class="container">
|
||||
<input type="text"/>
|
||||
<div part="placeholder" class="label"></div>
|
||||
<button class="clear hide" title="Clear" tabindex="-1">
|
||||
<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 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</label>
|
||||
<p class="feedback-text"></p>
|
||||
</div>
|
||||
`;
|
||||
customElements.define('sm-input',
|
||||
class extends HTMLElement {
|
||||
|
||||
@ -471,6 +475,7 @@ customElements.define('sm-input',
|
||||
this.focusOut = this.focusOut.bind(this);
|
||||
this.fireEvent = this.fireEvent.bind(this);
|
||||
this.checkInput = this.checkInput.bind(this);
|
||||
this.handleKeydown = this.handleKeydown.bind(this);
|
||||
this.vibrate = this.vibrate.bind(this);
|
||||
}
|
||||
|
||||
@ -552,9 +557,9 @@ customElements.define('sm-input',
|
||||
this.feedbackText.classList.add('error');
|
||||
this.feedbackText.classList.remove('success');
|
||||
this.feedbackText.innerHTML = `
|
||||
<svg class="status-icon status-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>
|
||||
${this._errorText}
|
||||
`;
|
||||
<svg class="status-icon status-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>
|
||||
${this._errorText}
|
||||
`;
|
||||
}
|
||||
}
|
||||
return (_isValid && _customValid);
|
||||
@ -607,6 +612,15 @@ customElements.define('sm-input',
|
||||
this.feedbackText.textContent = '';
|
||||
}
|
||||
}
|
||||
handleKeydown(e) {
|
||||
if (e.key.length === 1) {
|
||||
if (!['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
} else if (e.key === '.' && e.target.value.includes('.')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
vibrate() {
|
||||
this.outerContainer.animate([
|
||||
{ transform: 'translateX(-1rem)' },
|
||||
@ -648,6 +662,10 @@ customElements.define('sm-input',
|
||||
else if (name === 'type') {
|
||||
if (this.hasAttribute('type') && this.getAttribute('type') === 'number') {
|
||||
this.input.setAttribute('inputmode', 'decimal');
|
||||
this.input.addEventListener('keydown', this.handleKeydown);
|
||||
} else {
|
||||
this.input.removeEventListener('keydown', this.handleKeydown);
|
||||
|
||||
}
|
||||
}
|
||||
else if (name === 'helper-text') {
|
||||
@ -685,6 +703,7 @@ customElements.define('sm-input',
|
||||
disconnectedCallback() {
|
||||
this.input.removeEventListener('input', this.checkInput);
|
||||
this.clearBtn.removeEventListener('click', this.clear);
|
||||
this.input.removeEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
})
|
||||
|
||||
@ -3081,4 +3100,426 @@ customElements.define('sm-option', class extends HTMLElement {
|
||||
this.setAttribute('role', 'option')
|
||||
this.setAttribute('tabindex', '0')
|
||||
}
|
||||
})
|
||||
|
||||
const smCarousel = document.createElement('template')
|
||||
smCarousel.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host{
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
--arrow-left: 1rem;
|
||||
--arrow-right: 1rem;
|
||||
--arrow-top: auto;
|
||||
--arrow-bottom: auto;
|
||||
--nav-icon-fill: rgba(var(--background-color, (255,255,255)), 1);
|
||||
--nav-background-color: rgba(var(--text-color, (17,17,17)), 1);
|
||||
--nav-box-shadow: 0 0.2rem 0.2rem #00000020, 0 0.5rem 1rem #00000040;
|
||||
--indicator-top: auto;
|
||||
--indicator-bottom: -1.5rem;
|
||||
--indicator-height: 0.4rem;
|
||||
--indicator-width: 0.4rem;
|
||||
--indicator-border-radius: 0.4rem;
|
||||
--indicators-gap: 0.5rem;
|
||||
--active-indicator-color: var(--accent-color, teal);
|
||||
}
|
||||
.carousel__button{
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
top: var(--arrow-top);
|
||||
bottom: var(--arrow-bottom);
|
||||
border: none;
|
||||
background: var(--nav-background-color);
|
||||
-webkit-box-shadow: var(--nav-box-shadow);
|
||||
box-shadow: var(--nav-box-shadow);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
z-index: 1;
|
||||
border-radius: 3rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
button:focus{
|
||||
outline: none;
|
||||
}
|
||||
button:focus-visible{
|
||||
outline: rgba(var(--text-color, (17,17,17)), 1) 0.1rem solid;
|
||||
}
|
||||
.carousel__button:active{
|
||||
transform: scale(0.9);
|
||||
}
|
||||
.carousel__button--left{
|
||||
left: var(--arrow-left);
|
||||
}
|
||||
.carousel__button--right{
|
||||
right: var(--arrow-right);
|
||||
}
|
||||
.icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
fill: var(--nav-icon-fill);
|
||||
}
|
||||
.hide{
|
||||
display: none !important;
|
||||
}
|
||||
:host([indicator]) .carousel-container{
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.carousel-container{
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
.carousel{
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
overflow: auto hidden;
|
||||
-ms-scroll-snap-type: x mandatory;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
.indicators{
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
padding: 0.5rem 0;
|
||||
top: var(--indicator-top);
|
||||
bottom: var(--indicator-bottom);
|
||||
gap: var(--indicators-gap);
|
||||
width: 100%;
|
||||
}
|
||||
.indicator{
|
||||
position: relative;
|
||||
height: var(--indicator-height);
|
||||
width: var(--indicator-width);
|
||||
background: rgba(var(--text-color), 0.3);
|
||||
border-radius: var(--indicator-border-radius);
|
||||
-webkit-transition: 0.2s;
|
||||
-o-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.indicator.active{
|
||||
background: var(--active-indicator-color);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
slot::slotted(*){
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
:host([align-items="start"]) slot::slotted(*){
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
:host([align-items="center"]) slot::slotted(*){
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
:host([align-items="end"]) slot::slotted(*){
|
||||
scroll-snap-align: end;
|
||||
}
|
||||
@media (hover: hover){
|
||||
.carousel{
|
||||
overflow: hidden;
|
||||
}
|
||||
.carousel__button{
|
||||
opacity: 0.8;
|
||||
}
|
||||
:host(:hover) .carousel__button{
|
||||
opacity: 1;
|
||||
}
|
||||
.left,.right{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (hover: none){
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
.carousel{
|
||||
overflow: auto none;
|
||||
}
|
||||
.carousel__button{
|
||||
display: none;
|
||||
}
|
||||
.left,.right{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="carousel-container">
|
||||
<button class="carousel__button carousel__button--left hide">
|
||||
<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.828 12l4.95 4.95-1.414 1.414L8 12l6.364-6.364 1.414 1.414z"/></svg>
|
||||
</button>
|
||||
<div part="carousel" class="carousel">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<button class="carousel__button carousel__button--right hide">
|
||||
<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="M13.172 12l-4.95-4.95 1.414-1.414L16 12l-6.364 6.364-1.414-1.414z"/></svg>
|
||||
</button>
|
||||
<div class="indicators"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
customElements.define('sm-carousel', class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
}).append(smCarousel.content.cloneNode(true))
|
||||
|
||||
this.isAutoPlaying = false
|
||||
this.autoPlayInterval = 5000
|
||||
this.autoPlayTimeout
|
||||
this.initialTimeout
|
||||
this.activeSlideNum = 0
|
||||
this.carouselItems
|
||||
this.indicators
|
||||
this.showIndicator = false
|
||||
this.carousel = this.shadowRoot.querySelector('.carousel')
|
||||
this.carouselContainer = this.shadowRoot.querySelector('.carousel-container')
|
||||
this.carouselSlot = this.shadowRoot.querySelector('slot')
|
||||
this.navButtonRight = this.shadowRoot.querySelector('.carousel__button--right')
|
||||
this.navButtonLeft = this.shadowRoot.querySelector('.carousel__button--left')
|
||||
this.indicatorsContainer = this.shadowRoot.querySelector('.indicators')
|
||||
|
||||
this.scrollLeft = this.scrollLeft.bind(this)
|
||||
this.scrollRight = this.scrollRight.bind(this)
|
||||
this.handleIndicatorClick = this.handleIndicatorClick.bind(this)
|
||||
this.showSlide = this.showSlide.bind(this)
|
||||
this.nextSlide = this.nextSlide.bind(this)
|
||||
this.autoPlay = this.autoPlay.bind(this)
|
||||
this.startAutoPlay = this.startAutoPlay.bind(this)
|
||||
this.stopAutoPlay = this.stopAutoPlay.bind(this)
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['indicator', 'autoplay', 'interval']
|
||||
}
|
||||
|
||||
scrollLeft() {
|
||||
this.carousel.scrollBy({
|
||||
left: -this.scrollDistance,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
scrollRight() {
|
||||
this.carousel.scrollBy({
|
||||
left: this.scrollDistance,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
showSlide(slideNum) {
|
||||
this.carousel.scrollTo({
|
||||
left: (this.carouselItems[slideNum].getBoundingClientRect().left - this.carousel.getBoundingClientRect().left + this.carousel.scrollLeft),
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
nextSlide() {
|
||||
if (!this.carouselItems) return
|
||||
let showSlideNo = (this.activeSlideNum + 1) < this.carouselItems.length ? this.activeSlideNum + 1 : 0
|
||||
this.showSlide(showSlideNo)
|
||||
}
|
||||
|
||||
autoPlay() {
|
||||
this.nextSlide()
|
||||
if (this.isAutoPlaying) {
|
||||
this.autoPlayTimeout = setTimeout(() => {
|
||||
this.autoPlay()
|
||||
}, this.autoPlayInterval);
|
||||
}
|
||||
}
|
||||
|
||||
startAutoPlay() {
|
||||
this.setAttribute('autoplay', '')
|
||||
}
|
||||
|
||||
stopAutoPlay() {
|
||||
this.removeAttribute('autoplay')
|
||||
}
|
||||
|
||||
createIndicator(index) {
|
||||
let indicator = document.createElement('div')
|
||||
indicator.classList.add('indicator')
|
||||
indicator.dataset.rank = index
|
||||
return indicator
|
||||
}
|
||||
|
||||
handleIndicatorClick(e) {
|
||||
if (e.target.closest('.indicator')) {
|
||||
const slideNum = parseInt(e.target.closest('.indicator').dataset.rank)
|
||||
if (this.activeSlideNum !== slideNum) {
|
||||
this.showSlide(slideNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
if (e.code === 'ArrowLeft')
|
||||
this.scrollRight()
|
||||
else if (e.code === 'ArrowRight')
|
||||
this.scrollRight()
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
let frag = document.createDocumentFragment();
|
||||
|
||||
this.carouselSlot.addEventListener('slotchange', e => {
|
||||
this.carouselItems = this.carouselSlot.assignedElements()
|
||||
this.carouselItems.forEach(item => allElementsObserver.observe(item))
|
||||
if (this.carouselItems.length > 0) {
|
||||
firstOptionObserver.observe(this.carouselItems[0])
|
||||
lastOptionObserver.observe(this.carouselItems[this.carouselItems.length - 1])
|
||||
}
|
||||
else {
|
||||
navButtonLeft.classList.add('hide')
|
||||
navButtonRight.classList.add('hide')
|
||||
firstOptionObserver.disconnect()
|
||||
lastOptionObserver.disconnect()
|
||||
}
|
||||
if (this.showIndicator) {
|
||||
this.indicatorsContainer.innerHTML = ``
|
||||
this.carouselItems.forEach((item, index) => {
|
||||
frag.append(
|
||||
this.createIndicator(index)
|
||||
)
|
||||
item.dataset.rank = index
|
||||
})
|
||||
this.indicatorsContainer.append(frag)
|
||||
this.indicators = this.indicatorsContainer.children
|
||||
}
|
||||
})
|
||||
|
||||
const ioOptions = {
|
||||
threshold: 0.9,
|
||||
root: this
|
||||
}
|
||||
let activeElements = new Set()
|
||||
const allElementsObserver = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (this.showIndicator) {
|
||||
const activeRank = parseInt(entry.target.dataset.rank)
|
||||
if (entry.isIntersecting) {
|
||||
this.indicators[activeRank].classList.add('active')
|
||||
this.activeSlideNum = activeRank
|
||||
activeElements.add(activeRank)
|
||||
} else {
|
||||
this.indicators[activeRank].classList.remove('active')
|
||||
activeElements.delete(activeRank)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (activeElements.size === this.carouselItems.length) {
|
||||
this.indicatorsContainer.classList.add('hide')
|
||||
} else {
|
||||
this.indicatorsContainer.classList.remove('hide')
|
||||
}
|
||||
}, ioOptions)
|
||||
|
||||
|
||||
const firstOptionObserver = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.navButtonLeft.classList.add('hide')
|
||||
}
|
||||
else {
|
||||
this.navButtonLeft.classList.remove('hide')
|
||||
}
|
||||
})
|
||||
},
|
||||
ioOptions
|
||||
)
|
||||
const lastOptionObserver = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.navButtonRight.classList.add('hide')
|
||||
}
|
||||
else {
|
||||
this.navButtonRight.classList.remove('hide')
|
||||
}
|
||||
})
|
||||
},
|
||||
ioOptions
|
||||
)
|
||||
|
||||
const resObs = new ResizeObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.contentBoxSize) {
|
||||
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
|
||||
const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
|
||||
|
||||
this.scrollDistance = contentBoxSize.inlineSize * 0.6
|
||||
} else {
|
||||
this.scrollDistance = entry.contentRect.width * 0.6
|
||||
}
|
||||
})
|
||||
})
|
||||
resObs.observe(this)
|
||||
|
||||
this.addEventListener('keydown', this.handleKeyDown)
|
||||
|
||||
this.navButtonRight.addEventListener('click', this.scrollRight)
|
||||
this.navButtonLeft.addEventListener('click', this.scrollLeft)
|
||||
this.indicatorsContainer.addEventListener('click', this.handleIndicatorClick)
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (oldValue !== newValue) {
|
||||
if (name === 'indicator') {
|
||||
this.showIndicator = this.hasAttribute('indicator')
|
||||
}
|
||||
if (name === 'autoplay') {
|
||||
if (this.hasAttribute('autoplay')) {
|
||||
this.initialTimeout = setTimeout(() => {
|
||||
this.isAutoPlaying = true
|
||||
this.autoPlay()
|
||||
}, this.autoPlayInterval);
|
||||
}
|
||||
else {
|
||||
this.isAutoPlaying = false
|
||||
clearTimeout(this.autoPlayTimeout)
|
||||
clearTimeout(this.initialTimeout)
|
||||
}
|
||||
|
||||
}
|
||||
if (name === 'interval') {
|
||||
if (this.hasAttribute('interval') && this.getAttribute('interval').trim() !== '') {
|
||||
this.autoPlayInterval = Math.abs(parseInt(this.getAttribute('interval').trim()))
|
||||
}
|
||||
else {
|
||||
this.autoPlayInterval = 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.navButtonRight.removeEventListener('click', this.scrollRight)
|
||||
this.navButtonLeft.removeEventListener('click', this.scrollLeft)
|
||||
this.indicatorsContainer.removeEventListener('click', this.handleIndicatorClick)
|
||||
}
|
||||
})
|
||||
397
scripts/fn_ui.js
397
scripts/fn_ui.js
@ -140,12 +140,11 @@ function transferToExchange() {
|
||||
}
|
||||
|
||||
async function renderSavedUpiIds() {
|
||||
const frag = document.createDocumentFragment();
|
||||
let savedUpiIds = []
|
||||
for (const upiId in floGlobals.savedUserData.upiIds) {
|
||||
frag.append(render.savedUpiId(upiId));
|
||||
savedUpiIds.push(render.savedUpiId(upiId))
|
||||
}
|
||||
getRef('saved_upi_ids_list').innerHTML = '';
|
||||
getRef('saved_upi_ids_list').append(frag);
|
||||
renderElem(getRef('saved_upi_ids_list'), html`${savedUpiIds}`)
|
||||
}
|
||||
function saveUpiId() {
|
||||
const frag = document.createDocumentFragment();
|
||||
@ -158,10 +157,8 @@ function saveUpiId() {
|
||||
syncUserData('savedUserData', floGlobals.savedUserData).then(() => {
|
||||
notify(`Saved ${upiId}`, 'success');
|
||||
if (pagesData.lastPage === 'settings') {
|
||||
getRef('saved_upi_ids_list').append(render.savedUpiId(upiId));
|
||||
getRef('saved_upi_ids_list').append(html.node`${render.savedUpiId(upiId)}`);
|
||||
} else if (pagesData.lastPage === 'home') {
|
||||
// getRef('select_topup_upi_id').append(render.savedUpiIdOption(upiId));
|
||||
// getRef('select_topup_upi_id').parentNode.classList.remove('hide')
|
||||
getRef('select_withdraw_upi_id').append(render.savedUpiIdOption(upiId));
|
||||
getRef('select_withdraw_upi_id').parentNode.classList.remove('hide')
|
||||
}
|
||||
@ -511,10 +508,10 @@ function getFloIdTitle(floID) {
|
||||
return floGlobals.savedIds[floID] ? floGlobals.savedIds[floID].title : floID;
|
||||
}
|
||||
|
||||
function formatAmount(amount = 0) {
|
||||
function formatAmount(amount = 0, currency = 'inr') {
|
||||
if (!amount)
|
||||
return '₹0.00';
|
||||
return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' })
|
||||
return amount.toLocaleString(currency === 'inr'? `en-IN`: 'en-US', { style: 'currency', currency })
|
||||
}
|
||||
|
||||
const cashierRejectionErrors = {
|
||||
@ -525,34 +522,49 @@ const cashierRejectionErrors = {
|
||||
}
|
||||
|
||||
const render = {
|
||||
savedId(floID, details) {
|
||||
savedId(floID, details,ref) {
|
||||
const { title } = details;
|
||||
const clone = getRef('saved_id_template').content.cloneNode(true).firstElementChild;
|
||||
clone.dataset.floId = floID;
|
||||
clone.querySelector('.saved-id__initials').textContent = title.charAt(0);
|
||||
clone.querySelector('.saved-id__title').textContent = title;
|
||||
clone.querySelector('.saved-id__flo-id').textContent = floID;
|
||||
return clone;
|
||||
return html.for(ref,floID)`
|
||||
<li class="saved-id grid interact" tabindex="0" data-flo-id="${floID}">
|
||||
<button class="interact edit-saved icon-only" title="Edit name">
|
||||
<div class="saved-id__initials">${title.charAt(0)}</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>
|
||||
<div class="saved-id__title">${title}</div>
|
||||
<div class="grid align-center" style="grid-template-columns: 1fr auto;">
|
||||
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
||||
<button class="copy-saved-id icon-only margin-left-0-5" title="Copy FLO ID">
|
||||
<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>
|
||||
`;
|
||||
|
||||
},
|
||||
transactionCard(transactionDetails) {
|
||||
rupeeTxCard(transactionDetails) {
|
||||
const { txid, time, sender, receiver, tokenAmount } = transactionDetails;
|
||||
const clone = getRef('transaction_template').content.cloneNode(true).firstElementChild;
|
||||
clone.dataset.txid = txid;
|
||||
clone.querySelector('.transaction__time').textContent = getFormattedTime(time * 1000);
|
||||
clone.querySelector('.transaction__amount').textContent = formatAmount(tokenAmount);
|
||||
let transactionReceiver
|
||||
let className
|
||||
let icon
|
||||
if (sender === myFloID) {
|
||||
clone.classList.add('sent');
|
||||
clone.querySelector('.transaction__receiver').textContent = `Sent to ${getFloIdTitle(receiver) || 'Myself'}`;
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon sent" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
||||
className = 'transaction grid sent'
|
||||
transactionReceiver = `Sent to ${getFloIdTitle(receiver) || 'Myself'}`;
|
||||
icon = svg`<svg class="icon sent" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
||||
} else if (receiver === myFloID) {
|
||||
clone.classList.add('received');
|
||||
clone.querySelector('.transaction__receiver').textContent = `Received from ${getFloIdTitle(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 0h24v24H0V0z" fill="none"/><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
||||
className = 'transaction grid received'
|
||||
transactionReceiver = `Received from ${getFloIdTitle(sender)}`;
|
||||
icon = svg`<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
||||
} else { //This should not happen unless API returns transaction that does not involve myFloID
|
||||
row.insertCell().textContent = tx.sender;
|
||||
row.insertCell().textContent = tx.receiver;
|
||||
return html`sender: ${sender} | receiver: ${receiver}`;
|
||||
}
|
||||
return clone;
|
||||
return html.node`
|
||||
<li class="${className}" data-txid="${txid}">
|
||||
<div class="transaction__icon">${icon}</div>
|
||||
<div class="transaction__receiver wrap-around">${transactionReceiver}</div>
|
||||
<time class="transaction__time">${getFormattedTime(time * 1000)}</time>
|
||||
<div class="transaction__amount">${formatAmount(tokenAmount)}</div>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
cashierRequestCard(details) {
|
||||
const { time, senderID, message: { mode, amount = 0 }, note, tag, vectorClock } = details;
|
||||
@ -633,23 +645,24 @@ const render = {
|
||||
return clone;
|
||||
},
|
||||
savedUpiId(upiId) {
|
||||
const clone = getRef('saved_upi_template').content.cloneNode(true).firstElementChild;
|
||||
clone.dataset.upiId = upiId;
|
||||
clone.querySelector('.saved-upi__id').textContent = upiId;
|
||||
return clone;
|
||||
return html`
|
||||
<li class="saved-upi" data-upi-id="${upiId}">
|
||||
<div class="saved-upi__id">${upiId}</div>
|
||||
<button class="delete-upi" title="Delete this UPI ID">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none" /> <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" /> </svg>
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
savedIdPickerCard(floID, { title }) {
|
||||
return createElement('li', {
|
||||
className: 'saved-id grid interact',
|
||||
attributes: { 'tabindex': '0', 'data-flo-id': floID },
|
||||
innerHTML: `
|
||||
<div class="saved-id__initials">${title[0]}</div>
|
||||
<div class="grid gap-0-5">
|
||||
<h4 class="saved-id__title">${title}</h4>
|
||||
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
return html`
|
||||
<li class="saved-id grid interact" tabindex="0" data-flo-id="${floID}">
|
||||
<div class="saved-id__initials">${title[0]}</div>
|
||||
<div class="grid gap-0-5">
|
||||
<h4 class="saved-id__title">${title}</h4>
|
||||
<div class="saved-id__flo-id overflow-ellipsis">${floID}</div>
|
||||
</div>
|
||||
</li>`
|
||||
},
|
||||
savedUpiIdOption(upiId) {
|
||||
return createElement('sm-option', {
|
||||
@ -659,10 +672,10 @@ const render = {
|
||||
}
|
||||
})
|
||||
},
|
||||
paymentsHistory() {
|
||||
rupeeHistory() {
|
||||
let paymentTransactions = []
|
||||
if (paymentsHistoryLoader)
|
||||
paymentsHistoryLoader.clear()
|
||||
if (rupeeHistoryLoader)
|
||||
rupeeHistoryLoader.clear()
|
||||
getRef('payments_history').innerHTML = '<sm-spinner></sm-spinner>';
|
||||
floTokenAPI.getAllTxs(myFloID).then(({ transactions }) => {
|
||||
for (const transactionId in transactions) {
|
||||
@ -678,23 +691,77 @@ const render = {
|
||||
}
|
||||
// solve sorting issue at backend
|
||||
paymentTransactions.sort((a, b) => b.time - a.time);
|
||||
if (paymentsHistoryLoader) {
|
||||
paymentsHistoryLoader.update(paymentTransactions);
|
||||
if (rupeeHistoryLoader) {
|
||||
rupeeHistoryLoader.update(paymentTransactions);
|
||||
} else {
|
||||
paymentsHistoryLoader = new LazyLoader('#payments_history', paymentTransactions, render.transactionCard);
|
||||
rupeeHistoryLoader = new LazyLoader('#payments_history', paymentTransactions, render.rupeeTxCard);
|
||||
}
|
||||
paymentsHistoryLoader.init();
|
||||
rupeeHistoryLoader.init();
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
btcTxCard(transactionDetails) {
|
||||
let { amount, time, txid, sender, receiver, type, block } = transactionDetails;
|
||||
let transactionReceiver
|
||||
let icon
|
||||
// block = null
|
||||
if (block) {
|
||||
if (type === 'out') {
|
||||
transactionReceiver = `Sent to ${receiver}`;
|
||||
icon = svg`<svg class="icon sent" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
||||
} else if (type === 'in') {
|
||||
transactionReceiver = `Received from ${sender}`;
|
||||
icon = svg`<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
||||
} else if (type === 'self') {
|
||||
transactionReceiver = `Sent to self`;
|
||||
icon = svg`<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
||||
}
|
||||
} else {
|
||||
console.log(transactionDetails)
|
||||
transactionReceiver = (type === 'out' ? `Sent to ${receiver}` : `Received from ${sender}`);
|
||||
icon = svg`<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M6,2l0.01,6L10,12l-3.99,4.01L6,22h12v-6l-4-4l4-3.99V2H6z M16,16.5V20H8v-3.5l4-4L16,16.5z"/></g></svg>`;
|
||||
}
|
||||
const className = `btc-tx grid ${type} ${block === null ? 'unconfirmed-tx' : ''}`
|
||||
return html.node`
|
||||
<li class="${className}" data-txid="${txid}">
|
||||
<div class="btc-tx__icon">${icon}</div>
|
||||
<time class="btc-tx__time">${getFormattedTime(time)}</time>
|
||||
<div class="btc-tx__amount amount-shown" data-btc-amount="${amount}">${formatAmount(amount, 'btc')}</div>
|
||||
<div class="btc-tx__receiver wrap-around">${transactionReceiver}</div>
|
||||
<div class="btc-tx__id wrap-around">TXID: ${txid}</div>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
btcHistory() {
|
||||
try {
|
||||
// render transactions
|
||||
getRef('payments_history').innerHTML = '<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';
|
||||
getAddressDetails( btc_api.convert.legacy2bech(myFloID)).then(result => {
|
||||
if (result.txs.length) {
|
||||
let allTransactions = result.txs;
|
||||
const filter = getRef('payments_type_filter').querySelector('input:checked').value;
|
||||
if (filter !== 'all') {
|
||||
allTransactions = allTransactions.filter(t => filter === 'sent' ? t.type === 'out' : t.type === 'in')
|
||||
}
|
||||
console.log(allTransactions)
|
||||
if (btcHistoryLoader) {
|
||||
btcHistoryLoader.update(allTransactions)
|
||||
} else {
|
||||
btcHistoryLoader = new LazyLoader('#payments_history', allTransactions, render.btcTxCard)
|
||||
}
|
||||
btcHistoryLoader.init()
|
||||
} else {
|
||||
getRef('payments_history').textContent = 'No transactions found';
|
||||
}
|
||||
}).catch(error => console.error(error))
|
||||
} catch (err) {
|
||||
notify(err, 'error');
|
||||
}
|
||||
},
|
||||
async savedIds() {
|
||||
const frag = document.createDocumentFragment();
|
||||
await organizeSyncedData('savedIds');
|
||||
getArrayOfSavedIds().forEach(({ floID, details }) => {
|
||||
frag.append(render.savedId(floID, details));
|
||||
})
|
||||
getRef('saved_ids_list').append(frag);
|
||||
renderElem(getRef('saved_ids_list'), html`${getArrayOfSavedIds().map(({floID, details}) => render.savedId(floID, details, getRef('saved_ids_list')))}`)
|
||||
},
|
||||
conditionalSteps() {
|
||||
if (getRef('topup_wallet__qr_wrapper').open) {
|
||||
@ -765,6 +832,14 @@ async function refreshBalance(button) {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
btc_api.getBalance(btc_api.convert.legacy2bech(myFloID)).then(btcBalance => {
|
||||
if(btcBalance) {
|
||||
const [beforeDecimal, afterDecimal = '00'] = String(btcBalance).split('.')
|
||||
renderElem(getRef('btc_balance'), html`<span><b>${beforeDecimal}</b></span>.<span>${afterDecimal}</span>`)
|
||||
} else {
|
||||
renderElem(getRef('btc_balance'), html`<span><b>0</b></span>`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getArrayOfSavedIds() {
|
||||
@ -785,7 +860,7 @@ async function saveFloId() {
|
||||
floGlobals.savedIds[floID] = { title }
|
||||
buttonLoader('save_flo_id_button', true);
|
||||
syncUserData('savedIds', floGlobals.savedIds).then(() => {
|
||||
insertElementAlphabetically(title, render.savedId(floID, { title }))
|
||||
render.savedIds()
|
||||
notify(`Saved ${floID}`, 'success');
|
||||
closePopup();
|
||||
}).catch(error => {
|
||||
@ -797,6 +872,7 @@ async function saveFloId() {
|
||||
delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => {
|
||||
if (e.target.closest('.edit-saved')) {
|
||||
const target = e.target.closest('.saved-id');
|
||||
console.log(target)
|
||||
getRef('edit_saved_id').setAttribute('value', target.dataset.floId);
|
||||
getRef('get_new_title').value = getFloIdTitle(target.dataset.floId);
|
||||
openPopup('edit_saved_popup');
|
||||
@ -821,15 +897,7 @@ function saveIdChanges() {
|
||||
title = 'Unknown';
|
||||
floGlobals.savedIds[floID] = { title }
|
||||
syncUserData('savedIds', floGlobals.savedIds).then(() => {
|
||||
const potentialTarget = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${floID}"]`)
|
||||
if (potentialTarget) {
|
||||
potentialTarget.querySelector('.saved-id__title').textContent = title;
|
||||
potentialTarget.querySelector('.saved-id__initials').textContent = title.charAt(0).toUpperCase();
|
||||
// place the renamed card in alphabetically correct position
|
||||
const clone = potentialTarget.cloneNode(true);
|
||||
potentialTarget.remove();
|
||||
insertElementAlphabetically(title, clone)
|
||||
}
|
||||
render.savedIds()
|
||||
closePopup();
|
||||
}).catch(error => {
|
||||
notify(error, 'error');
|
||||
@ -855,7 +923,7 @@ function deleteSavedId() {
|
||||
}
|
||||
const savedIdsObserver = new MutationObserver((mutationList) => {
|
||||
mutationList.forEach(mutation => {
|
||||
getRef('saved_ids_tip').textContent = mutation.target.children.length === 0 ? `Click 'Add FLO ID' to add a new FLO ID.` : `Tap on saved IDs to see transaction history.`
|
||||
conditionalClassToggle(getRef('saved_ids_tip'), 'hide', !mutation.target.children.length);
|
||||
})
|
||||
})
|
||||
|
||||
@ -883,11 +951,7 @@ getRef('search_saved_ids_picker').addEventListener('input', debounce(async e =>
|
||||
const fuse = new Fuse(allSavedIds, { keys: ['floID', 'details.title'] })
|
||||
allSavedIds = fuse.search(searchKey).map(v => v.item)
|
||||
}
|
||||
allSavedIds.forEach(({ floID, details }) => {
|
||||
frag.append(render.savedIdPickerCard(floID, details))
|
||||
})
|
||||
getRef('saved_ids_picker_list').innerHTML = '';
|
||||
getRef('saved_ids_picker_list').append(frag);
|
||||
renderElem(getRef('saved_ids_picker_list'), html`${allSavedIds.map(({ floID, details }) => render.savedIdPickerCard(floID, details))}`)
|
||||
if (searchKey !== '') {
|
||||
const potentialTarget = getRef('saved_ids_picker_list').firstElementChild
|
||||
if (potentialTarget) {
|
||||
@ -1028,7 +1092,6 @@ function toggleFilters() {
|
||||
|
||||
function applyPaymentsFilters() {
|
||||
const filter = getRef('payments_type_filter').querySelector('input:checked').value;
|
||||
getRef('history_applied_filters').innerHTML = ``;
|
||||
if (filter !== 'all') {
|
||||
renderElem(getRef('history_applied_filters'),
|
||||
html`
|
||||
@ -1042,21 +1105,33 @@ function applyPaymentsFilters() {
|
||||
</button>`);
|
||||
}
|
||||
toggleFilters()
|
||||
render.paymentsHistory()
|
||||
if (pagesData.params.asset == 'rupee') {
|
||||
render.rupeeHistory()
|
||||
} else {
|
||||
render.btcHistory()
|
||||
}
|
||||
closePopup()
|
||||
}
|
||||
function resetPaymentsFilters() {
|
||||
getRef('payments_type_filter').querySelector('input[value="all"]').checked = true;
|
||||
render.paymentsHistory()
|
||||
if (pagesData.params.asset == 'rupee') {
|
||||
render.rupeeHistory()
|
||||
} else {
|
||||
render.btcHistory()
|
||||
}
|
||||
closePopup()
|
||||
toggleFilters()
|
||||
}
|
||||
|
||||
delegate(getRef('history_applied_filters'), 'click', '.applied-filter', e => {
|
||||
const filter = e.delegateTarget.dataset.filter
|
||||
const filterValue = e.delegateTarget.dataset.value
|
||||
// const filter = e.delegateTarget.dataset.filter
|
||||
// const filterValue = e.delegateTarget.dataset.value
|
||||
e.delegateTarget.remove()
|
||||
render.paymentsHistory()
|
||||
if (pagesData.params.asset == 'rupee') {
|
||||
render.rupeeHistory()
|
||||
} else {
|
||||
render.btcHistory()
|
||||
}
|
||||
toggleFilters()
|
||||
})
|
||||
|
||||
@ -1110,4 +1185,166 @@ function signOut() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAddressDetails(address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
btc_api.getAddressData(address).then(data => {
|
||||
console.debug(data);
|
||||
let details = {};
|
||||
details.balance = data.balance;
|
||||
details.address = data.address;
|
||||
details.txs = data.txs.map(tx => {
|
||||
let d = {
|
||||
txid: tx.txid,
|
||||
time: tx.time,
|
||||
block: tx.block_no
|
||||
}
|
||||
if (tx.outgoing) {
|
||||
d.type = "out";
|
||||
d.amount = 0;
|
||||
d.receiver = new Set();
|
||||
let change = 0;
|
||||
tx.outgoing.outputs.forEach(o => {
|
||||
if (o.address !== address) {
|
||||
d.receiver.add(o.address)
|
||||
d.amount += parseFloat(o.value)
|
||||
} else
|
||||
change += parseFloat(o.value)
|
||||
});
|
||||
d.receiver = Array.from(d.receiver);
|
||||
d.amount = parseFloat(d.amount.toFixed(8))
|
||||
d.fee = parseFloat((tx.outgoing.value - (d.amount + change)).toFixed(8))
|
||||
if (!d.amount && change > 0) {
|
||||
d.type = "self";
|
||||
d.amount = change
|
||||
delete d.receiver;
|
||||
d.address = address;
|
||||
}
|
||||
} else if (tx.incoming) {
|
||||
d.type = "in";
|
||||
d.amount = parseFloat(tx.incoming.value);
|
||||
d.sender = Array.from(new Set(tx.incoming.inputs.map(i => i.address)));
|
||||
}
|
||||
return d;
|
||||
})
|
||||
resolve(details);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
function calculateBtcFees() {
|
||||
fetch('https://bitcoiner.live/api/fees/estimates/latest').then(res => res.json()).then(data => {
|
||||
const satPerByte = data.estimates['60'].sat_per_vbyte;
|
||||
const legacyBytes = 200;
|
||||
const segwitBytes = 77;
|
||||
const fees = (legacyBytes * satPerByte + (0.25 * satPerByte) * segwitBytes) / Math.pow(10, 8);
|
||||
getRef('send_fee').value = fees.toFixed(8);
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
function togglePrivateKeyVisibility(input) {
|
||||
const target = input.closest('sm-input')
|
||||
target.type = target.type === 'password' ? 'text' : 'password';
|
||||
target.focusIn()
|
||||
}
|
||||
const txParticipantsObserver = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
if (mutation.addedNodes.length > 0 && mutation.target.children.length > 1) {
|
||||
const removeButton = mutation.target.firstElementChild.querySelector('.remove-card')
|
||||
if (!removeButton) {
|
||||
const newRemoveButton = html.node`
|
||||
<button class="remove-card button--small">
|
||||
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
|
||||
</svg>
|
||||
Remove
|
||||
</button>
|
||||
`
|
||||
mutation.target.firstElementChild.querySelector('.remove-card-wrapper').appendChild(newRemoveButton)
|
||||
}
|
||||
} else if (mutation.removedNodes.length > 0 && mutation.target.children.length === 1) {
|
||||
const removeButton = mutation.target.firstElementChild.querySelector('.remove-card')
|
||||
if (removeButton) {
|
||||
removeButton.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
txParticipantsObserver.observe(getRef('receiver_container'), {
|
||||
childList: true
|
||||
})
|
||||
let globalExchangeRate = {}
|
||||
async function getExchangeRate() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Promise.all(['usd', 'inr'].map(cur => fetch(`https://bitpay.com/api/rates/btc/${cur}`))).then(responses => {
|
||||
Promise.all(responses.map(res => res.json())).then(rates => {
|
||||
rates.forEach(rate => {
|
||||
globalExchangeRate[rate.code.toLowerCase()] = rate.rate
|
||||
})
|
||||
globalExchangeRate.btc = 1
|
||||
resolve(globalExchangeRate)
|
||||
}).catch(err => console.log(err))
|
||||
}).catch(err => console.log(err))
|
||||
})
|
||||
}
|
||||
getRef('add_receiver').onclick = evt => {
|
||||
let receiverCard = getRef('receiver_template').content.cloneNode(true)
|
||||
if (!getRef('receiver_container').children.length) {
|
||||
receiverCard.querySelector('.remove-card').remove()
|
||||
}
|
||||
receiverCard.querySelector('.currency-symbol').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg>`
|
||||
getRef('receiver_container').appendChild(receiverCard);
|
||||
getRef('receiver_container').querySelectorAll('sm-input[data-btc-address]').forEach(input => input.customValidation = btc_api.validateAddress)
|
||||
}
|
||||
delegate(getRef('receiver_container'), 'click', '.remove-card', e => {
|
||||
e.target.closest('.receiver-card').remove()
|
||||
})
|
||||
|
||||
getRef('fees_selector').addEventListener('change', e => {
|
||||
if (e.target.value !== 'custom') {
|
||||
getRef('send_fee').readOnly = true;
|
||||
}
|
||||
switch (e.target.value) {
|
||||
case 'custom':
|
||||
getRef('send_fee').readOnly = false;
|
||||
getRef('send_fee').focusIn();
|
||||
getRef('selected_fee_tip').textContent = 'Set custom fee';
|
||||
break;
|
||||
case 'suggested':
|
||||
getRef('selected_fee_tip').textContent = 'Estimated time of confirmation is 1hr'
|
||||
calculateBtcFees();
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
getRef('send_transaction').onclick = evt => {
|
||||
buttonLoader('send_transaction', true)
|
||||
const senders = btc_api.convert.legacy2bech(myFloID);
|
||||
const privKeys = btc_api.convert.wif(myPrivKey);
|
||||
const receivers = [...getRef('receiver_container').querySelectorAll('.receiver-input')].map(input => input.value.trim());
|
||||
const amounts = [...getRef('receiver_container').querySelectorAll('.amount-input')].map(input => {
|
||||
return parseFloat(input.value.trim())
|
||||
});
|
||||
const fee = parseFloat(getRef('send_fee').value.trim());
|
||||
console.debug(senders, receivers, amounts, fee);
|
||||
btc_api.sendTx(senders, privKeys, receivers, amounts, fee).then(result => {
|
||||
console.log(result);
|
||||
closePopup();
|
||||
getRef('txid').value = result.txid;
|
||||
openPopup('txid_popup');
|
||||
getRef('send_tx').reset()
|
||||
getExchangeRate().then(() => {
|
||||
calculateBtcFees()
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
}).catch(error => {
|
||||
notify(`Error sending transaction \n ${error}`, 'error');
|
||||
}).finally(_ => {
|
||||
buttonLoader('send_transaction', false)
|
||||
})
|
||||
}
|
||||
2883
scripts/lib_btc.js
Normal file
2883
scripts/lib_btc.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,9 @@
|
||||
/*jshint esversion: 9 */
|
||||
// Global variables
|
||||
const { html, render: renderElem } = uhtml;
|
||||
const { html, render: renderElem,svg } = uhtml;
|
||||
const domRefs = {};
|
||||
let paymentsHistoryLoader = null;
|
||||
let rupeeHistoryLoader = null;
|
||||
let btcHistoryLoader = null;
|
||||
let walletHistoryLoader = null;
|
||||
let contactHistoryLoader = null;
|
||||
let paymentRequestsLoader = null;
|
||||
@ -93,12 +94,10 @@ document.addEventListener('popupopened', async e => {
|
||||
const frag = document.createDocumentFragment()
|
||||
switch (e.target.id) {
|
||||
case 'saved_ids_popup':
|
||||
getArrayOfSavedIds().forEach(({ floID, details }) => {
|
||||
frag.append(render.savedIdPickerCard(floID, details))
|
||||
})
|
||||
getRef('saved_ids_picker_list').innerHTML = ''
|
||||
getRef('saved_ids_picker_list').append(frag)
|
||||
getRef('search_saved_ids_picker').focusIn()
|
||||
renderElem(getRef('saved_ids_picker_list'), html`${getArrayOfSavedIds().map(({ floID, details }) => render.savedIdPickerCard(floID, details))}`)
|
||||
setTimeout(() => {
|
||||
getRef('search_saved_ids_picker').focusIn()
|
||||
}, 0);
|
||||
break;
|
||||
case 'topup_wallet_popup':
|
||||
case 'withdraw_wallet_popup':
|
||||
@ -113,6 +112,9 @@ document.addEventListener('popupopened', async e => {
|
||||
getRef('select_withdraw_upi_id').parentNode.classList.remove('hide')
|
||||
}
|
||||
break;
|
||||
case 'send_btc_popup':
|
||||
calculateBtcFees();
|
||||
break;
|
||||
}
|
||||
})
|
||||
document.addEventListener('popupclosed', e => {
|
||||
@ -208,16 +210,13 @@ function notify(message, mode, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFormattedTime(time, format) {
|
||||
function getFormattedTime(timestamp, 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(),
|
||||
if (String(timestamp).length < 13)
|
||||
timestamp *= 1000
|
||||
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
|
||||
minutes = new Date(timestamp).getMinutes(),
|
||||
hours = new Date(timestamp).getHours(),
|
||||
currentTime = new Date().toString().split(' ')
|
||||
|
||||
minutes = minutes < 10 ? `0${minutes}` : minutes
|
||||
@ -233,15 +232,22 @@ function getFormattedTime(time, format) {
|
||||
switch (format) {
|
||||
case 'date-only':
|
||||
return `${month} ${date}, ${year}`;
|
||||
break;
|
||||
case 'time-only':
|
||||
return finalHours;
|
||||
case 'relative':
|
||||
// check if timestamp is older than a day
|
||||
if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000)
|
||||
return `${finalHours}`;
|
||||
else
|
||||
return relativeTime.from(timestamp)
|
||||
default:
|
||||
return `${month} ${date}, ${year} at ${finalHours}`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return time;
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
// implement event delegation
|
||||
function delegate(el, event, selector, fn) {
|
||||
el.addEventListener(event, function (e) {
|
||||
@ -277,12 +283,18 @@ window.addEventListener("load", () => {
|
||||
})
|
||||
});
|
||||
function toggleFloIDVisibility(hide) {
|
||||
document.querySelectorAll('.flo-id-wrapper').forEach(elem => {
|
||||
document.querySelectorAll('.logged-in-user-id').forEach(elem => {
|
||||
if(hide)
|
||||
elem.classList.add('hide')
|
||||
else
|
||||
elem.classList.remove('hide')
|
||||
})
|
||||
document.querySelectorAll('.app-name').forEach(elem => {
|
||||
if(hide)
|
||||
elem.classList.remove('hide')
|
||||
else
|
||||
elem.classList.add('hide')
|
||||
})
|
||||
}
|
||||
function createRipple(event, target) {
|
||||
const circle = document.createElement("span");
|
||||
@ -295,6 +307,10 @@ function createRipple(event, target) {
|
||||
circle.classList.add("ripple");
|
||||
const rippleAnimation = circle.animate(
|
||||
[
|
||||
{
|
||||
opacity: 1,
|
||||
transform: `scale(0)`
|
||||
},
|
||||
{
|
||||
transform: "scale(4)",
|
||||
opacity: 0,
|
||||
@ -386,7 +402,13 @@ async function showPage(targetPage, options = {}) {
|
||||
})
|
||||
break;
|
||||
case 'history':
|
||||
render.paymentsHistory()
|
||||
if (params.asset) {
|
||||
if (params.asset === 'rupee') {
|
||||
render.rupeeHistory()
|
||||
}else if(params.asset === 'btc'){
|
||||
render.btcHistory()
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'wallet':
|
||||
const walletTransactions = []
|
||||
@ -516,8 +538,10 @@ async function showPage(targetPage, options = {}) {
|
||||
break;
|
||||
}
|
||||
if (pageId !== 'history') {
|
||||
if (paymentsHistoryLoader)
|
||||
paymentsHistoryLoader.clear()
|
||||
if (rupeeHistoryLoader)
|
||||
rupeeHistoryLoader.clear()
|
||||
if (btcHistoryLoader)
|
||||
btcHistoryLoader.clear()
|
||||
}
|
||||
if (pageId !== 'wallet') {
|
||||
if (walletHistoryLoader)
|
||||
@ -668,102 +692,121 @@ const indicatorObserver = new IntersectionObserver(entries => {
|
||||
threshold: 1
|
||||
})
|
||||
|
||||
// class based lazy loading
|
||||
class LazyLoader {
|
||||
constructor(container, elementsToRender, renderFn, options = {}) {
|
||||
const { batchSize = 10, freshRender, bottomFirst = false } = options
|
||||
// class based lazy loading
|
||||
class LazyLoader {
|
||||
constructor(container, elementsToRender, renderFn, options = {}) {
|
||||
const { batchSize = 10, freshRender, bottomFirst = false, domUpdated } = options
|
||||
|
||||
this.elementsToRender = elementsToRender
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
this.renderFn = renderFn
|
||||
this.intersectionObserver
|
||||
this.elementsToRender = elementsToRender
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
this.renderFn = renderFn
|
||||
this.intersectionObserver
|
||||
|
||||
this.batchSize = batchSize
|
||||
this.freshRender = freshRender
|
||||
this.bottomFirst = bottomFirst
|
||||
this.batchSize = batchSize
|
||||
this.freshRender = freshRender
|
||||
this.domUpdated = domUpdated
|
||||
this.bottomFirst = bottomFirst
|
||||
|
||||
this.lazyContainer = document.querySelector(container)
|
||||
this.shouldLazyLoad = false
|
||||
this.lastScrollTop = 0
|
||||
this.lastScrollHeight = 0
|
||||
|
||||
this.update = this.update.bind(this)
|
||||
this.render = this.render.bind(this)
|
||||
this.init = this.init.bind(this)
|
||||
this.clear = this.clear.bind(this)
|
||||
}
|
||||
get elements() {
|
||||
return this.arrayOfElements
|
||||
}
|
||||
init() {
|
||||
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
observer.disconnect()
|
||||
this.render({ lazyLoad: true })
|
||||
}
|
||||
})
|
||||
}, {
|
||||
threshold: 0.3
|
||||
})
|
||||
this.mutationObserver = new MutationObserver(mutationList => {
|
||||
mutationList.forEach(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
if (mutation.addedNodes.length) {
|
||||
if (this.bottomFirst)
|
||||
this.intersectionObserver.observe(this.lazyContainer.firstElementChild)
|
||||
else
|
||||
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
|
||||
this.lazyContainer = document.querySelector(container)
|
||||
|
||||
this.update = this.update.bind(this)
|
||||
this.render = this.render.bind(this)
|
||||
this.init = this.init.bind(this)
|
||||
this.clear = this.clear.bind(this)
|
||||
}
|
||||
get elements() {
|
||||
return this.arrayOfElements
|
||||
}
|
||||
init() {
|
||||
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
observer.disconnect()
|
||||
this.render({ lazyLoad: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mutationObserver = new MutationObserver(mutationList => {
|
||||
mutationList.forEach(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
if (mutation.addedNodes.length) {
|
||||
if (this.bottomFirst) {
|
||||
if (this.lazyContainer.firstElementChild)
|
||||
this.intersectionObserver.observe(this.lazyContainer.firstElementChild)
|
||||
} else {
|
||||
if (this.lazyContainer.lastElementChild)
|
||||
this.intersectionObserver.observe(this.lazyContainer.lastElementChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mutationObserver.observe(this.lazyContainer, {
|
||||
childList: true,
|
||||
})
|
||||
this.render()
|
||||
}
|
||||
update(elementsToRender) {
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
}
|
||||
render(options = {}) {
|
||||
let { lazyLoad = false } = options
|
||||
this.shouldLazyLoad = lazyLoad
|
||||
const frag = document.createDocumentFragment();
|
||||
if (lazyLoad) {
|
||||
if (this.bottomFirst) {
|
||||
this.updateEndIndex = this.updateStartIndex
|
||||
this.updateStartIndex = this.updateEndIndex - this.batchSize
|
||||
} else {
|
||||
this.updateStartIndex = this.updateEndIndex
|
||||
this.updateEndIndex = this.updateEndIndex + this.batchSize
|
||||
}
|
||||
} else {
|
||||
this.intersectionObserver.disconnect()
|
||||
if (this.bottomFirst) {
|
||||
this.updateEndIndex = this.arrayOfElements.length
|
||||
this.updateStartIndex = this.updateEndIndex - this.batchSize - 1
|
||||
} else {
|
||||
this.updateStartIndex = 0
|
||||
this.updateEndIndex = this.batchSize
|
||||
}
|
||||
this.lazyContainer.innerHTML = ``;
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mutationObserver.observe(this.lazyContainer, {
|
||||
childList: true,
|
||||
})
|
||||
this.render()
|
||||
}
|
||||
update(elementsToRender) {
|
||||
this.arrayOfElements = (typeof elementsToRender === 'function') ? this.elementsToRender() : elementsToRender || []
|
||||
}
|
||||
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
|
||||
}
|
||||
if (this.bottomFirst) {
|
||||
for (let index = this.updateStartIndex; index < this.updateEndIndex; index++) {
|
||||
frag.prepend(this.renderFn(this.arrayOfElements[index]))
|
||||
this.lastScrollHeight = this.lazyContainer.scrollHeight
|
||||
this.lastScrollTop = this.lazyContainer.scrollTop
|
||||
this.arrayOfElements.slice(this.updateStartIndex, this.updateEndIndex).forEach((element, index) => {
|
||||
frag.append(this.renderFn(element))
|
||||
})
|
||||
if (this.bottomFirst) {
|
||||
this.lazyContainer.prepend(frag)
|
||||
// scroll anchoring for reverse scrolling
|
||||
this.lastScrollTop += this.lazyContainer.scrollHeight - this.lastScrollHeight
|
||||
this.lazyContainer.scrollTo({ top: this.lastScrollTop })
|
||||
this.lastScrollHeight = this.lazyContainer.scrollHeight
|
||||
} else {
|
||||
this.lazyContainer.append(frag)
|
||||
}
|
||||
if (!lazyLoad && this.bottomFirst) {
|
||||
this.lazyContainer.scrollTop = this.lazyContainer.scrollHeight
|
||||
}
|
||||
// Callback to be called if elements are updated or rendered for first time
|
||||
if (!lazyLoad && this.freshRender)
|
||||
this.freshRender()
|
||||
}
|
||||
this.lazyContainer.prepend(frag)
|
||||
} else {
|
||||
for (let index = this.updateStartIndex; index < this.updateEndIndex; index++) {
|
||||
frag.append(this.renderFn(this.arrayOfElements[index]))
|
||||
clear() {
|
||||
this.intersectionObserver.disconnect()
|
||||
this.mutationObserver.disconnect()
|
||||
this.lazyContainer.innerHTML = ``;
|
||||
}
|
||||
reset() {
|
||||
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
|
||||
this.render()
|
||||
}
|
||||
this.lazyContainer.append(frag)
|
||||
}
|
||||
if (!lazyLoad && this.bottomFirst)
|
||||
this.lazyContainer.scrollTo({
|
||||
top: this.lazyContainer.scrollHeight,
|
||||
})
|
||||
// Callback to be called if elements are updated or rendered for first time
|
||||
if (!lazyLoad && this.freshRender)
|
||||
this.freshRender()
|
||||
}
|
||||
clear() {
|
||||
this.intersectionObserver.disconnect()
|
||||
this.mutationObserver.disconnect()
|
||||
this.lazyContainer.innerHTML = ``;
|
||||
}
|
||||
reset() {
|
||||
this.arrayOfElements = (typeof this.elementsToRender === 'function') ? this.elementsToRender() : this.elementsToRender || []
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
function animateTo(element, keyframes, options) {
|
||||
const anime = element.animate(keyframes, { ...options, fill: 'both' })
|
||||
anime.finished.then(() => {
|
||||
@ -780,13 +823,31 @@ function handleMobileChange(e) {
|
||||
mobileQuery.addEventListener('change', handleMobileChange)
|
||||
handleMobileChange(mobileQuery)
|
||||
|
||||
function showChildElement(id, index) {
|
||||
[...getRef(id).children].forEach((child, i) => {
|
||||
if (i === index)
|
||||
child.classList.remove('hide')
|
||||
else
|
||||
child.classList.add('hide')
|
||||
})
|
||||
function showChildElement(id, index, options = {}) {
|
||||
const { mobileView = false, entry, exit } = options
|
||||
const animOptions = {
|
||||
duration: 150,
|
||||
easing: 'ease',
|
||||
fill: 'forwards'
|
||||
}
|
||||
const visibleElement = [...getRef(id).children].find(elem => !elem.classList.contains(mobileView ? 'hide-on-mobile' : 'hide'));
|
||||
if (visibleElement === getRef(id).children[index]) return;
|
||||
if (visibleElement) {
|
||||
if (exit) {
|
||||
visibleElement.animate(exit, animOptions).onfinish = () => {
|
||||
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hide')
|
||||
getRef(id).children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hide')
|
||||
if (entry)
|
||||
getRef(id).children[index].animate(entry, animOptions)
|
||||
}
|
||||
} else {
|
||||
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hide')
|
||||
getRef(id).children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hide')
|
||||
}
|
||||
} else {
|
||||
getRef(id).children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hide')
|
||||
getRef(id).children[index].animate(entry, animOptions)
|
||||
}
|
||||
}
|
||||
|
||||
// Reactivity system
|
||||
@ -880,4 +941,12 @@ function handleVisibilityChange() {
|
||||
if (floGlobals.loaded && floGlobals.isSubAdmin)
|
||||
startStatusInterval()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function conditionalClassToggle(el, className, condition) {
|
||||
if(condition)
|
||||
el.classList.add(className);
|
||||
else
|
||||
el.classList.remove(className);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user