diff --git a/css/main.css b/css/main.css index e4706de..f0bbe02 100644 --- a/css/main.css +++ b/css/main.css @@ -183,6 +183,7 @@ details summary { align-items: center; gap: 0.5rem; color: var(--accent-color); + font-weight: 500; } details summary .down-arrow { fill: var(--accent-color); @@ -773,6 +774,14 @@ h3 { width: 1.2em; } +.loan { + display: grid; + gap: 1rem; + background-color: rgba(var(--foreground-color), 1); + padding: max(1rem, 1.5vw); + border-radius: 0.5rem; +} + .loan-process { display: grid; gap: 1rem; @@ -781,6 +790,22 @@ h3 { border-radius: 0.5rem; --progress-line-thickness: 0.15rem; } +.loan-process__type { + position: relative; + padding-bottom: 0.5rem; + color: rgba(var(--text-color), 0.8); +} +.loan-process__type::before { + content: ""; + position: absolute; + height: 0.2rem; + width: 40%; + left: 0; + bottom: 0; + transform: translateY(-50%); + border-radius: 0 0.5rem 0.5rem 0; + background-color: var(--accent-color); +} .loan-process ul { display: grid; } diff --git a/css/main.min.css b/css/main.min.css index d708d67..29011d5 100644 --- a/css/main.min.css +++ b/css/main.min.css @@ -1 +1 @@ -*{padding:0;margin:0;box-sizing:border-box;font-family:"Roboto",sans-serif}:root{font-size:clamp(1rem,1.2vmax,1.5rem)}html,body{height:100%}body{--accent-color: #365eff;--text-color: 30, 30, 30;--background-color: 248, 248, 248;--foreground-color: 255, 255, 255;--danger-color: rgb(255, 75, 75);--green: #1cad59;scrollbar-width:thin;scrollbar-gutter:stable;color:rgba(var(--text-color), 1);background-color:rgba(var(--background-color), 1);transition:background-color .3s}body[data-theme=dark]{--accent-color: #86afff;--text-color: 220, 220, 220;--background-color: 10, 10, 10;--foreground-color: 24, 24, 24;--danger-color: rgb(255, 106, 106);--green: #00e676}body[data-theme=dark] sm-popup::part(popup){background-color:rgba(var(--foreground-color), 1)}body[data-theme=dark] ::-webkit-calendar-picker-indicator{filter:invert(1)}p,strong{font-size:.9rem;max-width:65ch;line-height:1.7;color:rgba(var(--text-color), 0.9)}p:not(:last-of-type),strong:not(:last-of-type){margin-bottom:1.5rem}a{text-decoration:none;color:var(--accent-color)}a:focus-visible{box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset}button,.button{-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative;display:inline-flex;border:none;background-color:rgba(0,0,0,0);overflow:hidden;color:inherit;-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;font-size:inherit;font-weight:500;white-space:nowrap;padding:.6rem .9rem;border-radius:.3rem;justify-content:center}button:focus-visible,.button:focus-visible{outline:var(--accent-color) solid medium}button:not(:disabled),.button:not(:disabled){cursor:pointer}.button{background-color:rgba(var(--text-color), 0.02);border:solid thin rgba(var(--text-color), 0.06)}.button--primary{color:rgba(var(--background-color), 1);background-color:var(--accent-color)}.button--primary .icon{fill:rgba(var(--background-color), 1)}.button--colored{color:var(--accent-color)}.button--colored .icon{fill:var(--accent-color)}.button--danger{background-color:rgba(255,115,115,.062745098);color:var(--danger-color)}.button--danger .icon{fill:var(--danger-color)}.button--small{padding:.4rem .6rem}.button--outlined{border:solid rgba(var(--text-color), 0.3) .1rem;background-color:rgba(var(--foreground-color), 1)}.button--transparent{background-color:rgba(0,0,0,0)}button:disabled{cursor:not-allowed;background-color:rgba(var(--text-color), 0.03);color:rgba(var(--text-color), 0.5)}.cta{text-transform:uppercase;font-size:.9rem;font-weight:700;letter-spacing:.05em;padding:.8rem 1rem}.icon{width:1.2rem;height:1.2rem;fill:rgba(var(--text-color), 0.8);flex-shrink:0}.icon-only{padding:.5rem;border-radius:.3rem}.icon--big{width:3rem;height:3rem}a:-webkit-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:-moz-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}details{padding:1rem 0}details summary{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;align-items:center;gap:.5rem;color:var(--accent-color)}details summary .down-arrow{fill:var(--accent-color)}details[open] summary{margin-bottom:1rem}details[open]>summary .down-arrow{transform:rotate(180deg)}sm-input,sm-textarea{font-size:.9rem;--border-radius: 0.5rem;--background-color: rgba(var(--foreground-color), 1)}sm-input button .icon,sm-textarea button .icon{fill:var(--accent-color)}sm-textarea{--max-height: auto}sm-spinner{--size: 1rem;--stroke-width: 0.1rem}sm-form{--gap: 1rem}sm-chips{--gap: 0.3rem}sm-chip{position:relative;font-size:.9rem;--border-radius: 0.5rem;--padding: 0.5rem 0.8rem;--background: rgba(var(--text-color), 0.06);-webkit-user-select:none;-moz-user-select:none;user-select:none}sm-chip[selected=true] .badge{background-color:rgba(var(--foreground-color), 1);color:var(--accent-color)}sm-chip .badge{position:relative;display:flex;align-items:center;justify-content:center;height:1.2rem;border-radius:2rem;margin-left:.5rem;font-weight:700;aspect-ratio:1/1;background-color:var(--accent-color);color:rgba(var(--background-color), 1)}sm-select::part(options){max-height:40vh}sm-option{flex-shrink:0}sm-option::part(option){grid-template-columns:none}ul{list-style:none}fieldset{display:grid;gap:.5rem;padding:1rem;border-radius:.5rem;border:solid 1px rgba(var(--text-color), 0.3)}fieldset legend{font-size:.9rem;font-weight:500}input[type=radio]{height:1.1em;width:1.1em;margin-right:.5rem;accent-color:var(--accent-color)}.overflow-ellipsis{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.wrap-around{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word}.full-bleed{grid-column:1/-1}.h1{font-size:1.5rem}.h2{font-size:1.2rem}h3{font-size:1.2rem;line-height:1.3}.h4{font-size:.9rem}.h5{font-size:.75rem}.uppercase{text-transform:uppercase}.capitalize::first-letter{text-transform:uppercase}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.flex{display:flex}.flex-1{flex:1}.flex-wrap{flex-wrap:wrap}.grid{display:grid}.flow-column{grid-auto-flow:column}.gap-0-3{gap:.3rem}.gap-0-5{gap:.5rem}.gap-1{gap:1rem}.gap-1-5{gap:1.5rem}.gap-2{gap:2rem}.gap-3{gap:3rem}.text-align-right{text-align:right}.align-start{align-content:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.align-content-center{align-content:center}.text-center{text-align:center}.justify-start{justify-content:start}.justify-content-center{justify-content:center}.justify-items-center{justify-items:center}.justify-right{margin-left:auto}.justify-items-center{justify-items:center}.align-self-center{align-self:center}.justify-self-center{justify-self:center}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.flex-direction-column{flex-direction:column}.space-between{justify-content:space-between}.w-100{width:100%}.h-100{height:100%}.margin-left-0-5{margin-left:.5rem}.margin-left-auto{margin-left:auto}.margin-right-0-5{margin-right:.5rem}.margin-right-auto{margin-right:auto}.ripple{height:8rem;width:8rem;position:absolute;border-radius:50%;transform:scale(0);background:radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);pointer-events:none}.button--primary .ripple,.button--danger .ripple{background:radial-gradient(circle, rgba(var(--background-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%)}.interact:not([disabled=true]){position:relative;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.empty-state{display:grid;width:100%;padding:1.5rem 0}.observe-empty-state:empty{display:none !important}.observe-empty-state:not(:empty)+.empty-state{display:none}.bullet-point{display:flex;align-items:center;justify-content:center;margin:0 .8ch}.bullet-point::after{content:"";height:.4ch;width:.4ch;border-radius:.5em;background-color:var(--accent-color)}.multi-state-button{display:grid;text-align:center;align-items:center;justify-items:center}.multi-state-button>*{grid-area:1/1/2/2}.multi-state-button button{z-index:1;width:100%}.password-field label{display:flex;justify-content:center}.password-field label input:checked~.visible{display:none}.password-field label input:not(:checked)~.invisible{display:none}#confirmation_popup,#prompt_popup{flex-direction:column}#confirmation_popup h4,#prompt_popup h4{margin-bottom:.5rem}#confirmation_popup .flex,#prompt_popup .flex{margin-top:1rem}.popup__header{position:relative;display:grid;gap:.5rem;width:100%;padding:0 1.5rem 0 .5rem;align-items:center;grid-template-columns:auto 1fr}.popup__header>*{grid-row:1}.popup__header h3,.popup__header h4{grid-column:1/-1;justify-self:center;align-self:center}.popup__header__close{grid-column:1}#app_body{display:block;height:100%;width:100%}#main_header{grid-area:header;background-color:rgba(var(--foreground-color), 1)}#sub_page_container{grid-area:main;padding:max(1rem,1.5vw)}#loading{position:fixed;display:grid;top:0;left:0;bottom:0;right:0;place-content:center;justify-items:center;background:rgba(var(--foreground-color), 1);z-index:10}#loading h4{margin-top:1.5rem;font-weight:500}#loading sm-spinner{--size: 1.5rem}#sign_in,#sign_up{display:grid;width:100%;height:100%;justify-items:center;align-content:center;padding:1.5rem}#sign_in section,#sign_up section{width:min(26rem,100%)}#sign_in sm-form,#sign_up sm-form{margin:2rem 0}.generated-keys-wrapper{padding:1rem;background-color:rgba(var(--foreground-color), 1);border-radius:.5rem}#flo_id_warning{padding-bottom:1.5rem}#flo_id_warning .icon{height:3rem;width:3rem;padding:.8rem;overflow:visible;background-color:#ffc107;border-radius:3rem;fill:rgba(0,0,0,.8)}#main_header{display:flex;gap:1rem;padding:1rem max(1rem,1.5vw);width:100%;align-items:center}.app-brand{display:flex;gap:.3rem;align-items:center}.app-brand .icon{height:1.7rem;width:1.7rem}.app-name__company{font-size:.8rem;font-weight:500;color:rgba(var(--text-color), 0.8)}#user_popup_button{background-color:rgba(var(--text-color), 0.06);border-radius:2rem;font-size:.9rem;text-overflow:ellipsis;overflow:hidden}.card{padding:1.5rem;border-radius:.5rem;box-shadow:0 0 0 1px rgba(var(--text-color), 0.1);background-color:rgba(var(--foreground-color), 1);transition:box-shadow .2s,background-color .2s}#main_page{display:grid;min-width:0;width:min(72rem,100%);margin:auto;grid-template-columns:minmax(0, 1fr);align-items:flex-start}#balance_list{display:flex;flex-wrap:wrap;justify-items:flex-start;gap:.5rem}.balance-card{display:flex;flex:1 1 12rem;justify-content:space-between;padding:.7rem;border-radius:.5rem;background-color:rgba(var(--foreground-color), 1)}.balance-card span:last-of-type{font-weight:700;font-size:1rem}#apply_loan{width:min(64rem,100%);margin:auto}#policy_list{display:grid;width:100%;gap:.5rem;grid-template-columns:repeat(auto-fill, minmax(16rem, 1fr))}.policy{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;padding:1rem;border-radius:.5rem;background-color:rgba(var(--foreground-color), 1);box-shadow:0 0 0 1px rgba(var(--text-color), 0.1);flex:1 1 12rem}.policy>div h5{font-weight:400;color:rgba(var(--text-color), 0.8)}.policy>div p{font-size:1.1rem;font-weight:500}.policy button{grid-column:1/-1;margin-left:auto}#request_loan_form label{display:flex;align-items:center;gap:.5rem}#request_loan_form label input{height:1.2em;width:1.2em}.loan-process{display:grid;gap:1rem;background-color:rgba(var(--foreground-color), 1);padding:max(1rem,1.5vw);border-radius:.5rem;--progress-line-thickness: 0.15rem}.loan-process ul{display:grid}.loan-process ul li{display:grid;gap:1rem;grid-template-columns:auto 1fr}.loan-process .progress{display:grid;justify-content:center;justify-items:center;isolation:isolate}.loan-process .progress>*{grid-area:1/1/2/2}.loan-process li.done .circle{border-width:.3rem;border-color:var(--green)}.loan-process li.done .line{background-color:var(--green)}.loan-process .circle{margin-top:.2rem;width:.8rem;height:.8rem;border-radius:50%;background-color:rgba(var(--foreground-color), 1);z-index:1;border:solid var(--progress-line-thickness) rgba(var(--text-color), 0.5)}.loan-process .line{margin-top:.2rem;width:var(--progress-line-thickness);height:100%;background-color:rgba(var(--text-color), 0.5)}.loan-process .details{display:grid;gap:.3rem;padding-bottom:2rem}.loan-process .details button{margin-top:.5rem}.loan-process time{font-size:.9rem;color:rgba(var(--text-color), 0.8)}#collateral_requests_list{display:grid;gap:1rem;grid-template-columns:repeat(auto-fill, minmax(20rem, 1fr))}.collateral-request{display:flex;gap:1.5rem;flex-direction:column;background-color:rgba(var(--foreground-color), 1);padding:1rem}.collateral-request b{color:rgba(var(--text-color), 0.9)}#loan_requests_list{display:grid;gap:1rem;align-items:flex-start;grid-template-columns:repeat(auto-fill, minmax(20rem, 1fr))}.loan-request{display:flex;flex-direction:column;justify-items:flex-start;gap:1rem;background-color:rgba(var(--foreground-color), 1);padding:1rem;border-radius:.5rem}.loan-request b{font-weight:500;color:rgba(var(--text-color), 0.9)}.loan-request .button{margin-top:auto}@media screen and (max-width: 40rem){theme-toggle{order:2}#user_popup_button{flex:1;order:1}.hide-on-small{display:none !important}}@media screen and (min-width: 40rem){h1{font-size:3vw}sm-popup{--width: 24rem}.popup__header{padding:1.5rem 1.5rem 0 .75rem}#request_loan_popup{--width: 32rem}}@media screen and (min-width: 46rem){#main_page{grid-template-columns:18rem 1fr}#main_page>:first-child{position:-webkit-sticky;position:sticky;top:1rem;overflow-y:auto}}@media(any-hover: hover){::-webkit-scrollbar{width:.5rem;height:.5rem}::-webkit-scrollbar-thumb{background:rgba(var(--text-color), 0.3);border-radius:1rem}::-webkit-scrollbar-thumb:hover{background:rgba(var(--text-color), 0.5)}.interact:not([disabled]){transition:background-color .3s}.interact:not([disabled]):hover{background-color:rgba(var(--text-color), 0.06)}.button:not([disabled]){transition:background-color .3s,filter .3s}.button:not([disabled]):hover{filter:contrast(2)}}@supports(overflow: overlay){body{overflow:overlay}}.hidden{display:none !important} \ No newline at end of file +*{padding:0;margin:0;box-sizing:border-box;font-family:"Roboto",sans-serif}:root{font-size:clamp(1rem,1.2vmax,1.5rem)}html,body{height:100%}body{--accent-color: #365eff;--text-color: 30, 30, 30;--background-color: 248, 248, 248;--foreground-color: 255, 255, 255;--danger-color: rgb(255, 75, 75);--green: #1cad59;scrollbar-width:thin;scrollbar-gutter:stable;color:rgba(var(--text-color), 1);background-color:rgba(var(--background-color), 1);transition:background-color .3s}body[data-theme=dark]{--accent-color: #86afff;--text-color: 220, 220, 220;--background-color: 10, 10, 10;--foreground-color: 24, 24, 24;--danger-color: rgb(255, 106, 106);--green: #00e676}body[data-theme=dark] sm-popup::part(popup){background-color:rgba(var(--foreground-color), 1)}body[data-theme=dark] ::-webkit-calendar-picker-indicator{filter:invert(1)}p,strong{font-size:.9rem;max-width:65ch;line-height:1.7;color:rgba(var(--text-color), 0.9)}p:not(:last-of-type),strong:not(:last-of-type){margin-bottom:1.5rem}a{text-decoration:none;color:var(--accent-color)}a:focus-visible{box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset}button,.button{-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative;display:inline-flex;border:none;background-color:rgba(0,0,0,0);overflow:hidden;color:inherit;-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;font-size:inherit;font-weight:500;white-space:nowrap;padding:.6rem .9rem;border-radius:.3rem;justify-content:center}button:focus-visible,.button:focus-visible{outline:var(--accent-color) solid medium}button:not(:disabled),.button:not(:disabled){cursor:pointer}.button{background-color:rgba(var(--text-color), 0.02);border:solid thin rgba(var(--text-color), 0.06)}.button--primary{color:rgba(var(--background-color), 1);background-color:var(--accent-color)}.button--primary .icon{fill:rgba(var(--background-color), 1)}.button--colored{color:var(--accent-color)}.button--colored .icon{fill:var(--accent-color)}.button--danger{background-color:rgba(255,115,115,.062745098);color:var(--danger-color)}.button--danger .icon{fill:var(--danger-color)}.button--small{padding:.4rem .6rem}.button--outlined{border:solid rgba(var(--text-color), 0.3) .1rem;background-color:rgba(var(--foreground-color), 1)}.button--transparent{background-color:rgba(0,0,0,0)}button:disabled{cursor:not-allowed;background-color:rgba(var(--text-color), 0.03);color:rgba(var(--text-color), 0.5)}.cta{text-transform:uppercase;font-size:.9rem;font-weight:700;letter-spacing:.05em;padding:.8rem 1rem}.icon{width:1.2rem;height:1.2rem;fill:rgba(var(--text-color), 0.8);flex-shrink:0}.icon-only{padding:.5rem;border-radius:.3rem}.icon--big{width:3rem;height:3rem}a:-webkit-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:-moz-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}details{padding:1rem 0}details summary{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;align-items:center;gap:.5rem;color:var(--accent-color);font-weight:500}details summary .down-arrow{fill:var(--accent-color)}details[open] summary{margin-bottom:1rem}details[open]>summary .down-arrow{transform:rotate(180deg)}sm-input,sm-textarea{font-size:.9rem;--border-radius: 0.5rem;--background-color: rgba(var(--foreground-color), 1)}sm-input button .icon,sm-textarea button .icon{fill:var(--accent-color)}sm-textarea{--max-height: auto}sm-spinner{--size: 1rem;--stroke-width: 0.1rem}sm-form{--gap: 1rem}sm-chips{--gap: 0.3rem}sm-chip{position:relative;font-size:.9rem;--border-radius: 0.5rem;--padding: 0.5rem 0.8rem;--background: rgba(var(--text-color), 0.06);-webkit-user-select:none;-moz-user-select:none;user-select:none}sm-chip[selected=true] .badge{background-color:rgba(var(--foreground-color), 1);color:var(--accent-color)}sm-chip .badge{position:relative;display:flex;align-items:center;justify-content:center;height:1.2rem;border-radius:2rem;margin-left:.5rem;font-weight:700;aspect-ratio:1/1;background-color:var(--accent-color);color:rgba(var(--background-color), 1)}sm-select::part(options){max-height:40vh}sm-option{flex-shrink:0}sm-option::part(option){grid-template-columns:none}ul{list-style:none}fieldset{display:grid;gap:.5rem;padding:1rem;border-radius:.5rem;border:solid 1px rgba(var(--text-color), 0.3)}fieldset legend{font-size:.9rem;font-weight:500}input[type=radio]{height:1.1em;width:1.1em;margin-right:.5rem;accent-color:var(--accent-color)}.overflow-ellipsis{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.wrap-around{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word}.full-bleed{grid-column:1/-1}.h1{font-size:1.5rem}.h2{font-size:1.2rem}h3{font-size:1.2rem;line-height:1.3}.h4{font-size:.9rem}.h5{font-size:.75rem}.uppercase{text-transform:uppercase}.capitalize::first-letter{text-transform:uppercase}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.flex{display:flex}.flex-1{flex:1}.flex-wrap{flex-wrap:wrap}.grid{display:grid}.flow-column{grid-auto-flow:column}.gap-0-3{gap:.3rem}.gap-0-5{gap:.5rem}.gap-1{gap:1rem}.gap-1-5{gap:1.5rem}.gap-2{gap:2rem}.gap-3{gap:3rem}.text-align-right{text-align:right}.align-start{align-content:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.align-content-center{align-content:center}.text-center{text-align:center}.justify-start{justify-content:start}.justify-content-center{justify-content:center}.justify-items-center{justify-items:center}.justify-right{margin-left:auto}.justify-items-center{justify-items:center}.align-self-center{align-self:center}.justify-self-center{justify-self:center}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.flex-direction-column{flex-direction:column}.space-between{justify-content:space-between}.w-100{width:100%}.h-100{height:100%}.margin-left-0-5{margin-left:.5rem}.margin-left-auto{margin-left:auto}.margin-right-0-5{margin-right:.5rem}.margin-right-auto{margin-right:auto}.ripple{height:8rem;width:8rem;position:absolute;border-radius:50%;transform:scale(0);background:radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);pointer-events:none}.button--primary .ripple,.button--danger .ripple{background:radial-gradient(circle, rgba(var(--background-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%)}.interact:not([disabled=true]){position:relative;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.empty-state{display:grid;width:100%;padding:1.5rem 0}.observe-empty-state:empty{display:none !important}.observe-empty-state:not(:empty)+.empty-state{display:none}.bullet-point{display:flex;align-items:center;justify-content:center;margin:0 .8ch}.bullet-point::after{content:"";height:.4ch;width:.4ch;border-radius:.5em;background-color:var(--accent-color)}.multi-state-button{display:grid;text-align:center;align-items:center;justify-items:center}.multi-state-button>*{grid-area:1/1/2/2}.multi-state-button button{z-index:1;width:100%}.password-field label{display:flex;justify-content:center}.password-field label input:checked~.visible{display:none}.password-field label input:not(:checked)~.invisible{display:none}#confirmation_popup,#prompt_popup{flex-direction:column}#confirmation_popup h4,#prompt_popup h4{margin-bottom:.5rem}#confirmation_popup .flex,#prompt_popup .flex{margin-top:1rem}.popup__header{position:relative;display:grid;gap:.5rem;width:100%;padding:0 1.5rem 0 .5rem;align-items:center;grid-template-columns:auto 1fr}.popup__header>*{grid-row:1}.popup__header h3,.popup__header h4{grid-column:1/-1;justify-self:center;align-self:center}.popup__header__close{grid-column:1}#app_body{display:block;height:100%;width:100%}#main_header{grid-area:header;background-color:rgba(var(--foreground-color), 1)}#sub_page_container{grid-area:main;padding:max(1rem,1.5vw)}#loading{position:fixed;display:grid;top:0;left:0;bottom:0;right:0;place-content:center;justify-items:center;background:rgba(var(--foreground-color), 1);z-index:10}#loading h4{margin-top:1.5rem;font-weight:500}#loading sm-spinner{--size: 1.5rem}#sign_in,#sign_up{display:grid;width:100%;height:100%;justify-items:center;align-content:center;padding:1.5rem}#sign_in section,#sign_up section{width:min(26rem,100%)}#sign_in sm-form,#sign_up sm-form{margin:2rem 0}.generated-keys-wrapper{padding:1rem;background-color:rgba(var(--foreground-color), 1);border-radius:.5rem}#flo_id_warning{padding-bottom:1.5rem}#flo_id_warning .icon{height:3rem;width:3rem;padding:.8rem;overflow:visible;background-color:#ffc107;border-radius:3rem;fill:rgba(0,0,0,.8)}#main_header{display:flex;gap:1rem;padding:1rem max(1rem,1.5vw);width:100%;align-items:center}.app-brand{display:flex;gap:.3rem;align-items:center}.app-brand .icon{height:1.7rem;width:1.7rem}.app-name__company{font-size:.8rem;font-weight:500;color:rgba(var(--text-color), 0.8)}#user_popup_button{background-color:rgba(var(--text-color), 0.06);border-radius:2rem;font-size:.9rem;text-overflow:ellipsis;overflow:hidden}.card{padding:1.5rem;border-radius:.5rem;box-shadow:0 0 0 1px rgba(var(--text-color), 0.1);background-color:rgba(var(--foreground-color), 1);transition:box-shadow .2s,background-color .2s}#main_page{display:grid;min-width:0;width:min(72rem,100%);margin:auto;grid-template-columns:minmax(0, 1fr);align-items:flex-start}#balance_list{display:flex;flex-wrap:wrap;justify-items:flex-start;gap:.5rem}.balance-card{display:flex;flex:1 1 12rem;justify-content:space-between;padding:.7rem;border-radius:.5rem;background-color:rgba(var(--foreground-color), 1)}.balance-card span:last-of-type{font-weight:700;font-size:1rem}#apply_loan{width:min(64rem,100%);margin:auto}#policy_list{display:grid;width:100%;gap:.5rem;grid-template-columns:repeat(auto-fill, minmax(16rem, 1fr))}.policy{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;padding:1rem;border-radius:.5rem;background-color:rgba(var(--foreground-color), 1);box-shadow:0 0 0 1px rgba(var(--text-color), 0.1);flex:1 1 12rem}.policy>div h5{font-weight:400;color:rgba(var(--text-color), 0.8)}.policy>div p{font-size:1.1rem;font-weight:500}.policy button{grid-column:1/-1;margin-left:auto}#request_loan_form label{display:flex;align-items:center;gap:.5rem}#request_loan_form label input{height:1.2em;width:1.2em}.loan{display:grid;gap:1rem;background-color:rgba(var(--foreground-color), 1);padding:max(1rem,1.5vw);border-radius:.5rem}.loan-process{display:grid;gap:1rem;background-color:rgba(var(--foreground-color), 1);padding:max(1rem,1.5vw);border-radius:.5rem;--progress-line-thickness: 0.15rem}.loan-process__type{position:relative;padding-bottom:.5rem;color:rgba(var(--text-color), 0.8)}.loan-process__type::before{content:"";position:absolute;height:.2rem;width:40%;left:0;bottom:0;transform:translateY(-50%);border-radius:0 .5rem .5rem 0;background-color:var(--accent-color)}.loan-process ul{display:grid}.loan-process ul li{display:grid;gap:1rem;grid-template-columns:auto 1fr}.loan-process .progress{display:grid;justify-content:center;justify-items:center;isolation:isolate}.loan-process .progress>*{grid-area:1/1/2/2}.loan-process li.done .circle{border-width:.3rem;border-color:var(--green)}.loan-process li.done .line{background-color:var(--green)}.loan-process .circle{margin-top:.2rem;width:.8rem;height:.8rem;border-radius:50%;background-color:rgba(var(--foreground-color), 1);z-index:1;border:solid var(--progress-line-thickness) rgba(var(--text-color), 0.5)}.loan-process .line{margin-top:.2rem;width:var(--progress-line-thickness);height:100%;background-color:rgba(var(--text-color), 0.5)}.loan-process .details{display:grid;gap:.3rem;padding-bottom:2rem}.loan-process .details button{margin-top:.5rem}.loan-process time{font-size:.9rem;color:rgba(var(--text-color), 0.8)}#collateral_requests_list{display:grid;gap:1rem;grid-template-columns:repeat(auto-fill, minmax(20rem, 1fr))}.collateral-request{display:flex;gap:1.5rem;flex-direction:column;background-color:rgba(var(--foreground-color), 1);padding:1rem}.collateral-request b{color:rgba(var(--text-color), 0.9)}#loan_requests_list{display:grid;gap:1rem;align-items:flex-start;grid-template-columns:repeat(auto-fill, minmax(20rem, 1fr))}.loan-request{display:flex;flex-direction:column;justify-items:flex-start;gap:1rem;background-color:rgba(var(--foreground-color), 1);padding:1rem;border-radius:.5rem}.loan-request b{font-weight:500;color:rgba(var(--text-color), 0.9)}.loan-request .button{margin-top:auto}@media screen and (max-width: 40rem){theme-toggle{order:2}#user_popup_button{flex:1;order:1}.hide-on-small{display:none !important}}@media screen and (min-width: 40rem){h1{font-size:3vw}sm-popup{--width: 24rem}.popup__header{padding:1.5rem 1.5rem 0 .75rem}#request_loan_popup{--width: 32rem}}@media screen and (min-width: 46rem){#main_page{grid-template-columns:18rem 1fr}#main_page>:first-child{position:-webkit-sticky;position:sticky;top:1rem;overflow-y:auto}}@media(any-hover: hover){::-webkit-scrollbar{width:.5rem;height:.5rem}::-webkit-scrollbar-thumb{background:rgba(var(--text-color), 0.3);border-radius:1rem}::-webkit-scrollbar-thumb:hover{background:rgba(var(--text-color), 0.5)}.interact:not([disabled]){transition:background-color .3s}.interact:not([disabled]):hover{background-color:rgba(var(--text-color), 0.06)}.button:not([disabled]){transition:background-color .3s,filter .3s}.button:not([disabled]):hover{filter:contrast(2)}}@supports(overflow: overlay){body{overflow:overlay}}.hidden{display:none !important} \ No newline at end of file diff --git a/css/main.scss b/css/main.scss index 4195bcb..b8c3181 100644 --- a/css/main.scss +++ b/css/main.scss @@ -165,6 +165,7 @@ details summary { align-items: center; gap: 0.5rem; color: var(--accent-color); + font-weight: 500; .down-arrow { fill: var(--accent-color); } @@ -729,6 +730,13 @@ h3 { } } } +.loan { + display: grid; + gap: 1rem; + background-color: rgba(var(--foreground-color), 1); + padding: max(1rem, 1.5vw); + border-radius: 0.5rem; +} .loan-process { display: grid; gap: 1rem; @@ -736,6 +744,22 @@ h3 { padding: max(1rem, 1.5vw); border-radius: 0.5rem; --progress-line-thickness: 0.15rem; + &__type { + position: relative; + padding-bottom: 0.5rem; + color: rgba(var(--text-color), 0.8); + &::before { + content: ""; + position: absolute; + height: 0.2rem; + width: 40%; + left: 0; + bottom: 0; + transform: translateY(-50%); + border-radius: 0 0.5rem 0.5rem 0; + background-color: var(--accent-color); + } + } ul { display: grid; li { diff --git a/index.html b/index.html index 7ae57ba..d5b6eed 100644 --- a/index.html +++ b/index.html @@ -530,7 +530,7 @@ } else { notify('Loan request sent to collateral provider', 'success') } - location.hash = '#/home/my-loans' + location.hash = '#/home/in-process' closePopup() } catch (err) { console.error(err) @@ -645,9 +645,9 @@ const { loanOpeningProcessID, borrower, isBorrower, coborrower, isCoborrower, lender, isLender, loanAmount, policyID, - initiationTime, loanRequestTime, loanResponseTime, collateralLockAckTime, + initiationTime, loanRequestTime, loanResponseTime, collateralLockAckTime, loanIssuedTime, hasProvidedCollateral, hasAgreedToLend, hasRequestedCollateralLock, hasLockedCollateral, hasIssuedLoan, hasRequestedCollateralRefund, - collateralRequestID, loanRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID + collateralRequestID, loanRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID } = details return Component(() => { const [verifyingCollateral, setVerifyCollateral] = useState(false) @@ -706,7 +706,8 @@ await btcMortgage.sendLoanAmount(collateralLockAckID, borrower, coborrower, await floDapps.user.private) notify('Loan issued successfully', 'success') } catch (err) { - notify(err.message, 'error') + notify(err.message || err, 'error') + console.error(err) } finally { setIsIssuingLoan(false) } @@ -773,131 +774,200 @@ openPopup('request_details_popup') } return html` -
  • -
    -

    Loan request: ${loanOpeningProcessID}

    - -
    - +

    Request ID: #${loanOpeningProcessID}

    +
  • + ` })() }, + loan(loanID) { + const { + blocktime, borrower, borrower_sign, btc_start_rate, + coborrower, coborrower_sign, collateral_lock_id, collateral_value, + lender, lender_sign, loan_amount, loan_id, loan_opening_process_id, loan_transfer_id, + open_time, policy_id, } = btcMortgage.loans[loanID] + return html` +
  • + +

    Loan ID: ${loan_id}

    +
    +
    +
    +

    Loan amount

    + ${formatAmount(loan_amount, 'usd')} +
    +
    +

    Collateral amount

    + ${formatAmount(collateral_value)} +
    +
    +

    Duration

    + ${btcMortgage.policies[policy_id]?.duration} +
    +
    +

    Interest

    + ${btcMortgage.policies[policy_id]?.interest * 100}% p.a +
    +
    +

    Loan start date

    + ${getFormattedTime(open_time)} +
    +
    +
    + + Borrower details + + +
    +
    +

    Borrower

    + + ${getBtcAddress(borrower)} + +
    + ${!floCrypto.isSameAddr(borrower, coborrower) ? html` +
    +

    Collateral provider

    + + ${getBtcAddress(coborrower)} + +
    + `: ''} +
    +
    +
  • + ` + } } // routing logic const router = new Router({ @@ -1060,6 +1130,7 @@ let userAddressTimeInterval; function renderHome(appState) { const { lastPage, page, params: { } = {}, wildcards: [section = 'my-loans'] = [] } = appState || {}; + const balanceCard = Component(() => { const [btcBalance, setBtcBalance] = useState(floGlobals.memoBalance['BTC']) const [floBalance, setFloBalance] = useState(floGlobals.memoBalance['FLO']) @@ -1164,7 +1235,8 @@ } else { // user homepage const inbox = Component(() => { - const { borrowed, coborrowed, lent } = groupLoanProcess() + const inProcessRequests = groupLoanProcess() + const { borrowed, coBorrowed, lent } = groupLoans() const [view, setView] = useState(section) let loanRequests = []; for (const key in floGlobals.loanRequests) { @@ -1178,25 +1250,29 @@ setView(e.target.value) } if ( - coborrowed.length === 0 && view === 'coborrowed' - || lent.length === 0 && view === 'lent' - || loanRequests.length === 0 && view === 'lend' + coBorrowed.length === 0 && view === 'coBorrowed' || + lent.length === 0 && view === 'lent' || + loanRequests.length === 0 && view === 'lend' ) { - // if no loans in coborrowed or lent or loan requests, redirect to my-loans + // if there are no loans in the selected view, switch to my-loans history.replaceState(null, null, `#/home/my-loans`) setView('my-loans') } return html`
    + + In process + ${inProcessRequests.length ? html`${inProcessRequests.length}` : ''} + - My Loans + My loans ${borrowed.length ? html`${borrowed.length}` : ''} - ${coborrowed.length ? html` - - Coborrowed - ${coborrowed.length} + ${coBorrowed.length ? html` + + Co-borrowed + ${coBorrowed.length} ` : ''} ${lent.length ? html` @@ -1216,7 +1292,7 @@ ${view === 'my-loans' ? html` ${borrowed.length ? html`
      - ${borrowed.map(loan => render.loanProcess(loan))} + ${borrowed.map(loan => render.loan(loan))}
    Apply for a new loan `: html` @@ -1229,9 +1305,25 @@ `} `: ''} - ${view === 'coborrowed' && coborrowed.length ? html` + ${view === 'in-process' ? html` + ${inProcessRequests.length ? html` +
      + ${inProcessRequests.map(loan => render.loanProcess(loan))} +
    + Apply for a new loan + `: html` +
    +
    +

    Looking for USD loans?

    +

    Get a loan against your BTC collateral

    +
    + Apply for a loan +
    + `} + `: ''} + ${view === 'coBorrowed' && coBorrowed.length ? html`
      - ${coborrowed.map(loan => render.loanProcess(loan))} + ${coBorrowed.map(loan => render.loanProcess(loan))}
    `: html``} ${view === 'lent' && lent.length ? html` @@ -1240,13 +1332,12 @@ `: html``} ${view === 'lend' ? html` -
    -
    -

    Earn interest on your USD tokens

    -

    Lend your USD tokens to earn interest assured by BTC collateral.

    +
    +
    +

    Earn interest on your USD tokens

    +

    Lend your USD tokens to earn interest assured by BTC collateral.

    +
    - -
    ${loanRequests.length ? html`
      ${loanRequests.reverse().map(requestId => render.loanRequest(requestId, floGlobals.loanRequests[requestId]))} @@ -1289,6 +1380,8 @@ for (const key in allMessages) { const { message: { borrower, coborrower, loan_amount, policy_id, loan_opening_process_id, lender, loan_request_id, collateral_lock_id } = {}, type, time } = allMessages[key]; if (!loan_opening_process_id) continue + if (Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id)) + continue // Loan is already open if (!loansInProcess[loan_opening_process_id]) { loansInProcess[loan_opening_process_id] = { loanOpeningProcessID: loan_opening_process_id, @@ -1358,17 +1451,36 @@ return Object.values(loansInProcess).sort((a, b) => { return (b.initiationTime || b.loanResponseTime) - (a.initiationTime || a.loanResponseTime) }) - .reduce((acc, loan) => { // group by borrower, coborrower and lender - if (loan.isBorrower) - acc.borrowed.push(loan) - else if (loan.isCoborrower) - acc.coborrowed.push(loan) - else if (loan.isLender) - acc.lent.push(loan) + // .reduce((acc, loan) => { // group by borrower, coborrower and lender + // if (loan.isBorrower) + // acc.borrowing.push(loan) + // else if (loan.isCoborrower) + // acc.coBorrowing.push(loan) + // else if (loan.isLender) + // acc.lending.push(loan) + // return acc + // }, { + // borrowing: [], + // coBorrowing: [], + // lending: [] + // }) + } + + function groupLoans() { + console.log(btcMortgage.loans) + return Object.values(btcMortgage.loans) + .reduce((acc, loan) => { + const { loan_id, borrower, coborrower, lender } = loan + if (floCrypto.isSameAddr(borrower, floDapps.user.id) && floCrypto.isSameAddr(coborrower, floDapps.user.id)) + acc.borrowed.push(loan_id) + else if (floCrypto.isSameAddr(coborrower, floDapps.user.id)) + acc.coBorrowed.push(loan_id) + else if (floCrypto.isSameAddr(lender, floDapps.user.id)) + acc.lent.push(loan_id) return acc }, { borrowed: [], - coborrowed: [], + coBorrowed: [], lent: [] }) } diff --git a/scripts/btcMortgage.js b/scripts/btcMortgage.js index fcf4942..2a8ea6d 100644 --- a/scripts/btcMortgage.js +++ b/scripts/btcMortgage.js @@ -535,7 +535,7 @@ if (token_tx.transactionDetails.senderAddress != floCrypto.toFloID(lender)) return reject("Sender is not lender"); if (token_tx.parsedFloData.tokenAmount !== loan_amount) - return reject("Token amount doesnot match the loan amount"); + return reject("Token amount doesn't match the loan amount"); resolve(true); }).catch(error => reject(error)) }) @@ -572,7 +572,7 @@ floBlockchainAPI.getTx(closing_txid).then(tx => { let closing_details = parseLoanCloseData(tx.floData, tx.txid, tx.time); if (loan_id !== closing_details.loan_id) - return reject("Closing doesnot match the loan ID") + return reject("Closing doesn't match the loan ID") getLoanDetails(closing_details.loan_id).then(loan_details => { validateLoanClosing(loan_details, closing_details) .then(result => resolve(Object.assign(loan_details, closing_details))) @@ -585,7 +585,7 @@ const validateLoanClosing = btcMortgage.validateLoanClosing = function (loan_details, closing_details) { return new Promise((resolve, reject) => { if (closing_details.loan_id !== loan_details.loan_id) - return reject("Closing doesnot belong to this loan") + return reject("Closing doesn't belong to this loan") if (!floCrypto.validateFloID(closing_details.borrower)) return reject("Invalid borrower floID"); if (closing_details.borrower != loan_details.borrower) @@ -655,7 +655,7 @@ floBlockchainAPI.getTx(failure_txid).then(tx => { let failure_details = parseLoanFailData(tx.floData, tx.txid, tx.time); if (loan_id !== failure_details.loan_id) - return reject("Failure doesnot match the loan ID") + return reject("Failure doesn't match the loan ID") getLoanDetails(failure_details.loan_id).then(loan_details => { validateLoanFailure(loan_details, failure_details) .then(result => resolve(Object.assign(loan_details, failure_details))) @@ -668,7 +668,7 @@ const validateLoanFailure = btcMortgage.validateLoanFailure = function (loan_details, failure_details) { return new Promise((resolve, reject) => { if (failure_details.loan_id !== loan_details.loan_id) - return reject("Failure doesnot belong to this loan") + return reject("Failure doesn't belong to this loan") if (!floCrypto.validateFloID(failure_details.lender)) return reject("Invalid lender floID"); if (failure_details.lender != loan_details.lender) @@ -1076,7 +1076,7 @@ let lender_floID = floCrypto.toFloID(lender); floTokenAPI.getBalance(lender_floID, CURRENCY).then(lender_tokenBalance => { if (lender_tokenBalance < loan_amount) - return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "lender doesnot have sufficient funds to lend")); + return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "lender doesn't have sufficient funds to lend")); result.lender = lender; result.lender_pubKey = pubKey; resolve(result); @@ -1237,12 +1237,12 @@ } //for retrying failsafe - btcOperator.retryFailSafe = function (fail_safe_id) { + btcOperator.retryFailSafe = function (fail_safe_id, privKey) { return new Promise((resolve, reject) => { compactIDB.readData("fail_safe", fail_safe_id).then(fail_safe_data => { let { borrower, coborrower } = parseLoanOpenData(fail_safe_data); let receivers = [borrower, coborrower].map(addr => floCrypto.toFloID(addr)); - floBlockchainAPI.writeDataMultiple([privKey], loan_blockchain_data, receivers).then(loan_txid => { + floBlockchainAPI.writeDataMultiple([privKey], fail_safe_data, receivers).then(loan_txid => { compactIDB.removeData("fail_safe", fail_safe_id); //remove fail safe as data is added to blockchain resolve(loan_txid) }).catch(error => reject(error)) @@ -1552,7 +1552,7 @@ getLoanDetails(loan_id).then(loan_details => { let policy = POLICIES[loan_details.policy_id]; if (isNaN(policy.pre_liquidation_threshold)) - return reject("This loan policy doesnot allow pre-liquidation"); + return reject("This loan policy doesn't allow pre-liquidation"); getRate["USD"].then(cur_btc_rate => { if (cur_btc_rate >= loan_details.btc_start_rate) return reject("BTC rate hasn't reduced from the start rate"); @@ -1636,7 +1636,7 @@ return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "Loan already closed")); let policy = POLICIES[loan_details.policy_id]; if (isNaN(policy.pre_liquidation_threshold)) - return reject("This loan policy doesnot allow pre-liquidation"); + return reject("This loan policy doesn't allow pre-liquidation"); getRate["USD"].then(cur_btc_rate => { if (cur_btc_rate >= loan_details.btc_start_rate) return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "BTC rate hasn't reduced from the start rate")); @@ -1726,7 +1726,7 @@ if (tx.ins.some(i => i.outpoint.hash !== collateral_lock_id))//vin other than this collateral is present in tx, ABORT return reject("Transaction Hex contains other/non collateral inputs"); if (tx.ins.length != collateral_utxos.length) - return reject("Transaction hex doesnot contain full collateral as input") + return reject("Transaction hex doesn't contain full collateral as input") //check output let return_amount = total_collateral_value - liquidate_amount; if (return_amount > 0) {