From 441ec609f15a650a828f2fd521de11dc0280fd54 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:35:04 +0200 Subject: [PATCH 01/27] feat: add three new variables, style svgs sizes, separate stats, create hook --- web/src/assets/svgs/icons/ethereum.svg | 2 +- .../svgs/icons/law-balance-with-pnk.svg | 3 + web/src/assets/svgs/icons/law-balance.svg | 2 +- web/src/assets/svgs/icons/min-stake.svg | 2 +- web/src/assets/svgs/icons/pnk.svg | 2 +- .../assets/svgs/icons/redistributed-pnk.svg | 2 +- web/src/assets/svgs/icons/rewards-per-pnk.svg | 3 + web/src/assets/svgs/icons/user.svg | 2 +- web/src/assets/svgs/icons/vote-stake.svg | 2 +- web/src/assets/svgs/icons/votes-per-pnk.svg | 3 + web/src/components/StatDisplay.tsx | 2 - web/src/hooks/queries/useCourtAllTimeQuery.ts | 120 +++++++++++++++ web/src/hooks/queries/useCourtDetails.ts | 2 + .../hooks/queries/useHomePageBlockQuery.ts | 142 ++++++++++++++++++ web/src/pages/Courts/CourtDetails/Stats.tsx | 118 +++++++++++++-- 15 files changed, 384 insertions(+), 23 deletions(-) create mode 100644 web/src/assets/svgs/icons/law-balance-with-pnk.svg create mode 100644 web/src/assets/svgs/icons/rewards-per-pnk.svg create mode 100644 web/src/assets/svgs/icons/votes-per-pnk.svg create mode 100644 web/src/hooks/queries/useCourtAllTimeQuery.ts create mode 100644 web/src/hooks/queries/useHomePageBlockQuery.ts diff --git a/web/src/assets/svgs/icons/ethereum.svg b/web/src/assets/svgs/icons/ethereum.svg index c570a3c65..7c5b0ec47 100644 --- a/web/src/assets/svgs/icons/ethereum.svg +++ b/web/src/assets/svgs/icons/ethereum.svg @@ -1,3 +1,3 @@ -<svg viewBox="0 0 13 22" xmlns="http://www.w3.org/2000/svg"> +<svg width="32" height="32" viewBox="0 0 13 22" xmlns="http://www.w3.org/2000/svg"> <path d="M12.6666 11.1969L6.53378 15.0031L0.396973 11.1969L6.53378 0.5L12.6666 11.1969ZM6.53378 16.2254L0.396973 12.4191L6.53378 21.5L12.6706 12.4191L6.53378 16.2254V16.2254Z" /> </svg> diff --git a/web/src/assets/svgs/icons/law-balance-with-pnk.svg b/web/src/assets/svgs/icons/law-balance-with-pnk.svg new file mode 100644 index 000000000..99a844905 --- /dev/null +++ b/web/src/assets/svgs/icons/law-balance-with-pnk.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 49 48" fill="none"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M28.2488 16.6644C28.2488 17.2258 28.1235 17.5488 27.9206 17.7316L33.5277 19.4222C33.6813 19.2884 33.8812 19.2068 34.1003 19.2068C34.5843 19.2068 34.9767 19.6018 34.9767 20.0889C34.9767 20.443 34.7689 20.7476 34.4698 20.8879L36.4321 26.5919C37.0543 26.6919 37.4656 26.8394 37.4656 27.0344C37.4656 27.8347 35.9147 28.4834 34.0017 28.4834C32.0886 28.4834 30.5377 27.8347 30.5377 27.0344C30.5377 26.8271 31.0023 26.6736 31.6908 26.5736L33.6597 20.8505C33.479 20.7443 33.3396 20.5752 33.2712 20.3726C33.2051 20.095 33.0512 19.9198 32.8096 19.8469C32.5208 19.7599 32.2379 19.75 31.9221 19.739C31.523 19.7251 31.0713 19.7094 30.4886 19.5337L27.6398 18.6748V24.5416C27.8617 24.7016 28.0067 24.9627 28.0067 25.2584C28.0067 25.4545 27.9424 25.6351 27.8348 25.7816C28.1241 25.9984 28.3124 26.3444 28.3124 26.7354C28.3124 27.2087 28.0373 27.6161 27.6398 27.8078V29.0326C28.9335 29.1008 29.9191 29.4532 29.9191 29.8777L29.9189 29.8864L29.9182 29.8992C32.0064 30.081 33.4481 30.4683 33.4481 30.9168L33.4584 31.5425C33.4584 32.1657 30.6804 32.6656 27.2474 32.6656C23.8145 32.6656 21.0366 32.1554 21.0366 31.5323L21.0162 30.9168C21.0162 30.4755 22.4123 30.0934 24.4465 29.908C24.4454 29.898 24.4448 29.8878 24.4448 29.8777C24.4448 29.4601 25.3987 29.1123 26.6615 29.0362V27.8275C26.242 27.6448 25.9483 27.2248 25.9483 26.7355C25.9483 26.3445 26.1365 25.9985 26.4259 25.7816C26.3184 25.6351 26.254 25.4546 26.254 25.2584C26.254 24.945 26.4169 24.6705 26.6615 24.514V18.3799L23.4126 17.4003C23.1964 17.3351 22.8799 17.162 22.5418 16.977C22.1539 16.7648 21.7374 16.537 21.4111 16.4386C21.185 16.3704 20.9529 16.4199 20.7149 16.5869C20.669 16.6246 20.6192 16.6574 20.5663 16.6853L22.5244 22.3771C23.1755 22.4771 23.6098 22.6273 23.6098 22.8277C23.6098 23.628 22.0589 24.2768 20.1458 24.2768C18.2327 24.2768 16.6819 23.628 16.6819 22.8277C16.6819 22.6333 17.0906 22.4862 17.7094 22.3862L19.6848 16.6442C19.4438 16.487 19.284 16.2145 19.284 15.9039C19.284 15.4168 19.6763 15.0218 20.1603 15.0218C20.5546 15.0218 20.8879 15.2839 20.998 15.6445L26.0335 17.1627C26.0059 17.0221 25.9911 16.8573 25.9911 16.6644C25.9911 15.9129 26.3674 15.0247 27.1199 14C27.8725 15.0247 28.2488 15.9129 28.2488 16.6644ZM34.2965 20.9479L36.2278 26.5618C35.0091 26.3969 33.1526 26.3918 31.8943 26.5464L33.8276 20.9268C33.9135 20.9551 34.0051 20.9709 34.1004 20.9709C34.1679 20.9709 34.2334 20.9625 34.2965 20.9479ZM17.9139 22.3559L19.85 16.728C19.9466 16.7649 20.0509 16.786 20.1603 16.786C20.2418 16.786 20.3204 16.7739 20.3952 16.753L22.3201 22.3482C21.0596 22.1847 19.1542 22.1873 17.9139 22.3559ZM18.0533 26.8646L14.0308 26.6875L11.6819 29.5699L13.3399 33.4323L17.3843 33.6875L19.6819 30.4295L18.0533 26.8646ZM14.0646 29.9972L16.9799 28.7424L16.557 31.8957L14.0646 29.9972ZM14.3126 27.1253L16.6423 28.1136L13.8232 29.2032L14.3126 27.1253ZM13.5198 30.5921L16.0518 32.3733L13.5811 33.0058L13.5198 30.5921ZM17.552 28.6967L19.2775 30.4216L17.0966 32.0136L17.552 28.6967ZM14.5101 33.3112L16.6455 32.7613L17.0213 33.4696L14.5101 33.3112ZM17.071 32.576L18.9403 31.2042L17.4425 33.2912L17.071 32.576ZM17.693 28.097L18.0561 27.3627L19.0444 29.4503L17.693 28.097ZM15.2307 26.9164L17.6038 27.0113L17.1882 27.7599L15.2307 26.9164ZM12.0221 29.4186L13.6843 27.3509L13.1334 29.6505L12.0221 29.4186ZM11.9498 29.8232L13.0664 30.0562L13.1149 32.5275L11.9498 29.8232Z"/> +</svg> \ No newline at end of file diff --git a/web/src/assets/svgs/icons/law-balance.svg b/web/src/assets/svgs/icons/law-balance.svg index 2b8dff341..696375f30 100644 --- a/web/src/assets/svgs/icons/law-balance.svg +++ b/web/src/assets/svgs/icons/law-balance.svg @@ -1,4 +1,4 @@ -<svg viewBox="0 0 14 15" xmlns="http://www.w3.org/2000/svg"> +<svg height="32" width="32" viewBox="0 0 14 15" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_12054_167323)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.79156 3.15129C7.79156 3.52555 7.70712 3.74087 7.57048 3.86274L11.3474 4.98979C11.4509 4.90059 11.5856 4.84623 11.7332 4.84623C12.0592 4.84623 12.3235 5.10951 12.3235 5.43429C12.3235 5.67037 12.1835 5.87337 11.982 5.96696L13.3039 9.76961C13.723 9.83625 14 9.93458 14 10.0646C14 10.5981 12.9553 11.0306 11.6667 11.0306C10.378 11.0306 9.33337 10.5981 9.33337 10.0646C9.33337 9.92642 9.64629 9.82404 10.1101 9.75743L11.4363 5.94197C11.3146 5.8712 11.2207 5.75845 11.1747 5.62343C11.1301 5.43834 11.0265 5.32151 10.8637 5.27295C10.6692 5.2149 10.4786 5.20833 10.2659 5.201C9.99706 5.19174 9.6928 5.18126 9.30029 5.06414L7.38131 4.49154V8.40273C7.53077 8.5094 7.62846 8.68344 7.62846 8.88059C7.62846 9.01134 7.58514 9.13172 7.51265 9.22937C7.70758 9.37395 7.83439 9.60457 7.83439 9.86526C7.83439 10.1808 7.64906 10.4524 7.38131 10.5802V11.3968C8.25276 11.4422 8.91665 11.6771 8.91665 11.9601C8.91665 11.9649 8.91645 11.9697 8.91607 11.9744C10.3227 12.0957 11.2938 12.3539 11.2938 12.6529L11.3007 13.07C11.3007 13.4854 9.42951 13.8188 7.11703 13.8188C4.80456 13.8188 2.93338 13.4786 2.93338 13.0632L2.91966 12.6529C2.91966 12.3587 3.86005 12.104 5.2303 11.9804C5.22955 11.9736 5.22916 11.9669 5.22916 11.9601C5.22916 11.6817 5.87174 11.4498 6.72236 11.3991V10.5934C6.43979 10.4715 6.2419 10.1916 6.2419 9.86531C6.2419 9.60465 6.36871 9.37399 6.56364 9.22941C6.49119 9.13176 6.44782 9.01142 6.44782 8.88063C6.44782 8.67167 6.55754 8.48864 6.72236 8.38435V4.29493L4.53389 3.64186C4.38825 3.5984 4.17506 3.48298 3.94728 3.35966L3.94727 3.35966L3.94727 3.35966L3.94726 3.35966L3.94726 3.35966C3.68597 3.2182 3.40547 3.06634 3.18566 3.00075C3.03336 2.9553 2.87703 2.98824 2.71666 3.09957C2.68578 3.12472 2.65221 3.14659 2.61659 3.16518L3.93556 6.95973C4.37412 7.02642 4.66664 7.12656 4.66664 7.26014C4.66664 7.79367 3.62198 8.22618 2.33332 8.22618C1.04466 8.22618 0 7.79367 0 7.26014C0 7.13055 0.275299 7.03244 0.692166 6.9658L2.02279 3.13777C1.86048 3.03298 1.75282 2.85131 1.75282 2.64428C1.75282 2.3195 2.0171 2.05622 2.34312 2.05622C2.60872 2.05622 2.83322 2.23096 2.90739 2.47131L6.29933 3.48345C6.2807 3.38972 6.27073 3.27989 6.27073 3.15129C6.27073 2.65026 6.5242 2.05816 7.03114 1.375C7.53808 2.05816 7.79156 2.65026 7.79156 3.15129ZM11.8653 6.00696L13.1662 9.74952C12.3453 9.63958 11.0947 9.63618 10.2472 9.73929L11.5495 5.99285C11.6073 6.01172 11.669 6.02227 11.7332 6.02227C11.7787 6.02227 11.8228 6.01665 11.8653 6.00696ZM0.829894 6.94559L2.13407 3.19369C2.19914 3.21827 2.2694 3.23234 2.34312 3.23234C2.39802 3.23234 2.45094 3.2243 2.50135 3.21035L3.79797 6.9405C2.9489 6.83147 1.66543 6.83317 0.829894 6.94559Z" /> </g> diff --git a/web/src/assets/svgs/icons/min-stake.svg b/web/src/assets/svgs/icons/min-stake.svg index 52f8f5a71..f32930b31 100644 --- a/web/src/assets/svgs/icons/min-stake.svg +++ b/web/src/assets/svgs/icons/min-stake.svg @@ -1,4 +1,4 @@ -<svg viewBox="10 9 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> +<svg width="32" height="32" viewBox="10 9 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_14856_32616)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M19.6025 9.3125L30.0528 9.78456L34.2837 19.285L28.3147 27.9678L17.8076 27.2878L13.5 16.9941L19.6025 9.3125ZM27.264 14.7888L19.6903 18.1329L26.1653 23.1925L27.264 14.7888ZM26.3871 13.1131L20.3345 10.4793L19.063 16.0169L26.3871 13.1131ZM24.853 24.4655L18.2749 19.7185L18.4341 26.1509L24.853 24.4655ZM33.233 19.264L28.7504 14.6671L27.5673 23.5068L33.233 19.264ZM26.3952 25.4994L20.8475 26.9648L27.3716 27.387L26.3952 25.4994ZM32.357 21.3497L27.5008 25.0057L28.4657 26.9115L32.357 21.3497ZM30.06 11.112L29.1168 13.0688L32.6276 16.6755L30.06 11.112ZM28.8848 10.1755L22.7197 9.92262L27.8052 12.1706L28.8848 10.1755ZM18.7022 11.0805L14.384 16.5911L17.2711 17.2091L18.7022 11.0805ZM17.097 18.2901L14.196 17.6694L17.2229 24.8762L17.097 18.2901Z"/> </g> diff --git a/web/src/assets/svgs/icons/pnk.svg b/web/src/assets/svgs/icons/pnk.svg index d4bc0f959..ba8427141 100644 --- a/web/src/assets/svgs/icons/pnk.svg +++ b/web/src/assets/svgs/icons/pnk.svg @@ -1,4 +1,4 @@ -<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<svg height="32" width="32" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_13719_38976)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M6.60249 1.8125L17.0528 2.28456L21.2837 11.785L15.3147 20.4678L4.80756 19.7878L0.5 9.49409L6.60249 1.8125ZM14.264 7.28878L6.69025 10.6329L13.1653 15.6925L14.264 7.28878ZM13.3871 5.61306L7.33447 2.97929L6.06304 8.51691L13.3871 5.61306ZM11.853 16.9655L5.27494 12.2185L5.43405 18.6509L11.853 16.9655ZM20.233 11.764L15.7504 7.1671L14.5673 16.0068L20.233 11.764ZM13.3952 17.9994L7.84753 19.4648L14.3716 19.887L13.3952 17.9994ZM19.357 13.8497L14.5008 17.5057L15.4657 19.4115L19.357 13.8497ZM17.06 3.61196L16.1168 5.56883L19.6276 9.1755L17.06 3.61196ZM15.8848 2.67553L9.71972 2.42262L14.8052 4.6706L15.8848 2.67553ZM5.7022 3.58046L1.38398 9.09109L4.27107 9.7091L5.7022 3.58046ZM4.09698 10.7901L1.19598 10.1694L4.22295 17.3762L4.09698 10.7901Z" /> </g> diff --git a/web/src/assets/svgs/icons/redistributed-pnk.svg b/web/src/assets/svgs/icons/redistributed-pnk.svg index 2d7ac1f79..d02975112 100644 --- a/web/src/assets/svgs/icons/redistributed-pnk.svg +++ b/web/src/assets/svgs/icons/redistributed-pnk.svg @@ -1,4 +1,4 @@ -<svg viewBox="6 6 36 36" xmlns="http://www.w3.org/2000/svg"> +<svg width="32" height="32" viewBox="6 6 36 36" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_13762_38788)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M19.6025 14.8125L30.0528 15.2846L34.2837 24.785L28.3147 33.4678L17.8076 32.7878L13.5 22.4941L19.6025 14.8125ZM27.264 20.2888L19.6903 23.6329L26.1653 28.6925L27.264 20.2888ZM26.3871 18.6131L20.3345 15.9793L19.063 21.5169L26.3871 18.6131ZM24.853 29.9655L18.2749 25.2185L18.4341 31.6509L24.853 29.9655ZM33.233 24.764L28.7504 20.1671L27.5673 29.0068L33.233 24.764ZM26.3952 30.9994L20.8475 32.4648L27.3716 32.887L26.3952 30.9994ZM32.357 26.8497L27.5008 30.5057L28.4657 32.4115L32.357 26.8497ZM30.06 16.612L29.1168 18.5688L32.6276 22.1755L30.06 16.612ZM28.8848 15.6755L22.7197 15.4226L27.8052 17.6706L28.8848 15.6755ZM18.7022 16.5805L14.384 22.0911L17.2711 22.7091L18.7022 16.5805ZM17.097 23.7901L14.196 23.1694L17.2229 30.3762L17.097 23.7901Z" /> </g> diff --git a/web/src/assets/svgs/icons/rewards-per-pnk.svg b/web/src/assets/svgs/icons/rewards-per-pnk.svg new file mode 100644 index 000000000..f0808ea2e --- /dev/null +++ b/web/src/assets/svgs/icons/rewards-per-pnk.svg @@ -0,0 +1,3 @@ +<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M14 29.6236V31.375C14 32.8229 17.5273 34 21.875 34C26.2227 34 29.75 32.8229 29.75 31.375V29.6236C28.0561 30.8172 24.9594 31.375 21.875 31.375C18.7906 31.375 15.6939 30.8172 14 29.6236ZM27.125 18.25C31.4727 18.25 35 17.0729 35 15.625C35 14.1771 31.4727 13 27.125 13C22.7773 13 19.25 14.1771 19.25 15.625C19.25 17.0729 22.7773 18.25 27.125 18.25ZM14 25.3211V27.4375C14 28.8854 17.5273 30.0625 21.875 30.0625C26.2227 30.0625 29.75 28.8854 29.75 27.4375V25.3211C28.0561 26.7156 24.9553 27.4375 21.875 27.4375C18.7947 27.4375 15.6939 26.7156 14 25.3211ZM31.0625 25.7723C33.4127 25.317 35 24.4721 35 23.5V21.7486C34.0484 22.4213 32.6498 22.8807 31.0625 23.1637V25.7723ZM21.875 19.5625C17.5273 19.5625 14 21.0309 14 22.8438C14 24.6566 17.5273 26.125 21.875 26.125C26.2227 26.125 29.75 24.6566 29.75 22.8438C29.75 21.0309 26.2227 19.5625 21.875 19.5625ZM30.8697 21.8717C33.3307 21.4287 35 20.5592 35 19.5625V17.8111C33.5439 18.8406 31.042 19.3943 28.4088 19.5256C29.6187 20.1121 30.5088 20.8996 30.8697 21.8717Z"/> +</svg> diff --git a/web/src/assets/svgs/icons/user.svg b/web/src/assets/svgs/icons/user.svg index 23dc8988b..f20f6f875 100644 --- a/web/src/assets/svgs/icons/user.svg +++ b/web/src/assets/svgs/icons/user.svg @@ -1,3 +1,3 @@ -<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<svg height="24" width="24" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> <path d="M10.8919 12.3125C14.119 12.3125 16.7373 9.66699 16.7373 6.40625C16.7373 3.14551 14.119 0.5 10.8919 0.5C7.6647 0.5 5.04644 3.14551 5.04644 6.40625C5.04644 9.66699 7.6647 12.3125 10.8919 12.3125ZM16.0878 13.625H13.8511C12.9499 14.0434 11.9473 14.2812 10.8919 14.2812C9.83643 14.2812 8.83784 14.0434 7.93261 13.625H5.69593C2.82599 13.625 0.5 15.9752 0.5 18.875V19.5312C0.5 20.6182 1.37275 21.5 2.44847 21.5H19.3352C20.411 21.5 21.2837 20.6182 21.2837 19.5312V18.875C21.2837 15.9752 18.9577 13.625 16.0878 13.625Z" /> </svg> diff --git a/web/src/assets/svgs/icons/vote-stake.svg b/web/src/assets/svgs/icons/vote-stake.svg index 39ee831f0..2ee706e41 100644 --- a/web/src/assets/svgs/icons/vote-stake.svg +++ b/web/src/assets/svgs/icons/vote-stake.svg @@ -1,4 +1,4 @@ -<svg viewBox="10 9 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> +<svg width="32" height="32" viewBox="10 9 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_14856_32598)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M19.6025 9.3125L30.0528 9.78456L34.2837 19.285L28.3147 27.9678L17.8076 27.2878L13.5 16.9941L19.6025 9.3125ZM27.264 14.7888L19.6903 18.1329L26.1653 23.1925L27.264 14.7888ZM26.3871 13.1131L20.3345 10.4793L19.063 16.0169L26.3871 13.1131ZM24.853 24.4655L18.2749 19.7185L18.4341 26.1509L24.853 24.4655ZM33.233 19.264L28.7504 14.6671L27.5673 23.5068L33.233 19.264ZM26.3952 25.4994L20.8475 26.9648L27.3716 27.387L26.3952 25.4994ZM32.357 21.3497L27.5008 25.0057L28.4657 26.9115L32.357 21.3497ZM30.06 11.112L29.1168 13.0688L32.6276 16.6755L30.06 11.112ZM28.8848 10.1755L22.7197 9.92262L27.8052 12.1706L28.8848 10.1755ZM18.7022 11.0805L14.384 16.5911L17.2711 17.2091L18.7022 11.0805ZM17.097 18.2901L14.196 17.6694L17.2229 24.8762L17.097 18.2901Z"/> </g> diff --git a/web/src/assets/svgs/icons/votes-per-pnk.svg b/web/src/assets/svgs/icons/votes-per-pnk.svg new file mode 100644 index 000000000..7c859a439 --- /dev/null +++ b/web/src/assets/svgs/icons/votes-per-pnk.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 49 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1196 11L29.5699 11.4721L33.8008 20.9725L27.8318 29.6553L17.3247 28.9753L13.0171 18.6816L19.1196 11ZM26.781 16.4763L19.2073 19.8204L25.6824 24.88L26.781 16.4763ZM25.9042 14.8006L19.8516 12.1668L18.5801 17.7044L25.9042 14.8006ZM24.3701 26.153L17.792 21.406L17.9511 27.8384L24.3701 26.153ZM32.7501 20.9515L28.2675 16.3546L27.0844 25.1943L32.7501 20.9515ZM25.9123 27.1869L20.3646 28.6523L26.8887 29.0745L25.9123 27.1869ZM31.8741 23.0372L27.0179 26.6932L27.9828 28.599L31.8741 23.0372ZM29.5771 12.7995L28.6339 14.7563L32.1447 18.363L29.5771 12.7995ZM28.4019 11.863L22.2368 11.6101L27.3223 13.8581L28.4019 11.863ZM18.2193 12.768L13.9011 18.2786L16.7882 18.8966L18.2193 12.768ZM16.6141 19.9776L13.7131 19.3569L16.74 26.5637L16.6141 19.9776ZM11.0171 30.1C11.0171 28.9406 11.9647 28 13.1326 28H13.8378V33.5999H33.5826V28H34.2878C35.4557 28 36.4033 28.9406 36.4033 30.1V34.2999C36.4033 35.4592 35.4557 36.3999 34.2878 36.3999H13.1326C11.9647 36.3999 11.0171 35.4592 11.0171 34.2999V30.1Z" fill="#B45FFF"/> +</svg> diff --git a/web/src/components/StatDisplay.tsx b/web/src/components/StatDisplay.tsx index 9c0e5a353..2cf5f36d3 100644 --- a/web/src/components/StatDisplay.tsx +++ b/web/src/components/StatDisplay.tsx @@ -27,8 +27,6 @@ const SVGContainer = styled.div<{ iconColor: string; backgroundColor: string }>` justify-content: center; svg { fill: ${({ iconColor }) => iconColor}; - height: ${({ iconColor, theme }) => (iconColor === theme.success ? "24px" : "32px")}; - width: ${({ iconColor, theme }) => (iconColor === theme.success ? "24px" : "32px")}; } `; diff --git a/web/src/hooks/queries/useCourtAllTimeQuery.ts b/web/src/hooks/queries/useCourtAllTimeQuery.ts new file mode 100644 index 000000000..76336beb7 --- /dev/null +++ b/web/src/hooks/queries/useCourtAllTimeQuery.ts @@ -0,0 +1,120 @@ +import { useQuery } from "@tanstack/react-query"; + +import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { useMemo } from "react"; + +import { graphql } from "src/graphql"; +import { CourtAllTimeQuery } from "src/graphql/graphql"; +export type { CourtAllTimeQuery }; + +const courtAllTimeQuery = graphql(` + query CourtAllTime { + presentCourts: courts(orderBy: id, orderDirection: asc) { + id + parent { + id + } + name + numberDisputes + numberVotes + feeForJuror + stake + } + } +`); + +export const useCourtAllTimeQuery = () => { + const { graphqlBatcher } = useGraphqlBatcher(); + + const usedQuery = useQuery({ + queryKey: [`courtAllTimeQuery`], + staleTime: Infinity, + queryFn: async () => { + const data = await graphqlBatcher.fetch({ + id: crypto.randomUUID(), + document: courtAllTimeQuery, + }); + return data; + }, + }); + + const courtActivityStats = useMemo(() => { + if (usedQuery.data && !usedQuery.isFetching) { + // 1. court with most disputes + // we only iterate through past courts, since more courts might exist at the present + // these diffCourts have: average stakes, and dispute diff + const diffCourts = usedQuery.data.presentCourts.map((c) => ({ + ...c, + numberDisputes: c.numberDisputes, + treeNumberDisputes: c.numberDisputes, + numberVotes: c.numberVotes, + treeNumberVotes: c.numberVotes, + stake: c.stake, + })); + + const mostDisputedCourt = diffCourts.sort((a, b) => b.numberDisputes - a.numberDisputes)[0]; + // 2. biggest chances of getting drawn + // fact a: getting drawn in a parent court also subjects you to its rewards + // fact b: staking in children, stakes in parents. but subgraph at this date doesn't reflect this + // so, stakes trickle up, rewards/disputes trickle down + + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeNumberVotes = String(Number(parent.treeNumberVotes) + Number(child.treeNumberVotes)); + child.treeNumberDisputes = String(Number(parent.treeNumberDisputes) + Number(child.treeNumberDisputes)); + } + } + } + diffCourts.reverse(); + for (const child of diffCourts) { + for (const parent of diffCourts) { + if (parent.id === child.parent?.id) { + parent.stake = String(BigInt(parent.stake) + BigInt(child.stake)); + } + } + } + diffCourts.reverse(); + for (const c of diffCourts) { + c.votesPerPnk = Number(c.numberVotes) / (Number(c.stake) / 1e18); + c.treeVotesPerPnk = c.votesPerPnk; + c.disputesPerPnk = Number(c.numberDisputes) / (Number(c.stake) / 1e18); + c.treeDisputesPerPnk = c.disputesPerPnk; + } + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeVotesPerPnk += parent.votesPerPnk; + child.treeDisputesPerPnk += parent.disputesPerPnk; + } + } + } + const bestDrawingChancesCourt = diffCourts.sort((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0]; + // 3. expected reward + // since we isolated the exclusive disputes from the cumulative disputes + // we can calculate the "isolated reward" of every court + // after that's done, then just trickle the rewards down + + for (const c of diffCourts) { + c.expectedRewardPerPnk = c.votesPerPnk * c.feeForJuror; + c.treeExpectedRewardPerPnk = c.expectedRewardPerPnk; + } + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeExpectedRewardPerPnk = parent.treeExpectedRewardPerPnk + child.treeExpectedRewardPerPnk; + } + } + } + const bestExpectedRewardCourt = diffCourts.sort( + (a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk + )[0]; + + return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt, diffCourts }; + } else { + return undefined; + } + }, [usedQuery]); + + return courtActivityStats; +}; diff --git a/web/src/hooks/queries/useCourtDetails.ts b/web/src/hooks/queries/useCourtDetails.ts index ced8c10ab..6d355ac26 100644 --- a/web/src/hooks/queries/useCourtDetails.ts +++ b/web/src/hooks/queries/useCourtDetails.ts @@ -17,10 +17,12 @@ const courtDetailsQuery = graphql(` numberClosedDisputes numberAppealingDisputes numberStakedJurors + numberVotes stake paidETH paidPNK timesPerPeriod + feeForJuror } } `); diff --git a/web/src/hooks/queries/useHomePageBlockQuery.ts b/web/src/hooks/queries/useHomePageBlockQuery.ts new file mode 100644 index 000000000..4e27a749b --- /dev/null +++ b/web/src/hooks/queries/useHomePageBlockQuery.ts @@ -0,0 +1,142 @@ +import { useQuery } from "@tanstack/react-query"; + +import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { useMemo } from "react"; + +import { graphql } from "src/graphql"; +import { HomePageBlockQuery } from "src/graphql/graphql"; +export type { HomePageBlockQuery }; + +const homePageBlockQuery = graphql(` + query HomePageBlock($blockNumber: Int) { + presentCourts: courts(orderBy: id, orderDirection: asc) { + id + parent { + id + } + name + numberDisputes + numberVotes + feeForJuror + stake + } + pastCourts: courts(orderBy: id, orderDirection: asc, block: { number: $blockNumber }) { + id + parent { + id + } + name + numberDisputes + numberVotes + feeForJuror + stake + } + } +`); + +export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boolean) => { + const isEnabled = blockNumber !== null || allTime; + const { graphqlBatcher } = useGraphqlBatcher(); + + const usedQuery = useQuery({ + queryKey: [`homePageBlockQuery${blockNumber}-${allTime}`], + enabled: isEnabled, + staleTime: Infinity, + queryFn: async () => { + const data = await graphqlBatcher.fetch({ + id: crypto.randomUUID(), + document: homePageBlockQuery, + variables: { blockNumber }, + }); + return data; + }, + }); + + const courtActivityStats = useMemo(() => { + if (usedQuery.data && !usedQuery.isFetching) { + // 1. court with most disputes + // we only iterate through past courts, since more courts might exist at the present + // these diffCourts have: average stakes, and dispute diff + const diffCourts = allTime + ? usedQuery.data.presentCourts.map((c) => ({ + ...c, + numberDisputes: c.numberDisputes, + treeNumberDisputes: c.numberDisputes, + numberVotes: c.numberVotes, + treeNumberVotes: c.numberVotes, + stake: c.stake, + })) + : usedQuery.data.pastCourts.map((c, i) => ({ + ...c, + numberDisputes: usedQuery.data.presentCourts[i].numberDisputes - c.numberDisputes, + treeNumberDisputes: usedQuery.data.presentCourts[i].numberDisputes - c.numberDisputes, + numberVotes: usedQuery.data.presentCourts[i].numberVotes - c.numberVotes, + treeNumberVotes: usedQuery.data.presentCourts[i].numberVotes - c.numberVotes, + stake: (BigInt(usedQuery.data.presentCourts[i].stake) + BigInt(c.stake)) / 2n, + })); + const mostDisputedCourt = diffCourts.sort((a, b) => b.numberDisputes - a.numberDisputes)[0]; + // 2. biggest chances of getting drawn + // fact a: getting drawn in a parent court also subjects you to its rewards + // fact b: staking in children, stakes in parents. but subgraph at this date doesn't reflect this + // so, stakes trickle up, rewards/disputes trickle down + + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeNumberVotes = String(Number(parent.treeNumberVotes) + Number(child.treeNumberVotes)); + child.treeNumberDisputes = String(Number(parent.treeNumberDisputes) + Number(child.treeNumberDisputes)); + } + } + } + diffCourts.reverse(); + for (const child of diffCourts) { + for (const parent of diffCourts) { + if (parent.id === child.parent?.id) { + parent.stake = String(BigInt(parent.stake) + BigInt(child.stake)); + } + } + } + diffCourts.reverse(); + for (const c of diffCourts) { + c.votesPerPnk = Number(c.numberVotes) / (Number(c.stake) / 1e18); + c.treeVotesPerPnk = c.votesPerPnk; + c.disputesPerPnk = Number(c.numberDisputes) / (Number(c.stake) / 1e18); + c.treeDisputesPerPnk = c.disputesPerPnk; + } + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeVotesPerPnk += parent.votesPerPnk; + child.treeDisputesPerPnk += parent.disputesPerPnk; + } + } + } + const bestDrawingChancesCourt = diffCourts.sort((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0]; + // 3. expected reward + // since we isolated the exclusive disputes from the cumulative disputes + // we can calculate the "isolated reward" of every court + // after that's done, then just trickle the rewards down + + for (const c of diffCourts) { + c.expectedRewardPerPnk = c.votesPerPnk * c.feeForJuror; + c.treeExpectedRewardPerPnk = c.expectedRewardPerPnk; + } + for (const parent of diffCourts) { + for (const child of diffCourts) { + if (parent.id === child.parent?.id) { + child.treeExpectedRewardPerPnk = parent.treeExpectedRewardPerPnk + child.treeExpectedRewardPerPnk; + } + } + } + const bestExpectedRewardCourt = diffCourts.sort( + (a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk + )[0]; + + return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt }; + } else { + return undefined; + } + }, [usedQuery]); + + return courtActivityStats; +}; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index a5f0e8747..55d8a53e3 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -1,14 +1,17 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled, { css } from "styled-components"; import { useParams } from "react-router-dom"; import EthereumIcon from "svgs/icons/ethereum.svg"; import BalanceIcon from "svgs/icons/law-balance.svg"; +import BalanceWithPNKIcon from "svgs/icons/law-balance-with-pnk.svg"; import MinStake from "svgs/icons/min-stake.svg"; +import VotesPerPNKIcon from "svgs/icons/votes-per-pnk.svg"; import PNKIcon from "svgs/icons/pnk.svg"; import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg"; import VoteStake from "svgs/icons/vote-stake.svg"; +import RewardsPerPnk from "svgs/icons/rewards-per-pnk.svg"; import { CoinIds } from "consts/coingecko"; import { useCoinPrice } from "hooks/useCoinPrice"; @@ -23,6 +26,26 @@ import { responsiveSize } from "styles/responsiveSize"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; +// import { useHomePageBlockQuery } from "queries/useHomePageBlockQuery"; +import { useCourtAllTimeQuery } from "queries/useCourtAllTimeQuery"; +import { commify } from "utils/commify"; + +function beautifyStatNumber(value: number): string { + const absValue = Math.abs(value); + + if (absValue >= 1e9) { + return `${commify((value / 1e9).toFixed(1))}B`; + } else if (absValue >= 1e6) { + return `${commify((value / 1e6).toFixed(1))}M`; + } else if (absValue >= 1e3) { + return `${commify((value / 1e3).toFixed(1))}K`; + } else if (absValue < 1 && absValue !== 0) { + const inverseValue = 1 / absValue; + return `1 per ${commify(inverseValue.toFixed(0))}`; + } + + return commify(value.toFixed(0)); +} const StyledCard = styled.div` width: auto; @@ -116,26 +139,93 @@ const stats: IStat[] = [ }, ]; +interface IStats { + title: string; + coinId?: number; + getText: any; //(data: CourtDetailsQuery["court"]) => string; + getSubtext?: any; // (data: CourtDetailsQuery["court"], coinPrice?: number) => string; + color: IStatDisplay["color"]; + icon: React.FC<React.SVGAttributes<SVGElement>>; +} + +const timeframedStats: IStats[] = [ + { + title: "ETH Rewards per PNK Staked", + getText: (data) => { + const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; + return beautifyStatNumber(treeExpectedRewardPerPnk / 1e18); + }, + color: "orange", + icon: RewardsPerPnk, + }, + { + title: "Cases per PNK Staked", + getText: (data) => { + const treeDisputesPerPnk = data?.treeDisputesPerPnk; + return beautifyStatNumber(treeDisputesPerPnk); + }, + color: "orange", + icon: BalanceWithPNKIcon, + }, + { + title: "Votes per PNK Staked", + getText: (data) => { + const treeVotesPerPnk = data?.treeVotesPerPnk; + return beautifyStatNumber(treeVotesPerPnk); + }, + color: "orange", + icon: VotesPerPNKIcon, + }, +]; + const Stats = () => { const { id } = useParams(); const { data } = useCourtDetails(id); + const courtData = useCourtAllTimeQuery(); const coinIds = [CoinIds.PNK, CoinIds.ETH]; const { prices: pricesData } = useCoinPrice(coinIds); + const foundCourt = useMemo(() => { + if (courtData?.diffCourts) { + const foundCourt = courtData?.diffCourts.find((c) => c.id === id); + console.log({ foundCourt }); + return foundCourt; + } else { + console.log("Court not found or diffCourts not available"); + return undefined; + } + }, [courtData, id]); + return ( - <StyledCard> - {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - <StatDisplay - key={i} - {...{ title, color, icon }} - text={data ? getText(data.court) : <StyledSkeleton />} - subtext={calculateSubtextRender(data ? data.court : undefined, getSubtext, coinPrice)} - /> - ); - })} - </StyledCard> + <> + <StyledCard> + {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + <StatDisplay + key={i} + {...{ title, color, icon }} + text={data ? getText(data.court) : <StyledSkeleton />} + subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} + /> + ); + })} + </StyledCard> + <hr /> + <StyledCard> + {timeframedStats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + <StatDisplay + key={i} + {...{ title, color, icon }} + text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} + subtext={calculateSubtextRender(foundCourt, getSubtext, coinPrice)} + /> + ); + })} + </StyledCard> + </> ); }; From b82ea0df9d12c3f6ec0a051ed5f86e6cb2c16a96 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:39:46 +0200 Subject: [PATCH 02/27] feat: refactor stat names, values, add homeblock query, add chart icon, add time selector, style --- web/src/assets/svgs/icons/chart.svg | 15 +++ web/src/consts/averageBlockTimeInSeconds.ts | 3 + web/src/hooks/queries/useCourtAllTimeQuery.ts | 120 ----------------- .../hooks/queries/useHomePageBlockQuery.ts | 121 +++++++++++------- .../hooks/queries/useHomePageExtraStats.ts | 21 +++ web/src/pages/Courts/CourtDetails/Stats.tsx | 112 ++++++++++++---- web/src/utils/calculateSubtextRender.tsx | 4 +- 7 files changed, 205 insertions(+), 191 deletions(-) create mode 100644 web/src/assets/svgs/icons/chart.svg create mode 100644 web/src/consts/averageBlockTimeInSeconds.ts delete mode 100644 web/src/hooks/queries/useCourtAllTimeQuery.ts create mode 100644 web/src/hooks/queries/useHomePageExtraStats.ts diff --git a/web/src/assets/svgs/icons/chart.svg b/web/src/assets/svgs/icons/chart.svg new file mode 100644 index 000000000..ddb1787bb --- /dev/null +++ b/web/src/assets/svgs/icons/chart.svg @@ -0,0 +1,15 @@ +<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_21837_70275)"> +<g clip-path="url(#clip1_21837_70275)"> +<path d="M11.3344 10.125H11.9469C12.1219 10.125 12.2969 9.95 12.2969 9.775V3.475C12.2969 3.3 12.1219 3.125 11.9469 3.125H11.3344C11.1594 3.125 10.9844 3.3 10.9844 3.475V9.775C10.9844 9.95 11.1594 10.125 11.3344 10.125ZM6.08437 10.125H6.69688C6.87187 10.125 7.04688 9.95 7.04688 9.775V4.35C7.04688 4.175 6.87187 4 6.69688 4H6.08437C5.90938 4 5.73438 4.175 5.73438 4.35V9.775C5.73438 9.95 5.90938 10.125 6.08437 10.125ZM8.70937 10.125H9.32188C9.49688 10.125 9.67188 9.95 9.67188 9.775V6.1C9.67188 5.925 9.49688 5.75 9.32188 5.75H8.70937C8.53437 5.75 8.35938 5.925 8.35938 6.1V9.775C8.35938 9.95 8.53437 10.125 8.70937 10.125ZM14.0469 11.4375H1.79688V2.6875C1.79688 2.44578 1.60109 2.25 1.35938 2.25H0.921875C0.680156 2.25 0.484375 2.44578 0.484375 2.6875V11.875C0.484375 12.3582 0.876211 12.75 1.35938 12.75H14.0469C14.2886 12.75 14.4844 12.5542 14.4844 12.3125V11.875C14.4844 11.6333 14.2886 11.4375 14.0469 11.4375ZM3.45938 10.125H4.07187C4.24687 10.125 4.42188 9.95 4.42188 9.775V7.85C4.42188 7.675 4.24687 7.5 4.07187 7.5H3.45938C3.28438 7.5 3.10938 7.675 3.10938 7.85V9.775C3.10938 9.95 3.28438 10.125 3.45938 10.125Z"/> +</g> +</g> +<defs> +<clipPath id="clip0_21837_70275"> +<rect width="14" height="14" fill="white" transform="translate(0.484375 0.5)"/> +</clipPath> +<clipPath id="clip1_21837_70275"> +<rect width="14" height="14" fill="white" transform="translate(0.484375 0.5)"/> +</clipPath> +</defs> +</svg> diff --git a/web/src/consts/averageBlockTimeInSeconds.ts b/web/src/consts/averageBlockTimeInSeconds.ts new file mode 100644 index 000000000..a3750bd8a --- /dev/null +++ b/web/src/consts/averageBlockTimeInSeconds.ts @@ -0,0 +1,3 @@ +import { arbitrum, arbitrumSepolia } from "viem/chains"; + +export const averageBlockTimeInSeconds = { [arbitrum.id]: 0.26, [arbitrumSepolia.id]: 0.268 }; diff --git a/web/src/hooks/queries/useCourtAllTimeQuery.ts b/web/src/hooks/queries/useCourtAllTimeQuery.ts deleted file mode 100644 index 76336beb7..000000000 --- a/web/src/hooks/queries/useCourtAllTimeQuery.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { useGraphqlBatcher } from "context/GraphqlBatcher"; -import { useMemo } from "react"; - -import { graphql } from "src/graphql"; -import { CourtAllTimeQuery } from "src/graphql/graphql"; -export type { CourtAllTimeQuery }; - -const courtAllTimeQuery = graphql(` - query CourtAllTime { - presentCourts: courts(orderBy: id, orderDirection: asc) { - id - parent { - id - } - name - numberDisputes - numberVotes - feeForJuror - stake - } - } -`); - -export const useCourtAllTimeQuery = () => { - const { graphqlBatcher } = useGraphqlBatcher(); - - const usedQuery = useQuery({ - queryKey: [`courtAllTimeQuery`], - staleTime: Infinity, - queryFn: async () => { - const data = await graphqlBatcher.fetch({ - id: crypto.randomUUID(), - document: courtAllTimeQuery, - }); - return data; - }, - }); - - const courtActivityStats = useMemo(() => { - if (usedQuery.data && !usedQuery.isFetching) { - // 1. court with most disputes - // we only iterate through past courts, since more courts might exist at the present - // these diffCourts have: average stakes, and dispute diff - const diffCourts = usedQuery.data.presentCourts.map((c) => ({ - ...c, - numberDisputes: c.numberDisputes, - treeNumberDisputes: c.numberDisputes, - numberVotes: c.numberVotes, - treeNumberVotes: c.numberVotes, - stake: c.stake, - })); - - const mostDisputedCourt = diffCourts.sort((a, b) => b.numberDisputes - a.numberDisputes)[0]; - // 2. biggest chances of getting drawn - // fact a: getting drawn in a parent court also subjects you to its rewards - // fact b: staking in children, stakes in parents. but subgraph at this date doesn't reflect this - // so, stakes trickle up, rewards/disputes trickle down - - for (const parent of diffCourts) { - for (const child of diffCourts) { - if (parent.id === child.parent?.id) { - child.treeNumberVotes = String(Number(parent.treeNumberVotes) + Number(child.treeNumberVotes)); - child.treeNumberDisputes = String(Number(parent.treeNumberDisputes) + Number(child.treeNumberDisputes)); - } - } - } - diffCourts.reverse(); - for (const child of diffCourts) { - for (const parent of diffCourts) { - if (parent.id === child.parent?.id) { - parent.stake = String(BigInt(parent.stake) + BigInt(child.stake)); - } - } - } - diffCourts.reverse(); - for (const c of diffCourts) { - c.votesPerPnk = Number(c.numberVotes) / (Number(c.stake) / 1e18); - c.treeVotesPerPnk = c.votesPerPnk; - c.disputesPerPnk = Number(c.numberDisputes) / (Number(c.stake) / 1e18); - c.treeDisputesPerPnk = c.disputesPerPnk; - } - for (const parent of diffCourts) { - for (const child of diffCourts) { - if (parent.id === child.parent?.id) { - child.treeVotesPerPnk += parent.votesPerPnk; - child.treeDisputesPerPnk += parent.disputesPerPnk; - } - } - } - const bestDrawingChancesCourt = diffCourts.sort((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0]; - // 3. expected reward - // since we isolated the exclusive disputes from the cumulative disputes - // we can calculate the "isolated reward" of every court - // after that's done, then just trickle the rewards down - - for (const c of diffCourts) { - c.expectedRewardPerPnk = c.votesPerPnk * c.feeForJuror; - c.treeExpectedRewardPerPnk = c.expectedRewardPerPnk; - } - for (const parent of diffCourts) { - for (const child of diffCourts) { - if (parent.id === child.parent?.id) { - child.treeExpectedRewardPerPnk = parent.treeExpectedRewardPerPnk + child.treeExpectedRewardPerPnk; - } - } - } - const bestExpectedRewardCourt = diffCourts.sort( - (a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk - )[0]; - - return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt, diffCourts }; - } else { - return undefined; - } - }, [usedQuery]); - - return courtActivityStats; -}; diff --git a/web/src/hooks/queries/useHomePageBlockQuery.ts b/web/src/hooks/queries/useHomePageBlockQuery.ts index 4e27a749b..94a2a4b29 100644 --- a/web/src/hooks/queries/useHomePageBlockQuery.ts +++ b/web/src/hooks/queries/useHomePageBlockQuery.ts @@ -18,7 +18,7 @@ const homePageBlockQuery = graphql(` numberDisputes numberVotes feeForJuror - stake + effectiveStake } pastCourts: courts(orderBy: id, orderDirection: asc, block: { number: $blockNumber }) { id @@ -29,7 +29,7 @@ const homePageBlockQuery = graphql(` numberDisputes numberVotes feeForJuror - stake + effectiveStake } } `); @@ -54,31 +54,78 @@ export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boole const courtActivityStats = useMemo(() => { if (usedQuery.data && !usedQuery.isFetching) { - // 1. court with most disputes - // we only iterate through past courts, since more courts might exist at the present - // these diffCourts have: average stakes, and dispute diff const diffCourts = allTime - ? usedQuery.data.presentCourts.map((c) => ({ - ...c, - numberDisputes: c.numberDisputes, - treeNumberDisputes: c.numberDisputes, - numberVotes: c.numberVotes, - treeNumberVotes: c.numberVotes, - stake: c.stake, + ? usedQuery.data.presentCourts.map((presentCourt) => ({ + ...presentCourt, + numberDisputes: presentCourt.numberDisputes, + treeNumberDisputes: presentCourt.numberDisputes, + numberVotes: presentCourt.numberVotes, + treeNumberVotes: presentCourt.numberVotes, + effectiveStake: presentCourt.effectiveStake, + votesPerPnk: Number(presentCourt.numberVotes) / (Number(presentCourt.effectiveStake) / 1e18), + treeVotesPerPnk: Number(presentCourt.numberVotes) / (Number(presentCourt.effectiveStake) / 1e18), + disputesPerPnk: Number(presentCourt.numberDisputes) / (Number(presentCourt.effectiveStake) / 1e18), + treeDisputesPerPnk: Number(presentCourt.numberDisputes) / (Number(presentCourt.effectiveStake) / 1e18), })) - : usedQuery.data.pastCourts.map((c, i) => ({ - ...c, - numberDisputes: usedQuery.data.presentCourts[i].numberDisputes - c.numberDisputes, - treeNumberDisputes: usedQuery.data.presentCourts[i].numberDisputes - c.numberDisputes, - numberVotes: usedQuery.data.presentCourts[i].numberVotes - c.numberVotes, - treeNumberVotes: usedQuery.data.presentCourts[i].numberVotes - c.numberVotes, - stake: (BigInt(usedQuery.data.presentCourts[i].stake) + BigInt(c.stake)) / 2n, - })); - const mostDisputedCourt = diffCourts.sort((a, b) => b.numberDisputes - a.numberDisputes)[0]; - // 2. biggest chances of getting drawn - // fact a: getting drawn in a parent court also subjects you to its rewards - // fact b: staking in children, stakes in parents. but subgraph at this date doesn't reflect this - // so, stakes trickle up, rewards/disputes trickle down + : usedQuery.data.presentCourts.map((presentCourt) => { + const pastCourt = usedQuery.data.pastCourts.find((pastCourt) => pastCourt.id === presentCourt.id); + + return { + ...presentCourt, + numberDisputes: pastCourt + ? presentCourt.numberDisputes - pastCourt.numberDisputes + : presentCourt.numberDisputes, + treeNumberDisputes: pastCourt + ? presentCourt.numberDisputes - pastCourt.numberDisputes + : presentCourt.numberDisputes, + numberVotes: pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes, + treeNumberVotes: pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes, + effectiveStake: pastCourt + ? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n + : presentCourt.effectiveStake, + votesPerPnk: + Number(pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes) / + (Number( + pastCourt + ? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n + : presentCourt.effectiveStake + ) / + 1e18), + treeVotesPerPnk: + Number(pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes) / + (Number( + pastCourt + ? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n + : presentCourt.effectiveStake + ) / + 1e18), + disputesPerPnk: + Number( + pastCourt ? presentCourt.numberDisputes - pastCourt.numberDisputes : presentCourt.numberDisputes + ) / + (Number( + pastCourt + ? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n + : presentCourt.effectiveStake + ) / + 1e18), + treeDisputesPerPnk: + Number( + pastCourt ? presentCourt.numberDisputes - pastCourt.numberDisputes : presentCourt.numberDisputes + ) / + (Number( + pastCourt + ? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n + : presentCourt.effectiveStake + ) / + 1e18), + }; + }); + + const mostDisputedCourt = diffCourts.toSorted((a, b) => b.numberDisputes - a.numberDisputes)[0]; + // 1. biggest chances of getting drawn + // fact: getting drawn in a parent court also subjects you to its rewards + // so, rewards/disputes trickle down for (const parent of diffCourts) { for (const child of diffCourts) { @@ -88,21 +135,7 @@ export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boole } } } - diffCourts.reverse(); - for (const child of diffCourts) { - for (const parent of diffCourts) { - if (parent.id === child.parent?.id) { - parent.stake = String(BigInt(parent.stake) + BigInt(child.stake)); - } - } - } - diffCourts.reverse(); - for (const c of diffCourts) { - c.votesPerPnk = Number(c.numberVotes) / (Number(c.stake) / 1e18); - c.treeVotesPerPnk = c.votesPerPnk; - c.disputesPerPnk = Number(c.numberDisputes) / (Number(c.stake) / 1e18); - c.treeDisputesPerPnk = c.disputesPerPnk; - } + for (const parent of diffCourts) { for (const child of diffCourts) { if (parent.id === child.parent?.id) { @@ -111,8 +144,8 @@ export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boole } } } - const bestDrawingChancesCourt = diffCourts.sort((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0]; - // 3. expected reward + const bestDrawingChancesCourt = diffCourts.toSorted((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0]; + // 2. expected reward // since we isolated the exclusive disputes from the cumulative disputes // we can calculate the "isolated reward" of every court // after that's done, then just trickle the rewards down @@ -128,11 +161,11 @@ export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boole } } } - const bestExpectedRewardCourt = diffCourts.sort( + const bestExpectedRewardCourt = diffCourts.toSorted( (a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk )[0]; - return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt }; + return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt, diffCourts }; } else { return undefined; } diff --git a/web/src/hooks/queries/useHomePageExtraStats.ts b/web/src/hooks/queries/useHomePageExtraStats.ts new file mode 100644 index 000000000..ce132d785 --- /dev/null +++ b/web/src/hooks/queries/useHomePageExtraStats.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import { DEFAULT_CHAIN } from "consts/chains"; +import { useHomePageBlockQuery } from "./useHomePageBlockQuery"; +import { useBlockNumber } from "wagmi"; +import { averageBlockTimeInSeconds } from "consts/averageBlockTimeInSeconds"; + +export const useHomePageExtraStats = (days: number | string) => { + const [pastBlockNumber, setPastBlockNumber] = useState<number>(); + const currentBlockNumber = useBlockNumber({ chainId: DEFAULT_CHAIN }); + + useEffect(() => { + if (typeof days !== "string" && currentBlockNumber?.data) { + const timeInBlocks = Math.floor((days * 24 * 3600) / averageBlockTimeInSeconds[DEFAULT_CHAIN]); + setPastBlockNumber(Number(currentBlockNumber.data) - timeInBlocks); + } + }, [DEFAULT_CHAIN, currentBlockNumber, days]); + + const data = useHomePageBlockQuery(pastBlockNumber, days === "allTime"); + + return data; +}; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 55d8a53e3..132e5cd3d 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useMemo, useState } from "react"; import styled, { css } from "styled-components"; import { useParams } from "react-router-dom"; @@ -12,6 +12,7 @@ import PNKIcon from "svgs/icons/pnk.svg"; import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg"; import VoteStake from "svgs/icons/vote-stake.svg"; import RewardsPerPnk from "svgs/icons/rewards-per-pnk.svg"; +import ChartIcon from "svgs/icons/chart.svg"; import { CoinIds } from "consts/coingecko"; import { useCoinPrice } from "hooks/useCoinPrice"; @@ -27,34 +28,62 @@ import { responsiveSize } from "styles/responsiveSize"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; // import { useHomePageBlockQuery } from "queries/useHomePageBlockQuery"; -import { useCourtAllTimeQuery } from "queries/useCourtAllTimeQuery"; import { commify } from "utils/commify"; +import { DropdownSelect } from "@kleros/ui-components-library"; +import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; function beautifyStatNumber(value: number): string { const absValue = Math.abs(value); if (absValue >= 1e9) { - return `${commify((value / 1e9).toFixed(1))}B`; + return `${commify((value / 1e9).toFixed(2))}B`; } else if (absValue >= 1e6) { - return `${commify((value / 1e6).toFixed(1))}M`; + return `${commify((value / 1e6).toFixed(2))}M`; } else if (absValue >= 1e3) { - return `${commify((value / 1e3).toFixed(1))}K`; + return `${commify((value / 1e3).toFixed(0))}K`; } else if (absValue < 1 && absValue !== 0) { const inverseValue = 1 / absValue; - return `1 per ${commify(inverseValue.toFixed(0))}`; + return commify(inverseValue.toFixed(0)); } return commify(value.toFixed(0)); } +const TimeDisplayContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + +const AllTimeContainer = styled(TimeDisplayContainer)` + padding-top: ${responsiveSize(12, 20)}; +`; + +const TimeSelectorContainer = styled(TimeDisplayContainer)` + padding-top: 12px; +`; + +const StyledAllTimeText = styled.p` + color: ${({ theme }) => theme.secondaryText}; + margin: 0; + font-size: 14px; +`; + +const StyledChartIcon = styled(ChartIcon)` + path { + fill: ${({ theme }) => theme.primaryText}; + } +`; + const StyledCard = styled.div` width: auto; height: fit-content; display: grid; gap: 32px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - padding: ${responsiveSize(0, 32)} 0; - padding-bottom: 0px; + padding-top: ${responsiveSize(28, 32)}; + padding-bottom: ${responsiveSize(20, 0)}; ${landscapeStyle( () => css` @@ -150,7 +179,7 @@ interface IStats { const timeframedStats: IStats[] = [ { - title: "ETH Rewards per PNK Staked", + title: "PNK for 1 ETH", getText: (data) => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; return beautifyStatNumber(treeExpectedRewardPerPnk / 1e18); @@ -159,45 +188,65 @@ const timeframedStats: IStats[] = [ icon: RewardsPerPnk, }, { - title: "Cases per PNK Staked", + title: "PNK for 1 Vote", getText: (data) => { - const treeDisputesPerPnk = data?.treeDisputesPerPnk; - return beautifyStatNumber(treeDisputesPerPnk); + const treeVotesPerPnk = data?.treeVotesPerPnk; + return beautifyStatNumber(treeVotesPerPnk); }, color: "orange", - icon: BalanceWithPNKIcon, + icon: VotesPerPNKIcon, }, { - title: "Votes per PNK Staked", + title: "PNK for 1 Case", getText: (data) => { - const treeVotesPerPnk = data?.treeVotesPerPnk; - return beautifyStatNumber(treeVotesPerPnk); + const treeDisputesPerPnk = data?.treeDisputesPerPnk; + return beautifyStatNumber(treeDisputesPerPnk); }, color: "orange", - icon: VotesPerPNKIcon, + icon: BalanceWithPNKIcon, }, ]; +const timeRanges = [ + { value: 7, text: "Last 7 days" }, + { value: 30, text: "Last 30 days" }, + { value: 90, text: "Last 90 days" }, + /* we can uncomment as court creation time increases, + but it's a bit tricky because this affects every court */ + // { value: 180, text: "Last 180 days" }, + // { value: 365, text: "Last 365 days" }, + { value: "allTime", text: "All Time" }, +]; + const Stats = () => { const { id } = useParams(); const { data } = useCourtDetails(id); - const courtData = useCourtAllTimeQuery(); + const [selectedRange, setSelectedRange] = useState(timeRanges[0].value); + const timeframedCourtData = useHomePageExtraStats(selectedRange); const coinIds = [CoinIds.PNK, CoinIds.ETH]; const { prices: pricesData } = useCoinPrice(coinIds); const foundCourt = useMemo(() => { - if (courtData?.diffCourts) { - const foundCourt = courtData?.diffCourts.find((c) => c.id === id); + if (timeframedCourtData?.diffCourts) { + const foundCourt = timeframedCourtData?.diffCourts.find((c) => c.id === id); console.log({ foundCourt }); return foundCourt; } else { console.log("Court not found or diffCourts not available"); return undefined; } - }, [courtData, id]); + }, [timeframedCourtData, id]); + + const handleTimeRangeChange = (value: string | number) => { + setSelectedRange(value); + }; return ( <> + <AllTimeContainer> + <StyledChartIcon /> + <StyledAllTimeText>All time</StyledAllTimeText> + </AllTimeContainer> <StyledCard> {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; @@ -211,16 +260,29 @@ const Stats = () => { ); })} </StyledCard> - <hr /> + <TimeSelectorContainer> + <StyledChartIcon /> + <StyledAllTimeText> + <DropdownSelect + smallButton + simpleButton + items={timeRanges.map((range) => ({ + value: range.value, + text: range.text, + }))} + defaultValue={selectedRange} + callback={handleTimeRangeChange} + /> + </StyledAllTimeText> + </TimeSelectorContainer> <StyledCard> - {timeframedStats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + {timeframedStats.map(({ title, getText, getSubtext, color, icon }, i) => { return ( <StatDisplay key={i} {...{ title, color, icon }} text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} - subtext={calculateSubtextRender(foundCourt, getSubtext, coinPrice)} + subtext={calculateSubtextRender(foundCourt, getSubtext)} /> ); })} diff --git a/web/src/utils/calculateSubtextRender.tsx b/web/src/utils/calculateSubtextRender.tsx index 556a197c1..33755a379 100644 --- a/web/src/utils/calculateSubtextRender.tsx +++ b/web/src/utils/calculateSubtextRender.tsx @@ -9,8 +9,8 @@ import { StyledSkeleton } from "components/StyledSkeleton"; export const calculateSubtextRender = ( countersOrCourtData: CourtDetailsQuery["court"] | HomePageQuery["counters"], - getSubtext: ((data: any, coinPrice: number) => string) | undefined, - coinPrice: number + getSubtext: ((data: any, coinPrice?: number) => string) | undefined, + coinPrice?: number ) => { if (!isUndefined(countersOrCourtData) && !isUndefined(getSubtext)) { return getSubtext(countersOrCourtData, coinPrice); From 3171cb2917e43709ff9aad1ece74679f74533afa Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:02:23 +0200 Subject: [PATCH 03/27] feat: add accordion, style divider --- web/src/pages/Courts/CourtDetails/Stats.tsx | 122 ++++++++++++-------- web/src/pages/Courts/CourtDetails/index.tsx | 12 +- 2 files changed, 84 insertions(+), 50 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 132e5cd3d..19c31815f 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -27,11 +27,28 @@ import { responsiveSize } from "styles/responsiveSize"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; -// import { useHomePageBlockQuery } from "queries/useHomePageBlockQuery"; import { commify } from "utils/commify"; -import { DropdownSelect } from "@kleros/ui-components-library"; +import { CustomAccordion, DropdownSelect } from "@kleros/ui-components-library"; import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; +const StyledAccordion = styled(CustomAccordion)` + width: 100%; + margin-bottom: 12px; + > * > button { + justify-content: unset; + background-color: ${({ theme }) => theme.whiteBackground} !important; + border: 1px solid ${({ theme }) => theme.stroke} !important; + color: ${({ theme }) => theme.primaryText} !important; + > svg { + fill: ${({ theme }) => theme.primaryText} !important; + } + } + //adds padding to body container + > * > div > div { + padding: 0; + } +`; + function beautifyStatNumber(value: number): string { const absValue = Math.abs(value); @@ -242,52 +259,61 @@ const Stats = () => { }; return ( - <> - <AllTimeContainer> - <StyledChartIcon /> - <StyledAllTimeText>All time</StyledAllTimeText> - </AllTimeContainer> - <StyledCard> - {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - <StatDisplay - key={i} - {...{ title, color, icon }} - text={data ? getText(data.court) : <StyledSkeleton />} - subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} - /> - ); - })} - </StyledCard> - <TimeSelectorContainer> - <StyledChartIcon /> - <StyledAllTimeText> - <DropdownSelect - smallButton - simpleButton - items={timeRanges.map((range) => ({ - value: range.value, - text: range.text, - }))} - defaultValue={selectedRange} - callback={handleTimeRangeChange} - /> - </StyledAllTimeText> - </TimeSelectorContainer> - <StyledCard> - {timeframedStats.map(({ title, getText, getSubtext, color, icon }, i) => { - return ( - <StatDisplay - key={i} - {...{ title, color, icon }} - text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} - subtext={calculateSubtextRender(foundCourt, getSubtext)} - /> - ); - })} - </StyledCard> - </> + <StyledAccordion + items={[ + { + title: "Statistics", + body: ( + <> + <AllTimeContainer> + <StyledChartIcon /> + <StyledAllTimeText>All time</StyledAllTimeText> + </AllTimeContainer> + <StyledCard> + {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + <StatDisplay + key={i} + {...{ title, color, icon }} + text={data ? getText(data.court) : <StyledSkeleton />} + subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} + /> + ); + })} + </StyledCard> + <TimeSelectorContainer> + <StyledChartIcon /> + <StyledAllTimeText> + <DropdownSelect + smallButton + simpleButton + items={timeRanges.map((range) => ({ + value: range.value, + text: range.text, + }))} + defaultValue={selectedRange} + callback={handleTimeRangeChange} + /> + </StyledAllTimeText> + </TimeSelectorContainer> + <StyledCard> + {timeframedStats.map(({ title, getText, getSubtext, color, icon }, i) => { + return ( + <StatDisplay + key={i} + {...{ title, color, icon }} + text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} + subtext={calculateSubtextRender(foundCourt, getSubtext)} + /> + ); + })} + </StyledCard> + </> + ), + }, + ]} + ></StyledAccordion> ); }; diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx index 1e9ebbd35..057661cdd 100644 --- a/web/src/pages/Courts/CourtDetails/index.tsx +++ b/web/src/pages/Courts/CourtDetails/index.tsx @@ -77,6 +77,15 @@ const StyledBreadcrumb = styled(Breadcrumb)` } `; +const Divider = styled.hr` + width: 100%; + display: flex; + border: none; + height: 1px; + background-color: ${({ theme }) => theme.stroke}; + margin: 0; +`; + const CourtDetails: React.FC = () => { const { id } = useParams(); const { data: policy } = useCourtPolicy(id); @@ -110,9 +119,8 @@ const CourtDetails: React.FC = () => { {!isProductionDeployment() && <ClaimPnkButton />} </ButtonContainer> </CourtHeader> - <hr /> <Stats /> - <hr /> + <Divider /> <StakePanel id={!isUndefined(id) ? id : ""} courtName={policy?.name} /> </StyledCard> <StyledCard> From c8b793f671d711881f27c8a779b9f8a0497d7cf3 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:35:00 +0200 Subject: [PATCH 04/27] feat: open accordion on default --- web/src/pages/Courts/CourtDetails/Stats.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 19c31815f..881951097 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -28,10 +28,10 @@ import { responsiveSize } from "styles/responsiveSize"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; import { commify } from "utils/commify"; -import { CustomAccordion, DropdownSelect } from "@kleros/ui-components-library"; +import { Accordion, DropdownSelect } from "@kleros/ui-components-library"; import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; -const StyledAccordion = styled(CustomAccordion)` +const StyledAccordion = styled(Accordion)` width: 100%; margin-bottom: 12px; > * > button { @@ -260,6 +260,7 @@ const Stats = () => { return ( <StyledAccordion + defaultExpanded={0} items={[ { title: "Statistics", From 2af88c1b7fcce0414f7c60773edc4d23662734be Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:39:11 +0200 Subject: [PATCH 05/27] fix: fix some types --- web/src/components/StatDisplay.tsx | 2 +- web/src/pages/Courts/CourtDetails/Stats.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/web/src/components/StatDisplay.tsx b/web/src/components/StatDisplay.tsx index 2cf5f36d3..8cbc0b52f 100644 --- a/web/src/components/StatDisplay.tsx +++ b/web/src/components/StatDisplay.tsx @@ -44,7 +44,7 @@ const createPair = (iconColor: string, backgroundColor: string) => ({ export interface IStatDisplay { title: string; text: string | React.ReactNode; - subtext: string | React.ReactNode; + subtext?: string | React.ReactNode; icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>; color: "red" | "orange" | "green" | "blue" | "purple"; } diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 881951097..8f4c694f0 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -185,16 +185,15 @@ const stats: IStat[] = [ }, ]; -interface IStats { +interface ITimeframedStat { title: string; coinId?: number; - getText: any; //(data: CourtDetailsQuery["court"]) => string; - getSubtext?: any; // (data: CourtDetailsQuery["court"], coinPrice?: number) => string; + getText: (data: { treeExpectedRewardPerPnk: number; treeVotesPerPnk: number; treeDisputesPerPnk: number }) => string; color: IStatDisplay["color"]; icon: React.FC<React.SVGAttributes<SVGElement>>; } -const timeframedStats: IStats[] = [ +const timeframedStats: ITimeframedStat[] = [ { title: "PNK for 1 ETH", getText: (data) => { @@ -299,13 +298,12 @@ const Stats = () => { </StyledAllTimeText> </TimeSelectorContainer> <StyledCard> - {timeframedStats.map(({ title, getText, getSubtext, color, icon }, i) => { + {timeframedStats.map(({ title, getText, color, icon }, i) => { return ( <StatDisplay key={i} {...{ title, color, icon }} text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} - subtext={calculateSubtextRender(foundCourt, getSubtext)} /> ); })} From aff2336349645da478b2b220ecc966bd29551194 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:42:31 +0200 Subject: [PATCH 06/27] fix: slight styling detail and use title as key instead of index --- web/src/pages/Courts/CourtDetails/Stats.tsx | 4 ++-- web/src/pages/Courts/CourtDetails/index.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 8f4c694f0..f18eb6209 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -274,7 +274,7 @@ const Stats = () => { const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; return ( <StatDisplay - key={i} + key={title} {...{ title, color, icon }} text={data ? getText(data.court) : <StyledSkeleton />} subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} @@ -301,7 +301,7 @@ const Stats = () => { {timeframedStats.map(({ title, getText, color, icon }, i) => { return ( <StatDisplay - key={i} + key={title} {...{ title, color, icon }} text={foundCourt ? getText(foundCourt) : <StyledSkeleton />} /> diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx index 057661cdd..10b4f0923 100644 --- a/web/src/pages/Courts/CourtDetails/index.tsx +++ b/web/src/pages/Courts/CourtDetails/index.tsx @@ -79,7 +79,6 @@ const StyledBreadcrumb = styled(Breadcrumb)` const Divider = styled.hr` width: 100%; - display: flex; border: none; height: 1px; background-color: ${({ theme }) => theme.stroke}; From 9925c37a9d64befb64c0665c20e0b60dc574c402 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:06:09 +0200 Subject: [PATCH 07/27] fix: adjust homepageblockquery hook, stats adjustments, add pnk for 1 usd --- .../hooks/queries/useHomePageBlockQuery.ts | 14 +++- web/src/pages/Courts/CourtDetails/Stats.tsx | 78 +++++++++++-------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/web/src/hooks/queries/useHomePageBlockQuery.ts b/web/src/hooks/queries/useHomePageBlockQuery.ts index 67b3c97ad..33bb47ffe 100644 --- a/web/src/hooks/queries/useHomePageBlockQuery.ts +++ b/web/src/hooks/queries/useHomePageBlockQuery.ts @@ -44,6 +44,8 @@ type CourtWithTree = Court & { treeNumberVotes: number; votesPerPnk: number; treeVotesPerPnk: number; + disputesPerPnk: number; + treeDisputesPerPnk: number; expectedRewardPerPnk: number; treeExpectedRewardPerPnk: number; }; @@ -101,6 +103,7 @@ const processData = (data: HomePageBlockQuery, allTime: boolean) => { treeNumberDisputes: court.treeNumberDisputes + processCourt(parentIndex).treeNumberDisputes, treeNumberVotes: court.treeNumberVotes + processCourt(parentIndex).treeNumberVotes, treeVotesPerPnk: court.treeVotesPerPnk + processCourt(parentIndex).treeVotesPerPnk, + treeDisputesPerPnk: court.treeDisputesPerPnk + processCourt(parentIndex).treeDisputesPerPnk, treeExpectedRewardPerPnk: court.treeExpectedRewardPerPnk + processCourt(parentIndex).treeExpectedRewardPerPnk, }; @@ -123,6 +126,7 @@ const processData = (data: HomePageBlockQuery, allTime: boolean) => { const addTreeValues = (court: Court): CourtWithTree => { const votesPerPnk = Number(court.numberVotes) / (Number(court.effectiveStake) / 1e18); + const disputesPerPnk = Number(court.numberDisputes) / (Number(court.effectiveStake) / 1e18); const expectedRewardPerPnk = votesPerPnk * (Number(court.feeForJuror) / 1e18); return { ...court, @@ -134,6 +138,8 @@ const addTreeValues = (court: Court): CourtWithTree => { treeNumberVotes: Number(court.numberVotes), votesPerPnk, treeVotesPerPnk: votesPerPnk, + disputesPerPnk, + treeDisputesPerPnk: disputesPerPnk, expectedRewardPerPnk, treeExpectedRewardPerPnk: expectedRewardPerPnk, }; @@ -143,9 +149,11 @@ const addTreeValuesWithDiff = (presentCourt: Court, pastCourt: Court): CourtWith const presentCourtWithTree = addTreeValues(presentCourt); const pastCourtWithTree = addTreeValues(pastCourt); const diffNumberVotes = presentCourtWithTree.numberVotes - pastCourtWithTree.numberVotes; + const diffNumberDisputes = presentCourtWithTree.numberDisputes - pastCourtWithTree.numberDisputes; const avgEffectiveStake = (presentCourtWithTree.effectiveStake + pastCourtWithTree.effectiveStake) / 2n; - const votesPerPnk = diffNumberVotes / Number(avgEffectiveStake); - const expectedRewardPerPnk = votesPerPnk * Number(presentCourt.feeForJuror); + const votesPerPnk = diffNumberVotes / (Number(avgEffectiveStake) / 1e18); + const disputesPerPnk = diffNumberDisputes / (Number(avgEffectiveStake) / 1e18); + const expectedRewardPerPnk = votesPerPnk * (Number(presentCourt.feeForJuror) / 1e18); return { ...presentCourt, numberDisputes: presentCourtWithTree.numberDisputes - pastCourtWithTree.numberDisputes, @@ -155,6 +163,8 @@ const addTreeValuesWithDiff = (presentCourt: Court, pastCourt: Court): CourtWith effectiveStake: avgEffectiveStake, votesPerPnk, treeVotesPerPnk: votesPerPnk, + disputesPerPnk, + treeDisputesPerPnk: disputesPerPnk, expectedRewardPerPnk, treeExpectedRewardPerPnk: expectedRewardPerPnk, }; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index f18eb6209..7749b342b 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -193,36 +193,6 @@ interface ITimeframedStat { icon: React.FC<React.SVGAttributes<SVGElement>>; } -const timeframedStats: ITimeframedStat[] = [ - { - title: "PNK for 1 ETH", - getText: (data) => { - const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - return beautifyStatNumber(treeExpectedRewardPerPnk / 1e18); - }, - color: "orange", - icon: RewardsPerPnk, - }, - { - title: "PNK for 1 Vote", - getText: (data) => { - const treeVotesPerPnk = data?.treeVotesPerPnk; - return beautifyStatNumber(treeVotesPerPnk); - }, - color: "orange", - icon: VotesPerPNKIcon, - }, - { - title: "PNK for 1 Case", - getText: (data) => { - const treeDisputesPerPnk = data?.treeDisputesPerPnk; - return beautifyStatNumber(treeDisputesPerPnk); - }, - color: "orange", - icon: BalanceWithPNKIcon, - }, -]; - const timeRanges = [ { value: 7, text: "Last 7 days" }, { value: 30, text: "Last 30 days" }, @@ -243,8 +213,8 @@ const Stats = () => { const { prices: pricesData } = useCoinPrice(coinIds); const foundCourt = useMemo(() => { - if (timeframedCourtData?.diffCourts) { - const foundCourt = timeframedCourtData?.diffCourts.find((c) => c.id === id); + if (timeframedCourtData?.data?.courts) { + const foundCourt = timeframedCourtData?.data?.courts.find((c) => c.id === id); console.log({ foundCourt }); return foundCourt; } else { @@ -257,6 +227,50 @@ const Stats = () => { setSelectedRange(value); }; + const timeframedStats: ITimeframedStat[] = [ + { + title: "PNK for 1 ETH", + getText: (data) => { + const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; + if (!treeExpectedRewardPerPnk) return "N/A"; + const pnkNeeded = treeExpectedRewardPerPnk; + return beautifyStatNumber(pnkNeeded); + }, + color: "orange", + icon: RewardsPerPnk, + }, + { + title: "PNK for 1 USD", + getText: (data) => { + const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; + const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; + if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; + const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; + return beautifyStatNumber(pnkNeeded); + }, + color: "orange", + icon: RewardsPerPnk, + }, + { + title: "PNK for 1 Vote", + getText: (data) => { + const treeVotesPerPnk = data?.treeVotesPerPnk; + return beautifyStatNumber(treeVotesPerPnk); + }, + color: "orange", + icon: VotesPerPNKIcon, + }, + { + title: "PNK for 1 Case", + getText: (data) => { + const treeDisputesPerPnk = data?.treeDisputesPerPnk; + return beautifyStatNumber(treeDisputesPerPnk); + }, + color: "orange", + icon: BalanceWithPNKIcon, + }, + ]; + return ( <StyledAccordion defaultExpanded={0} From 9e53b00db8b15d0a73b5b4510c5a70dfbc8292c2 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:54:54 +0200 Subject: [PATCH 08/27] chore: reorder stats --- web/src/pages/Courts/CourtDetails/Stats.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 7749b342b..2afe6a7e4 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -229,23 +229,23 @@ const Stats = () => { const timeframedStats: ITimeframedStat[] = [ { - title: "PNK for 1 ETH", + title: "PNK for 1 USD", getText: (data) => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - if (!treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk; + const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; + if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; + const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; return beautifyStatNumber(pnkNeeded); }, color: "orange", icon: RewardsPerPnk, }, { - title: "PNK for 1 USD", + title: "PNK for 1 ETH", getText: (data) => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; - if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; + if (!treeExpectedRewardPerPnk) return "N/A"; + const pnkNeeded = treeExpectedRewardPerPnk; return beautifyStatNumber(pnkNeeded); }, color: "orange", From bb4f76a0d954f260816fab8b207a6392c927505d Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:23:29 +0200 Subject: [PATCH 09/27] feat: first iteration of stake simulator, abstract jurorbalance, commify jurorbalance --- .../StakePanel/JurorStakeDisplay.tsx | 23 ++-- .../StakePanel/SimulatorPopup.tsx | 103 ++++++++++++++++++ .../Courts/CourtDetails/StakePanel/index.tsx | 31 +++++- 3 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx index c86499778..d84d5eb5b 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx @@ -1,16 +1,13 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect, useMemo, FC } from "react"; import styled, { css } from "styled-components"; import { useParams } from "react-router-dom"; import { formatEther } from "viem"; -import { useAccount } from "wagmi"; import DiceIcon from "svgs/icons/dice.svg"; import PNKIcon from "svgs/icons/pnk.svg"; -import { REFETCH_INTERVAL } from "consts/index"; -import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated"; -import { isUndefined } from "utils/index"; +import { commify } from "utils/commify"; import { useCourtDetails } from "queries/useCourtDetails"; @@ -68,16 +65,12 @@ const useCalculateJurorOdds = ( }, [jurorBalance, stakedByAllJurors, loading]); }; -const JurorBalanceDisplay = () => { +interface IJurorBalanceDisplay { + jurorBalance: readonly [bigint, bigint, bigint, bigint] | undefined; +} + +const JurorBalanceDisplay: FC<IJurorBalanceDisplay> = ({ jurorBalance }) => { const { id } = useParams(); - const { address } = useAccount(); - const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ - query: { - enabled: !isUndefined(address), - refetchInterval: REFETCH_INTERVAL, - }, - args: [address ?? "0x", BigInt(id ?? 0)], - }); const { data: courtDetails } = useCourtDetails(id); const stakedByAllJurors = courtDetails?.court?.stake; @@ -107,7 +100,7 @@ const JurorBalanceDisplay = () => { { icon: PNKIcon, name: "My Stake", - value: `${format(jurorBalance?.[2])} PNK`, + value: `${commify(format(jurorBalance?.[2]))} PNK`, }, { icon: DiceIcon, diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx new file mode 100644 index 000000000..9fdc6dd2c --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; + +import { useParams } from "react-router-dom"; + +import { CoinIds } from "consts/coingecko"; + +import { formatUSD } from "utils/format"; +import { commify } from "utils/commify"; + +import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; +import { useCoinPrice } from "hooks/useCoinPrice"; + +const SimulatorPopupContainer = styled.div` + position: absolute; + top: 100px; + right: 50px; + width: 400px; + background-color: ${({ theme }) => theme.lightBlue}; + box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); + padding: 20px; + border-radius: 8px; + border: 1px ${({ theme }) => theme.mediumBlue}; +`; + +const SimulatorItem = styled.div` + display: flex; + flex-direction: row; + font-size: 14px; + margin-bottom: 16px; + color: ${({ theme }) => theme.secondaryText}; + gap: 4px; +`; + +function beautifyStatNumber(value: number): string { + const absValue = Math.abs(value); + + if (absValue >= 1e9) { + return `${commify((value / 1e9).toFixed(2))}B`; + } else if (absValue >= 1e6) { + return `${commify((value / 1e6).toFixed(2))}M`; + } else if (absValue >= 1e3) { + return `${commify((value / 1e3).toFixed(0))}K`; + } else if (absValue > 0 && absValue < 1) { + return value.toFixed(2); + } + + return commify(value.toFixed(0)); +} + +const calculateJurorOdds = (stakingAmount: number, totalStake: number): string => { + const odds = (stakingAmount * 100) / totalStake; + return `${odds.toFixed(2)}%`; +}; + +const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) => { + const [courtData, setCourtData] = useState<any>(null); + const { id } = useParams(); + + const timeframedCourtData = useHomePageExtraStats(30); + const { prices: pricesData } = useCoinPrice([CoinIds.ETH]); + const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; + + useEffect(() => { + if (timeframedCourtData?.data?.courts) { + const foundCourt = timeframedCourtData?.data?.courts.find((c) => c.id === id); + if (foundCourt) { + setCourtData(foundCourt); + } + } + }, [timeframedCourtData, id]); + + if (!courtData) return null; + + const effectiveStakeAsNumber = Number(courtData.effectiveStake) / 1e18; + const expectedCases = beautifyStatNumber(stakingAmount * courtData.treeDisputesPerPnk); + const expectedVotes = beautifyStatNumber(stakingAmount * courtData.treeVotesPerPnk); + const expectedRewardsUSD = formatUSD(courtData.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD); + const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); + + return ( + <SimulatorPopupContainer> + <SimulatorItem> + <strong>Cases</strong> + You would have been drawn in: {expectedCases} cases + </SimulatorItem> + <SimulatorItem> + <strong>Votes</strong> + You would have: {expectedVotes} votes + </SimulatorItem> + <SimulatorItem> + <strong>Rewards</strong> + Potential earnings: {expectedRewardsUSD} + </SimulatorItem> + <SimulatorItem> + <strong>Juror Odds</strong> + Your juror odds: {jurorOdds} + </SimulatorItem> + </SimulatorPopupContainer> + ); +}; + +export default SimulatorPopup; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx index 1ad6c94d5..eb3f8dd93 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx @@ -1,19 +1,27 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; + +import { useAccount } from "wagmi"; +import { formatEther } from "viem"; import BalanceIcon from "svgs/icons/balance.svg"; import ThreePnksIcon from "svgs/styled/three-pnks.svg"; import { useLockOverlayScroll } from "hooks/useLockOverlayScroll"; - -import { landscapeStyle } from "styles/landscapeStyle"; +import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated"; import Popup, { PopupType } from "components/Popup/index"; import Tag from "components/Tag"; +import { uncommify } from "utils/commify"; +import { isUndefined } from "utils/index"; +import { REFETCH_INTERVAL } from "consts/index"; + import InputDisplay from "./InputDisplay"; import JurorBalanceDisplay from "./JurorStakeDisplay"; import { ActionType } from "./StakeWithdrawButton"; +import SimulatorPopup from "./SimulatorPopup"; const Container = styled.div` position: relative; @@ -79,6 +87,14 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = " const [isPopupOpen, setIsPopupOpen] = useState(false); const [isActive, setIsActive] = useState<boolean>(true); const [action, setAction] = useState<ActionType>(ActionType.stake); + const { address } = useAccount(); + const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ + query: { + enabled: !isUndefined(address), + refetchInterval: REFETCH_INTERVAL, + }, + args: [address ?? "0x", BigInt(id ?? 0)], + }); useLockOverlayScroll(isPopupOpen); @@ -101,8 +117,17 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = " </TextArea> <StakeArea> <InputDisplay {...{ action, isSending, setIsSending, setIsPopupOpen, amount, setAmount }} /> - <JurorBalanceDisplay /> + <JurorBalanceDisplay {...{ jurorBalance }} /> </StakeArea> + {isStaking && Number(uncommify(amount)) > 0 ? ( + <SimulatorPopup + stakingAmount={ + !isUndefined(jurorBalance) + ? Number(formatEther(jurorBalance?.[2])) + Number(uncommify(amount)) + : Number(uncommify(amount)) + } + /> + ) : null} {isPopupOpen && ( <Popup title={isStaking ? "Stake Confirmed" : "Withdraw Confirmed"} From 9e3fbff3c9ad442b22e0f13161682fd3a07f897d Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:23:14 +0200 Subject: [PATCH 10/27] feat: style simulator popup according to figma, adjust position in mobile and desktop --- .../StakePanel/SimulatorPopup/Header.tsx | 67 +++++++++++ .../StakePanel/SimulatorPopup/Info.tsx | 34 ++++++ .../index.tsx} | 104 +++++++++++++----- 3 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx create mode 100644 web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx rename web/src/pages/Courts/CourtDetails/StakePanel/{SimulatorPopup.tsx => SimulatorPopup/index.tsx} (54%) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx new file mode 100644 index 000000000..cf0207f18 --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import styled from "styled-components"; + +import PNKLogo from "svgs/styled/pnk.svg"; +import ChartIcon from "svgs/icons/chart.svg"; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +const PNKLogoAndTitle = styled.div` + display: flex; + gap: 0 16px; + align-items: center; +`; + +const StyledChartIcon = styled(ChartIcon)` + path { + fill: ${({ theme }) => theme.primaryText}; + } +`; + +const StyledPNKLogo = styled(PNKLogo)` + width: 32px; + height: 32px; + [class$="stop-1"] { + stop-color: ${({ theme }) => theme.primaryBlue}; + } + [class$="stop-2"] { + stop-color: ${({ theme }) => theme.secondaryPurple}; + } +`; + +const Title = styled.p` + margin: 0; + font-weight: 600; +`; + +const Last30DaysContainer = styled.div` + display: flex; + gap: 8px; + align-items: center; +`; + +const Last30DaysText = styled.p` + margin: 0; + font-size: 14px; + font-weight: 600; +`; + +const Header: React.FC = () => { + return ( + <Container> + <PNKLogoAndTitle> + <StyledPNKLogo /> + <Title>Simulator</Title> + </PNKLogoAndTitle> + <Last30DaysContainer> + <StyledChartIcon /> + <Last30DaysText>Last 30 Days</Last30DaysText> + </Last30DaysContainer> + </Container> + ); +}; +export default Header; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx new file mode 100644 index 000000000..baa1684c6 --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import styled from "styled-components"; +import InfoCircle from "svgs/icons/info-circle.svg"; + +const Container = styled.div` + display: inline-flex; + align-items: flex-start; + gap: 8px; + width: 100%; + margin-top: 32px; +`; + +const StyledSpan = styled.span` + color: ${({ theme }) => theme.secondaryText}; + font-size: 14px; +`; + +const StyledInfoCircle = styled(InfoCircle)` + display: inline-block; + width: 16px; + height: 16px; + flex-shrink: 0; + vertical-align: top; +`; + +const Info: React.FC = () => { + return ( + <Container> + <StyledInfoCircle /> + <StyledSpan>Note that past performance is not a guarantee of future results.</StyledSpan> + </Container> + ); +}; +export default Info; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx similarity index 54% rename from web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx rename to web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 9fdc6dd2c..71886d0de 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useState } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; import { useParams } from "react-router-dom"; @@ -8,28 +10,77 @@ import { CoinIds } from "consts/coingecko"; import { formatUSD } from "utils/format"; import { commify } from "utils/commify"; -import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; import { useCoinPrice } from "hooks/useCoinPrice"; +import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; + +import LawBalanceIcon from "svgs/icons/law-balance.svg"; +import PNKIcon from "svgs/icons/pnk.svg"; +import PileCoinsIcon from "svgs/icons/pile-coins.svg"; +import DiceIcon from "svgs/icons/dice.svg"; + +import Header from "./Header"; +import Info from "./Info"; const SimulatorPopupContainer = styled.div` position: absolute; - top: 100px; - right: 50px; - width: 400px; + top: 200px; + left: 50%; + transform: translateX(-50%); + width: ${responsiveSize(344, 480)}; background-color: ${({ theme }) => theme.lightBlue}; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); padding: 20px; border-radius: 8px; - border: 1px ${({ theme }) => theme.mediumBlue}; + border: 1px solid ${({ theme }) => theme.mediumBlue}; + + ${landscapeStyle( + () => css` + top: -4px; + left: auto; + right: 0px; + transform: none; + ` + )} +`; + +const ItemsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px 0; `; const SimulatorItem = styled.div` display: flex; - flex-direction: row; + align-items: center; font-size: 14px; - margin-bottom: 16px; +`; + +const IconWrapper = styled.div` + display: flex; + svg { + width: 14px; + height: 14px; + fill: ${({ theme }) => theme.secondaryPurple}; + } + margin-right: 8px; +`; + +const Divider = styled.hr` + display: flex; + border: none; + height: 1px; + background-color: ${({ theme }) => theme.mediumBlue}; + margin: 12px 0 16px 0; +`; + +const StyledDescription = styled.span` + margin-right: 4px; color: ${({ theme }) => theme.secondaryText}; - gap: 4px; +`; + +const StyledValue = styled.span` + font-weight: 600; + color: ${({ theme }) => theme.primaryText}; `; function beautifyStatNumber(value: number): string { @@ -78,24 +129,27 @@ const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) const expectedRewardsUSD = formatUSD(courtData.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD); const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); + const simulatorItems = [ + { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} cases` }, + { icon: <PNKIcon />, description: "You would have", value: `${expectedVotes} votes` }, + { icon: <PileCoinsIcon />, description: "You would have earned", value: `${expectedRewardsUSD}` }, + { icon: <DiceIcon />, description: "Your juror odds would be", value: `${jurorOdds}` }, + ]; + return ( <SimulatorPopupContainer> - <SimulatorItem> - <strong>Cases</strong> - You would have been drawn in: {expectedCases} cases - </SimulatorItem> - <SimulatorItem> - <strong>Votes</strong> - You would have: {expectedVotes} votes - </SimulatorItem> - <SimulatorItem> - <strong>Rewards</strong> - Potential earnings: {expectedRewardsUSD} - </SimulatorItem> - <SimulatorItem> - <strong>Juror Odds</strong> - Your juror odds: {jurorOdds} - </SimulatorItem> + <Header /> + <Divider /> + <ItemsContainer> + {simulatorItems.map((item, index) => ( + <SimulatorItem key={index}> + <IconWrapper>{item.icon}</IconWrapper> + <StyledDescription>{item.description} </StyledDescription> + <StyledValue>{item.value}</StyledValue> + </SimulatorItem> + ))} + </ItemsContainer> + <Info /> </SimulatorPopupContainer> ); }; From 8a97cfb974e2fd2b9d0e01496d071d620580c112 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:42:48 +0200 Subject: [PATCH 11/27] feat: new icons for stats, improve wording, reorder imports, style adjustments --- web/src/assets/svgs/icons/pnk-eth.svg | 3 + web/src/assets/svgs/icons/pnk-usd.svg | 8 ++ .../{StakePanel/SimulatorPopup => }/Info.tsx | 4 +- .../StakePanel/SimulatorPopup/index.tsx | 9 +- web/src/pages/Courts/CourtDetails/Stats.tsx | 90 ++++++++++--------- 5 files changed, 65 insertions(+), 49 deletions(-) create mode 100644 web/src/assets/svgs/icons/pnk-eth.svg create mode 100644 web/src/assets/svgs/icons/pnk-usd.svg rename web/src/pages/Courts/CourtDetails/{StakePanel/SimulatorPopup => }/Info.tsx (91%) diff --git a/web/src/assets/svgs/icons/pnk-eth.svg b/web/src/assets/svgs/icons/pnk-eth.svg new file mode 100644 index 000000000..209c55976 --- /dev/null +++ b/web/src/assets/svgs/icons/pnk-eth.svg @@ -0,0 +1,3 @@ +<svg width="49" height="48" viewBox="0 0 49 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M27.335 27.575L34.3304 23.225L27.335 11L20.335 23.225L27.335 27.575ZM20.335 24.6219L27.335 28.9719L34.335 24.6219L27.335 35L20.335 24.6219ZM13.2712 26L18.2993 26.2277L20.335 30.8111L17.463 35L12.4075 34.6719L10.335 29.7059L13.2712 26ZM16.9574 28.642L13.3134 30.2553L16.4288 32.6962L16.9574 28.642ZM16.5355 27.8335L13.6233 26.5629L13.0116 29.2345L16.5355 27.8335ZM15.7974 33.3104L12.6324 31.0202L12.709 34.1235L15.7974 33.3104ZM19.8294 30.801L17.6726 28.5833L17.1034 32.8479L19.8294 30.801ZM16.5395 33.8092L13.8702 34.5161L17.0092 34.7198L16.5395 33.8092ZM19.4079 31.8072L17.0714 33.571L17.5357 34.4904L19.4079 31.8072ZM18.3027 26.8681L17.8489 27.8122L19.5381 29.5522L18.3027 26.8681ZM17.7373 26.4164L14.771 26.2943L17.2178 27.3789L17.7373 26.4164ZM12.838 26.8529L10.7603 29.5115L12.1494 29.8096L12.838 26.8529ZM12.0656 30.3311L10.6698 30.0317L12.1262 33.5085L12.0656 30.3311Z"/> +</svg> diff --git a/web/src/assets/svgs/icons/pnk-usd.svg b/web/src/assets/svgs/icons/pnk-usd.svg new file mode 100644 index 000000000..c4abeeb06 --- /dev/null +++ b/web/src/assets/svgs/icons/pnk-usd.svg @@ -0,0 +1,8 @@ +<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5858 9.43018L20.5351 9L15.25 16L18.9806 25.3803L28.0804 26L33.25 18.0876L29.5858 9.43018ZM20.6112 17.0378L27.1705 13.9904L26.2189 21.6484L20.6112 17.0378ZM21.1691 10.0633L26.411 12.4633L20.0679 15.1095L21.1691 10.0633ZM19.3854 18.4827L25.0824 22.8085L19.5232 24.3444L19.3854 18.4827ZM28.4578 13.8795L32.34 18.0686L27.4332 21.9349L28.4578 13.8795ZM21.6134 25.0861L26.4181 23.7507L27.2637 25.4708L21.6134 25.0861ZM27.3756 23.3008L31.5814 19.9691L28.2113 25.0375L27.3756 23.3008ZM28.7751 12.423L29.592 10.6398L31.8157 15.7097L28.7751 12.423ZM23.2349 9.55598L28.5742 9.78645L27.6392 11.6045L23.2349 9.55598ZM16.0156 15.6328L19.7554 10.6111L18.516 16.1959L16.0156 15.6328ZM15.8528 16.6154L18.3652 17.181L18.4743 23.1827L15.8528 16.6154Z"/> +<path d="M9 29C9 28.8619 9.11193 28.75 9.25 28.75L39.25 28.75C39.3881 28.75 39.5 28.8619 39.5 29C39.5 29.1381 39.3881 29.25 39.25 29.25L9.25 29.25C9.11193 29.25 9 29.1381 9 29Z"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8424 36.2389C13.836 36.6283 13.9286 36.9351 14.1201 37.1593C14.3117 37.3835 14.6277 37.5015 15.0683 37.5133V35.3717C14.8065 35.3068 14.5447 35.2301 14.2829 35.1416C14.0275 35.0472 13.7945 34.9292 13.5838 34.7876C13.3795 34.6401 13.2134 34.4572 13.0857 34.2389C12.958 34.0206 12.8942 33.7493 12.8942 33.4248C12.8942 33.1416 12.9517 32.8938 13.0666 32.6814C13.1879 32.4631 13.3475 32.2832 13.5455 32.1416C13.7498 31.9941 13.9828 31.882 14.2446 31.8053C14.5064 31.7286 14.781 31.6903 15.0683 31.6903V31H15.4801V31.6903C15.7675 31.6903 16.0388 31.7257 16.2942 31.7965C16.5496 31.8673 16.7699 31.9735 16.9551 32.115C17.1466 32.2566 17.2967 32.4366 17.4052 32.6549C17.5201 32.8732 17.5776 33.1268 17.5776 33.4159H16.4858C16.473 33.115 16.3868 32.885 16.2272 32.7257C16.0739 32.5664 15.8249 32.4867 15.4801 32.4867V34.354C15.7675 34.4248 16.0484 34.5074 16.3229 34.6018C16.5975 34.6962 16.8401 34.8171 17.0508 34.9646C17.2615 35.1121 17.4307 35.295 17.5584 35.5133C17.6861 35.7316 17.75 36 17.75 36.3186C17.75 36.649 17.6893 36.9381 17.568 37.1858C17.4531 37.4277 17.2935 37.6313 17.0892 37.7965C16.8848 37.9558 16.6454 38.0767 16.3708 38.1593C16.0963 38.2478 15.7994 38.2979 15.4801 38.3097V39H15.0683V38.3097C14.7235 38.3038 14.4074 38.2537 14.1201 38.1593C13.8328 38.0649 13.587 37.9322 13.3826 37.7611C13.1783 37.5841 13.0187 37.3687 12.9038 37.115C12.7952 36.8614 12.7441 36.5693 12.7505 36.2389H13.8424ZM15.0683 32.4867C14.7235 32.4867 14.4553 32.5546 14.2638 32.6903C14.0786 32.8201 13.986 33.0413 13.986 33.354C13.986 33.4956 14.018 33.6165 14.0818 33.7168C14.1457 33.8171 14.2255 33.9027 14.3212 33.9735C14.4234 34.0442 14.5383 34.1032 14.666 34.1504C14.8001 34.1917 14.9342 34.2271 15.0683 34.2566V32.4867ZM15.4801 37.5133C15.8249 37.4897 16.1059 37.3982 16.3229 37.2389C16.5464 37.0737 16.6582 36.8142 16.6582 36.4602C16.6582 36.295 16.623 36.1563 16.5528 36.0442C16.489 35.9322 16.3996 35.8378 16.2846 35.7611C16.1761 35.6844 16.0516 35.6224 15.9111 35.5752C15.7706 35.528 15.627 35.4867 15.4801 35.4513V37.5133Z"/> +<path d="M30.4014 36.3812C30.4014 36.9181 30.2073 37.3393 29.8191 37.6449C29.431 37.9505 28.8955 38.1033 28.2127 38.1033C27.53 38.1033 26.9711 37.9973 26.5361 37.7853V36.852C26.8114 36.9814 27.1033 37.0833 27.4116 37.1576C27.7227 37.2319 28.0118 37.2691 28.2788 37.2691C28.6698 37.2691 28.9574 37.1948 29.1419 37.0461C29.3291 36.8974 29.4227 36.6978 29.4227 36.4473C29.4227 36.2216 29.3374 36.0302 29.1667 35.8733C28.996 35.7164 28.6436 35.5306 28.1095 35.3158C27.5589 35.0928 27.1707 34.8382 26.945 34.5518C26.7192 34.2655 26.6063 33.9214 26.6063 33.5194C26.6063 33.0156 26.7853 32.6192 27.1432 32.3301C27.5011 32.0411 27.9815 31.8965 28.5844 31.8965C29.1625 31.8965 29.7379 32.0232 30.3106 32.2764L29.9967 33.0817C29.4599 32.856 28.9808 32.7431 28.5596 32.7431C28.2403 32.7431 27.998 32.8133 27.8328 32.9537C27.6676 33.0913 27.585 33.2744 27.585 33.5029C27.585 33.6599 27.6181 33.7948 27.6842 33.9076C27.7502 34.0178 27.859 34.1224 28.0104 34.2215C28.1618 34.3206 28.4344 34.4514 28.828 34.6138C29.2713 34.7982 29.5962 34.9703 29.8026 35.13C30.0091 35.2897 30.1605 35.47 30.2569 35.671C30.3532 35.8719 30.4014 36.1087 30.4014 36.3812Z"/> +<path d="M25.5362 31.94V35.8466C25.5362 36.2926 25.4398 36.6835 25.2471 37.0194C25.0572 37.3525 24.7805 37.6099 24.4171 37.7916C24.0564 37.9705 23.6242 38.06 23.1204 38.06C22.3715 38.06 21.7893 37.8618 21.3736 37.4654C20.9579 37.0689 20.75 36.5238 20.75 35.83V31.94H21.7411V35.7598C21.7411 36.2581 21.8567 36.6257 22.088 36.8624C22.3192 37.0992 22.6744 37.2176 23.1534 37.2176C24.0839 37.2176 24.5492 36.7289 24.5492 35.7516V31.94H25.5362Z"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M35.4814 37.2297C36.0348 36.7011 36.3114 35.9385 36.3114 34.9419C36.3114 34.0031 36.0444 33.2749 35.5103 32.7574C34.9762 32.2398 34.2288 31.981 33.2679 31.981H31.4014V38.0184H33.0904C34.131 38.0184 34.928 37.7555 35.4814 37.2297ZM33.2432 32.8069C34.5949 32.8069 35.2708 33.5296 35.2708 34.9749C35.2708 36.4506 34.544 37.1884 33.0904 37.1884H32.3883V32.8069H33.2432Z"/> +</svg> diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx b/web/src/pages/Courts/CourtDetails/Info.tsx similarity index 91% rename from web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx rename to web/src/pages/Courts/CourtDetails/Info.tsx index baa1684c6..99db7b491 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Info.tsx +++ b/web/src/pages/Courts/CourtDetails/Info.tsx @@ -3,11 +3,9 @@ import styled from "styled-components"; import InfoCircle from "svgs/icons/info-circle.svg"; const Container = styled.div` - display: inline-flex; + display: flex; align-items: flex-start; gap: 8px; - width: 100%; - margin-top: 32px; `; const StyledSpan = styled.span` diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 71886d0de..abdc987eb 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -19,7 +19,7 @@ import PileCoinsIcon from "svgs/icons/pile-coins.svg"; import DiceIcon from "svgs/icons/dice.svg"; import Header from "./Header"; -import Info from "./Info"; +import Info from "../../Info"; const SimulatorPopupContainer = styled.div` position: absolute; @@ -47,6 +47,7 @@ const ItemsContainer = styled.div` display: flex; flex-direction: column; gap: 16px 0; + margin-bottom: 32px; `; const SimulatorItem = styled.div` @@ -130,10 +131,10 @@ const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); const simulatorItems = [ - { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} cases` }, - { icon: <PNKIcon />, description: "You would have", value: `${expectedVotes} votes` }, + { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} case(s)` }, + { icon: <PNKIcon />, description: "You would have had", value: `${expectedVotes} vote(s)` }, { icon: <PileCoinsIcon />, description: "You would have earned", value: `${expectedRewardsUSD}` }, - { icon: <DiceIcon />, description: "Your juror odds would be", value: `${jurorOdds}` }, + { icon: <DiceIcon />, description: "Your juror odds would have been", value: `${jurorOdds}` }, ]; return ( diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 2afe6a7e4..91dcd87a0 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -1,7 +1,10 @@ import React, { useMemo, useState } from "react"; import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; import { useParams } from "react-router-dom"; +import { Accordion, DropdownSelect } from "@kleros/ui-components-library"; import EthereumIcon from "svgs/icons/ethereum.svg"; import BalanceIcon from "svgs/icons/law-balance.svg"; @@ -11,25 +14,24 @@ import VotesPerPNKIcon from "svgs/icons/votes-per-pnk.svg"; import PNKIcon from "svgs/icons/pnk.svg"; import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg"; import VoteStake from "svgs/icons/vote-stake.svg"; -import RewardsPerPnk from "svgs/icons/rewards-per-pnk.svg"; +import PNKUSDIcon from "svgs/icons/pnk-usd.svg"; +import PNKETHIcon from "svgs/icons/pnk-eth.svg"; import ChartIcon from "svgs/icons/chart.svg"; import { CoinIds } from "consts/coingecko"; + import { useCoinPrice } from "hooks/useCoinPrice"; +import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; +import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; + import { calculateSubtextRender } from "utils/calculateSubtextRender"; import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; import { isUndefined } from "utils/index"; - -import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; - -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; +import { commify } from "utils/commify"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; -import { commify } from "utils/commify"; -import { Accordion, DropdownSelect } from "@kleros/ui-components-library"; -import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; +import Info from "./Info"; const StyledAccordion = styled(Accordion)` width: 100%; @@ -49,23 +51,6 @@ const StyledAccordion = styled(Accordion)` } `; -function beautifyStatNumber(value: number): string { - const absValue = Math.abs(value); - - if (absValue >= 1e9) { - return `${commify((value / 1e9).toFixed(2))}B`; - } else if (absValue >= 1e6) { - return `${commify((value / 1e6).toFixed(2))}M`; - } else if (absValue >= 1e3) { - return `${commify((value / 1e3).toFixed(0))}K`; - } else if (absValue < 1 && absValue !== 0) { - const inverseValue = 1 / absValue; - return commify(inverseValue.toFixed(0)); - } - - return commify(value.toFixed(0)); -} - const TimeDisplayContainer = styled.div` display: flex; flex-direction: row; @@ -79,6 +64,7 @@ const AllTimeContainer = styled(TimeDisplayContainer)` const TimeSelectorContainer = styled(TimeDisplayContainer)` padding-top: 12px; + flex-wrap: wrap; `; const StyledAllTimeText = styled.p` @@ -109,6 +95,27 @@ const StyledCard = styled.div` )} `; +const StyledDropdownSelect = styled(DropdownSelect)` + margin-right: 16px; +`; + +function beautifyStatNumber(value: number): string { + const absValue = Math.abs(value); + + if (absValue >= 1e9) { + return `${commify((value / 1e9).toFixed(2))}B`; + } else if (absValue >= 1e6) { + return `${commify((value / 1e6).toFixed(2))}M`; + } else if (absValue >= 1e3) { + return `${commify((value / 1e3).toFixed(0))}K`; + } else if (absValue < 1 && absValue !== 0) { + const inverseValue = 1 / absValue; + return commify(inverseValue.toFixed(0)); + } + + return commify(value.toFixed(0)); +} + interface IStat { title: string; coinId?: number; @@ -237,8 +244,8 @@ const Stats = () => { const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; return beautifyStatNumber(pnkNeeded); }, - color: "orange", - icon: RewardsPerPnk, + color: "purple", + icon: PNKUSDIcon, }, { title: "PNK for 1 ETH", @@ -248,8 +255,8 @@ const Stats = () => { const pnkNeeded = treeExpectedRewardPerPnk; return beautifyStatNumber(pnkNeeded); }, - color: "orange", - icon: RewardsPerPnk, + color: "blue", + icon: PNKETHIcon, }, { title: "PNK for 1 Vote", @@ -298,18 +305,17 @@ const Stats = () => { </StyledCard> <TimeSelectorContainer> <StyledChartIcon /> - <StyledAllTimeText> - <DropdownSelect - smallButton - simpleButton - items={timeRanges.map((range) => ({ - value: range.value, - text: range.text, - }))} - defaultValue={selectedRange} - callback={handleTimeRangeChange} - /> - </StyledAllTimeText> + <StyledDropdownSelect + smallButton + simpleButton + items={timeRanges.map((range) => ({ + value: range.value, + text: range.text, + }))} + defaultValue={selectedRange} + callback={handleTimeRangeChange} + /> + <Info /> </TimeSelectorContainer> <StyledCard> {timeframedStats.map(({ title, getText, color, icon }, i) => { From 638c5ba634a73b5b3d58aaf9a5928d02de95861d Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:16:57 +0200 Subject: [PATCH 12/27] feat: add tooltips to the 4 stat variables, typing, wording adjustment --- web/src/components/StatDisplay.tsx | 2 +- .../StakePanel/SimulatorPopup/index.tsx | 4 +-- web/src/pages/Courts/CourtDetails/Stats.tsx | 30 +++++++++++++++---- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/web/src/components/StatDisplay.tsx b/web/src/components/StatDisplay.tsx index 8cbc0b52f..bf0d2c6b9 100644 --- a/web/src/components/StatDisplay.tsx +++ b/web/src/components/StatDisplay.tsx @@ -42,7 +42,7 @@ const createPair = (iconColor: string, backgroundColor: string) => ({ }); export interface IStatDisplay { - title: string; + title: string | React.ReactNode; text: string | React.ReactNode; subtext?: string | React.ReactNode; icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index abdc987eb..46951d81d 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -131,8 +131,8 @@ const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); const simulatorItems = [ - { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} case(s)` }, - { icon: <PNKIcon />, description: "You would have had", value: `${expectedVotes} vote(s)` }, + { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} cases` }, + { icon: <PNKIcon />, description: "You would have had", value: `${expectedVotes} votes` }, { icon: <PileCoinsIcon />, description: "You would have earned", value: `${expectedRewardsUSD}` }, { icon: <DiceIcon />, description: "Your juror odds would have been", value: `${jurorOdds}` }, ]; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 91dcd87a0..44dc170f2 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -31,6 +31,7 @@ import { commify } from "utils/commify"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; +import WithHelpTooltip from "components/WithHelpTooltip"; import Info from "./Info"; const StyledAccordion = styled(Accordion)` @@ -193,7 +194,7 @@ const stats: IStat[] = [ ]; interface ITimeframedStat { - title: string; + title: string | React.ReactNode; coinId?: number; getText: (data: { treeExpectedRewardPerPnk: number; treeVotesPerPnk: number; treeDisputesPerPnk: number }) => string; color: IStatDisplay["color"]; @@ -236,7 +237,11 @@ const Stats = () => { const timeframedStats: ITimeframedStat[] = [ { - title: "PNK for 1 USD", + title: ( + <WithHelpTooltip place="top" tooltipMsg="Amount of PNK you need to stake to earn 1 USD in rewards."> + PNK for 1 USD + </WithHelpTooltip> + ), getText: (data) => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; @@ -248,7 +253,11 @@ const Stats = () => { icon: PNKUSDIcon, }, { - title: "PNK for 1 ETH", + title: ( + <WithHelpTooltip place="top" tooltipMsg="Amount of PNK you need to stake to earn 1 ETH in rewards."> + PNK for 1 ETH + </WithHelpTooltip> + ), getText: (data) => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; if (!treeExpectedRewardPerPnk) return "N/A"; @@ -259,7 +268,11 @@ const Stats = () => { icon: PNKETHIcon, }, { - title: "PNK for 1 Vote", + title: ( + <WithHelpTooltip place="top" tooltipMsg="Amount of PNK you need to stake to get 1 vote."> + PNK for 1 Vote + </WithHelpTooltip> + ), getText: (data) => { const treeVotesPerPnk = data?.treeVotesPerPnk; return beautifyStatNumber(treeVotesPerPnk); @@ -268,7 +281,14 @@ const Stats = () => { icon: VotesPerPNKIcon, }, { - title: "PNK for 1 Case", + title: ( + <WithHelpTooltip + place="top" + tooltipMsg="Amount of PNK you need to stake to be drawn in 1 case (which may involve one or more votes)." + > + PNK for 1 Case + </WithHelpTooltip> + ), getText: (data) => { const treeDisputesPerPnk = data?.treeDisputesPerPnk; return beautifyStatNumber(treeDisputesPerPnk); From 5245f2d73d218776a5f6a8d59c9540890f577843 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:50:36 +0200 Subject: [PATCH 13/27] fix: few comments by coderabbitai --- .../StakePanel/SimulatorPopup/index.tsx | 29 +++++++------------ .../Courts/CourtDetails/StakePanel/index.tsx | 2 +- web/src/pages/Courts/CourtDetails/Stats.tsx | 21 +++++++------- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 46951d81d..c2af67711 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -1,9 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useMemo } from "react"; import styled, { css } from "styled-components"; import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; import { useParams } from "react-router-dom"; +import Skeleton from "react-loading-skeleton"; import { CoinIds } from "consts/coingecko"; @@ -101,34 +102,26 @@ function beautifyStatNumber(value: number): string { } const calculateJurorOdds = (stakingAmount: number, totalStake: number): string => { - const odds = (stakingAmount * 100) / totalStake; + const odds = totalStake !== 0 ? (stakingAmount * 100) / totalStake : 0; return `${odds.toFixed(2)}%`; }; const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) => { - const [courtData, setCourtData] = useState<any>(null); const { id } = useParams(); const timeframedCourtData = useHomePageExtraStats(30); const { prices: pricesData } = useCoinPrice([CoinIds.ETH]); const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; - useEffect(() => { - if (timeframedCourtData?.data?.courts) { - const foundCourt = timeframedCourtData?.data?.courts.find((c) => c.id === id); - if (foundCourt) { - setCourtData(foundCourt); - } - } + const foundCourt = useMemo(() => { + return timeframedCourtData?.data?.courts?.find((c) => c.id === id); }, [timeframedCourtData, id]); - if (!courtData) return null; - - const effectiveStakeAsNumber = Number(courtData.effectiveStake) / 1e18; - const expectedCases = beautifyStatNumber(stakingAmount * courtData.treeDisputesPerPnk); - const expectedVotes = beautifyStatNumber(stakingAmount * courtData.treeVotesPerPnk); - const expectedRewardsUSD = formatUSD(courtData.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD); - const jurorOdds = calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); + const effectiveStakeAsNumber = foundCourt && Number(foundCourt.effectiveStake) / 1e18; + const expectedCases = foundCourt && beautifyStatNumber(stakingAmount * foundCourt.treeDisputesPerPnk); + const expectedVotes = foundCourt && beautifyStatNumber(stakingAmount * foundCourt.treeVotesPerPnk); + const expectedRewardsUSD = foundCourt && formatUSD(foundCourt.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD); + const jurorOdds = effectiveStakeAsNumber && calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); const simulatorItems = [ { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} cases` }, @@ -146,7 +139,7 @@ const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) <SimulatorItem key={index}> <IconWrapper>{item.icon}</IconWrapper> <StyledDescription>{item.description} </StyledDescription> - <StyledValue>{item.value}</StyledValue> + <StyledValue>{!item.value.includes("undefined") ? item.value : <Skeleton width={48} />}</StyledValue> </SimulatorItem> ))} </ItemsContainer> diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx index eb3f8dd93..608768d29 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx @@ -122,7 +122,7 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = " {isStaking && Number(uncommify(amount)) > 0 ? ( <SimulatorPopup stakingAmount={ - !isUndefined(jurorBalance) + !isUndefined(jurorBalance?.[2]) ? Number(formatEther(jurorBalance?.[2])) + Number(uncommify(amount)) : Number(uncommify(amount)) } diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 44dc170f2..89b8d446f 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -193,10 +193,16 @@ const stats: IStat[] = [ }, ]; +interface ITimeframedStatData { + treeExpectedRewardPerPnk: number; + treeVotesPerPnk: number; + treeDisputesPerPnk: number; +} + interface ITimeframedStat { title: string | React.ReactNode; coinId?: number; - getText: (data: { treeExpectedRewardPerPnk: number; treeVotesPerPnk: number; treeDisputesPerPnk: number }) => string; + getText: (data: ITimeframedStatData) => string; color: IStatDisplay["color"]; icon: React.FC<React.SVGAttributes<SVGElement>>; } @@ -221,14 +227,7 @@ const Stats = () => { const { prices: pricesData } = useCoinPrice(coinIds); const foundCourt = useMemo(() => { - if (timeframedCourtData?.data?.courts) { - const foundCourt = timeframedCourtData?.data?.courts.find((c) => c.id === id); - console.log({ foundCourt }); - return foundCourt; - } else { - console.log("Court not found or diffCourts not available"); - return undefined; - } + return timeframedCourtData?.data?.courts?.find((c) => c.id === id); }, [timeframedCourtData, id]); const handleTimeRangeChange = (value: string | number) => { @@ -311,7 +310,7 @@ const Stats = () => { <StyledAllTimeText>All time</StyledAllTimeText> </AllTimeContainer> <StyledCard> - {stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => { + {stats.map(({ title, coinId, getText, getSubtext, color, icon }) => { const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; return ( <StatDisplay @@ -338,7 +337,7 @@ const Stats = () => { <Info /> </TimeSelectorContainer> <StyledCard> - {timeframedStats.map(({ title, getText, color, icon }, i) => { + {timeframedStats.map(({ title, getText, color, icon }) => { return ( <StatDisplay key={title} From 5ebfe32765db030d59a65efae8b518e6f1a6833e Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:11:10 +0200 Subject: [PATCH 14/27] feat: styling issues in stats section --- web/src/pages/Courts/CourtDetails/Stats.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 89b8d446f..cdd90e65b 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -69,9 +69,10 @@ const TimeSelectorContainer = styled(TimeDisplayContainer)` `; const StyledAllTimeText = styled.p` - color: ${({ theme }) => theme.secondaryText}; + color: ${({ theme }) => theme.primaryText}; margin: 0; font-size: 14px; + font-weight: 600; `; const StyledChartIcon = styled(ChartIcon)` @@ -98,6 +99,12 @@ const StyledCard = styled.div` const StyledDropdownSelect = styled(DropdownSelect)` margin-right: 16px; + small { + color: ${({ theme }) => theme.primaryText}; + } + svg { + fill: ${({ theme }) => theme.primaryText}; + } `; function beautifyStatNumber(value: number): string { From 418f8aab07cbdc62b1d275aace8eb0d98d1fe1ba Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:28:11 +0200 Subject: [PATCH 15/27] feat: add new stake simulator design, add new juror effectivestake metric to subgraph --- subgraph/core/schema.graphql | 1 + .../core/src/entities/JurorTokensPerCourt.ts | 33 +++ web/src/assets/svgs/icons/arrow-right.svg | 10 + web/src/assets/svgs/icons/clock.svg | 2 +- web/src/assets/svgs/icons/dollar.svg | 10 + .../queries/useJurorStakeDetailsQuery.ts | 1 + web/src/pages/Courts/CourtDetails/Info.tsx | 3 +- .../CourtDetails/StakePanel/InputDisplay.tsx | 18 +- .../StakePanel/JurorStakeDisplay.tsx | 121 --------- .../StakePanel/SimulatorPopup/Header.tsx | 2 + .../SimulatorPopup/QuantityToSimulate.tsx | 81 ++++++ .../StakePanel/SimulatorPopup/index.tsx | 244 +++++++++++++++--- .../Courts/CourtDetails/StakePanel/index.tsx | 48 +--- 13 files changed, 359 insertions(+), 215 deletions(-) create mode 100644 web/src/assets/svgs/icons/arrow-right.svg create mode 100644 web/src/assets/svgs/icons/dollar.svg delete mode 100644 web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx create mode 100644 web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx diff --git a/subgraph/core/schema.graphql b/subgraph/core/schema.graphql index 0da8fa48a..d75c3b101 100644 --- a/subgraph/core/schema.graphql +++ b/subgraph/core/schema.graphql @@ -120,6 +120,7 @@ type JurorTokensPerCourt @entity { id: ID! # user.id-court.id juror: User! court: Court! + effectiveStake: BigInt! staked: BigInt! locked: BigInt! delayed: BigInt! diff --git a/subgraph/core/src/entities/JurorTokensPerCourt.ts b/subgraph/core/src/entities/JurorTokensPerCourt.ts index 42eba9e5f..8f5e31a7c 100644 --- a/subgraph/core/src/entities/JurorTokensPerCourt.ts +++ b/subgraph/core/src/entities/JurorTokensPerCourt.ts @@ -23,6 +23,7 @@ export function createJurorTokensPerCourt(jurorAddress: string, courtID: string) const jurorTokens = new JurorTokensPerCourt(id); jurorTokens.juror = jurorAddress; jurorTokens.court = courtID; + jurorTokens.effectiveStake = ZERO; jurorTokens.staked = ZERO; jurorTokens.locked = ZERO; jurorTokens.delayed = ZERO; @@ -31,6 +32,37 @@ export function createJurorTokensPerCourt(jurorAddress: string, courtID: string) return jurorTokens; } +export function updateJurorEffectiveStake(jurorAddress: string, courtID: string): void { + let court = Court.load(courtID); + if (!court) { + return; + } + + while (court) { + const jurorTokensPerCourt = ensureJurorTokensPerCourt(jurorAddress, court.id); + let totalStake = jurorTokensPerCourt.staked; + const childrenCourts = court.children.load(); + + for (let i = 0; i < childrenCourts.length; i++) { + const childCourtID = childrenCourts[i].id; + const childCourt = Court.load(childCourtID); + if (childCourt) { + const childJurorTokensPerCourt = ensureJurorTokensPerCourt(jurorAddress, childCourt.id); + totalStake = totalStake.plus(childJurorTokensPerCourt.effectiveStake); + } + } + + jurorTokensPerCourt.effectiveStake = totalStake; + jurorTokensPerCourt.save(); + + if (court.parent && court.parent !== null) { + court = Court.load(court.parent as string); + } else { + break; + } + } +} + export function updateJurorStake( jurorAddress: string, courtID: string, @@ -61,6 +93,7 @@ export function updateJurorStake( juror.save(); court.save(); updateEffectiveStake(courtID); + updateJurorEffectiveStake(jurorAddress, courtID); } export function updateJurorDelayedStake(jurorAddress: string, courtID: string, amount: BigInt): void { diff --git a/web/src/assets/svgs/icons/arrow-right.svg b/web/src/assets/svgs/icons/arrow-right.svg new file mode 100644 index 000000000..a46908344 --- /dev/null +++ b/web/src/assets/svgs/icons/arrow-right.svg @@ -0,0 +1,10 @@ +<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_22126_42369)"> +<path d="M9.5418 6.96634L4.0487 12.3068C3.78378 12.5644 3.35426 12.5644 3.08937 12.3068L2.44869 11.6839C2.18421 11.4268 2.18371 11.0101 2.44756 10.7524L6.80093 6.49999L2.44756 2.24765C2.18371 1.98991 2.18421 1.57318 2.44869 1.31606L3.08937 0.693175C3.35429 0.435608 3.7838 0.435608 4.0487 0.693175L9.54177 6.03366C9.80669 6.2912 9.8067 6.70878 9.5418 6.96634Z"/> +</g> +<defs> +<clipPath id="clip0_22126_42369"> +<rect width="12" height="12" fill="white" transform="translate(0 0.5)"/> +</clipPath> +</defs> +</svg> diff --git a/web/src/assets/svgs/icons/clock.svg b/web/src/assets/svgs/icons/clock.svg index 0d4bab3fb..26231aaf4 100644 --- a/web/src/assets/svgs/icons/clock.svg +++ b/web/src/assets/svgs/icons/clock.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="none"> <g clip-path="url(#clip0_19668_84870)"> - <path d="M8 0.5C3.58065 0.5 0 4.08065 0 8.5C0 12.9194 3.58065 16.5 8 16.5C12.4194 16.5 16 12.9194 16 8.5C16 4.08065 12.4194 0.5 8 0.5ZM8 14.9516C4.43548 14.9516 1.54839 12.0645 1.54839 8.5C1.54839 4.93548 4.43548 2.04839 8 2.04839C11.5645 2.04839 14.4516 4.93548 14.4516 8.5C14.4516 12.0645 11.5645 14.9516 8 14.9516ZM9.99355 11.5839L7.25484 9.59355C7.15484 9.51935 7.09677 9.40323 7.09677 9.28065V3.98387C7.09677 3.77097 7.27097 3.59677 7.48387 3.59677H8.51613C8.72903 3.59677 8.90323 3.77097 8.90323 3.98387V8.55484L11.0581 10.1226C11.2323 10.2484 11.2677 10.4903 11.1419 10.6645L10.5355 11.5C10.4097 11.671 10.1677 11.7097 9.99355 11.5839Z" fill="#999999"/> + <path d="M8 0.5C3.58065 0.5 0 4.08065 0 8.5C0 12.9194 3.58065 16.5 8 16.5C12.4194 16.5 16 12.9194 16 8.5C16 4.08065 12.4194 0.5 8 0.5ZM8 14.9516C4.43548 14.9516 1.54839 12.0645 1.54839 8.5C1.54839 4.93548 4.43548 2.04839 8 2.04839C11.5645 2.04839 14.4516 4.93548 14.4516 8.5C14.4516 12.0645 11.5645 14.9516 8 14.9516ZM9.99355 11.5839L7.25484 9.59355C7.15484 9.51935 7.09677 9.40323 7.09677 9.28065V3.98387C7.09677 3.77097 7.27097 3.59677 7.48387 3.59677H8.51613C8.72903 3.59677 8.90323 3.77097 8.90323 3.98387V8.55484L11.0581 10.1226C11.2323 10.2484 11.2677 10.4903 11.1419 10.6645L10.5355 11.5C10.4097 11.671 10.1677 11.7097 9.99355 11.5839Z"/> </g> <defs> <clipPath id="clip0_19668_84870"> diff --git a/web/src/assets/svgs/icons/dollar.svg b/web/src/assets/svgs/icons/dollar.svg new file mode 100644 index 000000000..b6c4ae1f8 --- /dev/null +++ b/web/src/assets/svgs/icons/dollar.svg @@ -0,0 +1,10 @@ +<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_22126_42353)"> +<path d="M6.81115 3.10177C6.25363 3.10177 5.82 3.2205 5.51026 3.45796C5.21085 3.6851 5.06115 4.07227 5.06115 4.61947C5.06115 4.86726 5.11277 5.07891 5.21602 5.25442C5.31926 5.42994 5.44832 5.57965 5.60318 5.70354C5.76838 5.82743 5.95422 5.93068 6.16071 6.01327C6.37752 6.08555 6.59433 6.14749 6.81115 6.19911V3.10177ZM4.82885 9.66814C4.81852 10.3496 4.96823 10.8864 5.27796 11.2788C5.5877 11.6711 6.09876 11.8776 6.81115 11.8982V8.15044C6.38784 8.03687 5.96454 7.90265 5.54124 7.74779C5.12826 7.5826 4.75141 7.37611 4.41071 7.12832C4.08032 6.87021 3.81189 6.55015 3.6054 6.16814C3.39891 5.78614 3.29566 5.31121 3.29566 4.74336C3.29566 4.24779 3.38858 3.81416 3.57442 3.44248C3.77059 3.06047 4.0287 2.74557 4.34876 2.49779C4.67914 2.23968 5.05599 2.04351 5.47929 1.90929C5.90259 1.77507 6.34655 1.70796 6.81115 1.70796V0.5H7.47708V1.70796C7.94168 1.70796 8.38047 1.76991 8.79345 1.8938C9.20643 2.0177 9.56262 2.20354 9.86203 2.45133C10.1718 2.69911 10.4144 3.01401 10.5899 3.39602C10.7758 3.77802 10.8687 4.22198 10.8687 4.72788H9.10318C9.08253 4.20133 8.94315 3.79867 8.68504 3.51991C8.43725 3.24115 8.0346 3.10177 7.47708 3.10177V6.36947C7.94168 6.49336 8.39596 6.6379 8.83991 6.8031C9.28386 6.96829 9.67619 7.17994 10.0169 7.43805C10.3576 7.69616 10.6312 8.01622 10.8377 8.39823C11.0442 8.78024 11.1474 9.25 11.1474 9.80752C11.1474 10.3857 11.0493 10.8916 10.8532 11.3252C10.6673 11.7485 10.4092 12.1047 10.0788 12.3938C9.74846 12.6726 9.3613 12.8842 8.91734 13.0288C8.47339 13.1836 7.9933 13.2714 7.47708 13.292V14.5H6.81115V13.292C6.25363 13.2817 5.74256 13.194 5.27796 13.0288C4.81336 12.8636 4.41587 12.6313 4.08548 12.3319C3.7551 12.0221 3.49699 11.6453 3.31115 11.2013C3.13563 10.7574 3.05304 10.2463 3.06336 9.66814H4.82885ZM7.47708 11.8982C8.0346 11.8569 8.48888 11.6969 8.83991 11.4181C9.20127 11.1291 9.38195 10.6748 9.38195 10.0553C9.38195 9.76622 9.32516 9.5236 9.21159 9.32743C9.10835 9.13127 8.9638 8.96607 8.77796 8.83186C8.60245 8.69764 8.40112 8.58923 8.17398 8.50664C7.94684 8.42404 7.71454 8.35177 7.47708 8.28982V11.8982Z" fill="#B45FFF"/> +</g> +<defs> +<clipPath id="clip0_22126_42353"> +<rect width="14" height="14" fill="white" transform="translate(0 0.5)"/> +</clipPath> +</defs> +</svg> diff --git a/web/src/hooks/queries/useJurorStakeDetailsQuery.ts b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts index b0155bfa5..51b54c51a 100644 --- a/web/src/hooks/queries/useJurorStakeDetailsQuery.ts +++ b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts @@ -14,6 +14,7 @@ const jurorStakeDetailsQuery = graphql(` id name } + effectiveStake staked locked } diff --git a/web/src/pages/Courts/CourtDetails/Info.tsx b/web/src/pages/Courts/CourtDetails/Info.tsx index 99db7b491..e8c8c8d28 100644 --- a/web/src/pages/Courts/CourtDetails/Info.tsx +++ b/web/src/pages/Courts/CourtDetails/Info.tsx @@ -6,6 +6,7 @@ const Container = styled.div` display: flex; align-items: flex-start; gap: 8px; + padding-top: 4px; `; const StyledSpan = styled.span` @@ -14,11 +15,9 @@ const StyledSpan = styled.span` `; const StyledInfoCircle = styled(InfoCircle)` - display: inline-block; width: 16px; height: 16px; flex-shrink: 0; - vertical-align: top; `; const Info: React.FC = () => { diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx index e93567fe3..3a98e7663 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx @@ -1,19 +1,21 @@ import React, { useState, useMemo, useEffect } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { useParams } from "react-router-dom"; import { useDebounce } from "react-use"; import { useAccount } from "wagmi"; import { REFETCH_INTERVAL } from "consts/index"; + import { useReadSortitionModuleGetJurorBalance, useReadPnkBalanceOf } from "hooks/contracts/generated"; import { useParsedAmount } from "hooks/useParsedAmount"; + import { commify, uncommify } from "utils/commify"; import { formatPNK, roundNumberDown } from "utils/format"; import { isUndefined } from "utils/index"; import { NumberInputField } from "components/NumberInputField"; - import StakeWithdrawButton, { ActionType } from "./StakeWithdrawButton"; const StyledField = styled(NumberInputField)` @@ -23,6 +25,12 @@ const StyledField = styled(NumberInputField)` const LabelArea = styled.div` display: flex; justify-content: space-between; + + ${landscapeStyle( + () => css` + width: 92%; + ` + )} `; const StyledLabel = styled.label` @@ -36,6 +44,12 @@ const InputArea = styled.div` align-items: center; gap: 12px; width: 100%; + + ${landscapeStyle( + () => css` + width: 92%; + ` + )} `; const InputFieldAndButton = styled.div` diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx deleted file mode 100644 index d84d5eb5b..000000000 --- a/web/src/pages/Courts/CourtDetails/StakePanel/JurorStakeDisplay.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useState, useEffect, useMemo, FC } from "react"; -import styled, { css } from "styled-components"; - -import { useParams } from "react-router-dom"; -import { formatEther } from "viem"; - -import DiceIcon from "svgs/icons/dice.svg"; -import PNKIcon from "svgs/icons/pnk.svg"; - -import { commify } from "utils/commify"; - -import { useCourtDetails } from "queries/useCourtDetails"; - -import { landscapeStyle } from "styles/landscapeStyle"; - -import Field from "components/Field"; - -const Container = styled.div` - display: flex; - width: 100%; - flex-direction: column; - justify-content: space-between; - gap: 8px; - margin-top: 12px; - - ${landscapeStyle( - () => css` - margin-top: 32px; - gap: 32px; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-start; - ` - )} -`; - -const format = (value: bigint | undefined): string => (value !== undefined ? formatEther(value) : "0"); - -const bigIntRatioToPercentage = (numerator: bigint, denominator: bigint): string => { - const decimalPlaces = 2; - const factor = BigInt(10) ** BigInt(decimalPlaces); - const intermediate = (numerator * factor * 100n) / denominator; - const result = intermediate.toString(); - const pointIndex = result.length - decimalPlaces; - const integerPart = result.slice(0, pointIndex) || "0"; - const decimalPart = result.slice(pointIndex); - return `${integerPart}${decimalPart.length > 0 ? "." + decimalPart : ".00"}%`; -}; - -const useCalculateJurorOdds = ( - jurorBalance: readonly [bigint, bigint, bigint, bigint] | undefined, - stakedByAllJurors: string | undefined, - loading: boolean -): string => { - return useMemo(() => { - if (loading) { - return "Calculating..."; - } - - if (!jurorBalance || !stakedByAllJurors || stakedByAllJurors === "0") { - return "0.00%"; - } - - return bigIntRatioToPercentage(jurorBalance[2], BigInt(stakedByAllJurors)); - }, [jurorBalance, stakedByAllJurors, loading]); -}; - -interface IJurorBalanceDisplay { - jurorBalance: readonly [bigint, bigint, bigint, bigint] | undefined; -} - -const JurorBalanceDisplay: FC<IJurorBalanceDisplay> = ({ jurorBalance }) => { - const { id } = useParams(); - const { data: courtDetails } = useCourtDetails(id); - const stakedByAllJurors = courtDetails?.court?.stake; - - const [loading, setLoading] = useState(false); - const [previousJurorBalance, setPreviousJurorBalance] = useState<bigint | undefined>(undefined); - const [previousStakedByAllJurors, setPreviousStakedByAllJurors] = useState<bigint | undefined>(undefined); - - useEffect(() => { - if (previousJurorBalance !== undefined && jurorBalance?.[2] !== previousJurorBalance) { - setLoading(true); - } - setPreviousJurorBalance(jurorBalance?.[2]); - }, [jurorBalance, previousJurorBalance]); - - useEffect(() => { - if (loading && stakedByAllJurors !== undefined && BigInt(stakedByAllJurors) !== previousStakedByAllJurors) { - setLoading(false); - } - if (stakedByAllJurors !== undefined) { - setPreviousStakedByAllJurors(BigInt(stakedByAllJurors)); - } - }, [stakedByAllJurors, loading, previousStakedByAllJurors]); - - const jurorOdds = useCalculateJurorOdds(jurorBalance, stakedByAllJurors, loading); - - const data = [ - { - icon: PNKIcon, - name: "My Stake", - value: `${commify(format(jurorBalance?.[2]))} PNK`, - }, - { - icon: DiceIcon, - name: "Juror Odds", - value: jurorOdds, - }, - ]; - - return ( - <Container> - {data.map(({ icon, name, value }) => ( - <Field isJurorBalance={true} key={name} {...{ icon, name, value }} /> - ))} - </Container> - ); -}; - -export default JurorBalanceDisplay; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx index cf0207f18..3f44cb01b 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx @@ -8,6 +8,8 @@ const Container = styled.div` display: flex; align-items: center; justify-content: space-between; + flex-wrap: wrap; + gap: 8px; `; const PNKLogoAndTitle = styled.div` diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx new file mode 100644 index 000000000..b8f807e0f --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import styled from "styled-components"; + +import { commify } from "utils/commify"; + +import WithHelpTooltip from "components/WithHelpTooltip"; + +const Container = styled.div` + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + gap: 0 8px; +`; + +const TextWithTooltipContainer = styled.div` + color: ${({ theme }) => theme.secondaryPurple}; + font-size: 14px; + + > div { + svg { + fill: ${({ theme }) => theme.secondaryPurple}; + margin-left: 4px; + } + } +`; + +const Quantity = styled.p` + font-size: 14px; + color: ${({ theme }) => theme.primaryText}; + margin: 0; +`; + +const FinalQuantity = styled(Quantity)` + font-weight: 600; +`; + +const StyledMathematicalOperation = styled.p` + font-size: 14px; + color: ${({ theme }) => theme.secondaryText}; + margin: 0; +`; + +interface IQuantityToSimulate { + isStaking: boolean; + currentEffectiveStake: number; + currentSpecificStake: number; + amountToStake: number; +} + +const QuantityToSimulate: React.FC<IQuantityToSimulate> = ({ + isStaking, + currentEffectiveStake, + currentSpecificStake, + amountToStake, +}) => { + return ( + <Container> + <Quantity>{commify(currentEffectiveStake)} PNK</Quantity> + <TextWithTooltipContainer> + <WithHelpTooltip + tooltipMsg={`Current Stake (Sum of): Amount of PNK staked in this court (${commify( + currentSpecificStake + )} PNK); Amount of PNK staked on its sub-courts (${commify( + currentEffectiveStake - currentSpecificStake + )} PNK)`} + > + Current Stake + </WithHelpTooltip> + </TextWithTooltipContainer> + <StyledMathematicalOperation>{isStaking ? "+" : "-"}</StyledMathematicalOperation> + <Quantity>{commify(amountToStake)} PNK</Quantity> + <StyledMathematicalOperation>=</StyledMathematicalOperation> + <FinalQuantity> + {isStaking ? commify(currentEffectiveStake + amountToStake) : commify(currentEffectiveStake - amountToStake)}{" "} + PNK + </FinalQuantity> + </Container> + ); +}; +export default QuantityToSimulate; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index c2af67711..905443ba4 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -1,70 +1,66 @@ import React, { useMemo } from "react"; import styled, { css } from "styled-components"; import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; import { useParams } from "react-router-dom"; import Skeleton from "react-loading-skeleton"; +import { useAccount } from "wagmi"; +import { formatEther } from "viem"; import { CoinIds } from "consts/coingecko"; import { formatUSD } from "utils/format"; import { commify } from "utils/commify"; +import { isUndefined } from "utils/index"; import { useCoinPrice } from "hooks/useCoinPrice"; import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; +import { useJurorStakeDetailsQuery } from "queries/useJurorStakeDetailsQuery"; +import GavelIcon from "svgs/icons/gavel.svg"; import LawBalanceIcon from "svgs/icons/law-balance.svg"; -import PNKIcon from "svgs/icons/pnk.svg"; -import PileCoinsIcon from "svgs/icons/pile-coins.svg"; import DiceIcon from "svgs/icons/dice.svg"; +import DollarIcon from "svgs/icons/dollar.svg"; +import ClockIcon from "svgs/icons/clock.svg"; +import ArrowRightIcon from "svgs/icons/arrow-right.svg"; import Header from "./Header"; +import QuantityToSimulate from "./QuantityToSimulate"; import Info from "../../Info"; +import WithHelpTooltip from "components/WithHelpTooltip"; -const SimulatorPopupContainer = styled.div` - position: absolute; - top: 200px; - left: 50%; - transform: translateX(-50%); - width: ${responsiveSize(344, 480)}; +const Container = styled.div` + display: flex; + flex-direction: column; + max-width: 480px; background-color: ${({ theme }) => theme.lightBlue}; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); padding: 20px; border-radius: 8px; border: 1px solid ${({ theme }) => theme.mediumBlue}; - - ${landscapeStyle( - () => css` - top: -4px; - left: auto; - right: 0px; - transform: none; - ` - )} + justify-content: center; `; const ItemsContainer = styled.div` display: flex; flex-direction: column; - gap: 16px 0; - margin-bottom: 32px; + gap: 8px 0; + margin: 24px 0; `; const SimulatorItem = styled.div` display: flex; align-items: center; font-size: 14px; + justify-content: space-between; `; const IconWrapper = styled.div` - display: flex; svg { width: 14px; height: 14px; fill: ${({ theme }) => theme.secondaryPurple}; } - margin-right: 8px; `; const Divider = styled.hr` @@ -72,19 +68,47 @@ const Divider = styled.hr` border: none; height: 1px; background-color: ${({ theme }) => theme.mediumBlue}; - margin: 12px 0 16px 0; + margin: 12px 0 8px 0; `; -const StyledDescription = styled.span` - margin-right: 4px; +const LeftContent = styled.div` + display: flex; + align-items: flex-start; + flex-direction: row; + gap: 8px; + + ${landscapeStyle( + () => css` + align-items: center; + ` + )} +`; + +const RightContent = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + +const StyledTitle = styled.span` + color: ${({ theme }) => theme.secondaryText}; +`; + +const StyledCurrentValue = styled.span` + font-weight: 600; color: ${({ theme }) => theme.secondaryText}; `; -const StyledValue = styled.span` +const StyledFutureValue = styled.span` font-weight: 600; color: ${({ theme }) => theme.primaryText}; `; +const StyledArrowRightIcon = styled(ArrowRightIcon)<{ isStaking: boolean }>` + fill: ${({ theme, isStaking }) => (isStaking ? theme.success : theme.warning)}; +`; + function beautifyStatNumber(value: number): string { const absValue = Math.abs(value); @@ -101,50 +125,184 @@ function beautifyStatNumber(value: number): string { return commify(value.toFixed(0)); } -const calculateJurorOdds = (stakingAmount: number, totalStake: number): string => { - const odds = totalStake !== 0 ? (stakingAmount * 100) / totalStake : 0; +const calculateJurorOdds = (newStake: number, totalStake: number): string => { + const odds = totalStake !== 0 ? (newStake * 100) / totalStake : 0; return `${odds.toFixed(2)}%`; }; -const SimulatorPopup: React.FC<{ stakingAmount: number }> = ({ stakingAmount }) => { +interface ISimulatorPopup { + amountToStake: number; + isStaking: boolean; +} + +const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) => { const { id } = useParams(); + const { address } = useAccount(); + const { data: stakeData } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`); + const courtStakeData = stakeData?.jurorTokensPerCourts?.find(({ court }) => court.id === id); + const currentEffectiveStake = !isUndefined(courtStakeData) ? Number(formatEther(courtStakeData.effectiveStake)) : 0; + const currentSpecificStake = !isUndefined(courtStakeData) ? Number(formatEther(courtStakeData.staked)) : 0; const timeframedCourtData = useHomePageExtraStats(30); - const { prices: pricesData } = useCoinPrice([CoinIds.ETH]); + const { prices: pricesData } = useCoinPrice([CoinIds.ETH, CoinIds.PNK]); const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; + const pnkPriceUSD = pricesData ? pricesData[CoinIds.PNK]?.price : undefined; const foundCourt = useMemo(() => { return timeframedCourtData?.data?.courts?.find((c) => c.id === id); }, [timeframedCourtData, id]); const effectiveStakeAsNumber = foundCourt && Number(foundCourt.effectiveStake) / 1e18; - const expectedCases = foundCourt && beautifyStatNumber(stakingAmount * foundCourt.treeDisputesPerPnk); - const expectedVotes = foundCourt && beautifyStatNumber(stakingAmount * foundCourt.treeVotesPerPnk); - const expectedRewardsUSD = foundCourt && formatUSD(foundCourt.treeExpectedRewardPerPnk * stakingAmount * ethPriceUSD); - const jurorOdds = effectiveStakeAsNumber && calculateJurorOdds(stakingAmount, effectiveStakeAsNumber + stakingAmount); + + const currentExpectedVotes = foundCourt && beautifyStatNumber(currentEffectiveStake * foundCourt.treeVotesPerPnk); + const futureExpectedVotes = + foundCourt && + beautifyStatNumber( + Math.max( + isStaking + ? (currentEffectiveStake + amountToStake) * foundCourt.treeVotesPerPnk + : (currentEffectiveStake - amountToStake) * foundCourt.treeVotesPerPnk, + 0 + ) + ); + + const currentExpectedCases = foundCourt && beautifyStatNumber(currentEffectiveStake * foundCourt.treeDisputesPerPnk); + const futureExpectedCases = + foundCourt && + beautifyStatNumber( + Math.max( + isStaking + ? (currentEffectiveStake + amountToStake) * foundCourt.treeDisputesPerPnk + : (currentEffectiveStake - amountToStake) * foundCourt.treeDisputesPerPnk, + 0 + ) + ); + + const currentDrawingOdds = + effectiveStakeAsNumber && calculateJurorOdds(currentEffectiveStake, effectiveStakeAsNumber + currentEffectiveStake); + const futureDrawingOdds = + effectiveStakeAsNumber && + calculateJurorOdds( + Math.max(isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake, 0), + Math.max( + effectiveStakeAsNumber + + (isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake), + 0 + ) + ); + + const currentExpectedRewardsUSD = + foundCourt && formatUSD(foundCourt.treeExpectedRewardPerPnk * currentEffectiveStake * ethPriceUSD); + const futureExpectedRewardsUSD = + foundCourt && + formatUSD( + Math.max( + foundCourt.treeExpectedRewardPerPnk * + (isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake) * + ethPriceUSD, + 0 + ) + ); + + // const monthlyRewardUSD = + // foundCourt && ethPriceUSD && currentEffectiveStake + // ? currentEffectiveStake * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD + // : undefined; + + // const currentPayback = + // foundCourt && pnkPriceUSD && monthlyRewardUSD + // ? Math.max((currentEffectiveStake * pnkPriceUSD) / monthlyRewardUSD, 0) + // : undefined; + + // const futureMonthlyRewardUSD = + // foundCourt && ethPriceUSD && currentEffectiveStake + // ? isStaking + // ? (currentEffectiveStake + amountToStake) * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD + // : (currentEffectiveStake - amountToStake) * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD + // : undefined; + + // const futurePayback = + // foundCourt && pnkPriceUSD && futureMonthlyRewardUSD + // ? Math.max( + // ((isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake) * pnkPriceUSD) / + // futureMonthlyRewardUSD, + // 0 + // ) + // : undefined; const simulatorItems = [ - { icon: <LawBalanceIcon />, description: "You would have been selected in", value: `${expectedCases} cases` }, - { icon: <PNKIcon />, description: "You would have had", value: `${expectedVotes} votes` }, - { icon: <PileCoinsIcon />, description: "You would have earned", value: `${expectedRewardsUSD}` }, - { icon: <DiceIcon />, description: "Your juror odds would have been", value: `${jurorOdds}` }, + { + title: "Votes", + icon: <GavelIcon />, + currentValue: `${currentExpectedVotes}`, + futureValue: `${futureExpectedVotes}`, + }, + { + title: "Cases", + icon: <LawBalanceIcon />, + currentValue: `${currentExpectedCases}`, + futureValue: `${futureExpectedCases}`, + }, + { + title: "Drawing Odds", + icon: <DiceIcon />, + currentValue: `${currentDrawingOdds}`, + futureValue: `${futureDrawingOdds}`, + }, + { + title: "Rewards", + icon: <DollarIcon />, + currentValue: `${currentExpectedRewardsUSD}`, + futureValue: `${futureExpectedRewardsUSD}`, + tooltipMsg: + "Estimated rewards in USD, assuming 100% coherent voting. If other jurors vote incoherently, additional rewards in the form of PNK tokens may be earned beyond this estimate.", + }, + { + title: "Payback", + icon: <ClockIcon />, + currentValue: `32 months`, + futureValue: `24 months`, + tooltipMsg: + "Estimated time to recover your PNK investment, assuming 100% coherent voting. If other jurors vote incoherently, the payback period may be shorter due to additional rewards in the form of PNK tokens.", + }, ]; return ( - <SimulatorPopupContainer> + <Container> <Header /> <Divider /> + <QuantityToSimulate {...{ currentEffectiveStake, currentSpecificStake, isStaking, amountToStake }} /> <ItemsContainer> {simulatorItems.map((item, index) => ( <SimulatorItem key={index}> - <IconWrapper>{item.icon}</IconWrapper> - <StyledDescription>{item.description} </StyledDescription> - <StyledValue>{!item.value.includes("undefined") ? item.value : <Skeleton width={48} />}</StyledValue> + <LeftContent> + <IconWrapper>{item.icon}</IconWrapper> + {item.tooltipMsg ? ( + <WithHelpTooltip place="top" tooltipMsg={item.tooltipMsg}> + <StyledTitle>{item.title}: </StyledTitle> + </WithHelpTooltip> + ) : ( + <StyledTitle>{item.title}: </StyledTitle> + )} + </LeftContent> + <RightContent> + <StyledCurrentValue> + {!item.currentValue.includes("undefined") ? item.currentValue : <Skeleton width={32} />} + </StyledCurrentValue> + <StyledArrowRightIcon {...{ isStaking }} /> + <StyledFutureValue> + {!amountToStake || amountToStake === 0 ? "Enter amount" : null} + {!isUndefined(amountToStake) && + amountToStake > 0 && + (!item.futureValue.includes("undefined") ? item.futureValue : <Skeleton width={32} />)} + </StyledFutureValue> + </RightContent> </SimulatorItem> ))} </ItemsContainer> + <Divider /> <Info /> - </SimulatorPopupContainer> + </Container> ); }; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx index 608768d29..166871a05 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx @@ -2,24 +2,16 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; import { landscapeStyle } from "styles/landscapeStyle"; -import { useAccount } from "wagmi"; -import { formatEther } from "viem"; - import BalanceIcon from "svgs/icons/balance.svg"; -import ThreePnksIcon from "svgs/styled/three-pnks.svg"; import { useLockOverlayScroll } from "hooks/useLockOverlayScroll"; -import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated"; import Popup, { PopupType } from "components/Popup/index"; import Tag from "components/Tag"; import { uncommify } from "utils/commify"; -import { isUndefined } from "utils/index"; -import { REFETCH_INTERVAL } from "consts/index"; import InputDisplay from "./InputDisplay"; -import JurorBalanceDisplay from "./JurorStakeDisplay"; import { ActionType } from "./StakeWithdrawButton"; import SimulatorPopup from "./SimulatorPopup"; @@ -34,6 +26,7 @@ const Container = styled.div` ${landscapeStyle( () => css` flex-direction: row; + justify-content: space-between; ` )}; `; @@ -64,37 +57,12 @@ const TextArea = styled.div` color: ${({ theme }) => theme.primaryText}; `; -const ThreePnksIconContainer = styled.div` - display: flex; - width: 100%; - justify-content: flex-start; - align-items: center; - - ${landscapeStyle( - () => css` - width: 50%; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: 42px; - margin-right: 52px; - ` - )}; -`; - const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = "General Court", id }) => { const [amount, setAmount] = useState(""); const [isSending, setIsSending] = useState<boolean>(false); const [isPopupOpen, setIsPopupOpen] = useState(false); const [isActive, setIsActive] = useState<boolean>(true); const [action, setAction] = useState<ActionType>(ActionType.stake); - const { address } = useAccount(); - const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ - query: { - enabled: !isUndefined(address), - refetchInterval: REFETCH_INTERVAL, - }, - args: [address ?? "0x", BigInt(id ?? 0)], - }); useLockOverlayScroll(isPopupOpen); @@ -117,17 +85,7 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = " </TextArea> <StakeArea> <InputDisplay {...{ action, isSending, setIsSending, setIsPopupOpen, amount, setAmount }} /> - <JurorBalanceDisplay {...{ jurorBalance }} /> </StakeArea> - {isStaking && Number(uncommify(amount)) > 0 ? ( - <SimulatorPopup - stakingAmount={ - !isUndefined(jurorBalance?.[2]) - ? Number(formatEther(jurorBalance?.[2])) + Number(uncommify(amount)) - : Number(uncommify(amount)) - } - /> - ) : null} {isPopupOpen && ( <Popup title={isStaking ? "Stake Confirmed" : "Withdraw Confirmed"} @@ -142,9 +100,7 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = " /> )} </LeftArea> - <ThreePnksIconContainer> - <ThreePnksIcon /> - </ThreePnksIconContainer> + <SimulatorPopup amountToStake={amount ? Number(uncommify(amount)) : 0} {...{ isStaking }} /> </Container> ); }; From f0931b1ca623addacffb37971242f1cfa40d18b3 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:49:27 +0200 Subject: [PATCH 16/27] chore: update subgraph package json version --- subgraph/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subgraph/package.json b/subgraph/package.json index f0c546309..0b24d5d05 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@kleros/kleros-v2-subgraph", - "version": "0.8.6", + "version": "0.8.7", "license": "MIT", "scripts": { "update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml", From 1f9b1558584559cfb571e431957a83dc3f158fd4 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:04:31 +0200 Subject: [PATCH 17/27] chore: update package json version --- subgraph/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subgraph/package.json b/subgraph/package.json index 0b24d5d05..4fa923ed7 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@kleros/kleros-v2-subgraph", - "version": "0.8.7", + "version": "0.8.8", "license": "MIT", "scripts": { "update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml", From 86162f094a7cf5aea2d26e1aac94505027f08dd2 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:52:14 +0100 Subject: [PATCH 18/27] chore: remove payback metric --- .../StakePanel/SimulatorPopup/index.tsx | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 905443ba4..c089c7de1 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -21,7 +21,6 @@ import GavelIcon from "svgs/icons/gavel.svg"; import LawBalanceIcon from "svgs/icons/law-balance.svg"; import DiceIcon from "svgs/icons/dice.svg"; import DollarIcon from "svgs/icons/dollar.svg"; -import ClockIcon from "svgs/icons/clock.svg"; import ArrowRightIcon from "svgs/icons/arrow-right.svg"; import Header from "./Header"; @@ -45,7 +44,7 @@ const ItemsContainer = styled.div` display: flex; flex-direction: column; gap: 8px 0; - margin: 24px 0; + margin: 24px 0 12px 0; `; const SimulatorItem = styled.div` @@ -146,7 +145,6 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) const timeframedCourtData = useHomePageExtraStats(30); const { prices: pricesData } = useCoinPrice([CoinIds.ETH, CoinIds.PNK]); const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; - const pnkPriceUSD = pricesData ? pricesData[CoinIds.PNK]?.price : undefined; const foundCourt = useMemo(() => { return timeframedCourtData?.data?.courts?.find((c) => c.id === id); @@ -204,32 +202,6 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) ) ); - // const monthlyRewardUSD = - // foundCourt && ethPriceUSD && currentEffectiveStake - // ? currentEffectiveStake * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD - // : undefined; - - // const currentPayback = - // foundCourt && pnkPriceUSD && monthlyRewardUSD - // ? Math.max((currentEffectiveStake * pnkPriceUSD) / monthlyRewardUSD, 0) - // : undefined; - - // const futureMonthlyRewardUSD = - // foundCourt && ethPriceUSD && currentEffectiveStake - // ? isStaking - // ? (currentEffectiveStake + amountToStake) * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD - // : (currentEffectiveStake - amountToStake) * foundCourt.treeExpectedRewardPerPnk * ethPriceUSD - // : undefined; - - // const futurePayback = - // foundCourt && pnkPriceUSD && futureMonthlyRewardUSD - // ? Math.max( - // ((isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake) * pnkPriceUSD) / - // futureMonthlyRewardUSD, - // 0 - // ) - // : undefined; - const simulatorItems = [ { title: "Votes", @@ -257,14 +229,6 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) tooltipMsg: "Estimated rewards in USD, assuming 100% coherent voting. If other jurors vote incoherently, additional rewards in the form of PNK tokens may be earned beyond this estimate.", }, - { - title: "Payback", - icon: <ClockIcon />, - currentValue: `32 months`, - futureValue: `24 months`, - tooltipMsg: - "Estimated time to recover your PNK investment, assuming 100% coherent voting. If other jurors vote incoherently, the payback period may be shorter due to additional rewards in the form of PNK tokens.", - }, ]; return ( From 44b00b3625497729252047a23ae72a29562b425b Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:39:06 +0100 Subject: [PATCH 19/27] fix: more correct calculations --- .../StakePanel/SimulatorPopup/index.tsx | 86 ++++++++----------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index c089c7de1..7a1758f78 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -150,57 +150,47 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) return timeframedCourtData?.data?.courts?.find((c) => c.id === id); }, [timeframedCourtData, id]); - const effectiveStakeAsNumber = foundCourt && Number(foundCourt.effectiveStake) / 1e18; - - const currentExpectedVotes = foundCourt && beautifyStatNumber(currentEffectiveStake * foundCourt.treeVotesPerPnk); - const futureExpectedVotes = - foundCourt && - beautifyStatNumber( - Math.max( - isStaking - ? (currentEffectiveStake + amountToStake) * foundCourt.treeVotesPerPnk - : (currentEffectiveStake - amountToStake) * foundCourt.treeVotesPerPnk, - 0 - ) - ); - - const currentExpectedCases = foundCourt && beautifyStatNumber(currentEffectiveStake * foundCourt.treeDisputesPerPnk); - const futureExpectedCases = - foundCourt && - beautifyStatNumber( - Math.max( - isStaking - ? (currentEffectiveStake + amountToStake) * foundCourt.treeDisputesPerPnk - : (currentEffectiveStake - amountToStake) * foundCourt.treeDisputesPerPnk, - 0 - ) - ); + const effectiveStakeAsNumber = foundCourt ? Number(foundCourt.effectiveStake) / 1e18 : 0; + + const currentTreeVotesPerPnk = foundCourt ? foundCourt.treeVotesPerPnk : 0; + const currentTreeDisputesPerPnk = foundCourt ? foundCourt.treeDisputesPerPnk : 0; + const currentTreeExpectedRewardPerPnk = foundCourt ? foundCourt.treeExpectedRewardPerPnk : 0; + + const totalVotes = effectiveStakeAsNumber * currentTreeVotesPerPnk; + const totalCases = effectiveStakeAsNumber * currentTreeDisputesPerPnk; + const totalRewards = effectiveStakeAsNumber * currentTreeExpectedRewardPerPnk; + + const futureTotalEffectiveStake = Math.max( + isStaking ? effectiveStakeAsNumber + amountToStake : effectiveStakeAsNumber - amountToStake, + 0 + ); + + const futureTreeVotesPerPnk = futureTotalEffectiveStake !== 0 ? totalVotes / futureTotalEffectiveStake : 0; + const futureTreeDisputesPerPnk = futureTotalEffectiveStake !== 0 ? totalCases / futureTotalEffectiveStake : 0; + const futureTreeExpectedRewardPerPnk = futureTotalEffectiveStake !== 0 ? totalRewards / futureTotalEffectiveStake : 0; + + const futureEffectiveStake = Math.max( + isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake, + 0 + ); + + const currentExpectedVotes = beautifyStatNumber(currentEffectiveStake * currentTreeVotesPerPnk); + const futureExpectedVotes = beautifyStatNumber(futureEffectiveStake * futureTreeVotesPerPnk); + + const currentExpectedCases = beautifyStatNumber(currentEffectiveStake * currentTreeDisputesPerPnk); + const futureExpectedCases = beautifyStatNumber(futureEffectiveStake * futureTreeDisputesPerPnk); const currentDrawingOdds = - effectiveStakeAsNumber && calculateJurorOdds(currentEffectiveStake, effectiveStakeAsNumber + currentEffectiveStake); + effectiveStakeAsNumber && calculateJurorOdds(currentEffectiveStake, effectiveStakeAsNumber); const futureDrawingOdds = - effectiveStakeAsNumber && - calculateJurorOdds( - Math.max(isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake, 0), - Math.max( - effectiveStakeAsNumber + - (isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake), - 0 - ) - ); - - const currentExpectedRewardsUSD = - foundCourt && formatUSD(foundCourt.treeExpectedRewardPerPnk * currentEffectiveStake * ethPriceUSD); - const futureExpectedRewardsUSD = - foundCourt && - formatUSD( - Math.max( - foundCourt.treeExpectedRewardPerPnk * - (isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake) * - ethPriceUSD, - 0 - ) - ); + effectiveStakeAsNumber && calculateJurorOdds(futureEffectiveStake, futureTotalEffectiveStake); + + const currentExpectedRewardsUSD = formatUSD( + currentEffectiveStake * currentTreeExpectedRewardPerPnk * (ethPriceUSD || 0) + ); + const futureExpectedRewardsUSD = formatUSD( + futureEffectiveStake * futureTreeExpectedRewardPerPnk * (ethPriceUSD || 0) + ); const simulatorItems = [ { From 0f16c7a1d21918fe8888a21e1614378b41d4bae5 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:52:43 +0100 Subject: [PATCH 20/27] fix: handle undefined values --- .../SimulatorPopup/QuantityToSimulate.tsx | 52 +++++-- .../StakePanel/SimulatorPopup/index.tsx | 142 +++++++++++------- 2 files changed, 129 insertions(+), 65 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx index b8f807e0f..4b43352f8 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx @@ -1,7 +1,9 @@ import React from "react"; import styled from "styled-components"; +import Skeleton from "react-loading-skeleton"; import { commify } from "utils/commify"; +import { isUndefined } from "utils/index"; import WithHelpTooltip from "components/WithHelpTooltip"; @@ -42,28 +44,52 @@ const StyledMathematicalOperation = styled.p` `; interface IQuantityToSimulate { + jurorCurrentEffectiveStake: number | undefined; + jurorCurrentSpecificStake: number | undefined; isStaking: boolean; - currentEffectiveStake: number; - currentSpecificStake: number; amountToStake: number; } const QuantityToSimulate: React.FC<IQuantityToSimulate> = ({ isStaking, - currentEffectiveStake, - currentSpecificStake, + jurorCurrentEffectiveStake, + jurorCurrentSpecificStake, amountToStake, }) => { + const effectiveStakeDisplay = !isUndefined(jurorCurrentEffectiveStake) ? ( + `${commify(jurorCurrentEffectiveStake)} PNK` + ) : ( + <Skeleton width={50} /> + ); + + const amountStakedInThisCourt = !isUndefined(jurorCurrentSpecificStake) + ? `${commify(jurorCurrentSpecificStake)} PNK` + : "..."; + + const amountStakedInSubCourts = + !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(jurorCurrentSpecificStake) + ? `${commify(jurorCurrentEffectiveStake - jurorCurrentSpecificStake)} PNK` + : "..."; + + const finalQuantityValue = + !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(amountToStake) + ? isStaking + ? jurorCurrentEffectiveStake + amountToStake + : jurorCurrentEffectiveStake - amountToStake + : undefined; + + const finalQuantityDisplay = !isUndefined(finalQuantityValue) ? ( + `${commify(finalQuantityValue)} PNK` + ) : ( + <Skeleton width={50} /> + ); + return ( <Container> - <Quantity>{commify(currentEffectiveStake)} PNK</Quantity> + <Quantity>{effectiveStakeDisplay}</Quantity> <TextWithTooltipContainer> <WithHelpTooltip - tooltipMsg={`Current Stake (Sum of): Amount of PNK staked in this court (${commify( - currentSpecificStake - )} PNK); Amount of PNK staked on its sub-courts (${commify( - currentEffectiveStake - currentSpecificStake - )} PNK)`} + tooltipMsg={`Current Stake (Sum of): Amount of PNK staked in this court (${amountStakedInThisCourt}); Amount of PNK staked on its sub-courts (${amountStakedInSubCourts})`} > Current Stake </WithHelpTooltip> @@ -71,11 +97,9 @@ const QuantityToSimulate: React.FC<IQuantityToSimulate> = ({ <StyledMathematicalOperation>{isStaking ? "+" : "-"}</StyledMathematicalOperation> <Quantity>{commify(amountToStake)} PNK</Quantity> <StyledMathematicalOperation>=</StyledMathematicalOperation> - <FinalQuantity> - {isStaking ? commify(currentEffectiveStake + amountToStake) : commify(currentEffectiveStake - amountToStake)}{" "} - PNK - </FinalQuantity> + <FinalQuantity>{finalQuantityDisplay}</FinalQuantity> </Container> ); }; + export default QuantityToSimulate; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 7a1758f78..6e046da95 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -139,8 +139,12 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) const { address } = useAccount(); const { data: stakeData } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`); const courtStakeData = stakeData?.jurorTokensPerCourts?.find(({ court }) => court.id === id); - const currentEffectiveStake = !isUndefined(courtStakeData) ? Number(formatEther(courtStakeData.effectiveStake)) : 0; - const currentSpecificStake = !isUndefined(courtStakeData) ? Number(formatEther(courtStakeData.staked)) : 0; + const jurorCurrentEffectiveStake = !isUndefined(courtStakeData) + ? Number(formatEther(courtStakeData.effectiveStake)) + : undefined; + const jurorCurrentSpecificStake = !isUndefined(courtStakeData) + ? Number(formatEther(courtStakeData.staked)) + : undefined; const timeframedCourtData = useHomePageExtraStats(30); const { prices: pricesData } = useCoinPrice([CoinIds.ETH, CoinIds.PNK]); @@ -150,72 +154,108 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) return timeframedCourtData?.data?.courts?.find((c) => c.id === id); }, [timeframedCourtData, id]); - const effectiveStakeAsNumber = foundCourt ? Number(foundCourt.effectiveStake) / 1e18 : 0; - - const currentTreeVotesPerPnk = foundCourt ? foundCourt.treeVotesPerPnk : 0; - const currentTreeDisputesPerPnk = foundCourt ? foundCourt.treeDisputesPerPnk : 0; - const currentTreeExpectedRewardPerPnk = foundCourt ? foundCourt.treeExpectedRewardPerPnk : 0; - - const totalVotes = effectiveStakeAsNumber * currentTreeVotesPerPnk; - const totalCases = effectiveStakeAsNumber * currentTreeDisputesPerPnk; - const totalRewards = effectiveStakeAsNumber * currentTreeExpectedRewardPerPnk; - - const futureTotalEffectiveStake = Math.max( - isStaking ? effectiveStakeAsNumber + amountToStake : effectiveStakeAsNumber - amountToStake, - 0 - ); - - const futureTreeVotesPerPnk = futureTotalEffectiveStake !== 0 ? totalVotes / futureTotalEffectiveStake : 0; - const futureTreeDisputesPerPnk = futureTotalEffectiveStake !== 0 ? totalCases / futureTotalEffectiveStake : 0; - const futureTreeExpectedRewardPerPnk = futureTotalEffectiveStake !== 0 ? totalRewards / futureTotalEffectiveStake : 0; - - const futureEffectiveStake = Math.max( - isStaking ? currentEffectiveStake + amountToStake : currentEffectiveStake - amountToStake, - 0 - ); - - const currentExpectedVotes = beautifyStatNumber(currentEffectiveStake * currentTreeVotesPerPnk); - const futureExpectedVotes = beautifyStatNumber(futureEffectiveStake * futureTreeVotesPerPnk); - - const currentExpectedCases = beautifyStatNumber(currentEffectiveStake * currentTreeDisputesPerPnk); - const futureExpectedCases = beautifyStatNumber(futureEffectiveStake * futureTreeDisputesPerPnk); + const courtCurrentEffectiveStake = foundCourt ? Number(foundCourt.effectiveStake) / 1e18 : undefined; + + const currentTreeVotesPerPnk = foundCourt?.treeVotesPerPnk; + const currentTreeDisputesPerPnk = foundCourt?.treeDisputesPerPnk; + const currentTreeExpectedRewardPerPnk = foundCourt?.treeExpectedRewardPerPnk; + + const totalVotes = + !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeVotesPerPnk) + ? courtCurrentEffectiveStake * currentTreeVotesPerPnk + : undefined; + const totalCases = + !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeDisputesPerPnk) + ? courtCurrentEffectiveStake * currentTreeDisputesPerPnk + : undefined; + const totalRewards = + !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeExpectedRewardPerPnk) + ? courtCurrentEffectiveStake * currentTreeExpectedRewardPerPnk + : undefined; + + const courtFutureEffectiveStake = !isUndefined(courtCurrentEffectiveStake) + ? Math.max(isStaking ? courtCurrentEffectiveStake + amountToStake : courtCurrentEffectiveStake - amountToStake, 0) + : undefined; + + const futureTreeVotesPerPnk = + !isUndefined(courtFutureEffectiveStake) && !isUndefined(totalVotes) + ? totalVotes / courtFutureEffectiveStake + : undefined; + const futureTreeDisputesPerPnk = + !isUndefined(courtFutureEffectiveStake) && !isUndefined(totalCases) + ? totalCases / courtFutureEffectiveStake + : undefined; + const futureTreeExpectedRewardPerPnk = + !isUndefined(courtFutureEffectiveStake) && !isUndefined(totalRewards) + ? totalRewards / courtFutureEffectiveStake + : undefined; + + const jurorFutureEffectiveStake = !isUndefined(jurorCurrentEffectiveStake) + ? Math.max(isStaking ? jurorCurrentEffectiveStake + amountToStake : jurorCurrentEffectiveStake - amountToStake, 0) + : undefined; + + const currentExpectedVotes = + !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(currentTreeVotesPerPnk) + ? beautifyStatNumber(jurorCurrentEffectiveStake * currentTreeVotesPerPnk) + : undefined; + const futureExpectedVotes = + !isUndefined(jurorFutureEffectiveStake) && !isUndefined(futureTreeVotesPerPnk) + ? beautifyStatNumber(jurorFutureEffectiveStake * futureTreeVotesPerPnk) + : undefined; + + const currentExpectedCases = + !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(currentTreeDisputesPerPnk) + ? beautifyStatNumber(jurorCurrentEffectiveStake * currentTreeDisputesPerPnk) + : undefined; + const futureExpectedCases = + !isUndefined(jurorFutureEffectiveStake) && !isUndefined(futureTreeDisputesPerPnk) + ? beautifyStatNumber(jurorFutureEffectiveStake * futureTreeDisputesPerPnk) + : undefined; const currentDrawingOdds = - effectiveStakeAsNumber && calculateJurorOdds(currentEffectiveStake, effectiveStakeAsNumber); + !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(courtCurrentEffectiveStake) + ? calculateJurorOdds(jurorCurrentEffectiveStake, courtCurrentEffectiveStake) + : undefined; const futureDrawingOdds = - effectiveStakeAsNumber && calculateJurorOdds(futureEffectiveStake, futureTotalEffectiveStake); - - const currentExpectedRewardsUSD = formatUSD( - currentEffectiveStake * currentTreeExpectedRewardPerPnk * (ethPriceUSD || 0) - ); - const futureExpectedRewardsUSD = formatUSD( - futureEffectiveStake * futureTreeExpectedRewardPerPnk * (ethPriceUSD || 0) - ); + !isUndefined(jurorFutureEffectiveStake) && !isUndefined(courtFutureEffectiveStake) + ? calculateJurorOdds(jurorFutureEffectiveStake, courtFutureEffectiveStake) + : undefined; + + const currentExpectedRewardsUSD = + !isUndefined(jurorCurrentEffectiveStake) && + !isUndefined(currentTreeExpectedRewardPerPnk) && + !isUndefined(ethPriceUSD) + ? formatUSD(jurorCurrentEffectiveStake * currentTreeExpectedRewardPerPnk * ethPriceUSD) + : undefined; + const futureExpectedRewardsUSD = + !isUndefined(jurorFutureEffectiveStake) && !isUndefined(futureTreeExpectedRewardPerPnk) && !isUndefined(ethPriceUSD) + ? formatUSD(jurorFutureEffectiveStake * futureTreeExpectedRewardPerPnk * ethPriceUSD) + : undefined; const simulatorItems = [ { title: "Votes", icon: <GavelIcon />, - currentValue: `${currentExpectedVotes}`, - futureValue: `${futureExpectedVotes}`, + currentValue: currentExpectedVotes, + futureValue: futureExpectedVotes, }, { title: "Cases", icon: <LawBalanceIcon />, - currentValue: `${currentExpectedCases}`, - futureValue: `${futureExpectedCases}`, + currentValue: currentExpectedCases, + futureValue: futureExpectedCases, }, { title: "Drawing Odds", icon: <DiceIcon />, - currentValue: `${currentDrawingOdds}`, - futureValue: `${futureDrawingOdds}`, + currentValue: currentDrawingOdds, + futureValue: futureDrawingOdds, }, { title: "Rewards", icon: <DollarIcon />, - currentValue: `${currentExpectedRewardsUSD}`, - futureValue: `${futureExpectedRewardsUSD}`, + currentValue: currentExpectedRewardsUSD, + futureValue: futureExpectedRewardsUSD, tooltipMsg: "Estimated rewards in USD, assuming 100% coherent voting. If other jurors vote incoherently, additional rewards in the form of PNK tokens may be earned beyond this estimate.", }, @@ -225,7 +265,7 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) <Container> <Header /> <Divider /> - <QuantityToSimulate {...{ currentEffectiveStake, currentSpecificStake, isStaking, amountToStake }} /> + <QuantityToSimulate {...{ jurorCurrentEffectiveStake, jurorCurrentSpecificStake, isStaking, amountToStake }} /> <ItemsContainer> {simulatorItems.map((item, index) => ( <SimulatorItem key={index}> @@ -241,14 +281,14 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) </LeftContent> <RightContent> <StyledCurrentValue> - {!item.currentValue.includes("undefined") ? item.currentValue : <Skeleton width={32} />} + {!isUndefined(item.currentValue) ? item.currentValue : <Skeleton width={32} />} </StyledCurrentValue> <StyledArrowRightIcon {...{ isStaking }} /> <StyledFutureValue> {!amountToStake || amountToStake === 0 ? "Enter amount" : null} {!isUndefined(amountToStake) && amountToStake > 0 && - (!item.futureValue.includes("undefined") ? item.futureValue : <Skeleton width={32} />)} + (!isUndefined(item.futureValue) ? item.futureValue : <Skeleton width={32} />)} </StyledFutureValue> </RightContent> </SimulatorItem> From fd3aa447f4fa252fa3c03a795a63b661b2c8b4f9 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:01:09 +0100 Subject: [PATCH 21/27] fix: simulator didnt work if wallet is not connected --- .../StakePanel/SimulatorPopup/index.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 6e046da95..c2b741977 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -139,15 +139,19 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) const { address } = useAccount(); const { data: stakeData } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`); const courtStakeData = stakeData?.jurorTokensPerCourts?.find(({ court }) => court.id === id); - const jurorCurrentEffectiveStake = !isUndefined(courtStakeData) - ? Number(formatEther(courtStakeData.effectiveStake)) - : undefined; - const jurorCurrentSpecificStake = !isUndefined(courtStakeData) - ? Number(formatEther(courtStakeData.staked)) - : undefined; + const jurorCurrentEffectiveStake = address + ? !isUndefined(courtStakeData) + ? Number(formatEther(courtStakeData.effectiveStake)) + : undefined + : 0; + const jurorCurrentSpecificStake = address + ? !isUndefined(courtStakeData) + ? Number(formatEther(courtStakeData.staked)) + : undefined + : 0; const timeframedCourtData = useHomePageExtraStats(30); - const { prices: pricesData } = useCoinPrice([CoinIds.ETH, CoinIds.PNK]); + const { prices: pricesData } = useCoinPrice([CoinIds.ETH]); const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; const foundCourt = useMemo(() => { From f23a29d5dbb41266f49b6c9cf4493b68b1d2b689 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:51:09 +0100 Subject: [PATCH 22/27] fix: abstract divider, few style issues --- .../DisputePreview/DisputeContext.tsx | 9 +-------- web/src/components/Divider.tsx | 10 ++++++++++ .../Popup/Description/SwapSuccess.tsx | 11 ++++------- web/src/components/Verdict/FinalDecision.tsx | 9 +++------ .../pages/Cases/CaseDetails/Overview/index.tsx | 10 +--------- web/src/pages/Courts/CourtDetails/Info.tsx | 1 - .../StakePanel/SimulatorPopup/index.tsx | 18 +++++++++++------- web/src/pages/Courts/CourtDetails/Stats.tsx | 4 +++- web/src/pages/Courts/CourtDetails/index.tsx | 9 +-------- web/src/pages/Resolver/Preview/index.tsx | 10 +--------- 10 files changed, 35 insertions(+), 56 deletions(-) create mode 100644 web/src/components/Divider.tsx diff --git a/web/src/components/DisputePreview/DisputeContext.tsx b/web/src/components/DisputePreview/DisputeContext.tsx index 3bb5f7b1a..1624bdb15 100644 --- a/web/src/components/DisputePreview/DisputeContext.tsx +++ b/web/src/components/DisputePreview/DisputeContext.tsx @@ -13,6 +13,7 @@ import ReactMarkdown from "components/ReactMarkdown"; import { StyledSkeleton } from "components/StyledSkeleton"; import AliasDisplay from "./Alias"; +import { Divider } from "../Divider"; const StyledH1 = styled.h1` margin: 0; @@ -60,14 +61,6 @@ const AliasesContainer = styled.div` gap: ${responsiveSize(8, 20)}; `; -const Divider = styled.hr` - width: 100%; - display: flex; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; - margin: 0; -`; interface IDisputeContext { disputeDetails?: DisputeDetails; isRpcError?: boolean; diff --git a/web/src/components/Divider.tsx b/web/src/components/Divider.tsx new file mode 100644 index 000000000..4def74af9 --- /dev/null +++ b/web/src/components/Divider.tsx @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +export const Divider = styled.hr` + display: flex; + width: 100%; + border: none; + height: 1px; + background-color: ${({ theme }) => theme.stroke}; + margin: 0; +`; diff --git a/web/src/components/Popup/Description/SwapSuccess.tsx b/web/src/components/Popup/Description/SwapSuccess.tsx index e046a013b..14ef5af9d 100644 --- a/web/src/components/Popup/Description/SwapSuccess.tsx +++ b/web/src/components/Popup/Description/SwapSuccess.tsx @@ -7,6 +7,7 @@ import PnkIcon from "svgs/tokens/pnk.svg"; import { responsiveSize } from "styles/responsiveSize"; import LightButton from "components/LightButton"; +import { Divider } from "components/Divider"; import { Token } from "pages/GetPnk/Swap/TokenSelect"; const Container = styled.div` @@ -50,14 +51,10 @@ const StyledButton = styled(LightButton)` padding-top: 0px; `; -const Divider = styled.hr` - width: 100%; - display: flex; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; +const StyledDivider = styled(Divider)` margin: ${responsiveSize(32, 64)} 0px 0px; `; + interface ISwapSuccess { hash: string; amount: string; @@ -83,7 +80,7 @@ const SwapSuccess: React.FC<ISwapSuccess> = ({ hash, amount, isClaim, from, to } Bridge from <small>Ethereum</small> to <small>Arbitrum</small> </Subtitle> )} - <Divider /> + <StyledDivider /> <StyledButton onClick={() => window.open(baseUrl, "_blank", "rel=noopener noreferrer")} text={"View transaction on Etherscan"} diff --git a/web/src/components/Verdict/FinalDecision.tsx b/web/src/components/Verdict/FinalDecision.tsx index be4c2d29f..bc0bcfc8b 100644 --- a/web/src/components/Verdict/FinalDecision.tsx +++ b/web/src/components/Verdict/FinalDecision.tsx @@ -23,6 +23,7 @@ import LightButton from "../LightButton"; import AnswerDisplay from "./Answer"; import VerdictBanner from "./VerdictBanner"; +import { Divider } from "../Divider"; const Container = styled.div` width: 100%; @@ -54,11 +55,7 @@ const StyledButton = styled(LightButton)` padding-top: 0px; `; -const Divider = styled.hr` - display: flex; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; +const StyledDivider = styled(Divider)` margin: ${responsiveSize(16, 32)} 0px; `; @@ -107,7 +104,7 @@ const FinalDecision: React.FC<IFinalDecision> = ({ arbitrable }) => { <AnswerDisplay {...{ answer, currentRuling }} /> </JuryContainer> )} - <Divider /> + <StyledDivider /> {isLoading && !isDisconnected ? ( <Skeleton width={250} height={20} /> ) : ( diff --git a/web/src/pages/Cases/CaseDetails/Overview/index.tsx b/web/src/pages/Cases/CaseDetails/Overview/index.tsx index 601fe882a..c01d5faea 100644 --- a/web/src/pages/Cases/CaseDetails/Overview/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Overview/index.tsx @@ -17,6 +17,7 @@ import { DisputeContext } from "components/DisputePreview/DisputeContext"; import { Policies } from "components/DisputePreview/Policies"; import DisputeInfo from "components/DisputeView/DisputeInfo"; import Verdict from "components/Verdict/index"; +import { Divider } from "components/Divider"; const Container = styled.div` width: 100%; @@ -27,15 +28,6 @@ const Container = styled.div` padding: ${responsiveSize(16, 32)}; `; -const Divider = styled.hr` - width: 100%; - display: flex; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; - margin: 0; -`; - interface IOverview { arbitrable?: `0x${string}`; courtID?: string; diff --git a/web/src/pages/Courts/CourtDetails/Info.tsx b/web/src/pages/Courts/CourtDetails/Info.tsx index e8c8c8d28..3fe2ffa36 100644 --- a/web/src/pages/Courts/CourtDetails/Info.tsx +++ b/web/src/pages/Courts/CourtDetails/Info.tsx @@ -6,7 +6,6 @@ const Container = styled.div` display: flex; align-items: flex-start; gap: 8px; - padding-top: 4px; `; const StyledSpan = styled.span` diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index c2b741977..3f89253f3 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -27,6 +27,7 @@ import Header from "./Header"; import QuantityToSimulate from "./QuantityToSimulate"; import Info from "../../Info"; import WithHelpTooltip from "components/WithHelpTooltip"; +import { Divider } from "components/Divider"; const Container = styled.div` display: flex; @@ -62,10 +63,7 @@ const IconWrapper = styled.div` } `; -const Divider = styled.hr` - display: flex; - border: none; - height: 1px; +const StyledDivider = styled(Divider)` background-color: ${({ theme }) => theme.mediumBlue}; margin: 12px 0 8px 0; `; @@ -108,6 +106,10 @@ const StyledArrowRightIcon = styled(ArrowRightIcon)<{ isStaking: boolean }>` fill: ${({ theme, isStaking }) => (isStaking ? theme.success : theme.warning)}; `; +const InfoContainer = styled.div` + padding-top: 4px; +`; + function beautifyStatNumber(value: number): string { const absValue = Math.abs(value); @@ -268,7 +270,7 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) return ( <Container> <Header /> - <Divider /> + <StyledDivider /> <QuantityToSimulate {...{ jurorCurrentEffectiveStake, jurorCurrentSpecificStake, isStaking, amountToStake }} /> <ItemsContainer> {simulatorItems.map((item, index) => ( @@ -298,8 +300,10 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) </SimulatorItem> ))} </ItemsContainer> - <Divider /> - <Info /> + <StyledDivider /> + <InfoContainer> + <Info /> + </InfoContainer> </Container> ); }; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index cdd90e65b..ced92478b 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -41,10 +41,12 @@ const StyledAccordion = styled(Accordion)` justify-content: unset; background-color: ${({ theme }) => theme.whiteBackground} !important; border: 1px solid ${({ theme }) => theme.stroke} !important; - color: ${({ theme }) => theme.primaryText} !important; > svg { fill: ${({ theme }) => theme.primaryText} !important; } + > p { + color: ${({ theme }) => theme.primaryText}; + } } //adds padding to body container > * > div > div { diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx index 10b4f0923..de51a910d 100644 --- a/web/src/pages/Courts/CourtDetails/index.tsx +++ b/web/src/pages/Courts/CourtDetails/index.tsx @@ -20,6 +20,7 @@ import HowItWorks from "components/HowItWorks"; import LatestCases from "components/LatestCases"; import Staking from "components/Popup/MiniGuides/Staking"; import { StyledSkeleton } from "components/StyledSkeleton"; +import { Divider } from "components/Divider"; import Description from "./Description"; import StakePanel from "./StakePanel"; @@ -77,14 +78,6 @@ const StyledBreadcrumb = styled(Breadcrumb)` } `; -const Divider = styled.hr` - width: 100%; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; - margin: 0; -`; - const CourtDetails: React.FC = () => { const { id } = useParams(); const { data: policy } = useCourtPolicy(id); diff --git a/web/src/pages/Resolver/Preview/index.tsx b/web/src/pages/Resolver/Preview/index.tsx index aa1f8f39e..cc70d88e8 100644 --- a/web/src/pages/Resolver/Preview/index.tsx +++ b/web/src/pages/Resolver/Preview/index.tsx @@ -13,6 +13,7 @@ import { responsiveSize } from "styles/responsiveSize"; import { DisputeContext } from "components/DisputePreview/DisputeContext"; import { Policies } from "components/DisputePreview/Policies"; import DisputeInfo from "components/DisputeView/DisputeInfo"; +import { Divider } from "components/Divider"; import NavigationButtons from "../NavigationButtons"; @@ -39,15 +40,6 @@ const PreviewContainer = styled.div` padding: ${responsiveSize(16, 32)}; `; -const Divider = styled.hr` - width: 100%; - display: flex; - border: none; - height: 1px; - background-color: ${({ theme }) => theme.stroke}; - margin: 0; -`; - const Header = styled.h2` margin-bottom: 32px; width: 84vw; From 024bdba0174747df98d6fd1efa89dcc1a16ba393 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:59:52 +0100 Subject: [PATCH 23/27] chore: abstract beautifystatnumber --- .../StakePanel/SimulatorPopup/index.tsx | 18 +------------ web/src/pages/Courts/CourtDetails/Stats.tsx | 27 ++++--------------- web/src/utils/beautifyStatNumber.ts | 22 +++++++++++++++ 3 files changed, 28 insertions(+), 39 deletions(-) create mode 100644 web/src/utils/beautifyStatNumber.ts diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 3f89253f3..7f6a02e40 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -10,8 +10,8 @@ import { formatEther } from "viem"; import { CoinIds } from "consts/coingecko"; import { formatUSD } from "utils/format"; -import { commify } from "utils/commify"; import { isUndefined } from "utils/index"; +import { beautifyStatNumber } from "utils/beautifyStatNumber"; import { useCoinPrice } from "hooks/useCoinPrice"; import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; @@ -110,22 +110,6 @@ const InfoContainer = styled.div` padding-top: 4px; `; -function beautifyStatNumber(value: number): string { - const absValue = Math.abs(value); - - if (absValue >= 1e9) { - return `${commify((value / 1e9).toFixed(2))}B`; - } else if (absValue >= 1e6) { - return `${commify((value / 1e6).toFixed(2))}M`; - } else if (absValue >= 1e3) { - return `${commify((value / 1e3).toFixed(0))}K`; - } else if (absValue > 0 && absValue < 1) { - return value.toFixed(2); - } - - return commify(value.toFixed(0)); -} - const calculateJurorOdds = (newStake: number, totalStake: number): string => { const odds = totalStake !== 0 ? (newStake * 100) / totalStake : 0; return `${odds.toFixed(2)}%`; diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index ced92478b..d695b1ea9 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -27,7 +27,7 @@ import { useHomePageExtraStats } from "queries/useHomePageExtraStats"; import { calculateSubtextRender } from "utils/calculateSubtextRender"; import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; import { isUndefined } from "utils/index"; -import { commify } from "utils/commify"; +import { beautifyStatNumber } from "utils/beautifyStatNumber"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; @@ -109,23 +109,6 @@ const StyledDropdownSelect = styled(DropdownSelect)` } `; -function beautifyStatNumber(value: number): string { - const absValue = Math.abs(value); - - if (absValue >= 1e9) { - return `${commify((value / 1e9).toFixed(2))}B`; - } else if (absValue >= 1e6) { - return `${commify((value / 1e6).toFixed(2))}M`; - } else if (absValue >= 1e3) { - return `${commify((value / 1e3).toFixed(0))}K`; - } else if (absValue < 1 && absValue !== 0) { - const inverseValue = 1 / absValue; - return commify(inverseValue.toFixed(0)); - } - - return commify(value.toFixed(0)); -} - interface IStat { title: string; coinId?: number; @@ -255,7 +238,7 @@ const Stats = () => { const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; - return beautifyStatNumber(pnkNeeded); + return beautifyStatNumber(pnkNeeded, true); }, color: "purple", icon: PNKUSDIcon, @@ -270,7 +253,7 @@ const Stats = () => { const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; if (!treeExpectedRewardPerPnk) return "N/A"; const pnkNeeded = treeExpectedRewardPerPnk; - return beautifyStatNumber(pnkNeeded); + return beautifyStatNumber(pnkNeeded, true); }, color: "blue", icon: PNKETHIcon, @@ -283,7 +266,7 @@ const Stats = () => { ), getText: (data) => { const treeVotesPerPnk = data?.treeVotesPerPnk; - return beautifyStatNumber(treeVotesPerPnk); + return beautifyStatNumber(treeVotesPerPnk, true); }, color: "orange", icon: VotesPerPNKIcon, @@ -299,7 +282,7 @@ const Stats = () => { ), getText: (data) => { const treeDisputesPerPnk = data?.treeDisputesPerPnk; - return beautifyStatNumber(treeDisputesPerPnk); + return beautifyStatNumber(treeDisputesPerPnk, true); }, color: "orange", icon: BalanceWithPNKIcon, diff --git a/web/src/utils/beautifyStatNumber.ts b/web/src/utils/beautifyStatNumber.ts new file mode 100644 index 000000000..ee2641ef1 --- /dev/null +++ b/web/src/utils/beautifyStatNumber.ts @@ -0,0 +1,22 @@ +import { commify } from "./commify"; + +export function beautifyStatNumber(value: number, invertValue: boolean = false): string { + const absValue = Math.abs(value); + + if (absValue >= 1e9) { + return `${commify((value / 1e9).toFixed(2))}B`; + } else if (absValue >= 1e6) { + return `${commify((value / 1e6).toFixed(2))}M`; + } else if (absValue >= 1e3) { + return `${commify((value / 1e3).toFixed(0))}K`; + } else if (absValue > 0 && absValue < 1) { + if (invertValue) { + const inverseValue = 1 / absValue; + return commify(inverseValue.toFixed(0)); + } else { + return value.toFixed(2); + } + } + + return commify(value.toFixed(0)); +} From 723cf9129e07a5c9a4901c548dfdc8e4980b2ebf Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:51:31 +0100 Subject: [PATCH 24/27] fix: beautify tweak --- web/src/utils/beautifyStatNumber.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/utils/beautifyStatNumber.ts b/web/src/utils/beautifyStatNumber.ts index ee2641ef1..36d8303f0 100644 --- a/web/src/utils/beautifyStatNumber.ts +++ b/web/src/utils/beautifyStatNumber.ts @@ -8,11 +8,11 @@ export function beautifyStatNumber(value: number, invertValue: boolean = false): } else if (absValue >= 1e6) { return `${commify((value / 1e6).toFixed(2))}M`; } else if (absValue >= 1e3) { - return `${commify((value / 1e3).toFixed(0))}K`; + return `${commify((value / 1e3).toFixed(2))}K`; } else if (absValue > 0 && absValue < 1) { if (invertValue) { const inverseValue = 1 / absValue; - return commify(inverseValue.toFixed(0)); + return beautifyStatNumber(Number(inverseValue)); } else { return value.toFixed(2); } From dafcad268fa4cbc626fc0ea44ed919a7d80ec933 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:36:53 +0100 Subject: [PATCH 25/27] fix: memoize some values to prevent recalculations --- .../StakePanel/SimulatorPopup/index.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 7f6a02e40..06481193e 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -150,18 +150,20 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) const currentTreeDisputesPerPnk = foundCourt?.treeDisputesPerPnk; const currentTreeExpectedRewardPerPnk = foundCourt?.treeExpectedRewardPerPnk; - const totalVotes = - !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeVotesPerPnk) - ? courtCurrentEffectiveStake * currentTreeVotesPerPnk - : undefined; - const totalCases = - !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeDisputesPerPnk) - ? courtCurrentEffectiveStake * currentTreeDisputesPerPnk - : undefined; - const totalRewards = - !isUndefined(courtCurrentEffectiveStake) && !isUndefined(currentTreeExpectedRewardPerPnk) - ? courtCurrentEffectiveStake * currentTreeExpectedRewardPerPnk - : undefined; + const totals = useMemo(() => { + if (isUndefined(courtCurrentEffectiveStake)) return {}; + return { + votes: !isUndefined(currentTreeVotesPerPnk) ? courtCurrentEffectiveStake * currentTreeVotesPerPnk : undefined, + cases: !isUndefined(currentTreeDisputesPerPnk) + ? courtCurrentEffectiveStake * currentTreeDisputesPerPnk + : undefined, + rewards: !isUndefined(currentTreeExpectedRewardPerPnk) + ? courtCurrentEffectiveStake * currentTreeExpectedRewardPerPnk + : undefined, + }; + }, [courtCurrentEffectiveStake, currentTreeVotesPerPnk, currentTreeDisputesPerPnk, currentTreeExpectedRewardPerPnk]); + + const { votes: totalVotes, cases: totalCases, rewards: totalRewards } = totals; const courtFutureEffectiveStake = !isUndefined(courtCurrentEffectiveStake) ? Math.max(isStaking ? courtCurrentEffectiveStake + amountToStake : courtCurrentEffectiveStake - amountToStake, 0) From 16e8547be3a8cbcbfab5d3b41f050d6629bb7071 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:32:05 +0100 Subject: [PATCH 26/27] fix: new wallets cant see simulator --- .../StakePanel/SimulatorPopup/index.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx index 06481193e..5c52bf08d 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx @@ -124,17 +124,9 @@ const SimulatorPopup: React.FC<ISimulatorPopup> = ({ amountToStake, isStaking }) const { id } = useParams(); const { address } = useAccount(); const { data: stakeData } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`); - const courtStakeData = stakeData?.jurorTokensPerCourts?.find(({ court }) => court.id === id); - const jurorCurrentEffectiveStake = address - ? !isUndefined(courtStakeData) - ? Number(formatEther(courtStakeData.effectiveStake)) - : undefined - : 0; - const jurorCurrentSpecificStake = address - ? !isUndefined(courtStakeData) - ? Number(formatEther(courtStakeData.staked)) - : undefined - : 0; + const jurorStakeData = stakeData?.jurorTokensPerCourts?.find(({ court }) => court.id === id); + const jurorCurrentEffectiveStake = address && jurorStakeData ? Number(formatEther(jurorStakeData.effectiveStake)) : 0; + const jurorCurrentSpecificStake = address && jurorStakeData ? Number(formatEther(jurorStakeData.staked)) : 0; const timeframedCourtData = useHomePageExtraStats(30); const { prices: pricesData } = useCoinPrice([CoinIds.ETH]); From 4ceb55a1f16f4ff8298f7fa65c59323c8bfb6219 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:17:43 +0100 Subject: [PATCH 27/27] chore: new dice icon --- web/src/assets/svgs/icons/dice.svg | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/web/src/assets/svgs/icons/dice.svg b/web/src/assets/svgs/icons/dice.svg index afe49f09d..7f527f391 100644 --- a/web/src/assets/svgs/icons/dice.svg +++ b/web/src/assets/svgs/icons/dice.svg @@ -1,3 +1,27 @@ -<svg viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M11.7841 1.375H2.69318C1.68807 1.375 0.875 2.15758 0.875 3.125V11.875C0.875 12.8424 1.68807 13.625 2.69318 13.625H11.7841C12.7892 13.625 13.6023 12.8424 13.6023 11.875V3.125C13.6023 2.15758 12.7898 1.375 11.7841 1.375ZM4.51136 5.75C4.01136 5.75 3.60227 5.35625 3.60227 4.875C3.60227 4.39375 4.0108 4 4.51136 4C5.01193 4 5.42045 4.3932 5.42045 4.875C5.42045 5.3568 5.01136 5.75 4.51136 5.75ZM9.96591 11C9.46534 11 9.05682 10.6068 9.05682 10.125C9.05682 9.6432 9.46534 9.25 9.96591 9.25C10.4665 9.25 10.875 9.6432 10.875 10.125C10.875 10.6068 10.4659 11 9.96591 11Z"/> -</svg> +<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 512 512" xml:space="preserve"> +<g> + <path class="st0" d="M454.609,111.204L280.557,6.804C272.992,2.268,264.503,0,255.999,0c-8.507,0-16.995,2.268-24.557,6.796 + L57.391,111.204c-5.346,3.202-9.917,7.369-13.556,12.192l207.904,124.708c2.622,1.575,5.9,1.575,8.519,0L468.16,123.396 + C464.524,118.573,459.951,114.406,454.609,111.204z M157.711,130.313c-10.96,7.611-28.456,7.422-39.081-0.452 + c-10.618-7.859-10.342-20.413,0.618-28.031c10.964-7.626,28.46-7.422,39.081,0.438C168.95,110.134,168.674,122.68,157.711,130.313z + M274.159,131.021c-10.594,7.362-27.496,7.166-37.762-0.429c-10.263-7.596-9.992-19.727,0.599-27.089 + c10.591-7.362,27.492-7.174,37.759,0.43C285.018,111.528,284.75,123.659,274.159,131.021z M391.908,132.702 + c-10.964,7.618-28.461,7.414-39.085-0.444c-10.617-7.86-10.343-20.42,0.621-28.046c10.957-7.61,28.456-7.422,39.078,0.452 + C403.147,112.523,402.868,125.076,391.908,132.702z"/> + <path class="st0" d="M246.136,258.366L38.007,133.523c-2.46,5.802-3.798,12.117-3.798,18.62v208.084 + c0,16.773,8.797,32.311,23.182,40.946l174.051,104.392c5.829,3.497,12.204,5.629,18.714,6.435V265.464 + C250.156,262.556,248.63,259.858,246.136,258.366z M75.845,369.736c-12.056-6.57-21.829-21.671-21.829-33.727 + c0-12.056,9.773-16.502,21.829-9.932c12.056,6.571,21.826,21.671,21.826,33.728C97.671,371.861,87.901,376.307,75.845,369.736z + M75.845,247.87c-12.056-6.579-21.829-21.679-21.829-33.728c0-12.056,9.773-16.502,21.829-9.931 + c12.056,6.57,21.826,21.671,21.826,33.728C97.671,249.987,87.901,254.44,75.845,247.87z M197.715,436.158 + c-12.052-6.57-21.826-21.671-21.826-33.728c0-12.048,9.773-16.494,21.826-9.924c12.056,6.571,21.826,21.671,21.826,33.72 + C219.541,438.284,209.771,442.729,197.715,436.158z M197.715,314.292c-12.052-6.571-21.826-21.671-21.826-33.728 + s9.773-16.502,21.826-9.931c12.056,6.57,21.826,21.671,21.826,33.727C219.541,316.417,209.771,320.862,197.715,314.292z"/> + <path class="st0" d="M473.993,133.523l-208.13,124.843c-2.494,1.492-4.02,4.19-4.02,7.099V512 + c6.511-0.806,12.886-2.938,18.714-6.435l174.052-104.392c14.38-8.635,23.182-24.173,23.182-40.946V152.142 + C477.791,145.64,476.453,139.325,473.993,133.523z M370.478,355.11c-19.287,10.512-34.922,3.398-34.922-15.892 + c0-19.282,15.635-43.447,34.922-53.951c19.293-10.519,34.925-3.406,34.925,15.884C405.403,320.434,389.771,344.598,370.478,355.11z + "/> +</g> +</svg> \ No newline at end of file