From 67ecf25387237c5404f02c5ca41b58f23431c734 Mon Sep 17 00:00:00 2001 From: Milad Raeisi <6504337+miladsoft@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:05:42 +0400 Subject: [PATCH 01/13] Update some colors (#149) * Update some colors * Update dashboard.css * Update MainLayout.razor * Update app.js * Remove redundant styles and implement color variables for consistency --- src/Angor/Client/Shared/MainLayout.razor | 2 +- src/Angor/Client/wwwroot/assets/css/app.css | 82 +- .../Client/wwwroot/assets/css/dashboard.css | 1965 +---------------- src/Angor/Client/wwwroot/assets/js/app.js | 8 +- 4 files changed, 77 insertions(+), 1980 deletions(-) diff --git a/src/Angor/Client/Shared/MainLayout.razor b/src/Angor/Client/Shared/MainLayout.razor index 65b1dd9f..cd6c46b4 100644 --- a/src/Angor/Client/Shared/MainLayout.razor +++ b/src/Angor/Client/Shared/MainLayout.razor @@ -12,7 +12,7 @@ @inject NavigationManager _navManager @inject NavMenuState NavMenuState; -
+
diff --git a/src/Angor/Client/wwwroot/assets/css/app.css b/src/Angor/Client/wwwroot/assets/css/app.css index d00df321..39b9e769 100644 --- a/src/Angor/Client/wwwroot/assets/css/app.css +++ b/src/Angor/Client/wwwroot/assets/css/app.css @@ -1,11 +1,56 @@ :root { + --bs-blue: #63B3ED; + --bs-indigo: #596CFF; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #F56565; + --bs-orange: #fd7e14; + --bs-yellow: #FBD38D; + --bs-green: #81E6D9; + --bs-teal: #20c997; + --bs-cyan: #0dcaf0; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #f0f2f5; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; --bs-primary: #1f5d6a; --bs-secondary: #ff9900; - --bs-body-bg: #FFFDFA; - --bs-body-color: #ff9900; + --bs-success: #4CAF50; + --bs-info: #1A73E8; + --bs-warning: #fb8c00; + --bs-danger: #F44335; + --bs-light: #f0f2f5; + --bs-dark: #344767; + --bs-dark-blue: #1A237E; --bs-primary-rgb: 31, 93, 106; --bs-secondary-rgb: 255, 153, 0; + --bs-success-rgb: 76, 175, 80; + --bs-info-rgb: 26, 115, 232; + --bs-warning-rgb: 251, 140, 0; + --bs-danger-rgb: 244, 67, 53; + --bs-light-rgb: 240, 242, 245; + --bs-dark-rgb: 52, 71, 103; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-body-color-rgb: 0, 128, 154; + --bs-body-bg-rgb: 255, 255, 255; --bs-font-sans-serif: "Roboto", Helvetica, Arial, sans-serif; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 1rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #ff9900; + --bs-body-bg: #cbdde1; --bs-border-width: 1px; --bs-border-style: solid; --bs-border-color: #dee2e6; @@ -18,6 +63,8 @@ --bs-border-radius-pill: 50rem; --bs-link-color: #1f5d6a; --bs-link-hover-color: #1f5d6a; + --bs-code-color: #d63384; + --bs-highlight-bg: #fcf8e3; --bs-modal-bg: #FFFDFA; --angor-white: #e7fbff; --bs-table-color: #ff9900; @@ -76,8 +123,8 @@ --bs-btn-link-color: #1f5d6a; --bs-btn-link-hover-color: #1f5d6a; --bs-card-bg: #FFFDFA; - --bs-scrollbar-thumb-bg: #888; - --bs-scrollbar-thumb-hover-bg: #e78a02; + --bs-scrollbar-thumb-bg: #f1fafc; + --bs-scrollbar-thumb-hover-bg: #e78a02c0; --bs-modal-header-border-color: #344767; --bs-modal-header-border-color-dark: #fff; --bs-loading-progress-stroke: #e0e0e0; @@ -129,7 +176,7 @@ .dark { --bs-primary: #e7fbff; --bs-secondary: #ff9900; - --bs-body-bg: #084a59; + --bs-body-bg: #022229; --bs-body-color: #FFFDFA; --bs-table-color: #ff9900; --bs-table-striped-color: #ff9900; @@ -187,8 +234,8 @@ --bs-btn-link-color: #e7fbff; --bs-btn-link-hover-color: #e7fbff; --bs-card-bg: #084a59; - --bs-scrollbar-thumb-bg: #888; - --bs-scrollbar-thumb-hover-bg: #e78a02; + --bs-scrollbar-thumb-bg: #083b46; + --bs-scrollbar-thumb-hover-bg: #e78a02c0; --bs-modal-header-border-color: #fff; --bs-loading-progress-stroke: #e0e0e0; --bs-loading-progress-stroke-active: #1b6ec2; @@ -232,8 +279,13 @@ --bs-modal-footer-border-width: 1px; --btn-border: #046479; } +:root.dark { + --bs-body-bg: #022229; +} +.bgcolor { + background: var(--bs-body-bg) !important; +} -/* Tables */ .table { --bs-table-color: var(--bs-table-color); --bs-table-striped-color: var(--bs-table-striped-color); @@ -241,7 +293,6 @@ --bs-table-hover-color: var(--bs-table-hover-color); } -/* Forms */ .col-form-label { color: var(--bs-body-color); } @@ -267,7 +318,6 @@ background-color: var(--bs-form-range-thumb-bg); } -/* Buttons */ .btn { --bs-btn-color: var(--bs-btn-color); } @@ -318,7 +368,6 @@ --bs-btn-active-color: var(--bs-btn-link-hover-color); } -/* Menus */ .dropdown-menu { --bs-dropdown-color: var(--bs-dropdown-color); --bs-dropdown-link-color: var(--bs-dropdown-link-color); @@ -334,7 +383,6 @@ --bs-accordion-btn-color: var(--bs-accordion-btn-color); } -/* Other elements */ .pagination { --bs-pagination-color: var(--bs-pagination-color); --bs-pagination-hover-color: var(--bs-pagination-hover-color); @@ -366,7 +414,6 @@ --bs-popover-body-color: var(--bs-popover-body-color); } -/* Colors and backgrounds */ .text-bg-primary { background-color: var(--bs-text-bg-primary) !important; } @@ -646,6 +693,7 @@ tr[style*="cursor: pointer;"] { align-items: center; z-index: 9999; flex-direction: column; + background-color: #e5eef0; } .loader { @@ -800,14 +848,14 @@ tr[style*="cursor: pointer;"] { border-radius: 5px; box-sizing: border-box; font-size: 16px; - background-color: #fefefd; + background-color: #f5fdff; } .form-control:focus { outline: none; border-color: #007bff; box-shadow: 0 0 5px #007bff; - background-color: #fefefd; + background-color: #f5fdff; } .dark .form-label { @@ -1136,7 +1184,7 @@ tr[style*="cursor: pointer;"] { } .navbar-main { - background: #fefefd !important; + background: #f5fdff !important; } .dark .navbar-main { @@ -1230,7 +1278,7 @@ tr[style*="cursor: pointer;"] { .btn-border { border: 1px solid var(--btn-border) !important; background-color: rgba(31, 93, 106, 0.2); - color:var(--bs-primary) !important; + color: var(--bs-primary) !important; } input[type="checkbox"] + label { diff --git a/src/Angor/Client/wwwroot/assets/css/dashboard.css b/src/Angor/Client/wwwroot/assets/css/dashboard.css index cee06daa..2d4bd7fd 100644 --- a/src/Angor/Client/wwwroot/assets/css/dashboard.css +++ b/src/Angor/Client/wwwroot/assets/css/dashboard.css @@ -1,75 +1,3 @@ -:root { - --bs-blue: #63B3ED; - --bs-indigo: #596CFF; - --bs-purple: #6f42c1; - --bs-pink: #d63384; - --bs-red: #F56565; - --bs-orange: #fd7e14; - --bs-yellow: #FBD38D; - --bs-green: #81E6D9; - --bs-teal: #20c997; - --bs-cyan: #0dcaf0; - --bs-white: #fff; - --bs-gray: #6c757d; - --bs-gray-dark: #343a40; - --bs-gray-100: #f8f9fa; - --bs-gray-200: #f0f2f5; - --bs-gray-300: #dee2e6; - --bs-gray-400: #ced4da; - --bs-gray-500: #adb5bd; - --bs-gray-600: #6c757d; - --bs-gray-700: #495057; - --bs-gray-800: #343a40; - --bs-gray-900: #212529; - --bs-primary: #1f5d6a; - --bs-secondary: #ff9900; - --bs-success: #4CAF50; - --bs-info: #1A73E8; - --bs-warning: #fb8c00; - --bs-danger: #F44335; - --bs-light: #f0f2f5; - --bs-dark: #344767; - --bs-white: #fff; - --bs-dark-blue: #1A237E; - --bs-primary-rgb: 233, 30, 99; - --bs-secondary-rgb: 0, 128, 154; - --bs-success-rgb: 76, 175, 80; - --bs-info-rgb: 26, 115, 232; - --bs-warning-rgb: 251, 140, 0; - --bs-danger-rgb: 244, 67, 53; - --bs-light-rgb: 240, 242, 245; - --bs-dark-rgb: 52, 71, 103; - --bs-white-rgb: 255, 255, 255; - --bs-dark-blue-rgb: 26, 35, 126; - --bs-white-rgb: 255, 255, 255; - --bs-black-rgb: 0, 0, 0; - --bs-body-color-rgb: 0, 128, 154; - --bs-body-bg-rgb: 255, 255, 255; - --bs-font-sans-serif: "Roboto", Helvetica, Arial, sans-serif; - --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - --bs-body-font-family: var(--bs-font-sans-serif); - --bs-body-font-size: 1rem; - --bs-body-font-weight: 400; - --bs-body-line-height: 1.5; - --bs-body-color: #ff9900; - --bs-body-bg: #fff; - --bs-border-width: 1px; - --bs-border-style: solid; - --bs-border-color: #dee2e6; - --bs-border-color-translucent: rgba(0, 0, 0, 0.175); - --bs-border-radius: 0.375rem; - --bs-border-radius-sm: 0.125rem; - --bs-border-radius-lg: 0.5rem; - --bs-border-radius-xl: 0.75rem; - --bs-border-radius-2xl: 1rem; - --bs-border-radius-pill: 50rem; - --bs-link-color: #1f5d6a; - --bs-link-hover-color: #1f5d6a; - --bs-code-color: #d63384; - --bs-highlight-bg: #fcf8e3; -} - *, *::before, *::after { @@ -88,7 +16,7 @@ body { font-size: var(--bs-body-font-size); font-weight: var(--bs-body-font-weight); line-height: var(--bs-body-line-height); - color: var(--bs-body-color); + color: var(--bs-body-color) !important; text-align: var(--bs-body-text-align); background-color: var(--bs-body-bg); overflow-y: scroll; @@ -5207,7 +5135,7 @@ fieldset:disabled .btn { --bs-card-cap-padding-x: 1rem; --bs-card-cap-bg: #fff; --bs-card-color: #1f5d6a; - --bs-card-bg: rgba(255, 255, 255, 0.9); + --bs-card-bg: rgba(245, 253, 255, 0.9); --bs-card-img-overlay-padding: 1rem; --bs-card-group-margin: 0.75rem; position: relative; @@ -10703,7 +10631,7 @@ fieldset:disabled .btn { } .bg-gray-100 { - background-color: #f4f1ec !important + background-color: #e5eef0 !important } .bg-gray-200 { @@ -20252,7 +20180,7 @@ fieldset:disabled .btn { } .alert-info { - background-image: linear-gradient(195deg, #fefefd 0%, #fefefd 100%); + background-image: linear-gradient(195deg, #f5fdff 0%, #f5fdff 100%); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12) !important; } @@ -21458,13 +21386,10 @@ fieldset:disabled .btn { } .sidenav { - background: #fefefd !important; + background: #f5fdff !important; color: #1f5d6a !important; } -.dark { - background-color: #022229 !important -} .dark.virtual-reality > div { background-image: none !important; @@ -21542,7 +21467,7 @@ fieldset:disabled .btn { background: #083b46; } - + .dark .kanban-item { background: transparent !important; border: 1px solid; @@ -24400,7 +24325,7 @@ body:not(.dark) .sidenav.bg-transparent .collapse .nav-item .nav-link.active.tex } .navbar-main { - --bs-card-bg: rgba(255, 255, 255, 0.9); + --bs-card-bg: rgba(245, 253, 255, 0.9); } .navbar-main.fixed-top { @@ -25789,1879 +25714,3 @@ body:not(.dark) .sidenav.bg-transparent .collapse .nav-item .nav-link.active.tex .bs-tooltip-left .tooltip-arrow { right: 1px; } - -html * { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body { - font-weight: 400; - line-height: 1.6; -} - -h1, -.h1, -.h1 { - font-size: 3rem; - line-height: 1.25; - letter-spacing: 0; -} - -@media (max-width: 575.98px) { - - h1, - .h1, - .h1 { - font-size: calc(1.425rem + 2.1vw); - } -} - -h2, -.h2, -.h2 { - font-size: 2.25rem; - line-height: 1.3; - letter-spacing: 0.05rem; -} - -@media (max-width: 575.98px) { - - h2, - .h2, - .h2 { - font-size: calc(1.35rem + 1.2vw); - } -} - -h3, -.h3, -.h3 { - font-size: 1.875rem; - line-height: 1.375; -} - -@media (max-width: 575.98px) { - - h3, - .h3, - .h3 { - font-size: calc(1.3125rem + 0.75vw); - } -} - -h4, -.h4, -.h4 { - font-size: 1.5rem; - line-height: 1.375; -} - -@media (max-width: 575.98px) { - - h4, - .h4, - .h4 { - font-size: calc(1.275rem + 0.3vw); - } -} - -h5, -.h5, -.h5 { - font-size: 1.25rem; - line-height: 1.375; -} - -@media (max-width: 575.98px) { - - h5, - .h5, - .h5 { - font-size: 1.25rem; - } -} - -h6, -.h6, -.h6 { - font-size: 1rem; - line-height: 1.625; -} - -p, -.p { - font-size: 1rem; - font-weight: 400; - line-height: 1.6; -} - -.lead { - font-size: 1.25rem; - font-weight: 400; - line-height: 1.625; -} - -h1, -.h1, -.h1, -h2, -.h2, -.h2, -h3, -.h3, -.h3 { - font-weight: 600; - font-family: var(--bs-font-sans-serif); -} - -h4, -.h4, -.h4, -h5, -.h5, -.h5, -h6, -.h6, -.h6 { - font-weight: 600; -} - -h1, -.h1, -.h1, -h2, -.h2, -.h2, -h3, -.h3, -.h3, -h4, -.h4, -.h4 { - letter-spacing: -0.05rem; -} - -a { - letter-spacing: 0rem; - color: #344767; -} - -.text-sm { - line-height: 1.5; -} - -.text-xs { - line-height: 1.25; -} - -p, -.p { - font-size: 1rem; -} - -.lead { - font-size: 1.25rem; -} - -.text-lg { - font-size: 1.125rem !important; -} - -.text-md { - font-size: 1rem !important; -} - -.text-sm { - font-size: 0.875rem !important; -} - -.text-xs { - font-size: 0.75rem !important; -} - -.text-xxs { - font-size: 0.65rem !important; -} - -p { - line-height: 1.625; - font-weight: 300; -} - -.text-sans-serif { - font-family: "Roboto", Helvetica, Arial, sans-serif !important; -} - -.text-monospace { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; -} - -.text-justify { - text-align: justify !important; -} - -.text-wrap { - white-space: normal !important; -} - -.text-nowrap { - white-space: nowrap !important; -} - -.text-truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.font-weight-light { - font-weight: 300 !important; -} - -.font-weight-lighter { - font-weight: lighter !important; -} - -.font-weight-normal { - font-weight: 400 !important; -} - -.font-weight-bold { - font-weight: 600 !important; -} - -.font-weight-bolder { - font-weight: 700 !important; -} - -.font-italic { - font-style: italic !important; -} - -.text-gradient { - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - position: relative; - z-index: 1; -} - - .text-gradient.text-primary { - background-image: linear-gradient(195deg, #EC407A, #D81B60); - } - - .text-gradient.text-info { - background-image: linear-gradient(195deg, #49a3f1, #1A73E8); - } - - .text-gradient.text-success { - background-image: linear-gradient(195deg, #66BB6A, #43A047); - } - - .text-gradient.text-warning { - background-image: linear-gradient(195deg, #FFA726, #FB8C00); - } - - .text-gradient.text-danger { - background-image: linear-gradient(195deg, #EF5350, #E53935); - } - - .text-gradient.text-dark { - background-image: linear-gradient(195deg, #42424a, #191919); - } - -.blockquote { - border-left: 3px solid #6c757d; -} - - .blockquote > span { - font-style: italic; - } - -.text-muted { - color: #ff9900 !important; -} - -.text-black-50 { - color: rgba(0, 0, 0, 0.5) !important; -} - -.text-white-50 { - color: rgba(255, 255, 255, 0.5) !important; -} - -.text-decoration-none { - text-decoration: none !important; -} - -.text-break { - word-wrap: break-word !important; -} - -.text-reset { - color: inherit !important; -} - -.letter-wider { - letter-spacing: 0.05rem; -} - -.letter-normal { - letter-spacing: 0rem; -} - -.letter-tighter { - letter-spacing: -0.05rem; -} - -.text-lighter { - font-weight: lighter; -} - -.text-light { - font-weight: 300; -} - -.text-normal { - font-weight: 400; -} - -.text-bold { - font-weight: 600; -} - -.text-bolder { - font-weight: 700; -} - -.text-2xl { - font-size: 1.5rem; -} - -.text-3xl { - font-size: 1.875rem; -} - -.text-4xl { - font-size: 2rem; -} - -.text-5xl { - font-size: 2.25rem; -} - -.text-6xl { - font-size: 3rem; -} - -.text-7xl { - font-size: 3.75rem; -} - -.text-8xl { - font-size: 4rem; -} - -.text-9xl { - font-size: 5rem; -} - -.flatpickr-calendar { - background: transparent; - opacity: 0; - display: none; - text-align: center; - visibility: hidden; - padding: 0; - -webkit-animation: none; - animation: none; - direction: ltr; - border: 0; - font-size: 14px; - line-height: 24px; - border-radius: 0.75rem; - position: absolute; - width: 307.875px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - -ms-touch-action: manipulation; - touch-action: manipulation; - background: #fff; - -webkit-box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - transform: scale(0.95) !important; -} - - .flatpickr-calendar.open, - .flatpickr-calendar.inline { - opacity: 1; - max-height: 640px; - visibility: visible; - transform: scale(1) !important; - } - - .flatpickr-calendar.open { - display: inline-block; - z-index: 99999; - } - - .flatpickr-calendar.animate.open { - -webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); - animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); - } - - .flatpickr-calendar.inline { - display: block; - position: relative; - top: 2px; - } - - .flatpickr-calendar.static { - position: absolute; - top: calc(100% + 2px); - } - - .flatpickr-calendar.static.open { - z-index: 999; - display: block; - } - - .flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) { - -webkit-box-shadow: none !important; - box-shadow: none !important; - } - - .flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) { - -webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - } - - .flatpickr-calendar .hasWeeks .dayContainer, - .flatpickr-calendar .hasTime .dayContainer { - border-bottom: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - } - - .flatpickr-calendar .hasWeeks .dayContainer { - border-left: 0; - } - - .flatpickr-calendar.hasTime .flatpickr-time { - height: 40px; - border-top: 1px solid #e6e6e6; - } - - .flatpickr-calendar.noCalendar.hasTime .flatpickr-time { - height: auto; - } - - .flatpickr-calendar:before, - .flatpickr-calendar:after { - position: absolute; - display: block; - pointer-events: none; - border: solid transparent; - content: ''; - height: 0; - width: 0; - left: 22px; - } - - .flatpickr-calendar.rightMost:before, - .flatpickr-calendar.arrowRight:before, - .flatpickr-calendar.rightMost:after, - .flatpickr-calendar.arrowRight:after { - left: auto; - right: 22px; - } - - .flatpickr-calendar.arrowCenter:before, - .flatpickr-calendar.arrowCenter:after { - left: 50%; - right: 50%; - } - - .flatpickr-calendar:before { - border-width: 5px; - margin: 0 -5px; - } - - .flatpickr-calendar:after { - border-width: 4px; - margin: 0 -4px; - } - - .flatpickr-calendar.arrowTop:before, - .flatpickr-calendar.arrowTop:after { - bottom: 100%; - } - - .flatpickr-calendar.arrowTop:before { - border-bottom-color: #fff; - } - - .flatpickr-calendar.arrowTop:after { - border-bottom-color: #fff; - } - - .flatpickr-calendar.arrowBottom:before, - .flatpickr-calendar.arrowBottom:after { - top: 100%; - } - - .flatpickr-calendar.arrowBottom:before { - border-top-color: #e6e6e6; - } - - .flatpickr-calendar.arrowBottom:after { - border-top-color: #fff; - } - - .flatpickr-calendar:focus { - outline: 0; - } - -.flatpickr-wrapper { - position: relative; - display: inline-block; -} - -.flatpickr-months { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - - .flatpickr-months .flatpickr-month { - background: transparent; - color: #344767; - fill: rgba(0, 0, 0, 0.8); - height: 34px; - line-height: 1; - text-align: center; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - overflow: hidden; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - } - - .flatpickr-months .flatpickr-prev-month, - .flatpickr-months .flatpickr-next-month { - text-decoration: none; - cursor: pointer; - position: absolute; - top: 0; - height: 34px; - padding: 10px; - z-index: 3; - color: rgba(0, 0, 0, 0.9); - fill: rgba(0, 0, 0, 0.9); - } - - .flatpickr-months .flatpickr-prev-month.flatpickr-disabled, - .flatpickr-months .flatpickr-next-month.flatpickr-disabled { - display: none; - } - - .flatpickr-months .flatpickr-prev-month i, - .flatpickr-months .flatpickr-next-month i { - position: relative; - } - - .flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, - .flatpickr-months .flatpickr-next-month.flatpickr-prev-month { - left: 0; - } - - .flatpickr-months .flatpickr-prev-month.flatpickr-next-month, - .flatpickr-months .flatpickr-next-month.flatpickr-next-month { - right: 0; - } - - .flatpickr-months .flatpickr-prev-month:hover, - .flatpickr-months .flatpickr-next-month:hover { - color: #959ea9; - } - - .flatpickr-months .flatpickr-prev-month:hover svg, - .flatpickr-months .flatpickr-next-month:hover svg { - fill: #f64747; - } - - .flatpickr-months .flatpickr-prev-month svg, - .flatpickr-months .flatpickr-next-month svg { - width: 14px; - height: 14px; - } - - .flatpickr-months .flatpickr-prev-month svg path, - .flatpickr-months .flatpickr-next-month svg path { - -webkit-transition: fill 0.1s; - transition: fill 0.1s; - fill: inherit; - } - -.numInputWrapper { - position: relative; - height: auto; -} - - .numInputWrapper input, - .numInputWrapper span { - display: inline-block; - } - - .numInputWrapper input { - width: 100%; - } - - .numInputWrapper input::-ms-clear { - display: none; - } - - .numInputWrapper input::-webkit-outer-spin-button, - .numInputWrapper input::-webkit-inner-spin-button { - margin: 0; - -webkit-appearance: none; - } - - .numInputWrapper span { - position: absolute; - right: 0; - width: 14px; - padding: 0 4px 0 2px; - height: 50%; - line-height: 50%; - opacity: 0; - cursor: pointer; - border: 1px solid rgba(57, 57, 57, 0.15); - -webkit-box-sizing: border-box; - box-sizing: border-box; - } - - .numInputWrapper span:hover { - background: rgba(0, 0, 0, 0.1); - } - - .numInputWrapper span:active { - background: rgba(0, 0, 0, 0.2); - } - - .numInputWrapper span:after { - display: block; - content: ""; - position: absolute; - } - - .numInputWrapper span.arrowUp { - top: 0; - border-bottom: 0; - } - - .numInputWrapper span.arrowUp:after { - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-bottom: 4px solid rgba(57, 57, 57, 0.6); - top: 26%; - } - - .numInputWrapper span.arrowDown { - top: 50%; - } - - .numInputWrapper span.arrowDown:after { - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid rgba(57, 57, 57, 0.6); - top: 40%; - } - - .numInputWrapper span svg { - width: inherit; - height: auto; - } - - .numInputWrapper span svg path { - fill: rgba(0, 0, 0, 0.5); - } - - .numInputWrapper:hover { - background: rgba(0, 0, 0, 0.05); - } - - .numInputWrapper:hover span { - opacity: 1; - } - -.flatpickr-current-month { - font-size: 135%; - line-height: inherit; - font-weight: 300; - color: inherit; - position: absolute; - width: 75%; - left: 12.5%; - padding: 7.48px 0 0 0; - line-height: 1; - height: 34px; - display: inline-block; - text-align: center; - -webkit-transform: translate3d(0px, 0px, 0px); - transform: translate3d(0px, 0px, 0px); -} - - .flatpickr-current-month span.cur-month { - font-family: inherit; - font-weight: 700; - color: inherit; - display: inline-block; - margin-left: 0.5ch; - padding: 0; - } - - .flatpickr-current-month span.cur-month:hover { - background: rgba(0, 0, 0, 0.05); - } - - .flatpickr-current-month .numInputWrapper { - width: 6ch; - width: 7ch\0; - display: inline-block; - } - - .flatpickr-current-month .numInputWrapper span.arrowUp:after { - border-bottom-color: rgba(0, 0, 0, 0.9); - } - - .flatpickr-current-month .numInputWrapper span.arrowDown:after { - border-top-color: rgba(0, 0, 0, 0.9); - } - - .flatpickr-current-month input.cur-year { - background: transparent; - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: inherit; - cursor: text; - padding: 0 0 0 0.5ch; - margin: 0; - display: inline-block; - font-size: inherit; - font-family: inherit; - font-weight: 300; - line-height: inherit; - height: auto; - border: 0; - border-radius: 0; - vertical-align: initial; - -webkit-appearance: textfield; - -moz-appearance: textfield; - appearance: textfield; - } - - .flatpickr-current-month input.cur-year:focus { - outline: 0; - } - - .flatpickr-current-month input.cur-year[disabled], - .flatpickr-current-month input.cur-year[disabled]:hover { - font-size: 100%; - color: rgba(0, 0, 0, 0.5); - background: transparent; - pointer-events: none; - } - - .flatpickr-current-month .flatpickr-monthDropdown-months { - appearance: menulist; - background: transparent; - border: none; - border-radius: 0; - box-sizing: border-box; - color: inherit; - cursor: pointer; - font-size: inherit; - font-family: inherit; - font-weight: 300; - height: auto; - line-height: inherit; - margin: -1px 0 0 0; - outline: none; - padding: 0 0 0 0.5ch; - position: relative; - vertical-align: initial; - -webkit-box-sizing: border-box; - -webkit-appearance: menulist; - -moz-appearance: menulist; - width: auto; - } - - .flatpickr-current-month .flatpickr-monthDropdown-months:focus, - .flatpickr-current-month .flatpickr-monthDropdown-months:active { - outline: none; - } - - .flatpickr-current-month .flatpickr-monthDropdown-months:hover { - background: rgba(0, 0, 0, 0.05); - } - - .flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month { - background-color: transparent; - outline: none; - padding: 0; - } - -.flatpickr-weekdays { - background: transparent; - text-align: center; - overflow: hidden; - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -webkit-align-items: center; - -ms-flex-align: center; - align-items: center; - height: 28px; -} - - .flatpickr-weekdays .flatpickr-weekdaycontainer { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - } - -span.flatpickr-weekday { - cursor: default; - font-size: 90%; - background: transparent; - color: rgba(0, 0, 0, 0.54); - line-height: 1; - margin: 0; - text-align: center; - display: block; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - font-weight: bolder; -} - -.dayContainer, -.flatpickr-weeks { - padding: 1px 0 0 0; -} - -.flatpickr-days { - position: relative; - overflow: hidden; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-align: start; - -webkit-align-items: flex-start; - -ms-flex-align: start; - align-items: flex-start; - width: 307.875px; -} - - .flatpickr-days:focus { - outline: 0; - } - -.dayContainer { - padding: 0; - outline: 0; - text-align: left; - width: 307.875px; - min-width: 307.875px; - max-width: 307.875px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - display: inline-block; - display: -ms-flexbox; - display: -webkit-box; - display: -webkit-flex; - display: flex; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-wrap: wrap; - -ms-flex-pack: justify; - -webkit-justify-content: space-around; - justify-content: space-around; - -webkit-transform: translate3d(0px, 0px, 0px); - transform: translate3d(0px, 0px, 0px); - opacity: 1; -} - - .dayContainer + .dayContainer { - -webkit-box-shadow: -1px 0 0 #e6e6e6; - box-shadow: -1px 0 0 #e6e6e6; - } - -.flatpickr-day { - background: none; - border: 1px solid transparent; - border-radius: 150px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: #344767; - cursor: pointer; - font-weight: 400; - width: 14.2857143%; - -webkit-flex-basis: 14.2857143%; - -ms-flex-preferred-size: 14.2857143%; - flex-basis: 14.2857143%; - max-width: 39px; - height: 39px; - line-height: 39px; - margin: 0; - display: inline-block; - position: relative; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; -} - - .flatpickr-day.inRange, - .flatpickr-day.prevMonthDay.inRange, - .flatpickr-day.nextMonthDay.inRange, - .flatpickr-day.today.inRange, - .flatpickr-day.prevMonthDay.today.inRange, - .flatpickr-day.nextMonthDay.today.inRange, - .flatpickr-day:hover, - .flatpickr-day.prevMonthDay:hover, - .flatpickr-day.nextMonthDay:hover, - .flatpickr-day:focus, - .flatpickr-day.prevMonthDay:focus, - .flatpickr-day.nextMonthDay:focus { - cursor: pointer; - outline: 0; - background: #e6e6e6; - border-color: #e6e6e6; - } - - .flatpickr-day.today { - border-color: #959ea9; - } - - .flatpickr-day.today:hover, - .flatpickr-day.today:focus { - border-color: #959ea9; - background: #959ea9; - color: #fff; - } - - .flatpickr-day.selected, - .flatpickr-day.startRange, - .flatpickr-day.endRange, - .flatpickr-day.selected.inRange, - .flatpickr-day.startRange.inRange, - .flatpickr-day.endRange.inRange, - .flatpickr-day.selected:focus, - .flatpickr-day.startRange:focus, - .flatpickr-day.endRange:focus, - .flatpickr-day.selected:hover, - .flatpickr-day.startRange:hover, - .flatpickr-day.endRange:hover, - .flatpickr-day.selected.prevMonthDay, - .flatpickr-day.startRange.prevMonthDay, - .flatpickr-day.endRange.prevMonthDay, - .flatpickr-day.selected.nextMonthDay, - .flatpickr-day.startRange.nextMonthDay, - .flatpickr-day.endRange.nextMonthDay { - background: #569ff7; - -webkit-box-shadow: none; - box-shadow: none; - color: #fff; - border-color: #569ff7; - } - - .flatpickr-day.selected.startRange, - .flatpickr-day.startRange.startRange, - .flatpickr-day.endRange.startRange { - border-radius: 50px 0 0 50px; - } - - .flatpickr-day.selected.endRange, - .flatpickr-day.startRange.endRange, - .flatpickr-day.endRange.endRange { - border-radius: 0 50px 50px 0; - } - - .flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), - .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), - .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) { - -webkit-box-shadow: -10px 0 0 #569ff7; - box-shadow: -10px 0 0 #569ff7; - } - - .flatpickr-day.selected.startRange.endRange, - .flatpickr-day.startRange.startRange.endRange, - .flatpickr-day.endRange.startRange.endRange { - border-radius: 50px; - } - - .flatpickr-day.inRange { - border-radius: 0; - -webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - } - - .flatpickr-day.flatpickr-disabled, - .flatpickr-day.flatpickr-disabled:hover, - .flatpickr-day.prevMonthDay, - .flatpickr-day.nextMonthDay, - .flatpickr-day.notAllowed, - .flatpickr-day.notAllowed.prevMonthDay, - .flatpickr-day.notAllowed.nextMonthDay { - color: rgba(57, 57, 57, 0.3); - background: transparent; - border-color: transparent; - cursor: default; - } - - .flatpickr-day.flatpickr-disabled, - .flatpickr-day.flatpickr-disabled:hover { - cursor: not-allowed; - color: rgba(57, 57, 57, 0.1); - } - - .flatpickr-day.week.selected { - border-radius: 0; - -webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; - box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; - } - - .flatpickr-day.hidden { - visibility: hidden; - } - -.rangeMode .flatpickr-day { - margin-top: 1px; -} - -.flatpickr-weekwrapper { - float: left; -} - - .flatpickr-weekwrapper .flatpickr-weeks { - padding: 0 12px; - -webkit-box-shadow: 1px 0 0 #e6e6e6; - box-shadow: 1px 0 0 #e6e6e6; - } - - .flatpickr-weekwrapper .flatpickr-weekday { - float: none; - width: 100%; - line-height: 28px; - } - - .flatpickr-weekwrapper span.flatpickr-day, - .flatpickr-weekwrapper span.flatpickr-day:hover { - display: block; - width: 100%; - max-width: none; - color: rgba(57, 57, 57, 0.3); - background: transparent; - cursor: default; - border: none; - } - -.flatpickr-innerContainer { - display: block; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-sizing: border-box; - box-sizing: border-box; - overflow: hidden; -} - -.flatpickr-rContainer { - display: inline-block; - padding: 0; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -.flatpickr-time { - text-align: center; - outline: 0; - display: block; - height: 0; - line-height: 40px; - max-height: 40px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - overflow: hidden; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - - .flatpickr-time:after { - content: ""; - display: table; - clear: both; - } - - .flatpickr-time .numInputWrapper { - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - width: 40%; - height: 40px; - float: left; - } - - .flatpickr-time .numInputWrapper span.arrowUp:after { - border-bottom-color: #393939; - } - - .flatpickr-time .numInputWrapper span.arrowDown:after { - border-top-color: #393939; - } - - .flatpickr-time.hasSeconds .numInputWrapper { - width: 26%; - } - - .flatpickr-time.time24hr .numInputWrapper { - width: 49%; - } - - .flatpickr-time input { - background: transparent; - -webkit-box-shadow: none; - box-shadow: none; - border: 0; - border-radius: 0; - text-align: center; - margin: 0; - padding: 0; - height: inherit; - line-height: inherit; - color: #393939; - font-size: 14px; - position: relative; - -webkit-box-sizing: border-box; - box-sizing: border-box; - -webkit-appearance: textfield; - -moz-appearance: textfield; - appearance: textfield; - } - - .flatpickr-time input.flatpickr-hour { - font-weight: bold; - } - - .flatpickr-time input.flatpickr-minute, - .flatpickr-time input.flatpickr-second { - font-weight: 400; - } - - .flatpickr-time input:focus { - outline: 0; - border: 0; - } - - .flatpickr-time .flatpickr-time-separator, - .flatpickr-time .flatpickr-am-pm { - height: inherit; - float: left; - line-height: inherit; - color: #393939; - font-weight: bold; - width: 2%; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-align-self: center; - -ms-flex-item-align: center; - align-self: center; - } - - .flatpickr-time .flatpickr-am-pm { - outline: 0; - width: 18%; - cursor: pointer; - text-align: center; - font-weight: 400; - } - - .flatpickr-time input:hover, - .flatpickr-time .flatpickr-am-pm:hover, - .flatpickr-time input:focus, - .flatpickr-time .flatpickr-am-pm:focus { - background: #eee; - } - -.flatpickr-input[readonly] { - cursor: pointer; -} - -@-webkit-keyframes fpFadeInDown { - from { - opacity: 0; - -webkit-transform: translate3d(0, -20px, 0); - transform: translate3d(0, -20px, 0); - } - - to { - opacity: 1; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -@keyframes fpFadeInDown { - from { - opacity: 0; - -webkit-transform: translate3d(0, -20px, 0); - transform: translate3d(0, -20px, 0); - } - - to { - opacity: 1; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} - -.datepicker.flatpickr-input { - background-color: #fff; -} - -.flatpickr-calendar.open { - margin-left: 0px; - margin-top: 4px; -} - -.flatpickr-calendar.arrowBottom { - margin-top: -20px; -} - -.flatpickr-calendar .flatpickr-innerContainer { - margin-top: 15px !important; -} - -.flatpickr-calendar .numInputWrapper span { - border: none; - border-bottom: 1px solid rgba(57, 57, 57, 0.15); -} - -.flatpickr-calendar .numInputWrapper:hover .arrowUp, -.flatpickr-calendar .numInputWrapper:hover .arrowDown { - margin-top: 3px; -} - -.flatpickr-calendar .flatpickr-day.today, -.flatpickr-calendar .flatpickr-day.selected, -.flatpickr-calendar .flatpickr-day.startRange, -.flatpickr-calendar .flatpickr-day.endRange { - background: #1f5d6a !important; - color: #fff; - border: none; -} - -.flatpickr-calendar .flatpickr-day.inRange { - background: rgba(94, 114, 228, 0.28); - border: none; - -webkit-box-shadow: -5px 0 0 #D7DCF8, 5px 0 0 #D7DCF8; - box-shadow: -5px 0 0 #D7DCF8, 5px 0 0 #D7DCF8; -} - -.flatpickr-calendar .flatpickr-day:not(.selected):hover, -.flatpickr-calendar .flatpickr-day:not(.selected):focus { - background: rgba(94, 114, 228, 0.28); - border: none; -} - -.flatpickr-calendar .flatpickr-time input:hover, -.flatpickr-calendar .flatpickr-time .flatpickr-am-pm:hover, -.flatpickr-calendar .flatpickr-time input:focus, -.flatpickr-calendar .flatpickr-time .flatpickr-am-pm:focus { - background: rgba(94, 114, 228, 0.28); -} - -.flatpickr.form-control { - background: #fff; -} - -.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)), -.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), -.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)) { - box-shadow: -10px 0 0 #1f5d6a; -} - -.noUi-target, -.noUi-target * { - -webkit-touch-callout: none; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-user-select: none; - -ms-touch-action: none; - touch-action: none; - -ms-user-select: none; - -moz-user-select: none; - user-select: none; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.noUi-target { - position: relative; -} - -.noUi-base, -.noUi-connects { - width: 100%; - height: 2px; - position: relative; - z-index: 1; - top: 0; -} - -.noUi-connects { - z-index: 0; - overflow: hidden; -} - -.noUi-connect, -.noUi-origin { - will-change: transform; - position: absolute; - z-index: 1; - top: 0; - right: 0; - -ms-transform-origin: 0 0; - -webkit-transform-origin: 0 0; - -webkit-transform-style: preserve-3d; - transform-origin: 0 0; - transform-style: flat; -} - -.noUi-connect { - height: 100%; - width: 100%; - border-radius: 0.25rem; -} - -.noUi-origin { - height: 10%; - width: 10%; -} - -.noUi-txt-dir-rtl.noUi-horizontal .noUi-origin { - left: 0; - right: auto; -} - -.noUi-vertical .noUi-origin { - width: 0; -} - -.noUi-horizontal .noUi-origin { - height: 0; -} - -.noUi-handle { - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: absolute; -} - -.noUi-touch-area { - height: 100%; - width: 100%; -} - -.noUi-state-tap .noUi-connect, -.noUi-state-tap .noUi-origin { - -webkit-transition: transform 0.3s; - transition: transform 0.3s; -} - -.noUi-state-drag * { - cursor: inherit !important; -} - -.noUi-horizontal { - height: 2px; -} - - .noUi-horizontal .noUi-handle { - border-radius: 50%; - background-color: #fff; - box-shadow: 0 1px 13px 0 rgba(0, 0, 0, 0.2); - height: 14px; - width: 14px; - cursor: pointer; - margin-top: -6px; - outline: none; - right: -10px; - } - -.noUi-vertical { - width: 3px; -} - - .noUi-vertical .noUi-handle { - width: 28px; - height: 34px; - right: -6px; - top: -17px; - } - -.noUi-txt-dir-rtl.noUi-horizontal .noUi-handle { - left: -17px; - right: auto; -} - -.noUi-target { - background: #f0f2f5; - border-radius: .25rem; -} - -.noUi-connects { - border-radius: 3px; -} - -.noUi-connect { - background: #1f5d6a; -} - -.noUi-draggable { - cursor: ew-resize; -} - -.noUi-vertical .noUi-draggable { - cursor: ns-resize; -} - -.noUi-handle { - border: 1px solid #1f5d6a; - border-radius: 3px; - background: #fff; - cursor: default; - box-shadow: inset 0 0 1px #fff, inset 0 1px 7px #EBEBEB, 0 3px 6px -3px #BBB; - webkit-transition: .3s ease 0s; - -moz-transition: .3s ease 0s; - -ms-transition: .3s ease 0s; - -o-transform: .3s ease 0s; - transition: .3s ease 0s; -} - -.noUi-active { - box-shadow: inset 0 0 1px #fff, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB; - transform: scale3d(1.5, 1.5, 1); -} - -[disabled] .noUi-connect { - background: #B8B8B8; -} - -[disabled].noUi-target, -[disabled].noUi-handle, -[disabled] .noUi-handle { - cursor: not-allowed; -} - -.noUi-pips, -.noUi-pips * { - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.noUi-pips { - position: absolute; - color: #999; -} - -.noUi-value { - position: absolute; - white-space: nowrap; - text-align: center; -} - -.noUi-value-sub { - color: #ccc; - font-size: 10px; -} - -.noUi-marker { - position: absolute; - background: #CCC; -} - -.noUi-marker-sub { - background: #AAA; -} - -.noUi-marker-large { - background: #AAA; -} - -.noUi-pips-horizontal { - padding: 10px 0; - height: 80px; - top: 100%; - left: 0; - width: 100%; -} - -.noUi-value-horizontal { - -webkit-transform: translate(-50%, 50%); - transform: translate(-50%, 50%); -} - -.noUi-rtl .noUi-value-horizontal { - -webkit-transform: translate(50%, 50%); - transform: translate(50%, 50%); -} - -.noUi-marker-horizontal.noUi-marker { - margin-left: -1px; - width: 2px; - height: 5px; -} - -.noUi-marker-horizontal.noUi-marker-sub { - height: 10px; -} - -.noUi-marker-horizontal.noUi-marker-large { - height: 15px; -} - -.noUi-pips-vertical { - padding: 0 10px; - height: 100%; - top: 0; - left: 100%; -} - -.noUi-value-vertical { - -webkit-transform: translate(0, -50%); - transform: translate(0, -50%); - padding-left: 25px; -} - -.noUi-rtl .noUi-value-vertical { - -webkit-transform: translate(0, 50%); - transform: translate(0, 50%); -} - -.noUi-marker-vertical.noUi-marker { - width: 5px; - height: 2px; - margin-top: -1px; -} - -.noUi-marker-vertical.noUi-marker-sub { - width: 10px; -} - -.noUi-marker-vertical.noUi-marker-large { - width: 15px; -} - -.noUi-tooltip { - display: block; - position: absolute; - border: 1px solid #D9D9D9; - border-radius: 3px; - background: #fff; - color: #000; - padding: 5px; - text-align: center; - white-space: nowrap; -} - -.noUi-horizontal .noUi-tooltip { - -webkit-transform: translate(-50%, 0); - transform: translate(-50%, 0); - left: 50%; - bottom: 120%; -} - -.noUi-vertical .noUi-tooltip { - -webkit-transform: translate(0, -50%); - transform: translate(0, -50%); - top: 50%; - right: 120%; -} - -.noUi-horizontal .noUi-origin > .noUi-tooltip { - -webkit-transform: translate(50%, 0); - transform: translate(50%, 0); - left: auto; - bottom: 10px; -} - -.noUi-vertical .noUi-origin > .noUi-tooltip { - -webkit-transform: translate(0, -18px); - transform: translate(0, -18px); - top: auto; - right: 28px; -} - -code[class*="language-"], -pre[class*="language-"] { - color: black; - background: none; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - font-size: 1em; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - - pre[class*="language-"]::-moz-selection, - pre[class*="language-"] ::-moz-selection, - code[class*="language-"]::-moz-selection, - code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; - } - - pre[class*="language-"]::selection, - pre[class*="language-"] ::selection, - code[class*="language-"]::selection, - code[class*="language-"] ::selection { - text-shadow: none; - background: #b3d4fc; - } - -@media print { - - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -pre[class*="language-"] { - padding: 1em; - overflow: auto; - border-radius: .75rem; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #f8f9fa; -} - -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; - white-space: normal; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.token.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.deleted { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string { - color: #9a6e3a; - background: rgba(255, 255, 255, 0.5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function, -.token.class-name { - color: #DD4A68; -} - -.token.regex, -.token.important, -.token.variable { - color: #e90; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.ps { - overflow: hidden !important; - overflow-anchor: none; - -ms-overflow-style: none; - touch-action: auto; - -ms-touch-action: auto; -} - -.ps__rail-x { - display: none; - opacity: 0; - transition: background-color .2s linear, opacity .2s linear; - -webkit-transition: background-color .2s linear, opacity .2s linear; - height: 15px; - bottom: 0px; - position: absolute; -} - -.ps__rail-y { - display: none; - opacity: 0; - transition: background-color .2s linear, opacity .2s linear; - -webkit-transition: background-color .2s linear, opacity .2s linear; - width: 15px; - right: 0; - position: absolute; -} - -.ps--active-x > .ps__rail-x, -.ps--active-y > .ps__rail-y { - display: block; - background-color: transparent; -} - -.ps:hover > .ps__rail-x, -.ps:hover > .ps__rail-y, -.ps--focus > .ps__rail-x, -.ps--focus > .ps__rail-y, -.ps--scrolling-x > .ps__rail-x, -.ps--scrolling-y > .ps__rail-y { - opacity: 0.6; -} - -.ps .ps__rail-x:hover, -.ps .ps__rail-y:hover, -.ps .ps__rail-x:focus, -.ps .ps__rail-y:focus, -.ps .ps__rail-x.ps--clicking, -.ps .ps__rail-y.ps--clicking { - background-color: #eee; - opacity: 0.9; -} - -.ps__thumb-x { - background-color: #aaa; - border-radius: 6px; - transition: background-color .2s linear, height .2s ease-in-out; - -webkit-transition: background-color .2s linear, height .2s ease-in-out; - height: 6px; - bottom: 2px; - position: absolute; -} - -.ps__thumb-y { - background-color: #aaa; - border-radius: 6px; - transition: background-color .2s linear, width .2s ease-in-out; - -webkit-transition: background-color .2s linear, width .2s ease-in-out; - width: 6px; - right: 2px; - position: absolute; -} - -.ps__rail-x:hover > .ps__thumb-x, -.ps__rail-x:focus > .ps__thumb-x, -.ps__rail-x.ps--clicking .ps__thumb-x { - background-color: #999; - height: 11px; -} - -.ps__rail-y:hover > .ps__thumb-y, -.ps__rail-y:focus > .ps__thumb-y, -.ps__rail-y.ps--clicking .ps__thumb-y { - background-color: #999; - width: 11px; -} - -@supports (-ms-overflow-style: none) { - .ps { - overflow: auto !important; - } -} - -@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - .ps { - overflow: auto !important; - } -} - -.fs-lable-s { - font-size: 0.6rem; -} diff --git a/src/Angor/Client/wwwroot/assets/js/app.js b/src/Angor/Client/wwwroot/assets/js/app.js index da7074d2..9c689df7 100644 --- a/src/Angor/Client/wwwroot/assets/js/app.js +++ b/src/Angor/Client/wwwroot/assets/js/app.js @@ -13,11 +13,11 @@ window.angor = { }, addDarkBackground: function () { - document.body.style.backgroundColor = "#022229"; + document.body.classList.add('dark'); }, addLightBackground: function () { - document.body.style.backgroundColor = "#f4f1ec"; - }, + document.body.classList.remove('dark'); + }, - }; +}; From 24853a5aec665410c63a566b74d7c90fd91ec43c Mon Sep 17 00:00:00 2001 From: ilior <78041027+itailiors@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:22:31 +0300 Subject: [PATCH 02/13] Btc to fiat rest api (#147) * prototype * add more currencies * move the logic to service * cleanup * switch from coingecko to mempool * fix merge * move to component * overall changes for the CurrencyService.cs * change CurrencyRate to its own service named CurrencyRateService.cs * refactor, not finished * only return conversion if btc * Refactored Bitcoin price fetching and conversion logic - Optimized CurrencyRateService with caching for API calls. - Improved CurrencyService for converting and formatting BTC balances. - Enhanced code structure for better readability and maintainability. * Update Wallet.razor * Update nuget packages * Update Index.razor * Update Wallet.razor * Use GetBtcValuesInPreferredCurrency for fiat conversion. * Update Angor.Client.csproj --------- Co-authored-by: Milad Raeisi --- src/Angor.Test/Angor.Test.csproj | 6 +- src/Angor/Client/Angor.Client.csproj | 5 +- .../Client/Components/BalanceDisplay.razor | 42 +++ src/Angor/Client/Pages/Index.razor | 5 +- src/Angor/Client/Pages/Settings.razor | 48 ++- src/Angor/Client/Pages/Wallet.razor | 284 ++++++++++-------- src/Angor/Client/Program.cs | 10 +- .../Client/Services/CurrencyRateService.cs | 98 ++++++ src/Angor/Client/Services/CurrencyService.cs | 118 ++++++++ .../Client/Services/ICurrencyRateService.cs | 4 + src/Angor/Client/Services/ICurrencyService.cs | 5 + src/Angor/Client/Storage/ClientStorage.cs | 12 + src/Angor/Client/Storage/ICacheStorage.cs | 2 + src/Angor/Client/Storage/IClientStorage.cs | 3 + .../Client/Storage/LocalSessionStorage.cs | 94 +++--- .../Client/wwwroot/assets/icons/currency.svg | 7 + src/Angor/Server/Angor.Server.csproj | 2 +- 17 files changed, 570 insertions(+), 175 deletions(-) create mode 100644 src/Angor/Client/Components/BalanceDisplay.razor create mode 100644 src/Angor/Client/Services/CurrencyRateService.cs create mode 100644 src/Angor/Client/Services/CurrencyService.cs create mode 100644 src/Angor/Client/Services/ICurrencyRateService.cs create mode 100644 src/Angor/Client/Services/ICurrencyService.cs create mode 100644 src/Angor/Client/wwwroot/assets/icons/currency.svg diff --git a/src/Angor.Test/Angor.Test.csproj b/src/Angor.Test/Angor.Test.csproj index 6da49efa..17a933e7 100644 --- a/src/Angor.Test/Angor.Test.csproj +++ b/src/Angor.Test/Angor.Test.csproj @@ -19,11 +19,11 @@ - + - + - + diff --git a/src/Angor/Client/Angor.Client.csproj b/src/Angor/Client/Angor.Client.csproj index 7a06ccd2..08d6413b 100644 --- a/src/Angor/Client/Angor.Client.csproj +++ b/src/Angor/Client/Angor.Client.csproj @@ -11,11 +11,12 @@ - - + + + diff --git a/src/Angor/Client/Components/BalanceDisplay.razor b/src/Angor/Client/Components/BalanceDisplay.razor new file mode 100644 index 00000000..e77b4998 --- /dev/null +++ b/src/Angor/Client/Components/BalanceDisplay.razor @@ -0,0 +1,42 @@ +@using Angor.Shared +@using Blockcore.Networks +@inject INetworkConfiguration NetworkConfiguration + + +
+ + @BtcBalance @_network.CoinTicker + + @if (ShowFiatInline && PreferredCurrency != "BTC" && !string.IsNullOrEmpty(BtcBalanceInFiat)) + { +
@BtcBalanceInFiat
+ } +
+ +@code { + [Parameter] + public decimal BtcBalance { get; set; } + + [Parameter] + public string BtcBalanceInFiat { get; set; } + + [Parameter] + public string PreferredCurrency { get; set; } + + [Parameter] + public bool ShowFiatInline { get; set; } = false; + + private Network _network; + + protected override void OnInitialized() + { + _network = NetworkConfiguration.GetNetwork(); + base.OnInitialized(); + } + + private string GetTooltip() => + PreferredCurrency != "BTC" && !string.IsNullOrEmpty(BtcBalanceInFiat) && !ShowFiatInline + ? $"Equivalent: {BtcBalanceInFiat}" + : string.Empty; +} + diff --git a/src/Angor/Client/Pages/Index.razor b/src/Angor/Client/Pages/Index.razor index 525a5652..8d85748d 100644 --- a/src/Angor/Client/Pages/Index.razor +++ b/src/Angor/Client/Pages/Index.razor @@ -1,5 +1,4 @@ @page "/" - Angor
@@ -20,10 +19,8 @@
Welcome to Angor
-

Stay in control of your investments with Angor

+

Stay in control of your investments with Angor

- - diff --git a/src/Angor/Client/Pages/Settings.razor b/src/Angor/Client/Pages/Settings.razor index 6f4362bc..941ed6b5 100644 --- a/src/Angor/Client/Pages/Settings.razor +++ b/src/Angor/Client/Pages/Settings.razor @@ -305,6 +305,41 @@ +@* Currency Display Settings *@ +
+
+
+
+ + + +
+
+ Currency Display +
+

+ Current: @selectedCurrency +

+
+
+
+ + +
+
+
+
+ + @* Wipe Storage *@
@@ -392,6 +427,8 @@ private bool confirmWipe = false; private bool showConfirmWipeMessage = false; private string selectedNetwork = "testnet"; // Default to "testnet" + + private string selectedCurrency = "BTC"; // Default to BTC private SettingsInfo settingsInfo; @@ -404,10 +441,12 @@ networkType = _networkConfiguration.GetNetwork().Name; _networkService.OnStatusChanged += UpdateUI; + + selectedCurrency = _clientStorage.GetCurrencyDisplaySetting(); if (!networkType.ToLower().Contains("test")) selectedNetwork = "mainnet"; - + return base.OnInitializedAsync(); } @@ -650,4 +689,11 @@ NavMenuState.NotifyStateChanged(); } + + private void OnCurrencyChanged(ChangeEventArgs e) + { + selectedCurrency = e.Value.ToString(); + _clientStorage.SetCurrencyDisplaySetting(selectedCurrency); + StateHasChanged(); + } } \ No newline at end of file diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 354e7ed4..2f66096f 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -1,13 +1,11 @@ @page "/wallet" -@using Blockcore.NBitcoin -@using Angor.Shared -@using Angor.Client.Services + +@using System.Text.Json @using Angor.Client.Storage +@using Angor.Shared @using Angor.Shared.Models -@using Angor.Shared.Services -@using Angor.Client.Components +@using Blockcore.NBitcoin @using Blockcore.Networks -@using System.Text.Json @inject HttpClient _httpClient; @inject IClientStorage storage; @@ -16,23 +14,22 @@ @inject ILogger Logger; @inject IWalletOperations _walletOperations @inject IClipboardService _clipboardService +@inject ICurrencyService _currencyService @inject IDerivationOperations _derivationOperations @inject NavMenuState NavMenuState @inject IEncryptionService _encryptionService @inject IClipboardService ClipboardService - - @inherits BaseComponent Wallet and balances - - + +
- +
@@ -57,13 +54,12 @@ @if (!hasWallet) { -
- + No Wallet Found
@@ -75,7 +71,7 @@
Create Wallet
@@ -85,7 +81,7 @@
Recover Wallet
@@ -103,7 +99,7 @@ - } - @code { - private bool balanceSpinner = false; - private bool createWalletSpinner = false; - private bool testCoinsSpinner = false; - private bool sendLoadSpinner = false; - private bool sendConfirmSpinner = false; + private bool balanceSpinner; + private bool createWalletSpinner; + private bool testCoinsSpinner; + private bool sendLoadSpinner; + private bool sendConfirmSpinner; private string? showWalletWords; private string? showWalletWordsPassphrase; @@ -698,7 +702,7 @@ else private string passwordInputType = "password"; private string passwordToggleText = "Show"; - private bool isNewWallet = false; + private bool isNewWallet; private string createWalletTitle = "Create Wallet"; private string createWalletDescription = "Generate a new wallet"; @@ -707,10 +711,10 @@ else private bool walletWordsModal; private bool walletWordsCreateModal; - private bool isShowExtraWord = false; + private bool isShowExtraWord; - private bool backupConfirmation = false; - private bool showBackupConfirmationError = false; + private bool backupConfirmation; + private bool showBackupConfirmationError; private int activeTab = 1; @@ -719,31 +723,31 @@ else private int FeePosition = 1; private SendInfo _sendInfo = new(); - private AccountBalanceInfo accountBalanceInfo = new AccountBalanceInfo(); + private readonly AccountBalanceInfo accountBalanceInfo = new(); - private FeeEstimations FeeEstimations = new(); + private readonly FeeEstimations FeeEstimations = new(); // Max index for the range input - private int FeeMin = 1; + private readonly int FeeMin = 1; private int _feeMax = 3; DateTime _lastFeeRefresh = DateTime.MinValue; ShowQrCode showQrCode; - Dictionary collapseStates = new Dictionary(); - + readonly Dictionary collapseStates = new(); + private string btcBalanceInUsd; + private readonly Dictionary fiatValues = new(); protected override async Task OnInitializedAsync() { - - if (hasWallet) { var accountInfo = storage.GetAccountInfo(network.Name); var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); - accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); + btcBalanceInUsd = await GetBtcValueInPreferredCurrency(Money.Satoshis(accountBalanceInfo.TotalBalance).ToUnit(MoneyUnit.BTC)); + await PrecomputeFiatValues(); } await base.OnInitializedAsync(); @@ -768,7 +772,7 @@ else storage.SetAccountInfo(network.Name, accountInfo); // lets log the entire account class to see what has changed - Logger.LogInformation(System.Text.Json.JsonSerializer.Serialize(accountInfo, new JsonSerializerOptions { WriteIndented = true })); + Logger.LogInformation(JsonSerializer.Serialize(accountInfo, new JsonSerializerOptions { WriteIndented = true })); var utxos = accountInfo.AllUtxos() .Select(x => x.outpoint.ToString()).ToList(); @@ -835,6 +839,7 @@ else notificationComponent.ShowErrorMessage("New wallet password is null or empty"); return; } + if (!backupConfirmation) { showBackupConfirmationError = true; @@ -847,8 +852,7 @@ else try { - - WalletWords data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; + var data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; var accountInfo = _walletOperations.BuildAccountInfoForWalletWords(data); await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); @@ -859,7 +863,7 @@ else accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, new List()); // pre-derive the angor wallet keys - FounderKeyCollection founderKeyCollection = _derivationOperations.DeriveProjectKeys(data, _networkConfiguration.GetAngorKey()); + var founderKeyCollection = _derivationOperations.DeriveProjectKeys(data, _networkConfiguration.GetAngorKey()); _walletStorage.SetFounderKeys(founderKeyCollection); hasWallet = _walletStorage.HasWallet(); @@ -1153,7 +1157,7 @@ else if (selected != null) { - if (int.TryParse(selected, out int res)) + if (int.TryParse(selected, out var res)) { if (res <= FeeEstimations.Fees.Count) { @@ -1201,7 +1205,6 @@ else unconfirmedInboundFunds.Add(new UtxoData { PendingSpent = true, address = receiveAddress, value = trx.Outputs.FirstOrDefault()?.Value.Satoshi ?? Money.Coins(50).Satoshi, outpoint = new Outpoint(trx.GetHash().ToString(), 0) }); _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInboundFunds); accountBalanceInfo.UpdateAccountBalanceInfo(accountBalanceInfo.AccountInfo, unconfirmedInboundFunds); - } catch (Exception e) { @@ -1255,4 +1258,37 @@ else { isShowExtraWord = !isShowExtraWord; } -} + + public async Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances) + { + return await _currencyService.GetBtcValuesInPreferredCurrency(btcBalances); + } + + public async Task GetBtcValueInPreferredCurrency(decimal btcBalance) + { + var results = await GetBtcValuesInPreferredCurrency(btcBalance); + return results.FirstOrDefault() ?? "Error fetching value"; + } + + + private async Task PrecomputeFiatValues() + { + fiatValues.Clear(); + + var btcBalance = Money.Satoshis(accountBalanceInfo.TotalBalance).ToUnit(MoneyUnit.BTC); + + var fiatValuesList = await _currencyService.GetBtcValuesInPreferredCurrency(btcBalance); + + if (fiatValuesList == null || !fiatValuesList.Any()) + { + Logger.LogError("Failed to fetch the fiat value for the total balance."); + return; + } + + fiatValues["TotalBalance"] = fiatValuesList.First(); + + Logger.LogInformation($"Fiat value for total balance: {fiatValuesList.First()}"); + } + + +} \ No newline at end of file diff --git a/src/Angor/Client/Program.cs b/src/Angor/Client/Program.cs index 9a60002e..7c9ec618 100644 --- a/src/Angor/Client/Program.cs +++ b/src/Angor/Client/Program.cs @@ -24,15 +24,17 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddTransient(); -builder.Services.AddTransient (); +builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddTransient (); +builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -50,10 +52,10 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); -await builder.Build().RunAsync(); +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/src/Angor/Client/Services/CurrencyRateService.cs b/src/Angor/Client/Services/CurrencyRateService.cs new file mode 100644 index 00000000..0af879c3 --- /dev/null +++ b/src/Angor/Client/Services/CurrencyRateService.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using Angor.Client.Storage; + +public class CurrencyRateService : ICurrencyRateService +{ + private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(10); + private readonly ICacheStorage _cacheStorage; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + private const string ApiUrl = "https://mempool.space/api/v1/prices"; + + public CurrencyRateService(HttpClient httpClient, ILogger logger, ICacheStorage cacheStorage) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _cacheStorage = cacheStorage ?? throw new ArgumentNullException(nameof(cacheStorage)); + } + + public async Task GetBtcToCurrencyRate(string currencyCode) + { + if (string.IsNullOrWhiteSpace(currencyCode)) + throw new ArgumentException("Currency code must not be null or empty.", nameof(currencyCode)); + + var preferredCurrency = currencyCode.ToUpper(); + + // Check if the rate is cached and still valid + var cachedRate = GetCachedRate(preferredCurrency); + if (cachedRate.HasValue) + { + _logger.LogInformation($"Using cached rate for {preferredCurrency}: {cachedRate.Value}"); + return cachedRate.Value; + } + + // Fetch the latest rate from API + var latestRate = await FetchRateFromApi(preferredCurrency); + CacheRate(preferredCurrency, latestRate); + + return latestRate; + } + + private decimal? GetCachedRate(string currencyCode) + { + var cachedEntry = _cacheStorage.GetCurrencyRate(currencyCode); + if (cachedEntry != null && DateTime.UtcNow - cachedEntry.Timestamp < _cacheDuration) + { + return cachedEntry.Rate; + } + + return null; + } + + private async Task FetchRateFromApi(string currencyCode) + { + try + { + var response = await _httpClient.GetAsync(ApiUrl); + response.EnsureSuccessStatusCode(); + + var jsonString = await response.Content.ReadAsStringAsync(); + _logger.LogInformation($"API Response: {jsonString}"); + + var data = JsonSerializer.Deserialize>(jsonString); + if (data != null && data.TryGetValue(currencyCode, out var rateElement)) + { + return rateElement.GetDecimal(); + } + + throw new KeyNotFoundException($"Currency '{currencyCode}' not found in the API response."); + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Error while fetching data from API."); + throw new Exception("Failed to fetch BTC rate from the API.", ex); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Error parsing the API response."); + throw new Exception("Invalid response format from the API.", ex); + } + } + + private void CacheRate(string currencyCode, decimal rate) + { + _cacheStorage.SetCurrencyRate(currencyCode, new RateCacheEntry + { + Rate = rate, + Timestamp = DateTime.UtcNow + }); + _logger.LogInformation($"Cached new rate for {currencyCode}: {rate}"); + } +} + +public class RateCacheEntry +{ + public decimal Rate { get; set; } + public DateTime Timestamp { get; set; } +} diff --git a/src/Angor/Client/Services/CurrencyService.cs b/src/Angor/Client/Services/CurrencyService.cs new file mode 100644 index 00000000..40bad83e --- /dev/null +++ b/src/Angor/Client/Services/CurrencyService.cs @@ -0,0 +1,118 @@ +using System.Globalization; +using Angor.Client.Storage; +using Angor.Shared; + +public class CurrencyService : ICurrencyService +{ + private readonly ICurrencyRateService _currencyRateService; + private readonly ILogger _logger; + private readonly INetworkConfiguration _networkConfiguration; + private readonly IClientStorage _storage; + + public CurrencyService( + ICurrencyRateService currencyRateService, + IClientStorage storage, + ILogger logger, + INetworkConfiguration network) + { + _currencyRateService = currencyRateService ?? throw new ArgumentNullException(nameof(currencyRateService)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _networkConfiguration = network ?? throw new ArgumentNullException(nameof(network)); + } + + public string GetCurrencySymbol(string currencyCode) + { + return currencyCode.ToUpper() switch + { + "USD" => "$", + "GBP" => "£", + "EUR" => "€", + "CAD" => "C$", + "CHF" => "CHF", + "AUD" => "A$", + "JPY" => "¥", + _ => currencyCode.ToUpper() // Return code if no symbol found + }; + } + + public async Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances) + { + if (btcBalances == null || btcBalances.Length == 0) + { + _logger.LogWarning("No BTC balances provided."); + return new List { "No balances provided" }; + } + + // Check if the current network is Bitcoin (BTC) + if (!_networkConfiguration.GetNetwork().CoinTicker.Equals("BTC", StringComparison.OrdinalIgnoreCase)) + { + return GenerateEmptyStringList(btcBalances.Length); + } + + string currencyCode; + decimal rate; + string currencySymbol; + CultureInfo cultureInfo; + + try + { + currencyCode = _storage.GetCurrencyDisplaySetting(); + + // If preferred currency is BTC, return empty strings + if (currencyCode.Equals("BTC", StringComparison.OrdinalIgnoreCase)) + { + return GenerateEmptyStringList(btcBalances.Length); + } + + // Get exchange rate, symbol, and culture info + rate = await _currencyRateService.GetBtcToCurrencyRate(currencyCode); + currencySymbol = GetCurrencySymbol(currencyCode); + cultureInfo = new CultureInfo(GetCultureCode(currencyCode)); + } + catch (Exception ex) + { + _logger.LogError($"Error fetching currency settings or rates: {ex.Message}"); + return new List { "Error fetching settings or rates" }; + } + + try + { + return btcBalances + .AsParallel() + .Select(btcBalance => CalculateFormattedValue(btcBalance, rate, currencySymbol, cultureInfo)) + .ToList(); + } + catch (Exception ex) + { + _logger.LogError($"Error calculating BTC values: {ex.Message}"); + return new List { "Error calculating values" }; + } + } + + private string CalculateFormattedValue(decimal btcBalance, decimal rate, string currencySymbol, CultureInfo cultureInfo) + { + var value = btcBalance * rate; + return value.ToString("C2", cultureInfo).Replace(cultureInfo.NumberFormat.CurrencySymbol, currencySymbol); + } + + private static string GetCultureCode(string currencyCode) + { + return currencyCode.ToUpper() switch + { + "USD" => "en-US", + "GBP" => "en-GB", + "EUR" => "fr-FR", + "CAD" => "en-CA", + "CHF" => "de-CH", + "AUD" => "en-AU", + "JPY" => "ja-JP", + _ => "en-US" // Default to US culture if no match found + }; + } + + private static List GenerateEmptyStringList(int length) + { + return Enumerable.Repeat(string.Empty, length).ToList(); + } +} diff --git a/src/Angor/Client/Services/ICurrencyRateService.cs b/src/Angor/Client/Services/ICurrencyRateService.cs new file mode 100644 index 00000000..b3db8da1 --- /dev/null +++ b/src/Angor/Client/Services/ICurrencyRateService.cs @@ -0,0 +1,4 @@ +public interface ICurrencyRateService +{ + Task GetBtcToCurrencyRate(string currencyCode); +} \ No newline at end of file diff --git a/src/Angor/Client/Services/ICurrencyService.cs b/src/Angor/Client/Services/ICurrencyService.cs new file mode 100644 index 00000000..2fe91596 --- /dev/null +++ b/src/Angor/Client/Services/ICurrencyService.cs @@ -0,0 +1,5 @@ +public interface ICurrencyService +{ + Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances); + string GetCurrencySymbol(string currencyCode); +} \ No newline at end of file diff --git a/src/Angor/Client/Storage/ClientStorage.cs b/src/Angor/Client/Storage/ClientStorage.cs index 68f57d5e..b697682c 100644 --- a/src/Angor/Client/Storage/ClientStorage.cs +++ b/src/Angor/Client/Storage/ClientStorage.cs @@ -10,6 +10,8 @@ public class ClientStorage : IClientStorage, INetworkStorage { private readonly ISyncLocalStorageService _storage; + private const string CurrencyDisplaySettingKey = "currencyDisplaySetting"; + private const string utxoKey = "utxo:{0}"; public ClientStorage(ISyncLocalStorageService storage) { @@ -224,4 +226,14 @@ public string GetNostrPublicKeyPerProject(string projectId) { return _storage.GetItem($"project:{projectId}:nostrKey"); } + + public string GetCurrencyDisplaySetting() + { + return _storage.GetItem(CurrencyDisplaySettingKey) ?? "BTC"; + } + + public void SetCurrencyDisplaySetting(string setting) + { + _storage.SetItem(CurrencyDisplaySettingKey, setting); + } } \ No newline at end of file diff --git a/src/Angor/Client/Storage/ICacheStorage.cs b/src/Angor/Client/Storage/ICacheStorage.cs index 59893751..c2929513 100644 --- a/src/Angor/Client/Storage/ICacheStorage.cs +++ b/src/Angor/Client/Storage/ICacheStorage.cs @@ -18,5 +18,7 @@ public interface ICacheStorage void SetUnconfirmedInboundFunds(List unconfirmedInfo); void SetUnconfirmedOutboundFunds(List unconfirmedInfo); void DeleteUnconfirmedInfo(); + void SetCurrencyRate(string currencyCode, RateCacheEntry rateCacheEntry); + RateCacheEntry? GetCurrencyRate(string currencyCode); void WipeSession(); } \ No newline at end of file diff --git a/src/Angor/Client/Storage/IClientStorage.cs b/src/Angor/Client/Storage/IClientStorage.cs index 45fb8d26..8eb5fa8a 100644 --- a/src/Angor/Client/Storage/IClientStorage.cs +++ b/src/Angor/Client/Storage/IClientStorage.cs @@ -33,5 +33,8 @@ public interface IClientStorage SettingsInfo GetSettingsInfo(); void SetSettingsInfo(SettingsInfo settingsInfo); void WipeStorage(); + + string GetCurrencyDisplaySetting(); + void SetCurrencyDisplaySetting(string setting); } } diff --git a/src/Angor/Client/Storage/LocalSessionStorage.cs b/src/Angor/Client/Storage/LocalSessionStorage.cs index 7e500548..41cfc82c 100644 --- a/src/Angor/Client/Storage/LocalSessionStorage.cs +++ b/src/Angor/Client/Storage/LocalSessionStorage.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Angor.Client.Models; using Angor.Shared.Models; using Angor.Shared.Services; @@ -7,10 +8,9 @@ namespace Angor.Client.Storage; public class LocalSessionStorage : ICacheStorage { - private ISyncSessionStorageService _sessionStorageService; - private const string BrowseIndexerData = "subscriptions"; private const string NostrPubKeyMapping = "NostrPubKeyMapping"; + private readonly ISyncSessionStorageService _sessionStorageService; public LocalSessionStorage(ISyncSessionStorageService sessionStorageService) { @@ -19,7 +19,7 @@ public LocalSessionStorage(ISyncSessionStorageService sessionStorageService) public void StoreProject(Project project) { - _sessionStorageService.SetItem(project.ProjectInfo.ProjectIdentifier,project); + _sessionStorageService.SetItem(project.ProjectInfo.ProjectIdentifier, project); } public ProjectMetadata? GetProjectMetadataByPubkey(string pubkey) @@ -27,11 +27,6 @@ public void StoreProject(Project project) return _sessionStorageService.GetItem(pubkey); } - public void StoreProjectMetadata(string pubkey, ProjectMetadata projectMetadata) - { - _sessionStorageService.SetItem(pubkey, projectMetadata); - } - public bool IsProjectMetadataStorageByPubkey(string pubkey) { return _sessionStorageService.ContainKey(pubkey); @@ -42,26 +37,6 @@ public bool IsProjectMetadataStorageByPubkey(string pubkey) return _sessionStorageService.GetItem(projectId); } - public void AddProjectIdToNostrPubKeyMapping(string npub, string projectId) - { - var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); - - dictionary ??= new (); - - dictionary.TryAdd(npub, projectId); - - _sessionStorageService.SetItem(NostrPubKeyMapping,dictionary); - } - - public string? GetProjectIdByNostrPubKey(string npub) - { - var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); - - if (dictionary is null) return null; - - return dictionary.TryGetValue(npub, out var value) ? value : null; - } - public bool IsProjectInStorageById(string projectId) { return _sessionStorageService.ContainKey(projectId); @@ -74,17 +49,17 @@ public bool IsProjectInStorageById(string projectId) public void SetProjectIndexerData(List list) { - _sessionStorageService.SetItem(BrowseIndexerData,list); + _sessionStorageService.SetItem(BrowseIndexerData, list); } public List GetUnconfirmedInboundFunds() { - return _sessionStorageService.GetItem>("unconfirmed-inbound") ?? new (); + return _sessionStorageService.GetItem>("unconfirmed-inbound") ?? new List(); } public List GetUnconfirmedOutboundFunds() { - return _sessionStorageService.GetItem>("unconfirmed-outbound") ?? new(); + return _sessionStorageService.GetItem>("unconfirmed-outbound") ?? new List(); } public void SetUnconfirmedInboundFunds(List unconfirmedInfo) @@ -96,19 +71,66 @@ public void SetUnconfirmedOutboundFunds(List unconfirmedInfo) { _sessionStorageService.SetItem("unconfirmed-outbound", unconfirmedInfo); } - - public List GetNamesOfCommunicatorsThatReceivedEose(string subscriptionName) - { - return _sessionStorageService.GetItem>("Eose" + subscriptionName); - } public void DeleteUnconfirmedInfo() { _sessionStorageService.RemoveItem("unconfirmed-info"); } + public void SetCurrencyRate(string currencyCode, RateCacheEntry rateCacheEntry) + { + var cacheJson = JsonSerializer.Serialize(rateCacheEntry); + _sessionStorageService.SetItem(currencyCode, cacheJson); + } + + public RateCacheEntry? GetCurrencyRate(string currencyCode) + { + var cacheJson = _sessionStorageService.GetItem(currencyCode); + + if (string.IsNullOrEmpty(cacheJson)) return null; + + try + { + return JsonSerializer.Deserialize(cacheJson); + } + catch (JsonException ex) + { + return null; + } + } + public void WipeSession() { _sessionStorageService.Clear(); } + + public void StoreProjectMetadata(string pubkey, ProjectMetadata projectMetadata) + { + _sessionStorageService.SetItem(pubkey, projectMetadata); + } + + public void AddProjectIdToNostrPubKeyMapping(string npub, string projectId) + { + var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); + + dictionary ??= new Dictionary(); + + dictionary.TryAdd(npub, projectId); + + _sessionStorageService.SetItem(NostrPubKeyMapping, dictionary); + } + + public string? GetProjectIdByNostrPubKey(string npub) + { + var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); + + if (dictionary is null) return null; + + return dictionary.TryGetValue(npub, out var value) ? value : null; + } + + public List GetNamesOfCommunicatorsThatReceivedEose(string subscriptionName) + { + return _sessionStorageService.GetItem>("Eose" + subscriptionName); + } } \ No newline at end of file diff --git a/src/Angor/Client/wwwroot/assets/icons/currency.svg b/src/Angor/Client/wwwroot/assets/icons/currency.svg new file mode 100644 index 00000000..401ca9d8 --- /dev/null +++ b/src/Angor/Client/wwwroot/assets/icons/currency.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Angor/Server/Angor.Server.csproj b/src/Angor/Server/Angor.Server.csproj index 3634b23f..42eff20f 100644 --- a/src/Angor/Server/Angor.Server.csproj +++ b/src/Angor/Server/Angor.Server.csproj @@ -10,7 +10,7 @@ - + From 6266cc434f54a72ce77587b83eb7170673d47b48 Mon Sep 17 00:00:00 2001 From: dangershony Date: Tue, 10 Sep 2024 20:24:57 +0100 Subject: [PATCH 03/13] Deploy version 0.60 --- package.json | 2 +- src/Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 26f945e9..7d17f89d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angor", - "version": "0.0.59", + "version": "0.0.60", "license": "MIT", "scripts": { "version": "node -p \"require('./package.json').version\"", diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a928b772..08602474 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 0.0.59 + 0.0.60 net7.0 Blockcore MIT From ef426a3e0ecd1492cc6d6bd4c1a09f6ef71a51aa Mon Sep 17 00:00:00 2001 From: dangershony Date: Tue, 10 Sep 2024 20:29:18 +0100 Subject: [PATCH 04/13] Deploy version 0.61 --- package.json | 2 +- src/Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7d17f89d..1f76249a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angor", - "version": "0.0.60", + "version": "0.0.61", "license": "MIT", "scripts": { "version": "node -p \"require('./package.json').version\"", diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 08602474..88478325 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 0.0.60 + 0.0.61 net7.0 Blockcore MIT From ae8291204b077190c2da0d791f19b90fd59fcf7a Mon Sep 17 00:00:00 2001 From: Milad Raeisi <6504337+miladsoft@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:32:12 +0400 Subject: [PATCH 05/13] Refactor CSS variables to use existing color variables for consistency and maintainability across root and dark themes. (#150) --- src/Angor/Client/Components/Icon.razor | 2 +- src/Angor/Client/Pages/Browse.razor | 2 +- src/Angor/Client/Pages/Founder.razor | 2 +- src/Angor/Client/Pages/Invest.razor | 12 +- src/Angor/Client/Pages/Penalties.razor | 2 +- src/Angor/Client/Pages/Settings.razor | 14 +- src/Angor/Client/Pages/Signatures.razor | 2 +- src/Angor/Client/Pages/View.razor | 2 +- src/Angor/Client/Pages/Wallet.razor | 44 +- .../Client/Shared/NotificationComponent.razor | 2 +- src/Angor/Client/wwwroot/assets/css/app.css | 833 +++-- .../Client/wwwroot/assets/css/dashboard.css | 2858 ++++++++--------- .../wwwroot/assets/icons/angor-logo.svg | 6 +- .../Client/wwwroot/assets/icons/copy.svg | 4 +- .../Client/wwwroot/assets/icons/github.svg | 2 +- .../Client/wwwroot/assets/icons/primary.svg | 2 +- .../Client/wwwroot/assets/icons/receive.svg | 10 +- .../Client/wwwroot/assets/icons/refresh.svg | 4 +- .../Client/wwwroot/assets/icons/send.svg | 10 +- src/Angor/Client/wwwroot/assets/icons/set.svg | 2 +- 20 files changed, 1901 insertions(+), 1914 deletions(-) diff --git a/src/Angor/Client/Components/Icon.razor b/src/Angor/Client/Components/Icon.razor index 315f6ec3..547a628d 100644 --- a/src/Angor/Client/Components/Icon.razor +++ b/src/Angor/Client/Components/Icon.razor @@ -3,7 +3,7 @@ [Parameter] public string IconName { get; set; } [Parameter] public int Width { get; set; } = 32; [Parameter] public int Height { get; set; } = 32; - [Parameter] public string Color { get; set; } = "var(--bs-primary)"; + [Parameter] public string Color { get; set; } = "var(--angor-primary)"; private string svgContent; protected override async Task OnParametersSetAsync() diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index 900e35cc..4683095a 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -66,7 +66,7 @@
- + No projects found.
diff --git a/src/Angor/Client/Pages/Founder.razor b/src/Angor/Client/Pages/Founder.razor index 670d3e36..0b8f5839 100644 --- a/src/Angor/Client/Pages/Founder.razor +++ b/src/Angor/Client/Pages/Founder.razor @@ -53,7 +53,7 @@
- + No projects found.
diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index 4df12406..e41d0ea9 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -67,7 +67,7 @@
- + You already invested. @@ -84,7 +84,7 @@
- + You are the founder. @@ -101,7 +101,7 @@
- + The project was not found. @@ -180,7 +180,9 @@ @if (showCreateModal) { - @@ -217,7 +217,7 @@ @@ -245,7 +245,7 @@
@@ -293,7 +293,7 @@ @@ -359,7 +359,7 @@
@if (showWipeallModal) { diff --git a/src/Angor/Client/Pages/Signatures.razor b/src/Angor/Client/Pages/Signatures.razor index d94e7b0a..373fc319 100644 --- a/src/Angor/Client/Pages/Signatures.razor +++ b/src/Angor/Client/Pages/Signatures.razor @@ -59,7 +59,7 @@
- + No pending signatures yet...
diff --git a/src/Angor/Client/Pages/View.razor b/src/Angor/Client/Pages/View.razor index f3033267..ee8ff572 100644 --- a/src/Angor/Client/Pages/View.razor +++ b/src/Angor/Client/Pages/View.razor @@ -41,7 +41,7 @@
- + @error
diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 2f66096f..1542ac43 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -59,7 +59,7 @@
- + No Wallet Found
@@ -118,7 +118,7 @@
+
@@ -620,21 +632,7 @@ else @addressInfo.Address - @{ - // Attempt to get the fiat value from the dictionary - if (fiatValues.TryGetValue(addressInfo.Address, out var fiatValue)) - { - - } - else - { - // Handle missing fiat value - Loading... - } - } + @Money.Satoshis(total).ToUnit(MoneyUnit.BTC) @network.CoinTicker @addressInfo.HdPath @count diff --git a/src/Angor/Client/Shared/NotificationComponent.razor b/src/Angor/Client/Shared/NotificationComponent.razor index 7eab4ef0..8e026005 100644 --- a/src/Angor/Client/Shared/NotificationComponent.razor +++ b/src/Angor/Client/Shared/NotificationComponent.razor @@ -49,7 +49,7 @@ Error
- +
+
+
+ @foreach (var project in projects) + { + Stats.TryGetValue(project.ProjectInfo.ProjectIdentifier, out var stats); + var nostrPubKey = project.ProjectInfo.NostrPubKey; + investmentRequestsMap.TryGetValue(nostrPubKey, out bool hasInvestmentRequests); + +
+ + + + + + - -
-
- - - - - - - - - - - - - - - - - - @foreach (var project in projects) - { - Stats.TryGetValue(project.ProjectInfo.ProjectIdentifier, out var stats); - var nostrPubKey = project.ProjectInfo.NostrPubKey; - investmentRequestsMap.TryGetValue(nostrPubKey, out bool hasInvestmentRequests); - - - - - - - - - - - - - - } - -
NameFunding Target (@network.CoinTicker)Raised (@network.CoinTicker)Raised (% Target)Project StatusMy Investment (@network.CoinTicker)Spent by FounderAvailable to FounderIn RecoveryFounder Approval
- @project.Metadata?.Name - @project.ProjectInfo.TargetAmount @network.CoinTicker@Money.Satoshis(stats?.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker@((stats?.AmountInvested ?? 0) * 100 / Money.Coins(project.ProjectInfo.TargetAmount).Satoshi) % - @if (project.ProjectInfo.StartDate < DateTime.UtcNow) - { - Funding - } - else - { - Live - } - - @Money.Satoshis(project.AmountInvested ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker - @if (!project.SignaturesInfo?.Signatures.Any() ?? false) - { - - } - --@Money.Satoshis(project.AmountInRecovery ?? 0).ToUnit(MoneyUnit.BTC) @network.CoinTicker - @if (hasInvestmentRequests) - { - Approved - } - else - { - Pending - } -
-
- + }
+ +
diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index fb5ee256..feaf71d9 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -51,278 +51,283 @@
-
- - - -

- Transaction ID: @StageInfo.Trxid | - View on explorer -

- -

Total funds to recover = @StageInfo.TotalSpendable @network.CoinTicker

- - @if (trxNotFound) - { -

Transaction was not found it may still be confirming

- } +
- @if (firstTimeRefreshSpinner && refreshSpinner) - { -
-
-
- } - else - { -
-
- @if (!refreshSpinner) - { - @if (StageInfo.CanRelease) - { -

Total funds in penalty = @StageInfo.TotalInPenalty @network.CoinTicker

- } +
- @if (StageInfo.CanRecover && !StageInfo.EndOfProject) - { - - } - @if (StageInfo.CanRelease) - { - - } +

+ Transaction ID: @StageInfo.Trxid | + View on explorer +

- @if (StageInfo.EndOfProject && StageInfo.TotalSpendable > 0) - { - - } - } -
+

Total funds to recover = @StageInfo.TotalSpendable @network.CoinTicker

-
- + } + + @if (StageInfo.CanRelease) + { + + } + + @if (StageInfo.EndOfProject && StageInfo.TotalSpendable > 0) + { + + } } - -
-
- } - -
-
- - - - - - - - - - - @foreach (var item in StageInfo.Items) - { - - - - - - } - -
Stage AmountStatus
@(item.StageIndex + 1)@item.Amount @network.CoinTicker - @if (item.IsSpent) + + +
+
+ +
+
+ } - @if (showRecoveryModal) - { -