Compare commits

...
Author SHA1 Message Date
Linkie Link 4ebcc44ded fix: filter available networks 2024-02-23 15:14:42 +01:00
Linkie Link b4647ff440 feat: created a new ChainSelect 2024-02-23 15:10:03 +01:00
Linkie Link 103246261f fix: introduce shortened connect button 2024-02-23 11:46:12 +01:00
Linkie Link 860079b071 Merge branch 'lend-table-adjustments' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-23 11:40:42 +01:00
Linkie Link 51e02eec8c feat: first v1 standalone adjustments 2024-02-23 11:40:28 +01:00
Linkie Link 071cceacec feat: separate v1 implementation 2024-02-21 08:03:17 +01:00
Linkie Link 89e9942b7e tidy: remove sentry 2024-02-21 07:47:08 +01:00
Linkie Link 1a45d64bac fix: fix getUrl 2024-02-19 13:10:24 +01:00
Linkie Link 89d41fa5cf fix: isNaN Leverage 2024-02-19 12:38:04 +01:00
Linkie Link df7ef30a43 Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-19 09:28:25 +01:00
Linkie Link fc9fcf13fc fix: fixed lending table widths 2024-02-16 17:15:59 +01:00
Linkie Link 5846e893d1 Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into develop 2024-02-16 09:07:22 +01:00
Linkie Link 40224ad201 feat: list DYDX (#815) 2024-02-16 08:58:12 +01:00
Linkie Link 21bedd7905 feat: added borrow and repay 2024-02-15 21:51:00 +01:00
Linkie Link 37be3240eb feat: finished deposit and withdraw 2024-02-15 20:40:22 +01:00
Linkie Link f609540809 feat: prepared everything for modal interaction 2024-02-15 16:22:20 +01:00
Linkie Link 106de661a8 feat: added v1 based buttons and a portfolio summary 2024-02-15 16:08:04 +01:00
Linkie Link 40593ae987 Merge branch 'develop' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation
# Conflicts:
#	src/components/borrow/Table/Columns/useDepositedColumns.tsx
#	src/components/borrow/Table/DepositedBorrowingsTable.tsx
#	src/components/earn/lend/Table/DepositedLendsTable.tsx
2024-02-15 14:11:12 +01:00
Linkie LinkandBob van der Helm 0e6f0edb5d Docker update (#814)
* feat: handle URLs with or without trailing slash (#803)

* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes

* Fix docker build (#805)

* fix: fixed the docker build

* tidy: cleanup

* env: remove env contents (#808)

* Portfolio fix (#809)

* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor

* align buttons, perps row clickable (#807)

* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

* Update .env.production

* Update Dockerbuild (#813)

* v2.2.4 (#810)

* feat: handle URLs with or without trailing slash (#803)

* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes

* Fix docker build (#805)

* fix: fixed the docker build

* tidy: cleanup

* env: remove env contents (#808)

* Portfolio fix (#809)

* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor

* align buttons, perps row clickable (#807)

* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

* Update Dockerfile

* remove   .env files

* tidy: format

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
2024-02-15 10:49:14 +01:00
Linkie Link e3b5e330ee Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into develop
# Conflicts:
#	.env
#	src/components/perps/BalancesTable/index.tsx
2024-02-14 19:30:55 +01:00
Linkie LinkandBob van der Helm 7eac8e7a35 Update Dockerbuild (#813)
* v2.2.4 (#810)

* feat: handle URLs with or without trailing slash (#803)

* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes

* Fix docker build (#805)

* fix: fixed the docker build

* tidy: cleanup

* env: remove env contents (#808)

* Portfolio fix (#809)

* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor

* align buttons, perps row clickable (#807)

* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

* Update Dockerfile

* remove   .env files

* tidy: format

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
2024-02-14 19:29:41 +01:00
Linkie Link af478c56d5 Update .env.production 2024-02-14 16:43:57 +01:00
Linkie LinkandBob van der Helm c08ee627fc v2.2.4 (#810)
* feat: handle URLs with or without trailing slash (#803)

* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes

* Fix docker build (#805)

* fix: fixed the docker build

* tidy: cleanup

* env: remove env contents (#808)

* Portfolio fix (#809)

* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor

* align buttons, perps row clickable (#807)

* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
2024-02-14 15:01:25 +01:00
Bob van der HelmandLinkie Link a6f4c24f15 align buttons, perps row clickable (#807)
* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
2024-02-14 14:49:15 +01:00
Linkie Link 6946ceddfc Portfolio fix (#809)
* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor
2024-02-14 14:18:10 +01:00
Linkie Link 0154065ffb env: remove env contents (#808) 2024-02-14 12:33:10 +01:00
Linkie Link 4f64234a75 Merge branch 'main' into develop 2024-02-14 12:01:10 +01:00
Linkie Link 26f1ef4a2c Fix docker build (#805)
* fix: fixed the docker build

* tidy: cleanup
2024-02-14 11:58:48 +01:00
Linkie Link f7f8136b38 feat: handle URLs with or without trailing slash (#803) (#804)
* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes
2024-02-14 11:16:06 +01:00
Linkie Link 34db8aea1a tidy: streamline borrow 2024-02-14 11:14:40 +01:00
Linkie Link 75edb21c02 Merge branch 'develop' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-14 11:11:42 +01:00
Linkie Link 442b7a3a8c feat: implement v1 tables into v2 with data fetching 2024-02-14 11:11:34 +01:00
136 changed files with 2631 additions and 671 deletions
-13
View File
@@ -1,13 +0,0 @@
# DO NOT EDIT THIS FILE WHEN USING DOCKER
# These values are used to replace the values in the built app,
# you should pass environment variables as defined in README.md
# CONFIG #
NEXT_PUBLIC_NETWORK=mainnet
# OSMOSIS-1 #
NEXT_PUBLIC_OSMOSIS_RPC=APP_NEXT_OSMOSIS_RPC
NEXT_PUBLIC_OSMOSIS_REST=APP_NEXT_OSMOSIS_REST
# WALLET CONNECT #
NEXT_PUBLIC_WALLET_CONNECT_ID=APP_NEXT_WALLET_CONNECT_ID
-13
View File
@@ -1,13 +0,0 @@
# DO NOT EDIT THIS FILE WHEN USING DOCKER
# These values are used to replace the values in the built app,
# you should pass environment variables as defined in README.md
# CONFIG #
NEXT_PUBLIC_NETWORK=mainnet
# OSMOSIS-1 #
NEXT_PUBLIC_OSMOSIS_RPC=APP_NEXT_OSMOSIS_RPC
NEXT_PUBLIC_OSMOSIS_REST=APP_NEXT_OSMOSIS_REST
# WALLET CONNECT #
NEXT_PUBLIC_WALLET_CONNECT_ID=APP_NEXT_WALLET_CONNECT_ID
+8 -2
View File
@@ -6,18 +6,24 @@ RUN yarn install
COPY . .
RUN apk --update add patch
RUN patch next.config.js next-config.patch
ENV NEXT_PUBLIC_NETWORK=mainnet
ENV NEXT_PUBLIC_OSMOSIS_RPC=APP_NEXT_OSMOSIS_RPC
ENV NEXT_PUBLIC_OSMOSIS_REST=APP_NEXT_OSMOSIS_REST
ENV NEXT_PUBLIC_WALLET_CONNECT_ID=APP_NEXT_WALLET_CONNECT_ID
ENV NODE_ENV=production
RUN yarn build
FROM node:20-alpine as runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/package.json .
COPY --from=builder /app/yarn.lock .
COPY --from=builder /app/next.config.js .
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY entrypoint.sh .
RUN apk add --no-cache --upgrade bash
+2 -1
View File
@@ -1,6 +1,7 @@
{
"name": "mars-v2-frontend",
"version": "2.2.3",
"version": "2.2.4",
"v1version": "1.7.5",
"homepage": "./",
"private": false,
"license": "SEE LICENSE IN LICENSE FILE",
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
opacity="0.05"
viewBox="0 0 1440 819.8"
>
<path
stroke="#FFF"
d="M-41.5,81c20,17.3,62.5,51.4,71.8,49.5s27.7,13.3,35.6,21.1
c19.3,16.7,58.9,49.8,62.9,48.9c1.2-0.3,5.1-0.3,10.8-0.2c17.4,0.1,51.4,0.4,76.6-8.2c33.4-11.5,50.7-6.6,61.8,0s71.8,37.4,82.4,35
c3.5-0.8,14-0.1,26.7,0.7c25.2,1.7,59.4,3.9,66.8-3.7c11.1-11.5,204.9-19.3,223.3-9.7c14.7,7.7,34.1,7.8,42.7,7.8s28-0.1,42.7-7.8
c18.4-9.7,212.2-1.8,223.3,9.7c7.4,7.6,41.6,5.4,66.8,3.7c12.7-0.8,23.2-1.5,26.7-0.7c10.6,2.4,71.3-28.4,82.4-35
c11.1-6.6,28.4-11.5,61.8,0c25.2,8.6,59.3,8.4,76.6,8.2c5.7,0,9.6-0.1,10.8,0.2c4,1,43.6-32.2,62.9-48.9c8-7.8,26.3-23.1,35.6-21.1
c9.4,1.9,51.8-32.2,71.8-49.5"
/>
<path
stroke="#FFF"
d="M-39.8,340.5c8.1,0.1,15.5,0.1,21.7,0.1
c18.9,0,60.1,10.9,95.2,30.4c2.9,0.7,5.7,1.4,8.3,2c17.6,4.3,28.9,7.1,50.7,7.1l152,3c2.6-0.4,8.9-2.6,13.4-7.9
c4.5-5.3,15.6-14.4,20.6-18.2c3.5-2.2,13.6-6.7,25.6-6.7c9.5,0,21.5,0.7,30.3,1.3c5.1,0.3,9.1,0.6,10.9,0.6c5,0,17.3-3,21.7-7.9
c3.6-3.9,39.4-47,56.8-68c6.1-6.9,22.4-19.3,38.4-14c20,6.7,75.2,6.7,84.1,0c8.9-6.7,29.5-18.2,49.6-14.6c2.2,0.4,4.8,0.9,7.8,1.4
c24.2,4.4,70.3,12.8,72.8,12.8s48.6-8.4,72.8-12.8c3-0.5,5.6-1,7.8-1.4c20-3.6,40.6,7.9,49.6,14.6c8.9,6.7,64,6.7,84.1,0
c16-5.3,32.3,7.1,38.4,14c17.4,21.1,53.2,64.1,56.8,68c4.5,4.9,16.7,7.9,21.7,7.9c1.8,0,5.8-0.2,10.9-0.6c8.8-0.5,20.8-1.3,30.3-1.3
c12,0,22.1,4.5,25.6,6.7c5,3.8,16.1,12.9,20.6,18.2c4.4,5.3,10.8,7.5,13.4,7.9l152-3c21.8,0,33.1-2.8,50.7-7.1
c2.6-0.6,5.4-1.3,8.3-2c35.1-19.4,76.3-30.4,95.2-30.4c6.2,0,13.6-0.1,21.7-0.1"
/>
<path
stroke="#FFF"
d="M-56.4,455c79.7-9.9,188.1-23.3,214.2-34.7
c17-2.8,93.6,6,161.8,13.8c52.2,6,99.5,11.3,111.1,10.5c8.2-0.6,28.2-8.6,55.3-19.5C546.6,400.7,642.6,362,720,362
s173.4,38.7,234.1,63.1c27,10.9,47,19,55.3,19.5c11.6,0.8,58.8-4.6,111.1-10.5c68.1-7.8,144.8-16.5,161.8-13.8
c26.1,11.4,134.5,24.8,214.2,34.7"
/>
<path
stroke="#FFF"
d="M-22.1,455.3c12.3,3,52.3,21.3,64,37.1
c11.7,15.8,33.3,14.7,35.1,12.8c1.7-1.8,16.7-13,24.5-18.2c4.6-2.4,16.8-5.8,28.4,0s79.4,39.7,111.9,55.9c4.3,2.4,13.1,7.3,14.5,7.3
c0.2,0,0.8,0.1,1.6,0.1c4.7,0.4,17.3,1.6,20.7-2.6c3.1-3.9,20.6-17.4,29-23.7c6.1-4,20.9-12.1,31.2-12.1s82.6,4,117.5,6.1
c20.2-0.4,61.8-1.3,66.3-1.8c3.3-0.4,34.5,1.6,60.7,3.3c17.8,1.1,33.4,2.1,36.2,2.1c3.4,0,13.7-7.1,26.7-16.3
c23.1-16.1,55-38.4,73.8-38.4s50.8,22.3,73.8,38.4c13.1,9.1,23.3,16.3,26.7,16.3c2.8,0,18.4-1,36.2-2.1c26.2-1.7,57.4-3.7,60.7-3.3
c4.5,0.5,46,1.4,66.3,1.8c34.9-2,107.2-6.1,117.5-6.1c10.2,0,25.1,8.1,31.2,12.1c8.3,6.3,25.8,19.8,29,23.7c3.3,4.1,16,3,20.7,2.6
c0.8-0.1,1.4-0.1,1.6-0.1c1.3,0,10.2-4.9,14.5-7.3c32.5-16.2,100.3-50.1,111.9-55.9c11.6-5.8,23.8-2.4,28.4,0
c7.8,5.3,22.8,16.4,24.5,18.2c1.8,1.9,23.4,3,35.1-12.8c11.7-15.8,51.8-34,64-37.1"
/>
<path
stroke="#FFF"
d="M-48.7,749.9c4.2,0.6,7.8,1.2,10.5,1.2c3.3,0,5.6-2.4,8-5
c2.8-3,5.8-6.3,10.9-5.9c7.6,0.5,75.5,0.2,108.6,0l11.7,2.4l53.5,35.8l6.1,10.9l34.5,3l35.6,12.8c13,3.4,40.2,10.1,45.1,9.1
c3.1-0.6,12.4-7.5,21.6-14.4c9-6.7,17.9-13.3,21.2-14.2c6.7-1.8,27.3-11.5,30.6-14.6c0.3-0.3,0.7-0.7,1.3-1.2
c5.5-5.3,22.7-21.6,33.8-18.8c5.1,1.3,10,4.1,14.9,6.8c6.9,3.9,13.4,7.6,19.6,6.5c7.1-1.2,29.3-20.4,44.1-33.1
c7.3-6.3,12.8-11,13.8-11.2c2.2-0.5,38.4-39.1,56.2-58.3c5.9-6.3,23.6-14.8,42.3-6.1c18.7,8.7,46,9.1,57.9,9.1l67.9,6.1l18.7-0.1
l18.7,0.1l67.9-6.1c11.9,0,39.2-0.4,57.9-9.1c18.7-8.7,36.4-0.2,42.3,6.1c17.8,19.2,54,57.8,56.2,58.3c0.9,0.2,6.4,4.9,13.8,11.2
c14.8,12.7,37.1,31.9,44.1,33.1c6.2,1.1,12.8-2.7,19.6-6.5c4.8-2.7,9.8-5.6,14.9-6.8c11.1-2.8,28.2,13.6,33.8,18.8
c0.6,0.5,1,0.9,1.3,1.2c3.3,3,23.9,12.8,30.6,14.6c3.3,0.9,12.3,7.5,21.2,14.2c9.2,6.9,18.5,13.7,21.6,14.4c4.9,1,32.1-5.7,45.1-9.1
l35.6-12.8l34.5-3l6.1-10.9l53.5-35.8l11.7-2.4c33,0.2,101,0.5,108.6,0c5.1-0.3,8.1,2.9,10.9,5.9c2.4,2.6,4.7,5,8,5
c2.7,0,6.3-0.5,10.5-1.2"
/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+36
View File
@@ -0,0 +1,36 @@
<svg viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="240" height="240" rx="120" fill="#E50571" />
<path d="M149.107 60.9396L63.3276 181.702H89.6637L175.888 60.9396H149.107Z" fill="white" />
<path
d="M92.4053 60.9396L117.644 96.5239L104.476 115.934L65.5205 60.9396H92.4053Z"
fill="url(#paint0_linear_6269_3439)"
/>
<path
d="M151.664 181.712L123.682 142.354L136.851 123.483L178 181.712H151.664Z"
fill="url(#paint1_linear_6269_3439)"
/>
<defs>
<linearGradient
id="paint0_linear_6269_3439"
x1="86.3698"
y1="68.4878"
x2="121.753"
y2="111.957"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.75" />
</linearGradient>
<linearGradient
id="paint1_linear_6269_3439"
x1="157.7"
y1="172.547"
x2="114.305"
y2="113.014"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.68" />
<stop offset="1" stop-color="white" stop-opacity="0.1" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+42
View File
@@ -0,0 +1,42 @@
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M100.881 178.044C144.869 178.044 180.531 142.388 180.531 98.4C180.531 54.4124 144.869 18.75 100.881 18.75C56.8939 18.75 21.2378 54.4124 21.2378 98.4C21.2378 142.388 56.8939 178.044 100.881 178.044Z"
fill="#2E3148"
/>
<path
d="M100.694 143.475C126.106 143.475 146.712 122.869 146.712 97.4562C146.712 72.0437 126.106 51.4375 100.694 51.4375C75.2749 51.4375 54.6748 72.0437 54.6748 97.4562C54.6748 122.869 75.2749 143.475 100.694 143.475Z"
fill="#1B1E36"
/>
<path
d="M200 97.5C200 110.631 197.412 123.631 192.387 135.769C187.362 147.9 179.994 158.925 170.712 168.206C161.425 177.494 150.4 184.862 138.269 189.887C126.137 194.912 113.131 197.5 100 197.5C86.8687 197.5 73.8625 194.912 61.7313 189.887C49.6001 184.862 38.5748 177.494 29.2873 168.206C20.0061 158.925 12.6376 147.9 7.61262 135.769C2.58761 123.631 0 110.631 0 97.5H23.9689C23.9689 107.481 25.9312 117.369 29.7562 126.594C33.5749 135.819 39.1751 144.2 46.2376 151.262C53.3001 158.319 61.6811 163.925 70.9063 167.744C80.1313 171.563 90.0125 173.531 100 173.531C109.988 173.531 119.869 171.563 129.094 167.744C138.319 163.925 146.7 158.319 153.762 151.262C160.825 144.2 166.425 135.819 170.244 126.594C174.069 117.369 176.031 107.481 176.031 97.5H200Z"
fill="#C73238"
/>
<path
d="M178.95 97.5C178.95 107.869 176.906 118.131 172.937 127.712C168.969 137.287 163.156 145.994 155.825 153.325C148.494 160.656 139.794 166.469 130.212 170.438C120.637 174.406 110.369 176.444 99.9998 176.444C89.631 176.444 79.3685 174.406 69.7873 170.438C60.2124 166.469 51.506 160.656 44.1747 153.325C36.8435 145.994 31.0312 137.287 27.0624 127.712C23.0937 118.131 21.0562 107.869 21.0562 97.5H99.9998H178.95Z"
fill="#070B09"
/>
<path
d="M99.2748 28.6934C90.8686 28.6934 84.0498 59.1809 84.0498 96.7934C84.0498 134.406 90.8686 164.893 99.2748 164.893C107.681 164.893 114.5 134.406 114.5 96.7934C114.5 59.1809 107.681 28.6934 99.2748 28.6934ZM100.325 161.05C99.3623 162.331 98.3998 161.368 98.3998 161.368C94.5311 156.887 92.5936 148.55 92.5936 148.55C85.8248 126.762 87.4373 79.9684 87.4373 79.9684C90.6186 42.8247 96.4061 34.0495 98.3748 32.0995C98.5811 31.8995 98.8436 31.7808 99.1248 31.7558C99.4061 31.737 99.6873 31.8121 99.9186 31.9746C102.775 33.9996 105.175 42.4745 105.175 42.4745C112.256 68.7496 111.612 93.4309 111.612 93.4309C112.256 114.9 108.069 138.937 108.069 138.937C104.844 157.2 100.325 161.05 100.325 161.05Z"
fill="white"
/>
<path
d="M158.338 62.9C154.156 55.6063 124.319 64.8687 91.6814 83.5812C59.0376 102.3 36.0126 123.387 40.2001 130.675C44.3814 137.969 74.2189 128.713 106.856 109.994C139.5 91.275 162.525 70.1875 158.338 62.9ZM44.0564 129.675C42.4564 129.475 42.8189 128.156 42.8189 128.156C44.7814 122.569 51.0377 116.744 51.0377 116.744C66.5814 100.025 107.969 78.1375 107.969 78.1375C141.769 62.4126 152.269 63.075 154.931 63.8062C155.206 63.8875 155.444 64.0562 155.6 64.2875C155.763 64.525 155.831 64.8062 155.806 65.0875C155.481 68.5812 149.313 74.8687 149.313 74.8687C130.056 94.1 108.331 105.825 108.331 105.825C90.0252 117.069 67.0939 125.394 67.0939 125.394C49.6437 131.681 44.0564 129.675 44.0564 129.675Z"
fill="white"
/>
<path
d="M158.194 130.919C162.412 123.644 139.45 102.457 106.925 83.6007C74.3998 64.7445 44.5623 55.3695 40.3498 62.657C36.1373 69.9445 59.1 91.1132 91.6436 109.969C124.187 128.832 153.981 138.207 158.194 130.919ZM43.1437 65.4695C42.5249 63.9882 43.8373 63.6382 43.8373 63.6382C49.6623 62.5382 57.8376 65.0382 57.8376 65.0382C80.0873 70.107 119.769 94.9632 119.769 94.9632C150.312 116.338 155 125.751 155.7 128.426C155.775 128.701 155.744 128.988 155.625 129.244C155.5 129.501 155.294 129.707 155.037 129.819C151.85 131.282 143.319 129.107 143.319 129.107C117.025 122.057 95.9936 109.138 95.9936 109.138C77.0998 98.9507 58.4124 83.2695 58.4124 83.2695C44.2187 71.3132 43.1498 65.482 43.1498 65.482L43.1437 65.4695Z"
fill="white"
/>
<path
d="M99.1126 104.63C103.538 104.63 107.125 101.043 107.125 96.618C107.125 92.193 103.538 88.6055 99.1126 88.6055C94.6876 88.6055 91.1001 92.193 91.1001 96.618C91.1001 101.043 94.6876 104.63 99.1126 104.63Z"
fill="white"
/>
<path
d="M131.969 70.6691C134.531 70.6691 136.613 68.5191 136.613 65.8628C136.613 63.2066 134.531 61.0566 131.969 61.0566C129.4 61.0566 127.319 63.2066 127.319 65.8628C127.319 68.5191 129.4 70.6691 131.969 70.6691Z"
fill="white"
/>
<path
d="M55.6873 87.0133C58.2498 87.0133 60.3313 84.857 60.3313 82.207C60.3313 79.5508 58.2498 77.3945 55.6873 77.3945C53.1185 77.3945 51.0376 79.5508 51.0376 82.207C51.0376 84.857 53.1185 87.0133 55.6873 87.0133Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+26
View File
@@ -0,0 +1,26 @@
<svg viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="256" fill="#00A3FF" />
<path
opacity="0.6"
d="M361.012 237.812L363.878 242.209C396.204 291.798 388.984 356.742 346.52 398.348C321.539 422.826 288.798 435.066 256.056 435.069C256.056 435.069 256.056 435.069 361.012 237.812Z"
fill="white"
/>
<path
opacity="0.2"
d="M256.044 297.764L361 237.812C256.045 435.069 256.044 435.069 256.044 435.069C256.044 392.108 256.044 342.88 256.044 297.764Z"
fill="white"
/>
<path
d="M150.988 237.812L148.122 242.209C115.796 291.798 123.016 356.742 165.48 398.348C190.461 422.826 223.202 435.066 255.944 435.069C255.944 435.069 255.944 435.069 150.988 237.812Z"
fill="white"
/>
<path
opacity="0.6"
d="M255.914 297.764L150.958 237.812C255.914 435.069 255.914 435.069 255.914 435.069C255.914 392.108 255.914 342.88 255.914 297.764Z"
fill="white"
/>
<path opacity="0.2" d="M256.083 163.833V267.233L346.491 215.566L256.083 163.833Z" fill="white" />
<path opacity="0.6" d="M256.056 163.833L165.583 215.565L256.056 267.233V163.833Z" fill="white" />
<path d="M256.056 76.875L165.583 215.599L256.056 163.722V76.875Z" fill="white" />
<path opacity="0.6" d="M256.083 163.706L346.56 215.585L256.083 76.7916V163.706Z" fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

-19
View File
@@ -1,19 +0,0 @@
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
Sentry.init({
dsn: SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
enabled: process.env.NODE_ENV !== 'development',
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})
-4
View File
@@ -1,4 +0,0 @@
defaults.url=https://sentry.io/
defaults.org=delphi-mars
defaults.project=mars-v2
cli.executable=../../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli
-19
View File
@@ -1,19 +0,0 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
Sentry.init({
dsn: SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
enabled: process.env.NODE_ENV !== 'development',
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})
+8 -1
View File
@@ -10,7 +10,12 @@ import {
TotalDepositResponse,
VaultConfigBaseForAddr,
} from 'types/generated/mars-params/MarsParams.types'
import { ArrayOfMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
import {
ArrayOfMarket,
ArrayOfUserCollateralResponse,
ArrayOfUserDebtResponse,
UserCollateralResponse,
} from 'types/generated/mars-red-bank/MarsRedBank.types'
interface Cache<T> extends Map<string, { data: T | null; timestamp: number }> {}
@@ -62,3 +67,5 @@ export const underlyingDebtCache: Cache<string> = new Map()
export const previewDepositCache: Cache<{ vaultAddress: string; amount: string }> = new Map()
export const stakingAprCache: Cache<StakingApr[]> = new Map()
export const assetParamsCache: Cache<AssetParamsBaseForAddr[]> = new Map()
export const userCollateralCache: Cache<ArrayOfUserCollateralResponse> = new Map()
export const userDebtCache: Cache<ArrayOfUserDebtResponse> = new Map()
+29 -8
View File
@@ -6,7 +6,9 @@ import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMo
import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client'
import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client'
import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client'
import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client'
import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
import { getUrl } from 'utils/url'
let _cosmWasmClient: Map<string, CosmWasmClient> = new Map()
let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map()
@@ -15,6 +17,7 @@ let _paramsQueryClient: Map<string, MarsParamsQueryClient> = new Map()
let _incentivesQueryClient: Map<string, MarsIncentivesQueryClient> = new Map()
let _swapperOsmosisClient: Map<string, MarsSwapperOsmosisQueryClient> = new Map()
let _perpsClient: Map<string, MarsPerpsQueryClient> = new Map()
let _redBankQueryClient: Map<string, MarsRedBankQueryClient> = new Map()
const getClient = async (rpc: string) => {
try {
@@ -32,7 +35,7 @@ const getClient = async (rpc: string) => {
const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.creditManager
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_creditManagerQueryClient.get(key)) {
@@ -49,7 +52,7 @@ const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
const getParamsQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.params
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_paramsQueryClient.get(key)) {
@@ -66,7 +69,7 @@ const getParamsQueryClient = async (chainConfig: ChainConfig) => {
const getOracleQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.oracle
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_oracleQueryClient.get(key)) {
@@ -82,7 +85,7 @@ const getOracleQueryClient = async (chainConfig: ChainConfig) => {
const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => {
try {
const client = await getClient(chainConfig.endpoints.rpc)
const client = await getClient(getUrl(chainConfig.endpoints.rpc))
return new MarsMockVaultQueryClient(client, address)
} catch (error) {
throw error
@@ -92,7 +95,7 @@ const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) =>
const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.incentives
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_incentivesQueryClient.get(key)) {
const client = await getClient(rpc)
@@ -108,7 +111,7 @@ const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.swapper
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_swapperOsmosisClient.get(key)) {
const client = await getClient(rpc)
@@ -124,7 +127,7 @@ const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.perps
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_perpsClient.get(key)) {
const client = await getClient(rpc)
@@ -137,13 +140,31 @@ const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
}
}
const getRedBankQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.redBank
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_redBankQueryClient.get(key)) {
const client = await getClient(rpc)
_redBankQueryClient.set(key, new MarsRedBankQueryClient(client, contract))
}
return _redBankQueryClient.get(key)!
} catch (error) {
throw error
}
}
export {
getClient,
getCreditManagerQueryClient,
getIncentivesQueryClient,
getOracleQueryClient,
getParamsQueryClient,
getPerpsQueryClient,
getRedBankQueryClient,
getSwapperQueryClient,
getVaultQueryClient,
getPerpsQueryClient,
}
+41
View File
@@ -0,0 +1,41 @@
import { cacheFn, userCollateralCache, userDebtCache } from 'api/cache'
import { getRedBankQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin'
import {
ArrayOfUserCollateralResponse,
ArrayOfUserDebtResponse,
} from 'types/generated/mars-red-bank/MarsRedBank.types'
export default async function getV1Positions(
chainConfig: ChainConfig,
user?: string,
): Promise<Account> {
if (!user) return new Promise((_, reject) => reject('No account Wallet ID found'))
const redBankQueryClient = await getRedBankQueryClient(chainConfig)
const userCollateral: ArrayOfUserCollateralResponse = await cacheFn(
() => redBankQueryClient.userCollaterals({ user: user, limit: 100 }),
userCollateralCache,
`${chainConfig.id}/v1/deposits/${user}`,
)
const userDebt: ArrayOfUserDebtResponse = await cacheFn(
() => redBankQueryClient.userDebts({ user: user, limit: 100 }),
userDebtCache,
`${chainConfig.id}/v1/debts/${user}`,
)
if (userCollateral && userDebt) {
return {
id: user,
debts: userDebt.map((debt) => new BNCoin(debt)),
lends: userCollateral.map((lend) => new BNCoin(lend)),
deposits: [],
vaults: [],
perps: [],
kind: 'default',
}
}
return new Promise((_, reject) => reject('No account found'))
}
@@ -10,12 +10,12 @@ import Text from 'components/common/Text'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
interface Props {
account: Account
asset: Asset
title: string
coinBalances: BNCoin[]
@@ -29,6 +29,7 @@ interface Props {
export default function AssetAmountSelectActionModal(props: Props) {
const {
account,
asset,
title,
coinBalances,
@@ -41,7 +42,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
} = props
const [amount, setAmount] = useState(BN_ZERO)
const maxAmount = BN(coinBalances.find(byDenom(asset.denom))?.amount ?? 0)
const account = useCurrentAccount()
const handleAmountChange = useCallback(
(value: BigNumber) => {
setAmount(value)
@@ -54,7 +54,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
onAction(amount, amount.isEqualTo(maxAmount))
}, [amount, maxAmount, onAction])
if (!account) return
return (
<Modal
onClose={onClose}
+43 -55
View File
@@ -35,18 +35,9 @@ interface Props {
modal: BorrowModal
}
function getDebtAmount(modal: BorrowModal) {
return BN((modal.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
}
function getAssetLogo(modal: BorrowModal) {
if (!modal.asset) return null
return <AssetImage asset={modal.asset} size={24} />
}
function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) {
return (
<Card className='mt-6'>
<Card className='w-full'>
<div className='flex items-start p-4'>
<InfoCircle className='w-6 mr-2 flex-0' />
<div className='flex flex-col flex-1 gap-1'>
@@ -90,7 +81,6 @@ function BorrowModal(props: Props) {
const apy = modal.marketData.apy.borrow
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
const { computeMaxBorrowAmount } = useHealthComputer(account)
const totalDebt = BN(getDebtAmount(modal))
const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO
const markets = useMarkets()
@@ -237,7 +227,7 @@ function BorrowModal(props: Props) {
onClose={onClose}
header={
<span className='flex items-center gap-4 px-4'>
{getAssetLogo(modal)}
<AssetImage asset={asset} size={24} />
<Text>
{isRepay ? 'Repay' : 'Borrow'} {asset.symbol}
</Text>
@@ -251,14 +241,14 @@ function BorrowModal(props: Props) {
title={formatPercent(modal.marketData.apy.borrow)}
sub={'Borrow Rate APY'}
/>
{totalDebt.isGreaterThan(0) && (
{accountDebt.isGreaterThan(0) && (
<>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={totalDebt.toNumber()}
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
@@ -267,7 +257,7 @@ function BorrowModal(props: Props) {
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, totalDebt)}
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
@@ -303,59 +293,57 @@ function BorrowModal(props: Props) {
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<div className='flex flex-wrap w-full'>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
{isRepay && maxRepayAmount.isZero() && (
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
)}
{isRepay ? (
<>
<Divider className='my-6' />
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
{isRepay && maxRepayAmount.isZero() && (
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
)}
{isRepay ? (
<>
<Divider />
<div className='flex items-center w-full'>
<div className='flex flex-wrap flex-1'>
<Text className='w-full mb-1'>Repay from Wallet</Text>
<Text size='xs' className='text-white/50'>
Repay your debt directly from your wallet
</Text>
</div>
<div className='flex flex-wrap items-center justify-end'>
<Switch
name='borrow-to-wallet'
checked={repayFromWallet}
onChange={setRepayFromWallet}
/>
</div>
</>
) : (
<>
<Divider className='my-6' />
<Switch
name='borrow-to-wallet'
checked={repayFromWallet}
onChange={setRepayFromWallet}
/>
</div>
</>
) : (
<>
<Divider />
<div className='flex items-center w-full'>
<div className='flex flex-wrap flex-1'>
<Text className='w-full mb-1'>Receive funds to Wallet</Text>
<Text size='xs' className='text-white/50'>
Your borrowed funds will directly go to your wallet
</Text>
</div>
<div className='flex flex-wrap items-center justify-end'>
<Switch
name='borrow-to-wallet'
checked={borrowToWallet}
onChange={setBorrowToWallet}
/>
</div>
</>
)}
</div>
<Switch
name='borrow-to-wallet'
checked={borrowToWallet}
onChange={setBorrowToWallet}
/>
</div>
</>
)}
<Button
onClick={onConfirmClick}
className='w-full'
@@ -23,6 +23,7 @@ interface Props {
}
function LendAndReclaimModal({ currentAccount, config }: Props) {
const account = useCurrentAccount()
const lend = useStore((s) => s.lend)
const reclaim = useStore((s) => s.reclaim)
const { close } = useLendAndReclaimModal()
@@ -65,8 +66,11 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
},
[asset.denom, close, currentAccount.id, isLendAction, lend, reclaim],
)
if (!account) return null
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={data} />}
coinBalances={coinBalances}
@@ -10,6 +10,8 @@ import {
LendAndReclaimModalController,
SettingsModal,
UnlockModal,
V1BorrowAndRepay,
V1DepositAndWithdraw,
VaultModal,
WalletAssets,
WithdrawFromVaultsModal,
@@ -32,6 +34,8 @@ export default function ModalsContainer() {
<AlertDialogController />
<HlsModal />
<HlsManageModal />
<V1DepositAndWithdraw />
<V1BorrowAndRepay />
</>
)
}
+4 -2
View File
@@ -4,11 +4,13 @@ export { default as AlertDialogController } from 'components/Modals/AlertDialog'
export { default as BorrowModal } from 'components/Modals/BorrowModal'
export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw'
export { default as GetStartedModal } from 'components/Modals/GetStartedModal'
export { default as HlsModal } from 'components/Modals/HLS'
export { default as HlsManageModal } from 'components/Modals/HLS/Manage'
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
export { default as SettingsModal } from 'components/Modals/Settings'
export { default as UnlockModal } from 'components/Modals/Unlock'
export { default as VaultModal } from 'components/Modals/Vault'
export { default as WalletAssets } from 'components/Modals/WalletAssets'
export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal'
export { default as HlsModal } from 'components/Modals/HLS'
export { default as HlsManageModal } from 'components/Modals/HLS/Manage'
export { default as V1BorrowAndRepay } from 'components/Modals/v1/V1BorrowAndRepay'
export { default as V1DepositAndWithdraw } from 'components/Modals/v1/V1DepositAndWithdraw'
+179
View File
@@ -0,0 +1,179 @@
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState } from 'react'
import Modal from 'components/Modals/Modal'
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
import Button from 'components/common/Button'
import Card from 'components/common/Card'
import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight } from 'components/common/Icons'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useHealthComputer from 'hooks/useHealthComputer'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { formatPercent } from 'utils/formatters'
import { getDebtAmountWithInterest } from 'utils/tokens'
interface Props {
account: Account
}
export default function Borrow(props: Props) {
const { account } = props
const modal = useStore((s) => s.v1BorrowAndRepayModal)
const baseAsset = useBaseAsset()
const [amount, setAmount] = useState(BN_ZERO)
const v1Action = useStore((s) => s.v1Action)
const asset = modal?.data.asset ?? baseAsset
const [max, setMax] = useState(BN_ZERO)
const { simulateBorrow } = useUpdatedAccount(account)
const apy = modal?.data.apy.borrow ?? 0
const { computeMaxBorrowAmount } = useHealthComputer(account)
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
const accountDebtWithInterest = useMemo(
() => getDebtAmountWithInterest(accountDebt, apy),
[accountDebt, apy],
)
const close = useCallback(() => {
setAmount(BN_ZERO)
useStore.setState({ v1BorrowAndRepayModal: null })
}, [setAmount])
const onConfirmClick = useCallback(() => {
v1Action('borrow', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
close()
}, [v1Action, asset, amount, close])
const handleChange = useCallback(
(newAmount: BigNumber) => {
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
},
[amount, setAmount],
)
const onDebounce = useCallback(() => {
const borrowCoin = BNCoin.fromDenomAndBigNumber(
asset.denom,
amount.isGreaterThan(max) ? max : amount,
)
simulateBorrow('wallet', borrowCoin)
}, [amount, max, asset, simulateBorrow])
const maxBorrow = useMemo(() => {
const maxBorrowAmount = computeMaxBorrowAmount(asset.denom, 'wallet')
return BigNumber.min(maxBorrowAmount, modal?.data.liquidity || 0)
}, [asset.denom, computeMaxBorrowAmount, modal?.data.liquidity])
useEffect(() => {
if (maxBorrow.isEqualTo(max)) return
setMax(maxBorrow)
}, [account, maxBorrow, max])
useEffect(() => {
if (amount.isLessThanOrEqualTo(max)) return
handleChange(max)
setAmount(max)
}, [amount, max, handleChange])
if (!modal) return null
return (
<Modal
onClose={close}
header={
<span className='flex items-center gap-4 px-4'>
<AssetImage asset={modal.data.asset} size={24} />
<Text>{`Borrow ${asset.symbol} from the Red Bank`}</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
{accountDebt.isGreaterThan(0) && (
<>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
suffix: ` ${asset.symbol}`,
}}
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Total Borrowed
</Text>
</div>
</>
)}
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={modal.data.liquidity.toNumber() ?? 0}
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
animate
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data.liquidity ?? BN_ZERO)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Liquidity available
</Text>
</div>
</div>
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
<Divider />
<Button
onClick={onConfirmClick}
className='w-full'
disabled={amount.isZero()}
text={`Borrow ${asset.symbol}`}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummaryInModal account={account} />
</div>
</Modal>
)
}
+82
View File
@@ -0,0 +1,82 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import WalletBridges from 'components/Wallet/WalletBridges'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { defaultFee } from 'utils/constants'
import { BN } from 'utils/helpers'
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
interface Props {
account: Account
}
export default function Deposit(props: Props) {
const { account } = props
const baseAsset = useBaseAsset()
const modal = useStore((s) => s.v1DepositAndWithdrawModal)
const address = useStore((s) => s.address)
const asset = modal?.data.asset ?? baseAsset
const [fundingAsset, setFundingAsset] = useState<BNCoin>(
BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO),
)
const { data: walletBalances } = useWalletBalances(address)
const { simulateDeposits } = useUpdatedAccount(account)
const balance = useCurrentWalletBalance(asset.denom)
const v1Action = useStore((s) => s.v1Action)
const baseBalance = useMemo(
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
[walletBalances, baseAsset],
)
const close = useCallback(() => {
useStore.setState({ v1DepositAndWithdrawModal: null })
}, [])
const handleClick = useCallback(async () => {
v1Action('deposit', fundingAsset)
close()
}, [v1Action, fundingAsset, close])
useEffect(() => {
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
useStore.setState({ focusComponent: { component: <WalletBridges /> } })
}
}, [baseBalance])
const onDebounce = useCallback(() => {
simulateDeposits('lend', [fundingAsset])
}, [fundingAsset, simulateDeposits])
const handleAmountChange = useCallback(
(value: BigNumber) => {
setFundingAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value))
},
[asset.denom],
)
if (!modal) return
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={modal.data} />}
coinBalances={balance ? [BNCoin.fromCoin(balance)] : []}
actionButtonText={`Deposit ${asset.symbol}`}
title={`Deposit ${asset.symbol} into the Red Bank`}
onClose={close}
onAction={handleClick}
onChange={handleAmountChange}
onDebounce={onDebounce}
/>
)
}
+208
View File
@@ -0,0 +1,208 @@
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState } from 'react'
import Modal from 'components/Modals/Modal'
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
import Button from 'components/common/Button'
import Card from 'components/common/Card'
import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight, InfoCircle } from 'components/common/Icons'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useMarkets from 'hooks/markets/useMarkets'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { formatPercent } from 'utils/formatters'
import { BN } from 'utils/helpers'
import { getDebtAmountWithInterest } from 'utils/tokens'
interface Props {
account: Account
}
function RepayNotAvailable(props: { asset: Asset }) {
return (
<Card className='w-full'>
<div className='flex items-start p-4'>
<InfoCircle className='w-6 mr-2 flex-0' />
<div className='flex flex-col flex-1 gap-1'>
<Text size='sm'>No funds for repay</Text>
<Text
size='xs'
className='text-white/40'
>{`Unfortunately you don't have any ${props.asset.symbol} in your Wallet to repay the debt.`}</Text>
</div>
</div>
</Card>
)
}
export default function Repay(props: Props) {
const { account } = props
const modal = useStore((s) => s.v1BorrowAndRepayModal)
const baseAsset = useBaseAsset()
const asset = modal?.data.asset ?? baseAsset
const [amount, setAmount] = useState(BN_ZERO)
const balance = useCurrentWalletBalance(asset.denom)
const v1Action = useStore((s) => s.v1Action)
const [max, setMax] = useState(BN_ZERO)
const { simulateRepay } = useUpdatedAccount(account)
const apy = modal?.data.apy.borrow ?? 0
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
const markets = useMarkets()
const accountDebtWithInterest = useMemo(
() => getDebtAmountWithInterest(accountDebt, apy),
[accountDebt, apy],
)
const overpayExeedsCap = useMemo(() => {
const marketAsset = markets.find((market) => market.asset.denom === asset.denom)
if (!marketAsset) return
const overpayAmount = accountDebtWithInterest.minus(accountDebt)
const marketCapAfterOverpay = marketAsset.cap.used.plus(overpayAmount)
return marketAsset.cap.max.isLessThanOrEqualTo(marketCapAfterOverpay)
}, [markets, asset.denom, accountDebt, accountDebtWithInterest])
const maxRepayAmount = useMemo(() => {
const maxBalance = BN(balance?.amount ?? 0)
return BigNumber.min(maxBalance, overpayExeedsCap ? accountDebt : accountDebtWithInterest)
}, [accountDebtWithInterest, overpayExeedsCap, accountDebt, balance?.amount])
const close = useCallback(() => {
setAmount(BN_ZERO)
useStore.setState({ v1BorrowAndRepayModal: null })
}, [setAmount])
const onConfirmClick = useCallback(() => {
v1Action('repay', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
close()
}, [v1Action, asset, amount, close])
const handleChange = useCallback(
(newAmount: BigNumber) => {
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
},
[amount, setAmount],
)
const onDebounce = useCallback(() => {
const repayCoin = BNCoin.fromDenomAndBigNumber(
asset.denom,
amount.isGreaterThan(accountDebt) ? accountDebt : amount,
)
simulateRepay(repayCoin, true)
}, [amount, accountDebt, asset, simulateRepay])
useEffect(() => {
if (maxRepayAmount.isEqualTo(max)) return
setMax(maxRepayAmount)
}, [max, maxRepayAmount])
useEffect(() => {
if (amount.isLessThanOrEqualTo(max)) return
handleChange(max)
setAmount(max)
}, [amount, max, handleChange])
if (!modal) return null
return (
<Modal
onClose={close}
header={
<span className='flex items-center gap-4 px-4'>
<AssetImage asset={modal.data.asset} size={24} />
<Text>
{'Repay'} {asset.symbol}
</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
suffix: ` ${asset.symbol}`,
}}
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Total Borrowed
</Text>
</div>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={modal.data?.liquidity.toNumber() ?? 0}
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
animate
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data?.liquidity ?? BN_ZERO)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Liquidity available
</Text>
</div>
</div>
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
<Divider />
{maxRepayAmount.isZero() && <RepayNotAvailable asset={asset} />}
<Button
onClick={onConfirmClick}
className='w-full'
disabled={amount.isZero()}
text={`Repay ${asset.symbol}`}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummaryInModal account={account} />
</div>
</Modal>
)
}
@@ -0,0 +1,15 @@
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
import Borrow from 'components/Modals/v1/Borrow'
import Repay from 'components/Modals/v1/Repay'
export default function V1BorrowAndRepayModal() {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const modal = useStore<V1BorrowAndRepayModal | null>((s) => s.v1BorrowAndRepayModal)
const isBorrow = modal?.type === 'borrow'
if (!modal || !account) return null
if (isBorrow) return <Borrow account={account} />
return <Repay account={account} />
}
@@ -0,0 +1,15 @@
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
import Deposit from 'components/Modals/v1/Deposit'
import Withdraw from 'components/Modals/v1/Withdraw'
export default function V1DepositAndWithdraw() {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const modal = useStore<V1DepositAndWithdrawModal | null>((s) => s.v1DepositAndWithdrawModal)
const isDeposit = modal?.type === 'deposit'
if (!modal || !account) return null
if (isDeposit) return <Deposit account={account} />
return <Withdraw account={account} />
}
+66
View File
@@ -0,0 +1,66 @@
import { useCallback, useState } from 'react'
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useHealthComputer from 'hooks/useHealthComputer'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
interface Props {
account: Account
}
export default function Withdraw(props: Props) {
const { account } = props
const baseAsset = useBaseAsset()
const modal = useStore((s) => s.v1DepositAndWithdrawModal)
const asset = modal?.data.asset ?? baseAsset
const [withdrawAsset, setWithdrawAsset] = useState<BNCoin>(
BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO),
)
const { computeMaxWithdrawAmount } = useHealthComputer(account)
const maxWithdrawAmount = computeMaxWithdrawAmount(asset.denom)
const { simulateWithdraw } = useUpdatedAccount(account)
const balance = BNCoin.fromDenomAndBigNumber(asset.denom, maxWithdrawAmount)
const v1Action = useStore((s) => s.v1Action)
const close = useCallback(() => {
useStore.setState({ v1DepositAndWithdrawModal: null })
}, [])
const handleClick = useCallback(async () => {
v1Action('withdraw', withdrawAsset)
close()
}, [v1Action, withdrawAsset, close])
const onDebounce = useCallback(() => {
simulateWithdraw(false, withdrawAsset)
}, [withdrawAsset, simulateWithdraw])
const handleAmountChange = useCallback(
(value: BigNumber) => {
setWithdrawAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value))
},
[asset.denom],
)
if (!modal) return
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={modal.data} />}
coinBalances={[balance]}
actionButtonText={`Withdraw ${asset.symbol}`}
title={`Withdraw ${asset.symbol} from the Red Bank`}
onClose={close}
onAction={handleClick}
onChange={handleAmountChange}
onDebounce={onDebounce}
/>
)
}
+4 -2
View File
@@ -7,8 +7,8 @@ import Text from 'components/common/Text'
import { TextLink } from 'components/common/TextLink'
import { generateToastContent } from 'components/common/Toaster'
import useTransactions from 'hooks/localStorage/useTransactions'
import useStore from 'store'
import useChainConfig from 'hooks/useChainConfig'
import useStore from 'store'
export default function RecentTransactions() {
const address = useStore((s) => s.address)
@@ -47,7 +47,9 @@ export default function RecentTransactions() {
key={hash}
>
<div className='flex items-start justify-between w-full pb-2'>
<Text className='flex font-bold'>Credit Account {accountId}</Text>
<Text className='flex font-bold'>
{accountId === address ? 'Red Bank' : `Credit Account ${accountId}`}
</Text>
<Text size='sm' className='text-white/70'>
{moment.unix(timestamp).format('lll')}
</Text>
@@ -15,6 +15,7 @@ interface Props {
color?: ButtonProps['color']
variant?: ButtonProps['variant']
size?: ButtonProps['size']
short?: boolean
}
export default function WalletConnectButton(props: Props) {
@@ -18,6 +18,7 @@ import chains from 'configs/chains'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import useChainConfig from 'hooks/useChainConfig'
import useCurrentWallet from 'hooks/useCurrentWallet'
import useICNSDomain from 'hooks/useICNSDomain'
import useToggle from 'hooks/useToggle'
@@ -27,7 +28,6 @@ import { NETWORK } from 'types/enums/network'
import { ChainInfoID } from 'types/enums/wallet'
import { truncate } from 'utils/formatters'
import { getPage, getRoute } from 'utils/route'
import useChainConfig from 'hooks/useChainConfig'
export default function WalletConnectedButton() {
// ---------------
@@ -90,7 +90,7 @@ export default function WalletConnectedButton() {
})
}
navigate(getRoute(getPage(pathname), searchParams))
navigate(getRoute(getPage(pathname), new URLSearchParams()))
}
useEffect(() => {
+3 -2
View File
@@ -10,6 +10,7 @@ import useChainConfig from 'hooks/useChainConfig'
import useCurrentWallet from 'hooks/useCurrentWallet'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { getUrl } from 'utils/url'
interface Props {
providerId?: string
@@ -53,7 +54,7 @@ export default function WalletConnecting(props: Props) {
setIsConnecting(true)
try {
const response = await connect({ extensionProviderId, chainId: chainConfig.id })
const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc)
const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc))
const walletClient: WalletClient = {
broadcast,
cosmWasmClient: cosmClient,
@@ -137,7 +138,7 @@ export default function WalletConnecting(props: Props) {
setIsConnecting(true)
try {
await mobileConnect({ mobileProviderId, chainId: chainConfig.id })
const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc)
const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc))
const walletClient: WalletClient = {
broadcast,
cosmWasmClient: cosmClient,
@@ -29,7 +29,7 @@ function FetchLoading() {
function Content() {
const address = useStore((s) => s.address)
const [searchParams] = useSearchParams()
const isV1 = useStore((s) => s.isV1)
const { address: urlAddress } = useParams()
const urlAccountId = useAccountId()
const navigate = useNavigate()
@@ -62,7 +62,7 @@ function Content() {
) {
const currentAccountIsHLS = urlAccountId && !accountIds.includes(urlAccountId)
const currentAccount = currentAccountIsHLS || !urlAccountId ? accountIds[0] : urlAccountId
navigate(getRoute(page, searchParams, address, currentAccount))
navigate(getRoute(page, searchParams, address, isV1 ? undefined : currentAccount))
useStore.setState({ balances: walletBalances, focusComponent: null })
}
}, [
@@ -75,11 +75,12 @@ function Content() {
urlAddress,
urlAccountId,
searchParams,
isV1,
])
if (isLoadingAccounts || isLoadingBalances) return <FetchLoading />
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return <WalletBridges />
if (accountIds && accountIds.length === 0) return <AccountCreateFirst />
if (accountIds && accountIds.length === 0 && !isV1) return <AccountCreateFirst />
return null
}
@@ -75,6 +75,7 @@ export default function AccountBalancesTable(props: Props) {
},
})
}}
short
/>
</div>
</ConditionalWrapper>
@@ -37,6 +37,7 @@ import {
export default function AccountDetailsController() {
const address = useStore((s) => s.address)
const isHLS = useStore((s) => s.isHLS)
const isV1 = useStore((s) => s.isV1)
const { data: _, isLoading } = useAccounts('default', address)
const { data: accountIds } = useAccountIds(address, false, true)
@@ -45,10 +46,11 @@ export default function AccountDetailsController() {
const account = useCurrentAccount()
const focusComponent = useStore((s) => s.focusComponent)
const isOwnAccount = accountId && accountIds?.includes(accountId)
const hideAccountDetails = !address || focusComponent || !isOwnAccount || isHLS || isV1
const isLoadingAccountDetails = (isLoading && accountId && !focusComponent) || !account
if (!address || focusComponent || !isOwnAccount || isHLS) return null
if ((isLoading && accountId && !focusComponent) || !account) return <Skeleton />
if (hideAccountDetails) return null
if (isLoadingAccountDetails) return <Skeleton />
return <AccountDetails account={account} />
}
+10 -4
View File
@@ -63,16 +63,22 @@ export default function HealthBar({
health={isUpdated ? updatedHealth : health}
healthFactor={isUpdated ? updatedHealthFactor : healthFactor}
>
<>
<div className={classNames('flex w-full', showIcon && 'gap-2')}>
{showIcon && (
<HealthIcon
health={health}
isLoading={healthFactor === 0}
className={classNames('mr-2', iconClassName)}
className={iconClassName}
colorClass='text-white'
/>
)}
<div className={classNames('flex w-full', 'rounded-full overflow-hidden', className)}>
<div
className={classNames(
'flex w-full flex-shrink',
'rounded-full overflow-hidden',
className,
)}
>
<svg
version='1.1'
xmlns='http://www.w3.org/2000/svg'
@@ -153,7 +159,7 @@ export default function HealthBar({
)}
</svg>
</div>
</>
</div>
</HealthTooltip>
)
}
+4 -4
View File
@@ -5,7 +5,7 @@ import { ExclamationMarkCircled, Heart } from 'components/common/Icons'
interface Props {
isLoading: boolean
health: number
className: string
className?: string
colorClass?: string
}
@@ -14,9 +14,9 @@ export default function HealthIcon(props: Props) {
const color = colorClass ?? 'text-white'
return (
<>
<div className='w-5'>
{!isLoading && health === 0 ? (
<ExclamationMarkCircled className={classNames('w-5 text-loss animate-pulse', className)} />
<ExclamationMarkCircled className={classNames(' text-loss animate-pulse', className)} />
) : (
<Heart
className={classNames(
@@ -26,6 +26,6 @@ export default function HealthIcon(props: Props) {
)}
/>
)}
</>
</div>
)
}
@@ -1,44 +0,0 @@
import { useCallback } from 'react'
import Button from 'components/common/Button'
import ActionButton from 'components/common/Button/ActionButton'
import { HandCoins, Plus } from 'components/common/Icons'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import useStore from 'store'
interface Props {
data: BorrowMarketTableData
}
export default function BorrowActionButtons(props: Props) {
const { asset, accountDebt } = props.data
const marketAssets = useMarketEnabledAssets()
const currentAsset = marketAssets.find((a) => a.denom === asset.denom)
const borrowHandler = useCallback(() => {
if (!currentAsset) return null
useStore.setState({ borrowModal: { asset: currentAsset, marketData: props.data } })
}, [currentAsset, props.data])
const repayHandler = useCallback(() => {
if (!currentAsset) return null
useStore.setState({
borrowModal: { asset: currentAsset, marketData: props.data, isRepay: true },
})
}, [currentAsset, props.data])
return (
<div className='flex flex-row space-x-2'>
<ActionButton
leftIcon={<Plus className='w-3' />}
onClick={borrowHandler}
color='secondary'
text={accountDebt ? 'Borrow more' : 'Borrow'}
className='text-center min-w-40'
/>
{accountDebt && (
<Button color='tertiary' leftIcon={<HandCoins />} text='Repay' onClick={repayHandler} />
)}
</div>
)
}
+5 -5
View File
@@ -1,19 +1,19 @@
import ActiveBorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable'
import AvailableBorrowingsTable from 'components/borrow/Table/AvailableBorrowingsTable'
import DepositedBorrowingsTable from 'components/borrow/Table/DepositedBorrowingsTable'
import useBorrowMarketAssetsTableData from 'components/borrow/Table/useBorrowMarketAssetsTableData'
import { BN_ZERO } from 'constants/math'
import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets'
export default function Borrowings() {
const data = useBorrowMarketAssetsTableData()
const { accountBorrowedAssets, availableAssets, allAssets } = useBorrowMarketAssetsTableData()
if (!data?.allAssets?.length) {
if (!allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedBorrowingsTable data={data.accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={data.availableAssets} isLoading={false} />
<ActiveBorrowingsTable data={accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={availableAssets} isLoading={false} />
</>
)
}
@@ -0,0 +1,41 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import { DEBT_VALUE_META } from 'components/borrow/Table/Columns/DebtValue'
import { NAME_META } from 'components/borrow/Table/Columns/Name'
import useBorrowingsColumns from 'components/borrow/Table/Columns/useActiveColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
v1?: boolean
}
export default function ActiveBorrowingsTable(props: Props) {
const columns = useBorrowingsColumns({ v1: props.v1 })
const renderExpanded = useCallback((row: Row<BorrowMarketTableData>) => {
return <MarketDetails row={row} type='borrow' />
}, [])
if (!props.data.length) return null
return (
<Table
title={props.v1 ? 'Borrowings' : 'Borrowed Assets'}
columns={columns}
data={props.data}
initialSorting={
props.v1
? [
{ id: DEBT_VALUE_META.id, desc: true },
{ id: NAME_META.id, desc: false },
]
: [{ id: NAME_META.id, desc: false }]
}
renderExpanded={renderExpanded}
/>
)
}
@@ -2,12 +2,10 @@ import { Row } from '@tanstack/react-table'
import { Table as TanstackTable } from '@tanstack/table-core/build/lib/types'
import { useCallback } from 'react'
import BorrowActionButtons from 'components/borrow/BorrowActionButtons'
import { NAME_META } from 'components/borrow/Table/Columns/Name'
import useAvailableColumns from 'components/borrow/Table/Columns/useAvailableColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
import ActionButtonRow from 'components/common/Table/ActionButtonRow'
type Props = {
data: BorrowMarketTableData[]
@@ -15,19 +13,12 @@ type Props = {
}
export default function AvailableBorrowingsTable(props: Props) {
const columns = useAvailableColumns()
const columns = useAvailableColumns({ v1: false })
const renderExpanded = useCallback(
(row: Row<BorrowMarketTableData>, _: TanstackTable<BorrowMarketTableData>) => {
const currentRow = row as Row<BorrowMarketTableData>
return (
<>
<ActionButtonRow row={currentRow}>
<BorrowActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={currentRow} type='borrow' />
</>
)
return <MarketDetails row={currentRow} type='borrow' />
},
[],
)
@@ -0,0 +1,62 @@
import { useCallback } from 'react'
import ActionButton from 'components/common/Button/ActionButton'
import { Plus } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useStore from 'store'
export const BORROW_BUTTON_META = {
accessorKey: 'borrow',
enableSorting: false,
header: '',
}
interface Props {
data: LendingMarketTableData
}
export default function BorrowButton(props: Props) {
const account = useCurrentAccount()
const address = useStore((s) => s.address)
const hasNoDeposits = !account?.deposits?.length && !account?.lends?.length && !!address
const borrowHandler = useCallback(() => {
if (!props.data.asset) return null
useStore.setState({ borrowModal: { asset: props.data.asset, marketData: props.data } })
}, [props.data])
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={hasNoDeposits}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have any collateral.
Please first deposit into your Credit Account before borrowing.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<Plus />}
disabled={hasNoDeposits}
color='tertiary'
onClick={(e) => {
borrowHandler()
e.stopPropagation()
}}
text='Borrow'
short
/>
</ConditionalWrapper>
</div>
)
}
@@ -4,7 +4,6 @@ import Loading from 'components/common/Loading'
export const BORROW_RATE_META = {
accessorKey: 'apy.borrow',
header: 'Borrow Rate APY',
meta: { className: 'w-40' },
}
interface Props {
@@ -0,0 +1,21 @@
import { ChevronDown, ChevronUp } from 'components/common/Icons'
export const CHEVRON_META = {
id: 'chevron',
enableSorting: false,
header: '',
meta: {
className: 'w-5',
},
}
interface Props {
isExpanded: boolean
}
export default function Chevron(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}
@@ -1,36 +0,0 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/common/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import { byDenom } from 'utils/array'
export const DEBT_META = {
accessorKey: 'debt',
header: 'Debt',
}
export const debtSortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const assetA = a.original.asset
const assetB = b.original.asset
if (!a.original.accountDebt || !b.original.accountDebt) return 0
const debtA = a.original.accountDebt.shiftedBy(-assetA.decimals)
const debtB = b.original.accountDebt.shiftedBy(-assetB.decimals)
return debtA.minus(debtB).toNumber()
}
interface Props {
data: BorrowMarketTableData
}
export default function Debt(props: Props) {
const marketAssets = useMarketEnabledAssets()
const asset = marketAssets.find(byDenom(props.data.asset.denom))
if (!asset) return null
return <AmountAndValue asset={asset} amount={props.data?.accountDebt ?? BN_ZERO} />
}
@@ -0,0 +1,30 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/common/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEBT_VALUE_META = {
id: 'accountDebtValue',
accessorKey: 'accountDebtValue',
header: 'Debt',
}
export const debtSortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const debtValueA = BN(a.original?.accountDebtValue ?? 0)
const debtValueB = BN(b.original?.accountDebtValue ?? 0)
return debtValueA.minus(debtValueB).toNumber()
}
interface Props {
asset: Asset
debtAmount?: BigNumber
}
export default function DebtValue(props: Props) {
return (
<AmountAndValue asset={props.asset} amount={props.debtAmount ? props.debtAmount : BN_ZERO} />
)
}
@@ -10,7 +10,6 @@ export const LIQUIDITY_META = {
accessorKey: 'liquidity',
header: 'Liquidity Available',
id: 'liquidity',
meta: { className: 'w-40' },
}
export const liquiditySortingFn = (
+41 -6
View File
@@ -1,20 +1,55 @@
import { ChevronDown, ChevronUp } from 'components/common/Icons'
import { useCallback, useMemo } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { HandCoins, Plus } from 'components/common/Icons'
import useStore from 'store'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
meta: { className: 'w-30' },
header: '',
}
interface Props {
isExpanded: boolean
data: BorrowMarketTableData
}
export default function Manage(props: Props) {
const address = useStore((s) => s.address)
const borrowHandler = useCallback(() => {
if (!props.data.asset) return null
useStore.setState({ borrowModal: { asset: props.data.asset, marketData: props.data } })
}, [props.data])
const repayHandler = useCallback(() => {
if (!props.data.asset) return null
useStore.setState({
borrowModal: { asset: props.data.asset, marketData: props.data, isRepay: true },
})
}, [props.data])
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <Plus />,
text: 'Borrow more',
onClick: borrowHandler,
},
{
icon: <HandCoins />,
text: 'Repay',
onClick: repayHandler,
},
],
[borrowHandler, repayHandler],
)
if (!address) return null
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}
+6 -1
View File
@@ -5,6 +5,7 @@ export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'sy
interface Props {
data: BorrowMarketTableData
v1?: boolean
}
export default function Name(props: Props) {
@@ -12,7 +13,11 @@ export default function Name(props: Props) {
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
<TitleAndSubCell
title={asset.symbol}
sub={props.v1 ? '' : asset.name}
className='text-left min-w-15'
/>
</div>
)
}
@@ -2,24 +2,35 @@ import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/borrow/Table/Columns/BorrowRate'
import Debt, { DEBT_META, debtSortingFn } from 'components/borrow/Table/Columns/Debt'
import Chevron, { CHEVRON_META } from 'components/borrow/Table/Columns/Chevron'
import DebtValue, {
DEBT_VALUE_META,
debtSortingFn,
} from 'components/borrow/Table/Columns/DebtValue'
import Liquidity, {
LIQUIDITY_META,
liquiditySortingFn,
} from 'components/borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/borrow/Table/Columns/Name'
import Action from 'components/v1/Table/borrowings/Columns/Action'
export default function useDepositedColumns() {
interface Props {
v1?: boolean
}
export default function useActiveColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
cell: ({ row }) => <Name data={row.original} v1={props.v1} />,
},
{
...DEBT_META,
cell: ({ row }) => <Debt data={row.original} />,
...DEBT_VALUE_META,
cell: ({ row }) => (
<DebtValue asset={row.original.asset} debtAmount={row.original.accountDebtAmount} />
),
sortingFn: debtSortingFn,
},
{
@@ -33,8 +44,13 @@ export default function useDepositedColumns() {
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
cell: ({ row }) =>
props.v1 ? <Action data={row.original} /> : <Manage data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}, [props.v1])
}
@@ -1,20 +1,25 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import BorrowButton, { BORROW_BUTTON_META } from 'components/borrow/Table/Columns/BorrowButton'
import BorrowRate, { BORROW_RATE_META } from 'components/borrow/Table/Columns/BorrowRate'
import Chevron, { CHEVRON_META } from 'components/borrow/Table/Columns/Chevron'
import Liquidity, {
LIQUIDITY_META,
liquiditySortingFn,
} from 'components/borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/borrow/Table/Columns/Name'
export default function useAvailableColumns() {
interface Props {
v1?: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
cell: ({ row }) => <Name data={row.original} v1={props.v1} />,
},
{
...BORROW_RATE_META,
@@ -26,9 +31,13 @@ export default function useAvailableColumns() {
sortingFn: liquiditySortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
...BORROW_BUTTON_META,
cell: ({ row }) => <BorrowButton data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}, [props.v1])
}
@@ -1,42 +0,0 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import BorrowActionButtons from 'components/borrow/BorrowActionButtons'
import { NAME_META } from 'components/borrow/Table/Columns/Name'
import useDepositedColumns from 'components/borrow/Table/Columns/useDepositedColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
import ActionButtonRow from 'components/common/Table/ActionButtonRow'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
}
export default function DepositedBorrowingsTable(props: Props) {
const columns = useDepositedColumns()
const renderExpanded = useCallback((row: Row<BorrowMarketTableData>) => {
const currentRow = row as Row<BorrowMarketTableData>
return (
<>
<ActionButtonRow row={currentRow}>
<BorrowActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='borrow' />
</>
)
}, [])
if (!props.data.length) return null
return (
<Table
title='Borrowed Assets'
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
renderExpanded={renderExpanded}
/>
)
}
@@ -1,11 +1,14 @@
import { useMemo } from 'react'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useMarkets from 'hooks/markets/useMarkets'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
export default function useBorrowMarketAssetsTableData() {
const account = useCurrentAccount()
const markets = useMarkets()
const { convertAmount } = useDisplayCurrencyPrice()
return useMemo((): {
accountBorrowedAssets: BorrowMarketTableData[]
@@ -18,15 +21,21 @@ export default function useBorrowMarketAssetsTableData() {
markets
.filter((market) => market.borrowEnabled)
.forEach((market) => {
const debt = account?.debts?.find((debt) => debt.denom === market.asset.denom)
const amount =
account?.debts?.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO
const value = amount ? convertAmount(market.asset, amount) : undefined
const borrowMarketAsset: BorrowMarketTableData = {
...market,
accountDebt: debt?.amount,
accountDebtAmount: amount,
accountDebtValue: value,
}
if (borrowMarketAsset.accountDebtAmount?.isZero()) {
availableAssets.push(borrowMarketAsset)
} else {
accountBorrowedAssets.push(borrowMarketAsset)
}
;(borrowMarketAsset.accountDebt ? accountBorrowedAssets : availableAssets).push(
borrowMarketAsset,
)
})
return {
@@ -34,5 +43,5 @@ export default function useBorrowMarketAssetsTableData() {
availableAssets,
allAssets: [...accountBorrowedAssets, ...availableAssets],
}
}, [account?.debts, markets])
}, [account?.debts, markets, convertAmount])
}
+20 -8
View File
@@ -15,19 +15,31 @@ export default function Background() {
)
const { pathname } = useLocation()
const page = getPage(pathname)
const isHLS = useMemo(() => page.split('-')[0] === 'hls', [page])
const [isHLS, isV1] = useMemo(() => [page.split('-')[0] === 'hls', page === 'v1'], [page])
useEffect(() => {
useStore.setState({ isHLS })
}, [isHLS])
useStore.setState({ isHLS: isHLS, isV1: isV1 })
}, [isHLS, isV1])
const [primaryOrbClassName, secondaryOrbClassName, tertiaryOrbClassName, bodyClassName] =
useMemo(() => {
if (isHLS) {
return ['bg-orb-primary-hls', 'bg-orb-secondary-hls', 'bg-orb-tertiary-hls', 'bg-body-hls']
}
if (isV1) {
return ['bg-transparent', 'bg-transparent', 'bg-transparent', 'bg-body bg-v1 blur-[2px]']
}
return ['bg-orb-primary', 'bg-orb-secondary', 'bg-orb-tertiary', 'bg-body']
}, [isHLS, isV1])
return (
<div
className={classNames(
'fixed inset-0',
'w-full h-full',
'overflow-hidden pointer-events-none background ',
isHLS ? 'bg-body-hls' : 'bg-body',
'overflow-hidden pointer-events-none background',
bodyClassName,
!reduceMotion && 'transition-bg duration-1000 delay-300',
)}
>
@@ -39,7 +51,7 @@ export default function Background() {
'max-h-[500px] max-w-[500px]',
'left-[-10vw] top-[-10vw]',
'blur-orb-primary',
isHLS ? ' bg-orb-primary-hls' : 'bg-orb-primary',
primaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]',
!reduceMotion && 'transition-bg duration-1000 delay-300',
@@ -53,7 +65,7 @@ export default function Background() {
'max-h-[1000px] max-w-[1000px]',
'bottom-[-20vw] right-[-10vw]',
'blur-orb-secondary',
isHLS ? ' bg-orb-secondary-hls' : 'bg-orb-secondary',
secondaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-30',
!reduceMotion && 'transition-bg duration-1000 delay-300',
)}
@@ -66,7 +78,7 @@ export default function Background() {
'max-h-[600px] max-w-[600px]',
'right-[-4vw] top-[-10vw]',
'blur-orb-tertiary ',
isHLS ? ' bg-orb-tertiary-hls' : 'bg-orb-tertiary',
tertiaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_180s_ease-in_infinite]',
!reduceMotion && 'transition-bg duration-1000 delay-300',
+10 -4
View File
@@ -9,10 +9,15 @@ import useAccountIds from 'hooks/accounts/useAccountIds'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
export default function ActionButton(props: ButtonProps) {
const { className, color, variant, size } = props
interface Props extends ButtonProps {
short?: boolean
}
export default function ActionButton(props: Props) {
const { className, color, variant, size, short } = props
const defaultProps = { className, color, variant, size }
const address = useStore((s) => s.address)
const isV1 = useStore((s) => s.isV1)
const { data: accountIds } = useAccountIds(address || '')
const selectedAccountId = useAccountId()
@@ -21,7 +26,8 @@ export default function ActionButton(props: ButtonProps) {
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
}, [])
if (!address) return <WalletConnectButton {...defaultProps} />
if (!address)
return <WalletConnectButton {...defaultProps} textOverride={short ? 'Connect' : undefined} />
if (accountIds && accountIds.length === 0) {
return (
@@ -34,7 +40,7 @@ export default function ActionButton(props: ButtonProps) {
)
}
if (!selectedAccountId) {
if (!selectedAccountId && !isV1) {
return (
<Button
text='Select Account'
+42 -11
View File
@@ -1,12 +1,16 @@
import classNames from 'classnames'
import Button from 'components/common/Button/index'
import { ChevronDown } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useToggle from 'hooks/useToggle'
interface Props extends ButtonProps {
items: DropDownItem[]
text: string
showProgressIndicator?: boolean
}
export default function DropDownButton(props: Props) {
@@ -16,16 +20,20 @@ export default function DropDownButton(props: Props) {
content={<DropDown closeMenu={() => toggleIsOpen(false)} {...props} />}
type='info'
placement='bottom'
contentClassName='!bg-white/10 backdrop-blur-xl !p-0'
contentClassName='!bg-white/10 backdrop-blur-xl !p-0 w-full min-w-[140px]'
interactive
hideArrow
visible={isOpen}
onClickOutside={() => toggleIsOpen(false)}
>
<Button
onClick={() => toggleIsOpen()}
onClick={(e) => {
toggleIsOpen()
e.stopPropagation()
}}
rightIcon={<ChevronDown />}
iconClassName='w-3 h-3'
showProgressIndicator={props.showProgressIndicator}
{...props}
/>
</Tooltip>
@@ -39,7 +47,7 @@ interface DropDownProps {
function DropDown(props: DropDownProps) {
return (
<div>
<div className='w-full'>
{props.items.map((item) => (
<DropDownItem key={item.text} item={item} closeMenu={props.closeMenu} />
))}
@@ -54,15 +62,38 @@ interface DropDownItemProps {
function DropDownItem(props: DropDownItemProps) {
return (
<button
onClick={() => {
props.item.onClick()
props.closeMenu()
<ConditionalWrapper
condition={!!props.item.disabled}
wrapper={(children) => {
if (!props.item.disabledTooltip) return children
return (
<Tooltip
type='warning'
content={<Text size='sm'>{props.item.disabledTooltip}</Text>}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)
}}
className=' px-4 py-3 flex gap-2 items-center hover:bg-white/5 w-full [&:not(:last-child)]:border-b border-white/10'
>
<div className='flex justify-center w-5 h-5'>{props.item.icon}</div>
<Text size='sm'>{props.item.text}</Text>
</button>
<button
onClick={(e) => {
e.preventDefault()
props.item.onClick()
props.closeMenu()
e.stopPropagation()
}}
className={classNames(
'z-1 px-4 py-3 flex gap-2 items-center w-full [&:not(:last-child)]:border-b border-white/10',
props.item.disabled ? 'bg-black/20 text-white/40 cursor-events-none' : 'hover:bg-white/5',
)}
disabled={props.item.disabled}
>
<div className='flex justify-center w-4 h-4'>{props.item.icon}</div>
<Text size='sm'>{props.item.text}</Text>
</button>
</ConditionalWrapper>
)
}
+5 -1
View File
@@ -18,7 +18,11 @@ export default function DepositCapMessage(props: Props) {
return (
<div className={classNames('flex items-start', props.className)}>
{props.showIcon && <InfoCircle width={26} className='mr-5' />}
{props.showIcon && (
<div className='w-6 mr-5'>
<InfoCircle />
</div>
)}
<div className='flex flex-col gap-2'>
<Text size='sm'>Deposit Cap Reached!</Text>
<Text size='xs' className='text-white/40'>{`Unfortunately you're not able to ${
+3 -1
View File
@@ -1,10 +1,12 @@
import { TextLink } from 'components/common/TextLink'
import { DocURL } from 'types/enums/docURL'
import useStore from 'store'
import packageInfo from '../../../package.json'
export default function Footer() {
const version = `v${packageInfo.version}`
const isV1 = useStore((s) => s.isV1)
const version = isV1 ? `v${packageInfo.v1version}` : `v${packageInfo.version}`
const flatVersion = packageInfo.version.split('.').join('')
return (
<footer className='flex items-center justify-center w-full h-6 -mt-6'>
+1 -1
View File
@@ -17,7 +17,7 @@ import useStore from 'store'
interface Props {
text: string | ReactNode
children?: ReactNode
bg: 'borrow' | 'lend' | 'farm' | 'portfolio' | 'hls-farm' | 'hls-staking'
bg: Page
}
function IntroBackground(props: { bg: Props['bg'] }) {
+2 -2
View File
@@ -23,7 +23,7 @@ export default function MarketDetails({ row, type }: Props) {
symbol: displayCurrencySymbol,
} = useDisplayCurrencyPrice()
const { asset, ltv, cap, liquidity, deposits, debt } = row.original
const { asset, ltv, deposits, debt } = row.original
const details: Detail[] = useMemo(() => {
const isDollar = displayCurrencySymbol === '$'
@@ -118,7 +118,7 @@ export default function MarketDetails({ row, type }: Props) {
<TitleAndSubCell
key={index}
className='text-center'
containerClassName='m-5 ml-10 mr-10 space-y-1'
containerClassName='m-5 mx-auto space-y-1'
title={
<FormattedNumber
className='text-xs text-center'
+6 -1
View File
@@ -10,6 +10,7 @@ interface Props<T> {
className?: string
isSelectable?: boolean
type?: TableType
onClick?: (id: string) => void
}
function getBorderColor(
@@ -36,7 +37,7 @@ export default function Row<T>(props: Props<T>) {
key={`${row.id}-row`}
className={classNames(
'group/row transition-bg',
(renderExpanded || isSelectable) && 'hover:cursor-pointer',
(renderExpanded || isSelectable || props.onClick) && 'hover:cursor-pointer',
canExpand && row.getIsExpanded() ? 'is-expanded bg-black/20' : 'hover:bg-white/5',
)}
onClick={(e) => {
@@ -49,6 +50,10 @@ export default function Row<T>(props: Props<T>) {
table.resetExpanded()
!isExpanded && row.toggleExpanded()
}
if (props.onClick) {
props.onClick((row.original as any).asset.denom)
}
}}
>
{row.getVisibleCells().map((cell) => {
+2
View File
@@ -31,6 +31,7 @@ interface Props<T> {
hideCard?: boolean
setRowSelection?: OnChangeFn<RowSelectionState>
selectedRows?: RowSelectionState
onClickRow?: (id: string) => void
}
export default function Table<T>(props: Props<T>) {
@@ -122,6 +123,7 @@ export default function Table<T>(props: Props<T>) {
spacingClassName={props.spacingClassName}
isSelectable={!!props.setRowSelection}
type={props.type}
onClick={props.onClickRow}
/>
))}
</tbody>
+9 -2
View File
@@ -11,11 +11,11 @@ import { TextLink } from 'components/common/TextLink'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useChainConfig from 'hooks/useChainConfig'
import useTransactionStore from 'hooks/useTransactionStore'
import useStore from 'store'
import { formatAmountWithSymbol } from 'utils/formatters'
import { BN } from 'utils/helpers'
import useChainConfig from 'hooks/useChainConfig'
const toastBodyClasses = classNames(
'flex flex-wrap w-full group/transaction',
@@ -99,6 +99,13 @@ export default function Toaster() {
if (!isError && toast.accountId) addTransaction(toast)
const generalMessage = isError ? 'Transaction failed!' : 'Transaction completed successfully!'
const showDetailElement = !!(!details && toast.hash)
const address = useStore.getState().address
let target: string
if (!isError) {
target = toast.accountId === address ? 'Red Bank' : `Credit Account ${toast.accountId}`
}
const Msg = () => (
<div className='relative flex flex-wrap w-full m-0 isolate'>
<div className='flex w-full gap-2 mb-2'>
@@ -141,7 +148,7 @@ export default function Toaster() {
)}
>
{!isError && toast.accountId && (
<Text className='mb-1 font-bold text-white'>{`Credit Account ${toast.accountId}`}</Text>
<Text className='mb-1 font-bold text-white'>{target}</Text>
)}
{showDetailElement && toast.message && (
<Text size='sm' className='w-full mb-1 text-white'>
@@ -16,9 +16,9 @@ export default function TooltipContent(props: Props) {
<div>
<div
className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-lg p-3 text-sm shadow-tooltip backdrop-blur-[100px]',
'flex max-w-[320px] flex-1 gap-2 rounded-base p-3 text-sm shadow-tooltip backdrop-blur-[100px]',
'relative isolate max-w-full overflow-hidden',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-lg before:p-[1px] before:border-glas',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
props.type === 'info' && 'bg-white/10',
props.type === 'warning' && 'bg-warning',
props.type === 'error' && 'bg-error',
+2 -2
View File
@@ -48,7 +48,7 @@ export const Tooltip = (props: Props) => {
visible={props.visible}
>
{props.children ? (
<span
<div
className={classNames(
props.underline &&
'border-b hover:cursor-help border-dashed border-white/20 pb-1 hover:border-transparent',
@@ -57,7 +57,7 @@ export const Tooltip = (props: Props) => {
)}
>
{props.children}
</span>
</div>
) : (
<span
className={classNames(
@@ -1,6 +1,7 @@
import React from 'react'
import ActionButton from 'components/common/Button/ActionButton'
import { Plus } from 'components/common/Icons'
import Loading from 'components/common/Loading'
import useStore from 'store'
@@ -9,6 +10,8 @@ interface Props {
isLoading: boolean
}
export const DEPOSIT_META = { accessorKey: 'deposit', enableSorting: false, header: '' }
export const Deposit = (props: Props) => {
const { vault } = props
@@ -26,7 +29,13 @@ export const Deposit = (props: Props) => {
return (
<div className='flex items-center justify-end'>
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
<ActionButton
onClick={enterVaultHandler}
color='tertiary'
text='Deposit'
leftIcon={<Plus />}
short
/>
</div>
)
}
@@ -1,24 +0,0 @@
import classNames from 'classnames'
import React from 'react'
import { ChevronDown } from 'components/common/Icons'
import Loading from 'components/common/Loading'
export const DETAILS_META = { accessorKey: 'details', enableSorting: false, header: 'Deposit' }
interface Props {
isLoading: boolean
isExpanded: boolean
}
export default function Details(props: Props) {
if (props.isLoading) return <Loading />
return (
<div className='flex items-center justify-end'>
<div className={classNames('w-4', props.isExpanded && 'rotate-180')}>
<ChevronDown />
</div>
</div>
)
}
@@ -0,0 +1,115 @@
import moment from 'moment/moment'
import { useCallback, useMemo, useState } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/common/Icons'
import Loading from 'components/common/Loading'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
import { VaultStatus } from 'types/enums/vault'
export const MANAGE_META = { accessorKey: 'details', enableSorting: false, header: '' }
interface Props {
vault: DepositedVault
isLoading: boolean
isExpanded: boolean
}
export default function Manage(props: Props) {
const accountId = useAccountId()
const address = useStore((s) => s.address)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
const [isConfirming, setIsConfirming] = useState(false)
const depositMoreHandler = useCallback(() => {
useStore.setState({
vaultModal: {
vault: props.vault,
isDeposited: true,
selectedBorrowDenoms: [props.vault.denoms.secondary],
isCreate: false,
},
})
}, [props.vault])
const unlockHandler = useCallback(
() => useStore.setState({ unlockModal: { vault: props.vault } }),
[props.vault],
)
const withdrawHandler = useCallback(async () => {
if (!accountId) return
setIsConfirming(true)
await withdrawFromVaults({
accountId: accountId,
vaults: [props.vault],
slippage,
})
}, [accountId, props.vault, slippage, withdrawFromVaults])
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <Plus />,
text: 'Deposit more',
onClick: depositMoreHandler,
},
...(props.vault.status === VaultStatus.ACTIVE
? [
{
icon: <LockUnlocked />,
text: 'Unlock to withdraw',
onClick: unlockHandler,
},
]
: []),
...(props.vault.status === VaultStatus.UNLOCKING
? [
{
icon: <LockLocked />,
text: `Withdraw in ${moment(props.vault?.unlocksAt).fromNow(true)}`,
onClick: () => {},
disabled: true,
disabledTooltip: '',
},
]
: []),
...(props.vault.status === VaultStatus.UNLOCKED
? [
{
icon: <AccountArrowDown />,
text: 'Withdraw funds',
onClick: withdrawHandler,
},
]
: []),
],
[
depositMoreHandler,
props.vault.status,
props.vault?.unlocksAt,
unlockHandler,
withdrawHandler,
],
)
if (props.isLoading) return <Loading />
if (!address) return null
return (
<div className='z-10 flex justify-end'>
<DropDownButton
items={ITEMS}
text='Manage'
color='tertiary'
showProgressIndicator={isConfirming}
/>
</div>
)
}
@@ -2,7 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/earn/farm/Table/Columns/Apy'
import { Deposit } from 'components/earn/farm/Table/Columns/Deposit'
import { Deposit, DEPOSIT_META } from 'components/earn/farm/Table/Columns/Deposit'
import DepositCap, {
DEPOSIT_CAP_META,
depositCapSortingFn,
@@ -10,7 +10,6 @@ import DepositCap, {
import MaxLTV, { LTV_MAX_META } from 'components/earn/farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/earn/farm/Table/Columns/Name'
import TVL, { TVL_META } from 'components/earn/farm/Table/Columns/TVL'
import { DETAILS_META } from 'components/earn/farm/Table/Columns/Details'
interface Props {
isLoading: boolean
@@ -41,7 +40,7 @@ export default function useAvailableColumns(props: Props) {
cell: ({ row }) => <MaxLTV vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
...DETAILS_META,
...DEPOSIT_META,
cell: ({ row }) => <Deposit vault={row.original as Vault} isLoading={props.isLoading} />,
},
]
@@ -6,7 +6,7 @@ import DepositCap, {
DEPOSIT_CAP_META,
depositCapSortingFn,
} from 'components/earn/farm/Table/Columns/DepositCap'
import Details, { DETAILS_META } from 'components/earn/farm/Table/Columns/Details'
import Manage, { MANAGE_META } from 'components/earn/farm/Table/Columns/Manage'
import MaxLTV, { LTV_MAX_META } from 'components/earn/farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/earn/farm/Table/Columns/Name'
import PositionValue, {
@@ -55,8 +55,14 @@ export default function useDepositedColumns(props: Props) {
),
},
{
...DETAILS_META,
cell: ({ row }) => <Details isLoading={props.isLoading} isExpanded={row.getIsExpanded()} />,
...MANAGE_META,
cell: ({ row }) => (
<Manage
vault={row.original}
isLoading={props.isLoading}
isExpanded={row.getIsExpanded()}
/>
),
},
]
}, [props.isLoading])
@@ -1,10 +1,7 @@
import { Row } from '@tanstack/react-table'
import { Table as TanStackTable } from '@tanstack/table-core/build/lib/types'
import React, { useCallback } from 'react'
import React from 'react'
import useDepositedColumns from 'components/earn/farm/Table/Columns/useDepositedColumns'
import VaultExpanded from 'components/earn/farm/VaultExpanded'
import Table from 'components/common/Table'
import useDepositedColumns from 'components/earn/farm/Table/Columns/useDepositedColumns'
type Props = {
data: DepositedVault[]
@@ -14,20 +11,12 @@ type Props = {
export default function DepositedVaultsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<DepositedVault>, table: TanStackTable<DepositedVault>) => (
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
),
[],
)
return (
<Table
title='Deposited Vaults'
columns={columns}
data={props.data}
initialSorting={[{ id: 'name', desc: true }]}
renderExpanded={renderExpanded}
/>
)
}
@@ -1,98 +0,0 @@
import { useCallback } from 'react'
import { ACCOUNT_MENU_BUTTON_ID } from 'components/account/AccountMenuContent'
import Button from 'components/common/Button'
import ActionButton from 'components/common/Button/ActionButton'
import { ArrowDownLine, ArrowUpLine, Enter, ExclamationMarkCircled } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useAccountId from 'hooks/useAccountId'
import useAlertDialog from 'hooks/useAlertDialog'
import useAutoLend from 'hooks/useAutoLend'
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: LendingMarketTableData
}
const buttonClassnames = 'm-0 flex w-40'
const iconClassnames = 'ml-0 mr-1 w-4 h-4'
export default function LendingActionButtons(props: Props) {
const { asset, accountLentValue: accountLendValue } = props.data
const accountDeposits = useCurrentAccountDeposits()
const { openLend, openReclaim } = useLendAndReclaimModal()
const { open: showAlertDialog } = useAlertDialog()
const { isAutoLendEnabledForCurrentAccount } = useAutoLend()
const assetDepositAmount = accountDeposits.find(byDenom(asset.denom))?.amount
const address = useStore((s) => s.address)
const accountId = useAccountId()
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
const handleUnlend = useCallback(() => {
if (isAutoLendEnabledForCurrentAccount) {
showAlertDialog({
icon: <ExclamationMarkCircled width={18} />,
title: 'Disable Automatically Lend Assets',
content:
"Your auto-lend feature is currently enabled. To unlend your funds, please confirm if you'd like to disable this feature in order to continue.",
positiveButton: {
onClick: () => document.getElementById(ACCOUNT_MENU_BUTTON_ID)?.click(),
text: 'Continue to Account Settings',
icon: <Enter />,
},
negativeButton: {
text: 'Cancel',
},
})
return
}
openReclaim(props.data)
}, [isAutoLendEnabledForCurrentAccount, openReclaim, props.data, showAlertDialog])
return (
<div className='flex flex-row space-x-2'>
{accountLendValue && accountLendValue.isGreaterThan(0) && (
<Button
leftIcon={<ArrowDownLine />}
iconClassName={iconClassnames}
color='secondary'
onClick={handleUnlend}
className={buttonClassnames}
>
Unlend
</Button>
)}
<ConditionalWrapper
condition={hasNoDeposit}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have any ${asset.symbol}. Please first deposit ${asset.symbol} into your Credit Account before lending.`}</Text>
}
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<ArrowUpLine />}
iconClassName={iconClassnames}
disabled={hasNoDeposit}
color='secondary'
onClick={() => openLend(props.data)}
className={buttonClassnames}
text='Lend'
/>
</ConditionalWrapper>
</div>
)
}
@@ -1,12 +1,10 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import LendingActionButtons from 'components/earn/lend/LendingActionButtons'
import { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import useAvailableColumns from 'components/earn/lend/Table/Columns/useAvailableColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
import ActionButtonRow from 'components/common/Table/ActionButtonRow'
import { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import useAvailableColumns from 'components/earn/lend/Table/Columns/useAvailableColumns'
type Props = {
data: LendingMarketTableData[]
@@ -19,9 +17,6 @@ export default function AvailableLendsTable(props: Props) {
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => (
<>
<ActionButtonRow row={row}>
<LendingActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='lend' />
</>
),
@@ -1,7 +1,7 @@
import AssetRate from 'components/common/assets/AssetRate'
import Loading from 'components/common/Loading'
export const APY_META = { accessorKey: 'apy.deposit', header: 'APY', meta: { className: 'w-40' } }
export const APY_META = { accessorKey: 'apy.deposit', header: 'APY' }
interface Props {
apy: number
@@ -0,0 +1,21 @@
import { ChevronDown, ChevronUp } from 'components/common/Icons'
export const CHEVRON_META = {
id: 'chevron',
enableSorting: false,
header: '',
meta: {
className: 'w-5',
},
}
interface Props {
isExpanded: boolean
}
export default function Chevron(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}
@@ -10,7 +10,9 @@ export const DEPOSIT_CAP_META = {
accessorKey: 'marketDepositCap',
header: 'Deposit Cap',
id: 'marketDepositCap',
meta: { className: 'w-40' },
meta: {
className: 'w-50',
},
}
export const marketDepositCapSortingFn = (
@@ -5,6 +5,7 @@ import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEPOSIT_VALUE_META = {
id: 'accountLentValue',
accessorKey: 'accountLentValue',
header: 'Deposited',
}
@@ -0,0 +1,64 @@
import ActionButton from 'components/common/Button/ActionButton'
import { ArrowUpLine } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useAccountId from 'hooks/useAccountId'
import useCurrentAccountDeposits from 'hooks/useCurrentAccountDeposits'
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
import useStore from 'store'
import { byDenom } from 'utils/array'
export const LEND_BUTTON_META = {
accessorKey: 'lend',
enableSorting: false,
header: '',
meta: {
className: 'w-40',
},
}
interface Props {
data: LendingMarketTableData
}
export default function LendButton(props: Props) {
const { openLend } = useLendAndReclaimModal()
const accountDeposits = useCurrentAccountDeposits()
const assetDepositAmount = accountDeposits.find(byDenom(props.data.asset.denom))?.amount
const address = useStore((s) => s.address)
const accountId = useAccountId()
const hasNoDeposit = !!(!assetDepositAmount && address && accountId)
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={hasNoDeposit}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have any ${props.data.asset.symbol}.
Please first deposit ${props.data.asset.symbol} into your Credit Account before lending.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<ArrowUpLine />}
disabled={hasNoDeposit}
color='tertiary'
onClick={(e) => {
openLend(props.data)
e.stopPropagation()
}}
text='Lend'
short
/>
</ConditionalWrapper>
</div>
)
}
@@ -1,21 +1,85 @@
import { ChevronDown, ChevronUp } from 'components/common/Icons'
import { useCallback, useMemo } from 'react'
import { ACCOUNT_MENU_BUTTON_ID } from 'components/account/AccountMenuContent'
import DropDownButton from 'components/common/Button/DropDownButton'
import { ArrowDownLine, ArrowUpLine, Enter, ExclamationMarkCircled } from 'components/common/Icons'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useAlertDialog from 'hooks/useAlertDialog'
import useAutoLend from 'hooks/useAutoLend'
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
import useStore from 'store'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
header: '',
meta: {
className: 'w-30',
className: 'w-40',
},
}
interface Props {
isExpanded: boolean
data: LendingMarketTableData
}
export default function Manage(props: Props) {
const { openLend, openReclaim } = useLendAndReclaimModal()
const { isAutoLendEnabledForCurrentAccount } = useAutoLend()
const { open: showAlertDialog } = useAlertDialog()
const address = useStore((s) => s.address)
const account = useCurrentAccount()
const hasAssetInDeposits = useMemo(
() => !!account?.deposits?.find((deposit) => deposit.denom === props.data.asset.denom),
[account?.deposits, props.data.asset.denom],
)
const handleUnlend = useCallback(() => {
if (isAutoLendEnabledForCurrentAccount) {
showAlertDialog({
icon: <ExclamationMarkCircled width={18} />,
title: 'Disable Automatically Lend Assets',
content:
"Your auto-lend feature is currently enabled. To unlend your funds, please confirm if you'd like to disable this feature in order to continue.",
positiveButton: {
onClick: () => document.getElementById(ACCOUNT_MENU_BUTTON_ID)?.click(),
text: 'Continue to Account Settings',
icon: <Enter />,
},
negativeButton: {
text: 'Cancel',
},
})
return
}
openReclaim(props.data)
}, [isAutoLendEnabledForCurrentAccount, openReclaim, props.data, showAlertDialog])
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <ArrowUpLine />,
text: 'Lend more',
onClick: () => openLend(props.data),
disabled: !hasAssetInDeposits,
disabledTooltip: `You dont have any ${props.data.asset.symbol}.
Please first deposit ${props.data.asset.symbol} into your Credit Account before lending.`,
},
{
icon: <ArrowDownLine />,
text: 'Unlend',
onClick: handleUnlend,
},
],
[handleUnlend, hasAssetInDeposits, openLend, props.data],
)
if (!address) return null
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}
@@ -4,13 +4,18 @@ import TitleAndSubCell from 'components/common/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'symbol' }
interface Props {
asset: Asset
v1?: boolean
}
export default function Name(props: Props) {
const { asset } = props
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
<TitleAndSubCell
title={asset.symbol}
sub={props.v1 ? '' : asset.name}
className='text-left min-w-15'
/>
</div>
)
}
@@ -2,15 +2,17 @@ import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/earn/lend/Table/Columns/Apy'
import Chevron, { CHEVRON_META } from 'components/earn/lend/Table/Columns/Chevron'
import DepositCap, {
DEPOSIT_CAP_META,
marketDepositCapSortingFn,
} from 'components/earn/lend/Table/Columns/DepositCap'
import Manage, { MANAGE_META } from 'components/earn/lend/Table/Columns/Manage'
import LendButton, { LEND_BUTTON_META } from 'components/earn/lend/Table/Columns/LendButton'
import Name, { NAME_META } from 'components/earn/lend/Table/Columns/Name'
interface Props {
isLoading: boolean
v1?: boolean
}
export default function useAvailableColumns(props: Props) {
@@ -18,7 +20,7 @@ export default function useAvailableColumns(props: Props) {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
cell: ({ row }) => <Name asset={row.original.asset} v1={props.v1} />,
},
{
...APY_META,
@@ -36,9 +38,13 @@ export default function useAvailableColumns(props: Props) {
sortingFn: marketDepositCapSortingFn,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
...LEND_BUTTON_META,
cell: ({ row }) => <LendButton data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}, [props.isLoading, props.v1])
}
@@ -2,6 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/earn/lend/Table/Columns/Apy'
import Chevron, { CHEVRON_META } from 'components/earn/lend/Table/Columns/Chevron'
import DepositCap, {
DEPOSIT_CAP_META,
marketDepositCapSortingFn,
@@ -12,9 +13,11 @@ import DepositValue, {
} from 'components/earn/lend/Table/Columns/DepositValue'
import Manage, { MANAGE_META } from 'components/earn/lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import Action from 'components/v1/Table/deposits/Columns/Action'
interface Props {
isLoading: boolean
v1?: boolean
}
export default function useDepositedColumns(props: Props) {
@@ -22,7 +25,7 @@ export default function useDepositedColumns(props: Props) {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
cell: ({ row }) => <Name asset={row.original.asset} v1={props.v1} />,
},
{
...DEPOSIT_VALUE_META,
@@ -48,8 +51,13 @@ export default function useDepositedColumns(props: Props) {
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
cell: ({ row }) =>
props.v1 ? <Action data={row.original} /> : <Manage data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}, [props.isLoading, props.v1])
}
@@ -1,30 +1,23 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import LendingActionButtons from 'components/earn/lend/LendingActionButtons'
import { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import useDepositedColumns from 'components/earn/lend/Table/Columns/useDepositedColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
import ActionButtonRow from 'components/common/Table/ActionButtonRow'
import { DEPOSIT_VALUE_META } from 'components/earn/lend/Table/Columns/DepositValue'
import { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import useDepositedColumns from 'components/earn/lend/Table/Columns/useDepositedColumns'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
v1?: boolean
}
export default function DepositedLendsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const columns = useDepositedColumns({ isLoading: props.isLoading, v1: props.v1 })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => (
<>
<ActionButtonRow row={row}>
<LendingActionButtons data={row.original} />
</ActionButtonRow>
<MarketDetails row={row} type='lend' />
</>
),
(row: Row<LendingMarketTableData>) => <MarketDetails row={row} type='lend' />,
[],
)
@@ -32,10 +25,17 @@ export default function DepositedLendsTable(props: Props) {
return (
<Table
title='Lent Assets'
title={props.v1 ? 'Deposits' : 'Lent Assets'}
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
initialSorting={
props.v1
? [
{ id: DEPOSIT_VALUE_META.id, desc: true },
{ id: NAME_META.id, desc: false },
]
: [{ id: NAME_META.id, desc: false }]
}
renderExpanded={renderExpanded}
/>
)
+125 -66
View File
@@ -1,11 +1,12 @@
import classNames from 'classnames'
import { useCallback, useMemo } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import React, { useCallback, useMemo } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useSWRConfig } from 'swr'
import Button from 'components/common/Button'
import { ExternalLink } from 'components/common/Icons'
import Overlay from 'components/common/Overlay'
import Radio from 'components/common/Radio'
import Text from 'components/common/Text'
import ChainLogo from 'components/common/chain/ChainLogo'
import chains from 'configs/chains'
@@ -15,11 +16,52 @@ import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { NETWORK } from 'types/enums/network'
import { ChainInfoID } from 'types/enums/wallet'
import { getPage, getRoute } from 'utils/route'
import { getRoute } from 'utils/route'
const v1Outposts = [
{ chainId: ChainInfoID.Neutron1, name: 'Neutron', url: 'https://neutron.marsprotocol.io' },
{ chainId: ChainInfoID.Osmosis1, name: 'Osmosis', url: 'https://v1.marsprotocol.io' },
interface V1Outpost {
chainId: ChainInfoID
name: string
url: string
network: NETWORK.MAINNET | NETWORK.TESTNET
target: '_blank' | '_self'
}
interface ChainOptionProps {
chainConfig?: ChainConfig
onSelect?: (chain: ChainConfig) => void
active: boolean
outpost?: V1Outpost
}
const v1Outposts: V1Outpost[] = [
{
chainId: ChainInfoID.Neutron1,
name: 'Neutron',
url: 'https://neutron.marsprotocol.io',
network: NETWORK.MAINNET,
target: '_blank',
},
{
chainId: ChainInfoID.Pion1,
name: 'Neutron Testnet',
url: '/v1',
network: NETWORK.TESTNET,
target: '_self',
},
{
chainId: ChainInfoID.Osmosis1,
name: 'Osmosis',
url: '/v1',
network: NETWORK.MAINNET,
target: '_self',
},
{
chainId: ChainInfoID.OsmosisDevnet,
name: 'Osmosis Devnet',
network: NETWORK.TESTNET,
url: '/v1',
target: '_self',
},
]
export default function ChainSelect() {
@@ -27,8 +69,8 @@ export default function ChainSelect() {
const chainConfig = useChainConfig()
const { mutate } = useSWRConfig()
const navigate = useNavigate()
const { pathname } = useLocation()
const [searchParams] = useSearchParams()
const isV1 = useStore((s) => s.isV1)
const [_, setCurrentChainId] = useCurrentChainId()
@@ -39,20 +81,65 @@ export default function ChainSelect() {
mutate(() => true)
useStore.setState({
chainConfig,
isV1: false,
client: undefined,
address: undefined,
userDomain: undefined,
balances: [],
})
navigate(getRoute(getPage(pathname), searchParams))
navigate(getRoute('trade', searchParams))
},
[setCurrentChainId, setShowMenu, mutate, navigate, pathname, searchParams],
[setCurrentChainId, setShowMenu, mutate, navigate, searchParams],
)
const currentChains = useMemo(() => {
const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET
const ChainOption = (props: ChainOptionProps) => {
const { onSelect, active, outpost, chainConfig } = props
return (
<div
className={classNames(
'w-full px-4 py-3 flex gap-3 group/chain text-white items-center',
active ? 'pointer-events-none' : 'opacity-60 hover:opacity-100',
)}
role='button'
onClick={
onSelect && chainConfig
? () => onSelect(chainConfig)
: () => {
if (chainConfig) {
setCurrentChainId(chainConfig.id)
useStore.setState({
chainConfig,
})
}
window.open(outpost?.url, outpost?.target)
}
}
>
<Radio active={active} className='group-hover/account:opacity-100' />
<Text size='sm'>{outpost ? 'v1' : 'v2'} Outpost</Text>
{outpost && outpost.target !== '_self' && <ExternalLink className='inline w-4 -mb-0.5' />}
</div>
)
}
return Object.entries(chains).filter(([_, chain]) => chain.network === currentNetworkType)
const availableChains = useMemo(() => {
const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET
const availableChains: { chainId: ChainInfoID; name: string }[] = []
Object.entries(chains).forEach(([chainId, chainConfig]) => {
if (chainConfig.network !== currentNetworkType) return
availableChains.push({ chainId: chainId as ChainInfoID, name: chainConfig.name })
})
if (currentNetworkType === NETWORK.TESTNET) return availableChains
v1Outposts.forEach((v1Outpost) => {
if (
!availableChains.find((chain) => chain.chainId === v1Outpost.chainId) &&
v1Outpost.network === currentNetworkType
)
availableChains.push({ chainId: v1Outpost.chainId, name: v1Outpost.name })
})
return availableChains
}, [])
return (
@@ -63,65 +150,37 @@ export default function ChainSelect() {
color='secondary'
onClick={() => setShowMenu()}
className={classNames('!p-0 w-8 flex items-center justify-center')}
></Button>
<Overlay show={showMenu} setShow={setShowMenu} className='right-0 w-[180px] mt-2'>
<div
className={classNames(
'flex w-full items-center bg-white/5 px-4 py-3',
'border border-transparent border-b-white/10',
)}
>
<Text size='lg' className='font-bold'>
Select Chain
</Text>
</div>
<ul className='w-full px-4 py-3 list-none'>
{currentChains.map(([name, chain]) => (
<li
className={classNames(
'w-full py-2 flex gap-3 group/chain text-white items-center',
chainConfig.name === chain.name
? 'pointer-events-none'
: 'opacity-60 hover:opacity-100',
)}
role='button'
key={name}
onClick={() => selectChain(chain)}
>
<ChainLogo chainID={chain.id} className='w-6' />
<Text size='sm'>{chain.name}</Text>
</li>
))}
</ul>
{process.env.NEXT_PUBLIC_NETWORK === NETWORK.MAINNET && (
<>
/>
<Overlay
show={showMenu}
setShow={setShowMenu}
className='right-0 w-[200px] mt-2 overflow-hidden'
>
{availableChains.map((chain, index) => (
<React.Fragment key={chain.chainId}>
<div
className={classNames(
'flex w-full items-center bg-white/5 px-4 py-3',
'border border-transparent border-y-white/10',
'flex items-center gap-2 px-4 py-3 border-b bg-white/10 border-white/20',
index > 0 && 'border-t',
)}
>
<Text size='lg' className='font-bold'>
V1 Outposts
</Text>
<ChainLogo chainID={chain.chainId} className='w-5' />
<Text>{chain.name}</Text>
</div>
<ul className='w-full px-4 py-3 list-none'>
{v1Outposts.map((outpost) => (
<li
className='flex items-center w-full gap-3 py-2 text-white group/chain opacity-60 hover:opacity-100'
role='button'
onClick={() => window.open(outpost.url, '_blank')}
key={outpost.name}
>
<ChainLogo chainID={outpost.chainId} className='w-6' />
<Text size='sm'>
{outpost.name} <ExternalLink className='w-4 ml-1 mb-0.5 inline' />
</Text>
</li>
))}
</ul>
</>
)}
{!!chains[chain.chainId] && (
<ChainOption
chainConfig={chains[chain.chainId]}
onSelect={() => selectChain(chains[chain.chainId])}
active={chainConfig.name === chain.name && !isV1}
/>
)}
<ChainOption
chainConfig={chains[chain.chainId]}
outpost={v1Outposts.find((outpost) => outpost.chainId === chain.chainId)}
active={chainConfig.name === chain.name && isV1}
/>
</React.Fragment>
))}
</Overlay>
</div>
)
+4 -3
View File
@@ -16,7 +16,7 @@ import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
import { getGovernanceUrl } from 'utils/helpers'
export const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
{
pages: ['trade', 'trade-advanced'],
label: 'Trade',
@@ -49,6 +49,7 @@ export default function DesktopHeader() {
const isOracleStale = useStore((s) => s.isOracleStale)
const isHLS = useStore((s) => s.isHLS)
const accountId = useAccountId()
const showAccountMenu = address && !isHLS
function handleCloseFocusMode() {
if (focusComponent && focusComponent.onClose) focusComponent.onClose()
@@ -72,7 +73,7 @@ export default function DesktopHeader() {
focusComponent ? 'relative isolate' : 'border-b border-white/20',
)}
>
<DesktopNavigation />
<DesktopNavigation menuTree={menuTree} />
{focusComponent ? (
<div className='flex justify-between w-full'>
@@ -92,7 +93,7 @@ export default function DesktopHeader() {
<div className='flex gap-4'>
{showStaleOracle && <OracleResyncButton />}
{accountId && <RewardsCenter />}
{address && !isHLS && <AccountMenu />}
{showAccountMenu && <AccountMenu />}
<Wallet />
<ChainSelect />
<Settings />
+81
View File
@@ -0,0 +1,81 @@
import classNames from 'classnames'
import { useMemo } from 'react'
import { isDesktop } from 'react-device-detect'
import Wallet from 'components/Wallet'
import EscButton from 'components/common/Button/EscButton'
import Settings from 'components/common/Settings'
import ChainSelect from 'components/header/ChainSelect'
import OracleResyncButton from 'components/header/OracleResyncButton'
import RewardsCenter from 'components/header/RewardsCenter'
import DesktopNavigation from 'components/header/navigation/DesktopNavigation'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
import { getGovernanceUrl } from 'utils/helpers'
const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
{
pages: ['v1'],
label: 'Red Bank',
},
{ pages: ['governance'], label: 'Governance', externalUrl: getGovernanceUrl(walletId) },
]
export default function DesktopHeader() {
const address = useStore((s) => s.address)
const focusComponent = useStore((s) => s.focusComponent)
const isOracleStale = useStore((s) => s.isOracleStale)
const accountId = useAccountId()
function handleCloseFocusMode() {
if (focusComponent && focusComponent.onClose) focusComponent.onClose()
useStore.setState({ focusComponent: null })
}
const showStaleOracle = useMemo(() => isOracleStale && address, [isOracleStale, address])
if (!isDesktop) return null
return (
<header
className={classNames(
'fixed left-0 top-0 z-50 w-full',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:h-full before:w-full before:rounded-sm before:backdrop-blur-sticky',
)}
>
<div
className={classNames(
'flex items-center justify-between px-4 py-4',
focusComponent ? 'relative isolate' : 'border-b border-white/20',
)}
>
<DesktopNavigation menuTree={menuTree} />
{focusComponent ? (
<div className='flex justify-between w-full'>
<div className='flex h-5 w-13' />
{address && (
<div className='flex gap-4'>
<Wallet />
<ChainSelect />
</div>
)}
<div className='flex gap-4'>
{!address && <ChainSelect />}
<EscButton onClick={handleCloseFocusMode} />
</div>
</div>
) : (
<div className='flex gap-4'>
{showStaleOracle && <OracleResyncButton />}
{accountId && <RewardsCenter />}
<Wallet />
<ChainSelect />
<Settings />
</div>
)}
</div>
</header>
)
}
@@ -4,7 +4,6 @@ import { useMemo } from 'react'
import Button from 'components/common/Button'
import { ChevronDown, Logo } from 'components/common/Icons'
import { menuTree } from 'components/header/DesktopHeader'
import { NavLink } from 'components/header/navigation/NavLink'
import { NavMenu } from 'components/header/navigation/NavMenu'
import useChainConfig from 'hooks/useChainConfig'
@@ -12,19 +11,24 @@ import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
interface Props {
menuTree: (walletId: WalletID, chainConfig: ChainConfig) => MenuTreeEntry[]
}
export function getIsActive(pages: string[]) {
const segments = location.pathname.split('/')
return pages.some((page) => segments.includes(page))
}
export default function DesktopNavigation() {
export default function DesktopNavigation(props: Props) {
const { menuTree } = props
const [showMenu, setShowMenu] = useToggle()
const { recentWallet } = useShuttle()
const chainConfig = useChainConfig()
const walletId = (recentWallet?.providerId as WalletID) ?? WalletID.Keplr
const focusComponent = useStore((s) => s.focusComponent)
const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig])
const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig, menuTree])
return (
<div
@@ -12,6 +12,7 @@ import PerpsPage from 'pages/PerpsPage'
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
import PortfolioPage from 'pages/PortfolioPage'
import TradePage from 'pages/TradePage'
import V1Page from 'pages/V1Page'
import Layout from 'pages/_layout'
export default function Routes() {
@@ -32,6 +33,7 @@ export default function Routes() {
<Route path='/lend' element={<LendPage />} />
<Route path='/borrow' element={<BorrowPage />} />
<Route path='/portfolio' element={<PortfolioPage />} />
<Route path='/v1' element={<V1Page />} />
<Route path='/mobile' element={<MobilePage />} />
{chainConfig.hls && <Route path='/hls-staking' element={<HLSStakingPage />} />}
{chainConfig.hls && <Route path='/hls-farm' element={<HLSFarmPage />} />}
@@ -47,6 +49,7 @@ export default function Routes() {
<Route path='portfolio' element={<PortfolioPage />} />
{chainConfig.hls && <Route path='hls-staking' element={<HLSStakingPage />} />}
{chainConfig.hls && <Route path='hls-farm' element={<HLSFarmPage />} />}
<Route path='v1' element={<V1Page />} />
<Route path='portfolio/:accountId'>
<Route path='' element={<PortfolioAccountPage />} />
</Route>
+27 -2
View File
@@ -1,10 +1,35 @@
import { useCallback } from 'react'
import { useSearchParams } from 'react-router-dom'
import Table from 'components/common/Table'
import usePerpsBalancesColumns from 'components/perps/BalancesTable/Columns/usePerpsBalancesColumns'
import usePerpsBalancesData from 'components/perps/BalancesTable/usePerpsBalancesData'
import Table from 'components/common/Table'
import { SearchParams } from 'types/enums/searchParams'
import { getSearchParamsObject } from 'utils/route'
export default function PerpsBalancesTable() {
const data = usePerpsBalancesData()
const columns = usePerpsBalancesColumns()
const [searchParams, setSearchParams] = useSearchParams()
return <Table title='Perp Positions' columns={columns} data={data} initialSorting={[]} />
const onClickRow = useCallback(
(denom: string) => {
const params = getSearchParamsObject(searchParams)
setSearchParams({
...params,
[SearchParams.PERPS_MARKET]: denom,
})
},
[searchParams, setSearchParams],
)
return (
<Table
title='Perp Positions'
columns={columns}
data={data}
initialSorting={[]}
onClickRow={onClickRow}
/>
)
}
@@ -19,7 +19,9 @@ export default function PortfolioAccountPageHeader(props: Props) {
<NavLink to={getRoute('portfolio', searchParams, address, selectedAccountId)}>
<Text className='text-white/40'>Portfolio</Text>
</NavLink>
<ArrowRight className='h-3 text-white/60 ' />
<div className='h-3'>
<ArrowRight className='h-3 text-white/60 ' />
</div>
<Text tag='span'>Credit Account {props.accountId}</Text>
</div>
)
+5 -4
View File
@@ -17,6 +17,7 @@ import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
interface Props {
accountId: string
v1?: boolean
}
function Content(props: Props) {
@@ -74,21 +75,21 @@ function Content(props: Props) {
title: (
<FormattedNumber
className='text-xl'
amount={leverage.toNumber()}
amount={isNaN(leverage.toNumber()) ? 1 : leverage.toNumber()}
options={{ suffix: 'x' }}
/>
),
sub: DEFAULT_PORTFOLIO_STATS[4].sub,
sub: props.v1 ? 'Total Leverage' : DEFAULT_PORTFOLIO_STATS[4].sub,
},
]
}, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs])
}, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs, props.v1])
return (
<Skeleton
stats={stats}
health={health}
healthFactor={healthFactor}
title={`Credit Account ${props.accountId}`}
title={props.v1 ? 'V1 Portfolio' : `Credit Account ${props.accountId}`}
accountId={props.accountId}
/>
)
+2 -2
View File
@@ -2,9 +2,9 @@ import React from 'react'
import HealthBar from 'components/account/Health/HealthBar'
import Card from 'components/common/Card'
import HLSTag from 'components/hls/HLSTag'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import HLSTag from 'components/hls/HLSTag'
interface Props {
stats: { title: React.ReactNode; sub: string }[]
@@ -33,7 +33,7 @@ export default function Skeleton(props: Props) {
<TitleAndSubCell key={`${accountId}-${sub}`} title={title} sub={sub} />
))}
</div>
<div className='flex gap-1 mt-6'>
<div className='flex mt-6'>
<HealthBar health={health} healthFactor={healthFactor} showIcon />
</div>
</Card>
@@ -89,7 +89,7 @@ export default function PortfolioSummary() {
title: (
<FormattedNumber
className='text-xl'
amount={leverage.toNumber()}
amount={isNaN(leverage.toNumber()) ? 1 : leverage.toNumber()}
options={{ suffix: 'x' }}
/>
),
+6 -4
View File
@@ -3,10 +3,10 @@ import React from 'react'
import HealthBar from 'components/account/Health/HealthBar'
import HealthIcon from 'components/account/Health/HealthIcon'
import Card from 'components/common/Card'
import HLSTag from 'components/hls/HLSTag'
import Loading from 'components/common/Loading'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import HLSTag from 'components/hls/HLSTag'
import useAccount from 'hooks/accounts/useAccount'
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
@@ -31,9 +31,11 @@ export default function SummarySkeleton(props: Props) {
{account?.kind === 'high_levered_strategy' && <HLSTag />}
</div>
{health !== undefined && healthFactor !== undefined && (
<div className='flex gap-1 max-w-[300px] flex-grow'>
<HealthIcon isLoading={healthFactor === 0} health={health} className='w-5' />
<HealthBar health={health} healthFactor={healthFactor} className='h-full' />
<div className='flex items-center justify-end flex-grow gap-2'>
<HealthIcon isLoading={healthFactor === 0} health={health} />
<div className='w-[260px]'>
<HealthBar health={health} healthFactor={healthFactor} className='h-3' />
</div>
</div>
)}
</div>
+47
View File
@@ -0,0 +1,47 @@
import BorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable'
import useV1BorrowingsTableData from 'components/v1/Table/useV1BorrowingsTableData'
import { BN_ZERO } from 'constants/math'
import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets'
export default function Borrowings() {
const { debtAssets } = useV1BorrowingsTableData()
if (!debtAssets?.length) {
return <Fallback />
}
return (
<>
<BorrowingsTable data={debtAssets} isLoading={false} v1 />
</>
)
}
function Fallback() {
const assets = useBorrowEnabledAssets()
const data: BorrowMarketTableData[] = assets.map((asset) => ({
asset,
apy: {
borrow: 0,
deposit: 0,
},
ltv: {
max: 0,
liq: 0,
},
liquidity: BN_ZERO,
marketLiquidityRate: 0,
cap: {
denom: asset.denom,
max: BN_ZERO,
used: BN_ZERO,
},
debt: BN_ZERO,
borrowEnabled: true,
depositEnabled: true,
deposits: BN_ZERO,
accountDebt: BN_ZERO,
}))
return <BorrowingsTable data={data} isLoading v1 />
}
+46
View File
@@ -0,0 +1,46 @@
import DepositsTable from 'components/earn/lend/Table/DepositedLendsTable'
import useV1DepositsTableData from 'components/v1/Table/useV1DepositsTableData'
import { BN_ZERO } from 'constants/math'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
export default function Deposits() {
const { depositAssets } = useV1DepositsTableData()
if (!depositAssets?.length) {
return <Fallback />
}
return (
<>
<DepositsTable data={depositAssets} isLoading={false} v1 />
</>
)
}
function Fallback() {
const assets = useMarketEnabledAssets()
const data: LendingMarketTableData[] = assets.map((asset) => ({
asset,
borrowEnabled: true,
depositEnabled: true,
debt: BN_ZERO,
deposits: BN_ZERO,
liquidity: BN_ZERO,
cap: {
max: BN_ZERO,
used: BN_ZERO,
denom: asset.denom,
},
apy: {
borrow: 0,
deposit: 0,
},
ltv: {
max: 0,
liq: 0,
},
}))
return <DepositsTable data={data} isLoading v1 />
}
@@ -0,0 +1,20 @@
import BorrowButton from 'components/v1/Table/borrowings/Columns/BorrowButton'
import Manage from 'components/v1/Table/borrowings/Columns/Manage'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: '',
}
interface Props {
data: BorrowMarketTableData
}
export default function Action(props: Props) {
const hasDebt = !props.data.accountDebtAmount?.isZero() ?? false
if (hasDebt) return <Manage data={props.data} />
return <BorrowButton data={props.data} />
}
@@ -0,0 +1,51 @@
import ActionButton from 'components/common/Button/ActionButton'
import { Plus } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
interface Props {
data: BorrowMarketTableData
}
export default function BorrowButton(props: Props) {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const hasCollateral = account?.lends?.length ?? 0 > 0
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={!hasCollateral}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have assets deposited in the Red Bank. Please deposit assets before you borrow.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<Plus />}
disabled={!hasCollateral}
color='tertiary'
onClick={(e) => {
useStore.setState({
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
})
e.stopPropagation()
}}
text='Borrow'
short
/>
</ConditionalWrapper>
</div>
)
}
@@ -0,0 +1,47 @@
import { useMemo } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { HandCoins, Plus } from 'components/common/Icons'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: BorrowMarketTableData
}
export default function Manage(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <Plus />,
text: 'Borrow more',
onClick: () =>
useStore.setState({
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
}),
},
{
icon: <HandCoins />,
text: 'Repay',
onClick: () =>
useStore.setState({
v1BorrowAndRepayModal: { type: 'repay', data: props.data },
}),
disabled: !hasBalance,
disabledTooltip: `You dont have any ${props.data.asset.symbol} in your Wallet.`,
},
],
[hasBalance, props.data],
)
return (
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}
@@ -0,0 +1,19 @@
import DepositButton from 'components/v1/Table/deposits/Columns/DepositButton'
import Manage from 'components/v1/Table/deposits/Columns/Manage'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: '',
}
interface Props {
data: LendingMarketTableData
}
export default function Action(props: Props) {
const hasDeposits = !props.data.accountLentAmount?.isZero() ?? false
if (hasDeposits) return <Manage data={props.data} />
return <DepositButton data={props.data} />
}
@@ -0,0 +1,51 @@
import ActionButton from 'components/common/Button/ActionButton'
import { ArrowUpLine } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: LendingMarketTableData
}
export default function DepositButton(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={!hasBalance}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have any ${props.data.asset.symbol} in your Wallet.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<ArrowUpLine />}
disabled={!hasBalance}
color='tertiary'
onClick={(e) => {
useStore.setState({
v1DepositAndWithdrawModal: { type: 'deposit', data: props.data },
})
e.stopPropagation()
}}
text='Deposit'
short
/>
</ConditionalWrapper>
</div>
)
}
@@ -0,0 +1,48 @@
import { useMemo } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { ArrowDownLine, ArrowUpLine } from 'components/common/Icons'
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: LendingMarketTableData
}
export default function Manage(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <ArrowUpLine />,
text: 'Deposit more',
onClick: () =>
useStore.setState({
v1DepositAndWithdrawModal: { type: 'deposit', data: props.data },
}),
disabled: !hasBalance,
disabledTooltip: `You dont have any ${props.data.asset.symbol} in your Wallet.`,
},
{
icon: <ArrowDownLine />,
text: 'Withdraw',
onClick: () =>
useStore.setState({
v1DepositAndWithdrawModal: { type: 'withdraw', data: props.data },
}),
},
],
[hasBalance, props.data],
)
return (
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}
@@ -0,0 +1,38 @@
import { useMemo } from 'react'
import { BN_ZERO } from 'constants/math'
import useAccount from 'hooks/accounts/useAccount'
import useMarkets from 'hooks/markets/useMarkets'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
import useStore from 'store'
export default function useV1BorrowingsTableData() {
const address = useStore((s) => s.address)
const markets = useMarkets()
const { data: v1Positions } = useAccount(address)
const { convertAmount } = useDisplayCurrencyPrice()
return useMemo((): {
debtAssets: BorrowMarketTableData[]
} => {
const userDebts = v1Positions?.debts ?? []
const debtAssets: BorrowMarketTableData[] = []
markets
.filter((market) => market.borrowEnabled)
.forEach((market) => {
const amount =
userDebts.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO
const value = amount ? convertAmount(market.asset, amount) : undefined
const borrowMarketAsset: BorrowMarketTableData = {
...market,
accountDebtAmount: amount,
accountDebtValue: value,
}
debtAssets.push(borrowMarketAsset)
})
return { debtAssets }
}, [v1Positions, markets, convertAmount])
}

Some files were not shown because too many files have changed in this diff Show More