initiating public sequence
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
yarn.lock
|
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
BIN
Mars Web App License.pdf
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Mars Protocol Interface
|
||||||
|
|
||||||
|
## Terms of Use
|
||||||
|
|
||||||
|
To use this repository you have to agree to the terms of the [Mars Web App License](https://github.com/mars-protocol/interface/blob/main/Mars%20Web%20App%20License.pdf)
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm run install`
|
||||||
|
|
||||||
|
Installs all packages listed inside the [package.json](https://github.com/mars-protocol/interface/blob/main/package.json)
|
||||||
|
|
||||||
|
### `npm run start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `build` folder.
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.
|
||||||
|
Your app is ready to be deployed!
|
42050
package-lock.json
generated
Normal file
94
package.json
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"name": "mars",
|
||||||
|
"homepage": "./",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@apollo/client": "^3.4.15",
|
||||||
|
"@material-ui/core": "^4.11.3",
|
||||||
|
"@material-ui/icons": "^4.11.2",
|
||||||
|
"@ramonak/react-progress-bar": "^4.2.0",
|
||||||
|
"@sentry/react": "^6.17.9",
|
||||||
|
"@sentry/tracing": "^6.17.9",
|
||||||
|
"@terra-dev/wallet-types": "^3.2.0",
|
||||||
|
"@terra-money/terra.js": "^3.0.1",
|
||||||
|
"@terra-money/wallet-provider": "^3.8.0",
|
||||||
|
"@testing-library/dom": "^8.11.1",
|
||||||
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
|
"@testing-library/react": "^11.1.0",
|
||||||
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"@tippyjs/react": "^4.2.3",
|
||||||
|
"@tns-money/tns.js": "^1.2.0",
|
||||||
|
"@types/chart.js": "^2.9.31",
|
||||||
|
"@types/jest": "^26.0.15",
|
||||||
|
"@types/lodash.throttle": "^4.1.6",
|
||||||
|
"@types/node": "^12.0.0",
|
||||||
|
"@types/numeral": "^2.0.1",
|
||||||
|
"@types/ramda": "^0.27.38",
|
||||||
|
"@types/react": "^17.0.0",
|
||||||
|
"@types/react-dom": "^17.0.0",
|
||||||
|
"@types/react-modal": "^3.12.0",
|
||||||
|
"@types/react-router-dom": "^5.1.7",
|
||||||
|
"@types/react-table": "^7.0.28",
|
||||||
|
"bignumber.js": "^9.0.1",
|
||||||
|
"chart.js": "^2.9.4",
|
||||||
|
"create-conical-gradient": "^1.1.0",
|
||||||
|
"graphql": "^15.6.0",
|
||||||
|
"graphql-request": "^4.2.0",
|
||||||
|
"i18next": "^21.0.2",
|
||||||
|
"i18next-browser-languagedetector": "^6.1.2",
|
||||||
|
"i18next-xhr-backend": "^3.2.2",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"moment-duration-format": "^2.3.2",
|
||||||
|
"numeral": "^2.0.6",
|
||||||
|
"ramda": "^0.27.1",
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-chartjs-2": "^2.11.1",
|
||||||
|
"react-currency-input-field": "^3.6.4",
|
||||||
|
"react-device-detect": "^1.17.0",
|
||||||
|
"react-dom": "^17.0.1",
|
||||||
|
"react-i18next": "^11.11.4",
|
||||||
|
"react-modal": "^3.12.1",
|
||||||
|
"react-query": "^3.34.19",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-scripts": "^4.0.3",
|
||||||
|
"react-table": "^7.6.3",
|
||||||
|
"react-use-clipboard": "^1.0.7",
|
||||||
|
"sass": "^1.35.2",
|
||||||
|
"styled-components": "^5.3.3",
|
||||||
|
"typescript": "^4.1.2",
|
||||||
|
"web-vitals": "^1.0.1",
|
||||||
|
"zustand": "^3.7.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "REACT_APP_STAGE=localhost react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash.isequal": "^4.5.5",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"pretty-quick": "^3.1.3"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
public/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/banner.png
Normal file
After Width: | Height: | Size: 326 KiB |
9
public/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#ffffff</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
29
public/favicon.svg
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||||
|
<path d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
|
||||||
|
<path d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z" style="fill:none;fill-rule:nonzero;stroke:white;stroke-width:1px;"/>
|
||||||
|
<path d="M11.999,0.166L15.656,0.746L18.955,2.426L21.573,5.044L23.254,8.342L23.834,11.999L23.254,15.656L21.573,18.955L18.955,21.573L15.656,23.254L11.999,23.834L8.343,23.254L5.044,21.573L2.426,18.955L0.746,15.656L0.166,11.999L0.746,8.343L2.426,5.044L5.044,2.426L8.343,0.746L11.999,0.166ZM8.549,1.381L5.436,2.966L2.967,5.436L1.381,8.549L0.834,11.999L1.381,15.45L2.967,18.562L5.436,21.032L8.549,22.619L11.999,23.166L15.45,22.619L18.562,21.032L21.032,18.562L22.619,15.45L23.166,11.999L22.619,8.549L21.032,5.436L18.562,2.966L15.45,1.381L11.999,0.834L8.549,1.381Z" style="fill:white;"/>
|
||||||
|
<path d="M11.915,3.928C11.993,3.881 12.094,3.907 12.141,3.985L14.738,8.343C14.785,8.421 14.759,8.523 14.681,8.569C14.603,8.616 14.501,8.59 14.455,8.512L11.857,4.154C11.811,4.076 11.836,3.975 11.915,3.928Z" style="fill:white;"/>
|
||||||
|
<path d="M11.999,3.904L17.36,4.484L17.325,4.812L11.999,4.236L6.674,4.812L6.639,4.484L11.999,3.904Z" style="fill:white;"/>
|
||||||
|
<path d="M9.237,8.426C9.237,8.334 9.311,8.261 9.402,8.261L14.596,8.261C14.687,8.261 14.761,8.334 14.761,8.426C14.761,8.517 14.687,8.591 14.596,8.591L9.402,8.591C9.311,8.591 9.237,8.517 9.237,8.426Z" style="fill:white;"/>
|
||||||
|
<path d="M12.083,3.928C12.161,3.975 12.187,4.076 12.14,4.154L9.544,8.512C9.497,8.59 9.396,8.616 9.318,8.569C9.24,8.523 9.214,8.421 9.261,8.343L11.857,3.985C11.903,3.907 12.004,3.881 12.083,3.928Z" style="fill:white;"/>
|
||||||
|
<path d="M8.392,0.803L11.999,3.853L15.606,0.803L17.49,4.573L17.195,4.721L15.499,1.326L11.999,4.285L8.499,1.326L6.804,4.721L6.509,4.573L8.392,0.803Z" style="fill:white;"/>
|
||||||
|
<path d="M5.143,2.563C5.217,2.509 5.32,2.525 5.374,2.599L11.999,11.719L18.625,2.599C18.679,2.525 18.782,2.509 18.856,2.563C18.929,2.616 18.946,2.719 18.892,2.793L12.133,12.097C12.102,12.14 12.052,12.165 11.999,12.165C11.947,12.165 11.897,12.14 11.866,12.097L5.107,2.793C5.053,2.719 5.07,2.616 5.143,2.563Z" style="fill:white;"/>
|
||||||
|
<path d="M9.525,8.314C9.565,8.358 9.578,8.42 9.56,8.476L7.992,13.303L12.081,16.274L16.727,18.266C16.811,18.302 16.85,18.399 16.814,18.482C16.778,18.566 16.681,18.605 16.597,18.569L11.934,16.57C11.923,16.565 11.912,16.559 11.902,16.552L7.701,13.499C7.643,13.457 7.619,13.383 7.641,13.315L9.156,8.651L4.493,9.709C4.404,9.729 4.315,9.674 4.295,9.585C4.275,9.496 4.331,9.408 4.42,9.387L9.367,8.265C9.424,8.251 9.485,8.27 9.525,8.314Z" style="fill:white;"/>
|
||||||
|
<path d="M4.347,9.426C4.416,9.366 4.52,9.373 4.58,9.442L7.922,13.259C7.982,13.328 7.975,13.432 7.907,13.492C7.838,13.552 7.734,13.545 7.674,13.477L4.332,9.659C4.272,9.591 4.279,9.486 4.347,9.426Z" style="fill:white;"/>
|
||||||
|
<path d="M6.931,4.439L4.635,9.553L4.661,9.616L4.603,9.652L3.534,14.751L7.435,18.284L12.078,20.948L15.442,22.696L16.535,18.244L21.105,18.581L20.481,14.837L20.807,14.783L21.499,18.941L16.789,18.593L15.662,23.182L11.917,21.236L7.24,18.553L3.238,14.928L0.234,11.971L4.25,9.483L2.464,5.109L6.931,4.439ZM4.215,9.893L0.765,12.03L3.254,14.48L4.215,9.893ZM4.463,9.131L6.382,4.855L2.928,5.373L4.463,9.131Z" style="fill:white;"/>
|
||||||
|
<path d="M7.814,13.202C7.904,13.211 7.971,13.291 7.963,13.382L7.502,18.433C7.494,18.524 7.414,18.591 7.323,18.583C7.232,18.574 7.165,18.494 7.174,18.403L7.634,13.352C7.642,13.261 7.723,13.194 7.814,13.202Z" style="fill:white;"/>
|
||||||
|
<path d="M12.151,16.353C12.187,16.436 12.148,16.533 12.065,16.569L7.403,18.569C7.319,18.604 7.222,18.566 7.186,18.482C7.15,18.398 7.189,18.301 7.273,18.265L11.935,16.266C12.018,16.23 12.115,16.269 12.151,16.353Z" style="fill:white;"/>
|
||||||
|
<path d="M2.893,18.581L3.517,14.837L3.191,14.783L2.499,18.941L7.211,18.594L8.336,23.182L12.075,21.24L11.923,20.947L8.556,22.696L7.465,18.244L2.893,18.581Z" style="fill:white;"/>
|
||||||
|
<path d="M12.097,11.865C12.14,11.896 12.165,11.946 12.165,11.998L12.165,23.5C12.165,23.591 12.091,23.665 12,23.665C11.909,23.665 11.835,23.591 11.835,23.5L11.835,12.226L1.115,15.709C1.029,15.737 0.936,15.69 0.908,15.603C0.879,15.517 0.927,15.423 1.013,15.395L11.949,11.842C11.999,11.825 12.054,11.834 12.097,11.865Z" style="fill:white;"/>
|
||||||
|
<path d="M17.212,4.547C17.248,4.499 17.308,4.475 17.368,4.484L21.327,5.078C21.377,5.085 21.421,5.115 21.446,5.159C21.471,5.203 21.474,5.256 21.455,5.303L19.749,9.483L23.587,11.861C23.63,11.887 23.658,11.932 23.664,11.982C23.669,12.033 23.652,12.083 23.616,12.118L20.762,14.926L20.756,14.932L20.755,14.932C20.755,14.933 20.754,14.934 20.753,14.934L16.773,18.541C16.727,18.583 16.661,18.595 16.602,18.572C16.544,18.55 16.504,18.496 16.498,18.434L16.039,13.4L14.44,8.478C14.421,8.421 14.435,8.359 14.475,8.315C14.515,8.272 14.575,8.253 14.633,8.266L19.258,9.315L17.193,4.715C17.168,4.659 17.175,4.595 17.212,4.547ZM19.237,9.649L14.843,8.652L16.269,13.04L19.237,9.649ZM16.372,13.422L16.797,18.074L20.464,14.751L19.45,9.907L16.372,13.422ZM19.784,9.893L20.744,14.48L23.234,12.03L19.784,9.893ZM19.536,9.13L21.07,5.373L17.617,4.855L19.536,9.13Z" style="fill:white;"/>
|
||||||
|
<path d="M11.842,11.947C11.87,11.861 11.963,11.813 12.05,11.842L22.987,15.395C23.074,15.423 23.121,15.517 23.093,15.603C23.065,15.69 22.972,15.737 22.885,15.709L11.948,12.155C11.861,12.127 11.814,12.034 11.842,11.947Z" style="fill:white;"/>
|
||||||
|
<path d="M16.335,13.27C16.389,13.343 16.372,13.447 16.298,13.5L12.096,16.552C12.022,16.606 11.919,16.59 11.866,16.516C11.812,16.442 11.828,16.339 11.902,16.285L16.104,13.233C16.178,13.18 16.281,13.196 16.335,13.27Z" style="fill:white;"/>
|
||||||
|
<path d="M16.58,18.276C16.653,18.234 16.746,18.254 16.796,18.322L18.892,21.208C18.945,21.282 18.929,21.385 18.855,21.439C18.782,21.492 18.678,21.476 18.625,21.402L16.616,18.636L12.081,21.237C12.002,21.282 11.901,21.255 11.856,21.176C11.81,21.097 11.838,20.996 11.917,20.951L16.58,18.276Z" style="fill:white;"/>
|
||||||
|
<path d="M11.999,0.335C12.09,0.335 12.164,0.409 12.164,0.5L12.164,4.068C12.164,4.159 12.09,4.233 11.999,4.233C11.908,4.233 11.834,4.159 11.834,4.068L11.834,0.5C11.834,0.409 11.908,0.335 11.999,0.335Z" style="fill:white;"/>
|
||||||
|
<path d="M23.093,8.396C23.121,8.483 23.073,8.576 22.987,8.604L19.594,9.706C19.507,9.735 19.414,9.687 19.386,9.601C19.358,9.514 19.405,9.421 19.492,9.393L22.885,8.29C22.971,8.262 23.064,8.309 23.093,8.396Z" style="fill:white;"/>
|
||||||
|
<path d="M7.435,18.286C7.509,18.34 7.525,18.443 7.471,18.516L5.374,21.402C5.32,21.476 5.217,21.492 5.143,21.439C5.069,21.385 5.053,21.282 5.107,21.208L7.204,18.322C7.258,18.249 7.361,18.232 7.435,18.286Z" style="fill:white;"/>
|
||||||
|
<path d="M0.908,8.396C0.936,8.309 1.029,8.262 1.115,8.29L4.508,9.393C4.595,9.421 4.642,9.514 4.614,9.601C4.586,9.687 4.493,9.735 4.406,9.706L1.013,8.604C0.927,8.576 0.879,8.483 0.908,8.396Z" style="fill:white;"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-14.8673,-14.7971,14.7971,-14.8673,20.4244,16.9042)"><stop offset="0" style="stop-color:rgb(239,65,54);stop-opacity:1"/><stop offset="0.01" style="stop-color:rgb(239,65,54);stop-opacity:1"/><stop offset="0.32" style="stop-color:rgb(223,81,83);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(172,11,27);stop-opacity:1"/></linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
35
public/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="https://app.marsprotocol.io/favicon.svg" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#dd5b65" />
|
||||||
|
<meta name="robots" content="index,follow" />
|
||||||
|
<meta name="description"
|
||||||
|
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies." />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@mars_protocol" />
|
||||||
|
<meta name="twitter:creator" content="@mars_protocol" />
|
||||||
|
<meta property="og:url" content="https://app.marsprotocol.io" />
|
||||||
|
<meta property="og:title" content="Mars Protocol Application - Powered by Terra" />
|
||||||
|
<meta property="og:description"
|
||||||
|
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies." />
|
||||||
|
<meta property="og:image" content="https://app.marsprotocol.io/banner.png" />
|
||||||
|
<meta property="og:site_name" content="Mars Protocol" />
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||||
|
<meta name="theme-color" content="#ffffff" />
|
||||||
|
<meta name="terra-webextension" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no" />
|
||||||
|
<title>Mars Protocol</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
24
public/logo.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.6386 3.90217L12.0968 2.36037L10.1538 1.37085L7.99998 1.0293L5.8462 1.37085L3.90315 2.36037L2.36135 3.90217L1.37182 5.84523L1.03027 7.999L1.37182 10.1528L2.36135 12.0958L3.90315 13.6376L5.8462 14.628L7.99998 14.9696L10.1538 14.628L12.0968 13.6376L13.6386 12.0958L14.629 10.1528L14.9706 7.999L14.629 5.84523L13.6386 3.90217Z" stroke="white" stroke-width="0.8" stroke-miterlimit="10"/>
|
||||||
|
<path d="M9.5738 5.83309L7.99951 3.19202" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.76147 3.54235L7.9996 3.19202L11.2386 3.54235" stroke="white" stroke-width="0.4" stroke-miterlimit="10"/>
|
||||||
|
<path d="M6.4259 5.83167H9.5736" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99931 3.19202L6.4259 5.83309" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.76147 3.54158L5.84583 1.37024L7.9996 3.19125L10.1534 1.37024L11.2386 3.54158" stroke="white" stroke-width="0.4" stroke-miterlimit="10"/>
|
||||||
|
<path d="M12.0966 2.35925L7.99978 7.99788L3.90295 2.35925" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.42786 6.51213L6.42629 5.83167L5.45344 8.82571L7.9997 10.6757L10.826 11.8874" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.42786 6.51355L5.45344 8.82713" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2.75948 9.70068L1.02979 7.9982L3.42765 6.51259L2.36086 3.90137L4.76136 3.54138L3.42765 6.51259L2.75948 9.70068ZM2.75948 9.70068L5.17403 11.8878L7.99949 13.5086L10.1533 14.6272L10.8258 11.8878L13.6381 12.095L13.2395 9.70068" stroke="white" stroke-width="0.4" stroke-miterlimit="10"/>
|
||||||
|
<path d="M5.45328 8.82605L5.17407 11.8877" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.17407 11.8871L7.99953 10.6754" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99923 13.509L5.84545 14.6276L5.17376 11.8882L2.3606 12.0954L2.75922 9.70105" stroke="white" stroke-width="0.4" stroke-miterlimit="10"/>
|
||||||
|
<path d="M1.3717 10.151L7.99986 7.99719V14.9678" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10.5468 8.82617L10.826 11.8878L13.2397 9.70068L14.9703 7.9982L12.5724 6.51259L13.6383 3.90137L11.2387 3.54138L12.5724 6.51259L9.57397 5.83213L10.5468 8.82617ZM10.5468 8.82617L12.5724 6.51259L13.2397 9.70068" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99951 7.99719L14.6285 10.151" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99951 10.676L10.5466 8.82605" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99951 13.5094L10.8259 11.8885L12.0963 13.6376" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.99951 3.191V1.02844" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12.572 6.51278L14.6283 5.8446" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.17432 11.8885L3.90295 13.6376" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.42802 6.51278L1.3717 5.8446" stroke="white" stroke-width="0.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/mstile-144x144.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/mstile-150x150.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
public/mstile-310x150.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
public/mstile-310x310.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
public/mstile-70x70.png
Normal file
After Width: | Height: | Size: 17 KiB |
3
public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
247
public/safari-pinned-tab.svg
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M3405 6875 c-44 -12 -642 -268 -654 -280 -2 -3 158 -5 357 -5 l362 0
|
||||||
|
0 150 c0 83 -3 150 -7 149 -5 0 -30 -6 -58 -14z"/>
|
||||||
|
<path d="M3530 6739 l0 -149 363 0 c209 0 356 4 347 9 -53 31 -642 279 -672
|
||||||
|
284 l-38 6 0 -150z"/>
|
||||||
|
<path d="M3865 6832 c10 -9 519 -232 529 -232 7 0 99 130 95 134 -2 2 -139 25
|
||||||
|
-304 51 -165 25 -307 48 -315 51 -9 3 -11 2 -5 -4z"/>
|
||||||
|
<path d="M2795 6783 c-154 -25 -282 -47 -284 -49 -4 -4 88 -134 95 -134 4 0
|
||||||
|
351 150 499 216 l30 13 -30 0 c-16 -1 -156 -21 -310 -46z"/>
|
||||||
|
<path d="M2148 6573 c-158 -81 -285 -149 -284 -151 5 -4 660 150 668 157 7 6
|
||||||
|
-81 141 -92 141 -3 0 -134 -67 -292 -147z"/>
|
||||||
|
<path d="M4511 6656 c-25 -35 -44 -67 -43 -72 1 -8 649 -165 665 -161 7 2
|
||||||
|
-564 297 -574 297 -2 0 -24 -29 -48 -64z"/>
|
||||||
|
<path d="M2797 6325 c80 -113 148 -205 152 -205 8 0 461 398 461 405 0 3 -171
|
||||||
|
5 -379 5 l-380 0 146 -205z"/>
|
||||||
|
<path d="M3811 6325 c128 -113 236 -205 239 -205 7 0 290 394 290 404 0 3
|
||||||
|
-171 6 -380 6 l-381 0 232 -205z"/>
|
||||||
|
<path d="M3261 6312 l-234 -207 237 -3 c130 -1 342 -1 473 0 l236 3 -233 205
|
||||||
|
c-128 113 -235 206 -239 207 -3 1 -111 -91 -240 -205z"/>
|
||||||
|
<path d="M2246 6208 c-185 -167 -332 -304 -326 -306 11 -4 927 175 952 186 14
|
||||||
|
6 -9 43 -130 214 -81 114 -150 208 -154 208 -4 0 -158 -136 -342 -302z"/>
|
||||||
|
<path d="M4258 6302 c-121 -171 -144 -208 -130 -214 24 -10 943 -189 953 -186
|
||||||
|
9 3 -643 596 -664 603 -7 3 -76 -86 -159 -203z"/>
|
||||||
|
<path d="M2040 6401 c-405 -97 -500 -124 -500 -143 0 -10 247 -355 260 -363 5
|
||||||
|
-3 534 466 679 601 12 11 -67 -6 -439 -95z"/>
|
||||||
|
<path d="M4521 6496 c174 -161 674 -604 679 -601 13 8 260 353 260 363 0 20
|
||||||
|
-92 46 -503 143 -394 93 -446 105 -436 95z"/>
|
||||||
|
<path d="M1448 6182 c-33 -33 -490 -773 -483 -780 4 -4 755 426 773 443 11 10
|
||||||
|
-10 45 -113 186 -70 96 -130 177 -134 181 -4 5 -23 -9 -43 -30z"/>
|
||||||
|
<path d="M5381 6039 c-107 -147 -130 -184 -119 -194 18 -16 769 -447 773 -443
|
||||||
|
7 8 -451 747 -484 781 l-38 39 -132 -183z"/>
|
||||||
|
<path d="M3022 6008 c14 -18 124 -174 247 -345 122 -172 226 -313 231 -313 5
|
||||||
|
0 109 141 231 313 122 171 233 327 247 345 l24 32 -502 0 -502 0 24 -32z"/>
|
||||||
|
<path d="M2350 5924 c-261 -52 -481 -97 -487 -99 -11 -4 355 -524 407 -578 17
|
||||||
|
-18 24 -10 185 210 430 582 416 563 392 562 -12 -1 -236 -44 -497 -95z"/>
|
||||||
|
<path d="M4213 5908 c45 -62 177 -241 291 -396 200 -272 209 -283 226 -265 51
|
||||||
|
54 418 574 407 578 -22 8 -975 195 -991 195 -11 0 12 -38 67 -112z"/>
|
||||||
|
<path d="M2643 5613 c-161 -219 -291 -399 -289 -400 4 -5 1078 96 1083 101 6
|
||||||
|
6 -486 696 -495 696 -4 0 -139 -179 -299 -397z"/>
|
||||||
|
<path d="M3805 5665 c-136 -191 -245 -348 -243 -351 5 -5 1080 -105 1084 -101
|
||||||
|
5 4 -576 791 -586 795 -5 2 -119 -153 -255 -343z"/>
|
||||||
|
<path d="M995 5730 l-229 -229 38 -55 c37 -54 64 -83 70 -75 1 2 76 126 166
|
||||||
|
274 90 149 170 280 178 293 8 12 13 22 11 22 -2 0 -107 -103 -234 -230z"/>
|
||||||
|
<path d="M5782 5940 c8 -14 90 -150 183 -302 l168 -278 48 65 c27 35 49 68 49
|
||||||
|
72 0 5 -104 112 -232 238 -127 127 -225 219 -216 205z"/>
|
||||||
|
<path d="M1546 5334 c-136 -245 -246 -447 -244 -449 5 -5 910 294 921 305 8 7
|
||||||
|
-409 590 -422 590 -3 0 -118 -201 -255 -446z"/>
|
||||||
|
<path d="M4981 5489 c-116 -159 -211 -293 -211 -297 1 -8 922 -313 928 -307 5
|
||||||
|
5 -491 895 -499 895 -4 0 -101 -131 -218 -291z"/>
|
||||||
|
<path d="M1313 5531 c-216 -124 -393 -230 -393 -234 0 -4 68 -100 152 -212
|
||||||
|
130 -176 153 -202 163 -187 28 42 477 853 474 856 -2 1 -181 -99 -396 -223z"/>
|
||||||
|
<path d="M5294 5743 c8 -19 452 -816 471 -845 10 -16 32 9 163 186 84 113 152
|
||||||
|
208 152 213 0 4 -80 54 -178 109 -97 56 -275 159 -396 228 -136 79 -216 120
|
||||||
|
-212 109z"/>
|
||||||
|
<path d="M583 5158 c-78 -155 -141 -283 -140 -284 1 -1 52 55 113 125 61 69
|
||||||
|
148 168 194 218 l82 93 -48 65 c-26 36 -50 65 -53 65 -3 0 -70 -127 -148 -282z"/>
|
||||||
|
<path d="M6216 5375 l-48 -65 139 -157 c76 -86 163 -184 193 -219 30 -34 56
|
||||||
|
-61 57 -60 4 4 -282 566 -288 566 -3 0 -27 -29 -53 -65z"/>
|
||||||
|
<path d="M2868 5200 c-278 -26 -511 -49 -517 -51 -9 -4 567 -808 579 -809 5 0
|
||||||
|
501 879 508 900 5 14 -6 13 -570 -40z"/>
|
||||||
|
<path d="M3562 5240 c10 -29 504 -899 509 -897 9 3 588 801 585 805 -3 3
|
||||||
|
-1047 102 -1079 102 -10 0 -17 -4 -15 -10z"/>
|
||||||
|
<path d="M877 5233 c-4 -8 -166 -501 -213 -651 l-23 -74 22 15 c12 7 128 76
|
||||||
|
257 152 129 76 241 144 249 150 11 11 -11 46 -130 206 -167 228 -155 213 -162
|
||||||
|
202z"/>
|
||||||
|
<path d="M5967 5038 c-84 -112 -145 -204 -140 -209 17 -17 516 -309 521 -305
|
||||||
|
5 5 -217 697 -227 708 -3 3 -73 -84 -154 -194z"/>
|
||||||
|
<path d="M3243 4771 c-139 -248 -253 -452 -253 -455 0 -3 230 -6 510 -6 281 0
|
||||||
|
510 3 510 6 0 9 -505 904 -510 904 -3 0 -119 -202 -257 -449z"/>
|
||||||
|
<path d="M555 4910 c-198 -224 -276 -324 -263 -335 2 -1 65 -23 140 -48 l138
|
||||||
|
-45 111 340 c61 187 109 341 107 343 -2 2 -107 -113 -233 -255z"/>
|
||||||
|
<path d="M6210 5165 c0 -5 50 -161 110 -346 l110 -337 138 45 c75 25 138 47
|
||||||
|
140 48 14 11 -69 117 -263 335 -264 296 -235 265 -235 255z"/>
|
||||||
|
<path d="M1745 4965 c-258 -86 -459 -158 -458 -164 2 -8 477 -655 499 -680 2
|
||||||
|
-2 6 -2 9 1 16 16 431 998 422 997 -7 -1 -219 -70 -472 -154z"/>
|
||||||
|
<path d="M4804 5043 c19 -43 116 -269 216 -503 100 -234 183 -426 184 -427 1
|
||||||
|
-2 118 153 259 344 142 191 254 350 250 354 -8 6 -925 309 -937 309 -3 0 10
|
||||||
|
-35 28 -77z"/>
|
||||||
|
<path d="M2286 5101 c-11 -17 -436 -1005 -433 -1007 3 -3 1017 203 1022 208 3
|
||||||
|
2 -562 788 -580 806 -2 2 -6 -1 -9 -7z"/>
|
||||||
|
<path d="M4413 4707 c-160 -221 -290 -403 -288 -406 4 -3 1019 -210 1022 -207
|
||||||
|
3 2 -428 1000 -435 1008 -4 4 -139 -174 -299 -395z"/>
|
||||||
|
<path d="M896 4595 c-142 -85 -261 -156 -264 -159 -6 -6 239 -576 247 -576 5
|
||||||
|
0 278 830 289 876 2 8 0 14 -5 13 -4 0 -124 -70 -267 -154z"/>
|
||||||
|
<path d="M1083 4283 c-84 -257 -151 -469 -150 -470 4 -4 801 248 809 255 6 7
|
||||||
|
-483 674 -499 679 -5 2 -77 -207 -160 -464z"/>
|
||||||
|
<path d="M5504 4414 c-137 -185 -249 -339 -249 -342 0 -7 806 -264 811 -259 5
|
||||||
|
5 -301 937 -307 937 -3 0 -118 -151 -255 -336z"/>
|
||||||
|
<path d="M5832 4733 c21 -75 284 -873 289 -873 7 0 253 570 247 575 -8 8 -528
|
||||||
|
315 -534 315 -4 0 -4 -8 -2 -17z"/>
|
||||||
|
<path d="M266 4503 c-11 -28 -5 -131 24 -428 17 -165 30 -307 30 -314 0 -9 3
|
||||||
|
-11 9 -5 12 13 224 665 217 671 -2 3 -65 24 -140 48 -111 35 -136 40 -140 28z"/>
|
||||||
|
<path d="M6591 4473 c-73 -23 -136 -44 -138 -47 -6 -5 206 -658 218 -670 6 -6
|
||||||
|
9 -2 9 10 0 10 14 154 30 319 30 305 36 402 22 421 -5 7 -51 -4 -141 -33z"/>
|
||||||
|
<path d="M6282 4074 c-68 -157 -121 -288 -118 -291 7 -8 470 -152 474 -148 4
|
||||||
|
3 -215 689 -227 712 -4 7 -62 -116 -129 -273z"/>
|
||||||
|
<path d="M588 4343 c-11 -18 -230 -705 -226 -708 4 -4 467 141 475 148 5 5
|
||||||
|
-224 544 -238 560 -4 5 -9 5 -11 0z"/>
|
||||||
|
<path d="M3000 4244 c0 -11 492 -684 500 -684 8 0 500 673 500 684 0 3 -225 6
|
||||||
|
-500 6 -275 0 -500 -3 -500 -6z"/>
|
||||||
|
<path d="M2367 4137 c-329 -66 -497 -104 -495 -112 5 -19 693 -763 699 -756 4
|
||||||
|
3 66 191 139 416 184 568 180 555 167 554 -7 -1 -236 -46 -510 -102z"/>
|
||||||
|
<path d="M4124 4199 c54 -176 300 -925 305 -930 7 -7 694 737 699 756 2 8
|
||||||
|
-155 43 -485 109 -268 54 -498 100 -510 103 -22 5 -22 4 -9 -38z"/>
|
||||||
|
<path d="M2830 3869 c-61 -189 -131 -404 -155 -478 -25 -74 -43 -136 -42 -138
|
||||||
|
3 -3 809 256 815 262 4 3 -470 658 -493 684 -11 11 -33 -48 -125 -330z"/>
|
||||||
|
<path d="M3800 3864 c-138 -190 -250 -347 -248 -349 6 -5 812 -265 814 -262 4
|
||||||
|
3 -303 947 -309 953 -3 3 -118 -151 -257 -342z"/>
|
||||||
|
<path d="M166 3874 c-27 -168 -46 -307 -43 -311 7 -6 144 34 150 45 5 8 -48
|
||||||
|
561 -55 567 -2 2 -26 -133 -52 -301z"/>
|
||||||
|
<path d="M6776 4150 c-3 -19 -15 -134 -26 -255 -11 -121 -23 -236 -26 -256
|
||||||
|
l-5 -35 75 -24 c42 -13 79 -21 82 -17 5 5 -79 576 -90 612 -2 6 -6 -6 -10 -25z"/>
|
||||||
|
<path d="M1346 3879 c-225 -71 -410 -130 -412 -131 -1 -2 19 -32 45 -68 27
|
||||||
|
-36 158 -218 292 -404 198 -277 244 -336 250 -320 10 23 240 1046 237 1049 -2
|
||||||
|
2 -187 -55 -412 -126z"/>
|
||||||
|
<path d="M5243 3987 c3 -12 56 -245 117 -517 61 -272 114 -504 118 -514 7 -16
|
||||||
|
61 54 299 385 159 222 289 405 289 406 -1 1 -175 57 -387 123 -211 66 -397
|
||||||
|
125 -413 130 -26 9 -28 8 -23 -13z"/>
|
||||||
|
<path d="M1694 3448 c-66 -293 -117 -534 -113 -536 3 -1 215 65 470 148 255
|
||||||
|
83 468 151 472 153 9 3 -691 767 -702 767 -4 0 -61 -240 -127 -532z"/>
|
||||||
|
<path d="M4844 3623 c-345 -375 -369 -401 -362 -409 8 -7 930 -305 937 -302 9
|
||||||
|
4 -231 1068 -240 1068 -3 0 -154 -161 -335 -357z"/>
|
||||||
|
<path d="M590 3643 c-129 -41 -236 -76 -237 -77 -3 -3 348 -791 365 -822 14
|
||||||
|
-24 12 -32 72 476 27 234 52 442 56 463 4 27 2 37 -8 36 -7 -1 -119 -35 -248
|
||||||
|
-76z"/>
|
||||||
|
<path d="M6154 3684 c3 -22 28 -230 56 -464 27 -234 52 -441 56 -460 5 -31 7
|
||||||
|
-32 17 -15 17 31 367 818 364 820 -2 2 -423 134 -481 152 -16 4 -17 1 -12 -33z"/>
|
||||||
|
<path d="M900 3669 c0 -8 -27 -240 -60 -514 -32 -275 -58 -501 -56 -502 3 -4
|
||||||
|
687 216 695 223 4 3 -539 767 -565 794 -12 12 -14 12 -14 -1z"/>
|
||||||
|
<path d="M5810 3288 c-157 -219 -286 -401 -288 -406 -2 -7 687 -236 694 -229
|
||||||
|
3 2 -110 968 -119 1017 -1 8 -130 -164 -287 -382z"/>
|
||||||
|
<path d="M188 3516 c-37 -12 -70 -25 -73 -28 -5 -5 90 -629 99 -653 3 -5 5 1
|
||||||
|
5 14 1 30 49 604 55 654 5 43 5 43 -86 13z"/>
|
||||||
|
<path d="M6726 3503 c5 -43 54 -627 55 -658 0 -11 2 -15 5 -10 7 18 105 647
|
||||||
|
101 651 -5 5 -148 54 -158 54 -4 0 -5 -17 -3 -37z"/>
|
||||||
|
<path d="M3058 3327 c-219 -72 -398 -134 -398 -138 0 -7 782 -579 802 -587 4
|
||||||
|
-2 8 190 8 427 0 237 -3 431 -7 430 -5 -1 -187 -60 -405 -132z"/>
|
||||||
|
<path d="M3530 3029 c0 -236 2 -429 5 -429 3 0 537 386 764 553 l53 39 -363
|
||||||
|
119 c-200 66 -385 126 -411 133 l-48 15 0 -430z"/>
|
||||||
|
<path d="M326 3428 c-9 -44 -66 -784 -66 -853 0 -38 4 -75 8 -81 5 -7 73 10
|
||||||
|
215 56 114 37 211 70 214 74 5 5 -355 826 -363 826 -2 0 -6 -10 -8 -22z"/>
|
||||||
|
<path d="M6480 3040 c-100 -226 -180 -413 -177 -416 3 -4 100 -37 214 -74 142
|
||||||
|
-46 210 -63 215 -56 4 6 8 45 8 86 -1 88 -64 858 -72 865 -3 3 -88 -179 -188
|
||||||
|
-405z"/>
|
||||||
|
<path d="M2070 3006 c-256 -84 -465 -156 -465 -161 1 -11 814 -729 820 -724 2
|
||||||
|
2 28 222 59 489 31 267 58 500 61 518 3 17 2 32 -2 31 -4 0 -217 -69 -473
|
||||||
|
-153z"/>
|
||||||
|
<path d="M4454 3123 c3 -21 31 -254 61 -517 31 -264 57 -482 60 -484 6 -6 819
|
||||||
|
712 818 723 -1 7 -917 314 -941 315 -2 0 -1 -17 2 -37z"/>
|
||||||
|
<path d="M2545 2630 c-32 -280 -56 -512 -53 -515 5 -6 923 409 940 424 6 6
|
||||||
|
-794 593 -817 599 -7 2 -31 -175 -70 -508z"/>
|
||||||
|
<path d="M3968 2842 c-224 -163 -404 -300 -400 -303 16 -15 934 -430 939 -425
|
||||||
|
10 10 -111 1026 -122 1025 -5 0 -193 -134 -417 -297z"/>
|
||||||
|
<path d="M1152 2707 c-189 -62 -343 -116 -343 -120 0 -4 157 -177 348 -384
|
||||||
|
l348 -376 2 289 c2 159 0 382 -5 496 l-7 206 -343 -111z"/>
|
||||||
|
<path d="M5495 2617 c-3 -111 -5 -334 -3 -495 l3 -294 348 376 c191 206 348
|
||||||
|
379 348 383 1 6 -442 156 -642 217 l-47 14 -7 -201z"/>
|
||||||
|
<path d="M1561 2498 c1 -161 5 -386 8 -499 l6 -206 409 137 409 136 -404 355
|
||||||
|
c-222 195 -410 358 -416 362 -10 7 -13 -53 -12 -285z"/>
|
||||||
|
<path d="M5020 2429 l-412 -362 409 -139 c224 -76 409 -138 409 -138 5 0 16
|
||||||
|
992 11 996 -2 3 -190 -158 -417 -357z"/>
|
||||||
|
<path d="M495 2494 c-110 -37 -202 -68 -204 -70 -13 -12 82 -133 323 -413 152
|
||||||
|
-176 282 -323 288 -327 7 -5 9 1 5 17 -3 13 -45 212 -93 442 -70 329 -92 417
|
||||||
|
-104 417 -8 0 -105 -30 -215 -66z"/>
|
||||||
|
<path d="M6181 2124 c-50 -240 -90 -438 -87 -441 7 -8 575 656 597 699 11 22
|
||||||
|
19 41 17 43 -7 6 -407 135 -420 135 -11 0 -37 -108 -107 -436z"/>
|
||||||
|
<path d="M802 2480 c21 -115 183 -867 189 -874 7 -8 465 138 483 154 4 5 -137
|
||||||
|
165 -314 357 -402 433 -363 394 -358 363z"/>
|
||||||
|
<path d="M5856 2134 c-186 -201 -334 -369 -330 -374 15 -13 475 -162 481 -156
|
||||||
|
7 7 193 877 190 888 -1 4 -154 -158 -341 -358z"/>
|
||||||
|
<path d="M2993 2272 c-260 -119 -473 -219 -473 -222 1 -8 941 -563 946 -558 2
|
||||||
|
3 3 228 2 500 l-3 495 -472 -215z"/>
|
||||||
|
<path d="M3530 1990 c0 -275 3 -500 6 -500 10 0 943 554 944 560 0 4 -803 376
|
||||||
|
-922 427 l-28 12 0 -499z"/>
|
||||||
|
<path d="M451 2105 c6 -15 291 -579 295 -584 6 -6 154 41 160 51 3 4 -95 124
|
||||||
|
-217 265 -245 284 -243 282 -238 268z"/>
|
||||||
|
<path d="M6316 1843 c-125 -146 -224 -268 -220 -273 16 -14 143 -52 154 -45 9
|
||||||
|
6 301 577 297 581 -1 1 -105 -117 -231 -263z"/>
|
||||||
|
<path d="M2008 1877 c-212 -73 -389 -135 -392 -138 -4 -3 172 -135 390 -293
|
||||||
|
217 -158 397 -286 399 -284 2 2 7 194 11 426 6 360 5 422 -7 421 -8 0 -188
|
||||||
|
-59 -401 -132z"/>
|
||||||
|
<path d="M4583 1589 c3 -232 8 -423 11 -426 2 -3 183 125 400 283 218 158 393
|
||||||
|
291 389 294 -9 7 -785 270 -798 270 -5 0 -5 -190 -2 -421z"/>
|
||||||
|
<path d="M2477 1798 c-9 -274 -9 -658 1 -658 14 -1 942 295 942 300 0 4 -622
|
||||||
|
376 -901 539 l-36 21 -6 -202z"/>
|
||||||
|
<path d="M4048 1722 c-258 -152 -468 -279 -468 -282 0 -5 929 -301 943 -300 8
|
||||||
|
0 7 355 -2 657 l-6 203 -467 -278z"/>
|
||||||
|
<path d="M1278 1632 c-126 -42 -229 -81 -230 -87 -1 -10 594 -447 600 -440 2
|
||||||
|
2 -26 136 -62 299 -36 163 -66 298 -66 301 0 8 -6 7 -242 -73z"/>
|
||||||
|
<path d="M5477 1688 c-27 -108 -127 -573 -125 -579 4 -9 600 422 602 435 1 7
|
||||||
|
-247 95 -455 161 -11 4 -19 -2 -22 -17z"/>
|
||||||
|
<path d="M1590 1662 c0 -16 131 -596 136 -601 6 -6 624 52 624 58 0 6 -669
|
||||||
|
494 -731 534 -16 11 -29 14 -29 9z"/>
|
||||||
|
<path d="M5023 1397 c-205 -150 -373 -275 -373 -278 0 -6 618 -65 624 -58 4 4
|
||||||
|
136 588 136 602 0 4 -3 7 -7 6 -5 0 -176 -123 -380 -272z"/>
|
||||||
|
<path d="M873 1497 c-34 -12 -64 -24 -67 -27 -3 -3 96 -107 220 -230 l226
|
||||||
|
-225 -44 75 c-204 348 -254 430 -263 429 -5 0 -38 -10 -72 -22z"/>
|
||||||
|
<path d="M5922 1313 c-67 -115 -134 -228 -148 -253 -20 -33 32 13 199 180 125
|
||||||
|
123 225 225 224 227 -5 4 -137 53 -145 53 -5 0 -63 -93 -130 -207z"/>
|
||||||
|
<path d="M1081 1419 c9 -13 88 -148 177 -299 89 -152 177 -290 195 -308 l34
|
||||||
|
-33 63 88 c35 48 74 102 87 120 l24 31 -287 209 c-158 114 -292 210 -298 212
|
||||||
|
-6 2 -3 -7 5 -20z"/>
|
||||||
|
<path d="M5635 1234 c-154 -112 -283 -208 -287 -212 -4 -4 31 -60 79 -125 l85
|
||||||
|
-119 34 33 c40 39 392 632 373 628 -2 0 -130 -92 -284 -205z"/>
|
||||||
|
<path d="M2998 1242 c-251 -81 -459 -149 -462 -152 -3 -3 194 -97 437 -210
|
||||||
|
243 -112 454 -209 470 -217 l27 -14 0 371 c0 203 -3 370 -7 369 -5 -1 -214
|
||||||
|
-67 -465 -147z"/>
|
||||||
|
<path d="M3530 1021 c0 -351 1 -371 18 -363 9 5 221 103 470 217 248 115 449
|
||||||
|
212 445 216 -5 4 -210 71 -458 149 -247 79 -456 145 -462 147 -10 4 -13 -73
|
||||||
|
-13 -366z"/>
|
||||||
|
<path d="M2065 1031 c-148 -14 -270 -29 -270 -33 0 -5 560 -416 603 -442 9 -6
|
||||||
|
12 47 12 248 l0 256 -37 -1 c-21 -1 -159 -13 -308 -28z"/>
|
||||||
|
<path d="M4590 805 c0 -213 2 -255 14 -248 7 4 147 105 310 223 225 163 292
|
||||||
|
217 279 222 -14 5 -560 58 -595 58 -5 0 -8 -115 -8 -255z"/>
|
||||||
|
<path d="M2467 792 c-4 -140 -5 -258 -2 -261 3 -6 880 82 907 91 10 4 -145 77
|
||||||
|
-738 351 l-161 74 -6 -255z"/>
|
||||||
|
<path d="M4071 836 c-250 -116 -449 -212 -442 -214 24 -8 902 -98 905 -92 1 3
|
||||||
|
0 120 -3 260 l-6 256 -454 -210z"/>
|
||||||
|
<path d="M1622 866 c-45 -62 -82 -118 -82 -123 0 -6 20 -18 43 -27 51 -19 676
|
||||||
|
-158 690 -153 9 3 -553 417 -565 417 -3 0 -41 -51 -86 -114z"/>
|
||||||
|
<path d="M5010 780 c-271 -198 -321 -236 -290 -226 8 3 161 37 339 76 321 71
|
||||||
|
401 93 401 112 0 11 -162 238 -170 238 -3 -1 -129 -91 -280 -200z"/>
|
||||||
|
<path d="M1960 524 c69 -36 196 -101 283 -145 l157 -79 0 85 c0 80 -1 85 -22
|
||||||
|
90 -181 41 -524 115 -532 115 -6 0 45 -30 114 -66z"/>
|
||||||
|
<path d="M4871 529 c-146 -32 -269 -63 -274 -68 -6 -6 -7 -40 -2 -85 l7 -75
|
||||||
|
156 79 c258 130 408 210 392 209 -8 0 -134 -27 -279 -60z"/>
|
||||||
|
<path d="M3205 545 c-115 -13 -306 -33 -422 -46 -117 -12 -213 -25 -213 -28 0
|
||||||
|
-3 62 -32 138 -63 75 -31 261 -108 412 -171 151 -63 292 -117 313 -120 l37 -6
|
||||||
|
0 230 0 229 -27 -1 c-16 -1 -122 -12 -238 -24z"/>
|
||||||
|
<path d="M3530 340 l0 -230 25 5 c14 3 37 8 52 11 24 5 811 331 822 340 2 2 2
|
||||||
|
6 0 8 -3 3 -868 96 -891 96 -5 0 -8 -104 -8 -230z"/>
|
||||||
|
<path d="M2460 359 c0 -49 3 -89 6 -89 3 0 148 -23 321 -50 174 -28 318 -49
|
||||||
|
320 -47 3 4 -98 47 -519 223 l-128 53 0 -90z"/>
|
||||||
|
<path d="M4203 308 c-172 -73 -311 -133 -309 -135 2 -2 146 19 321 47 174 27
|
||||||
|
319 50 321 50 2 0 4 38 4 85 0 62 -3 85 -12 85 -7 -1 -154 -60 -325 -132z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 15 KiB |
19
public/site.webmanifest
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "Mars",
|
||||||
|
"short_name": "Mars",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
191
src/components/Button.module.scss
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
transition: background-color 0.2s, border 0.2s;
|
||||||
|
color: $fontColorLightPrimary;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: $borderRadiusXXXL;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
word-wrap: normal;
|
||||||
|
word-break: normal;
|
||||||
|
|
||||||
|
.prefix,
|
||||||
|
.suffix {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
&.small {
|
||||||
|
@include buttonS;
|
||||||
|
|
||||||
|
svg,
|
||||||
|
.progressIndicator {
|
||||||
|
width: rem-calc(10);
|
||||||
|
height: rem-calc(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefix {
|
||||||
|
margin-inline-end: space(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suffix {
|
||||||
|
margin-inline-start: space(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
@include buttonM;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: rem-calc(12);
|
||||||
|
height: rem-calc(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefix {
|
||||||
|
margin-inline-end: space(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suffix {
|
||||||
|
width: rem-calc(12);
|
||||||
|
margin-inline-start: space(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
@include buttonL;
|
||||||
|
svg {
|
||||||
|
width: rem-calc(18);
|
||||||
|
height: rem-calc(18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefix {
|
||||||
|
margin-inline-end: space(4.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suffix {
|
||||||
|
margin-inline-start: space(4.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variants
|
||||||
|
&.solid,
|
||||||
|
&.round {
|
||||||
|
@include buttonSolidPrimary;
|
||||||
|
@include buttonSolidSecondary;
|
||||||
|
@include buttonSolidTertiary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.round {
|
||||||
|
border-radius: $borderRadiusRound;
|
||||||
|
@include padding(0);
|
||||||
|
|
||||||
|
.prefix,
|
||||||
|
.suffix {
|
||||||
|
@include margin(0);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
height: rem-calc(32);
|
||||||
|
width: rem-calc(32);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: rem-calc(12);
|
||||||
|
height: rem-calc(12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
height: rem-calc(40);
|
||||||
|
width: rem-calc(40);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: rem-calc(14);
|
||||||
|
height: rem-calc(14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
height: rem-calc(56);
|
||||||
|
width: rem-calc(56);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: rem-calc(20);
|
||||||
|
height: rem-calc(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.transparent {
|
||||||
|
background: none;
|
||||||
|
@include padding(0);
|
||||||
|
height: unset;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: $colorPrimary;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: $colorPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $colorPrimaryHighlight;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: $colorPrimaryHighlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: $colorPrimaryHighlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
color: $colorSecondary;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: $colorSecondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: $colorSecondaryHighlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tertiary {
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: lighten($colorSecondaryDark, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
88
src/components/Button.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { CircularProgress } from '@material-ui/core'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { ButtonStyleOverride } from '../types/components'
|
||||||
|
import styles from './Button.module.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: any
|
||||||
|
color?: 'primary' | 'secondary' | 'tertiary'
|
||||||
|
disabled?: boolean
|
||||||
|
externalLink?: string
|
||||||
|
id?: string
|
||||||
|
suffix?: ReactNode
|
||||||
|
prefix?: ReactNode
|
||||||
|
showProgressIndicator?: boolean
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
styleOverride?: ButtonStyleOverride
|
||||||
|
text?: string | ReactNode
|
||||||
|
variant?: 'solid' | 'transparent' | 'round'
|
||||||
|
onClick?: (e: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = ({
|
||||||
|
className = '',
|
||||||
|
color = 'primary',
|
||||||
|
disabled,
|
||||||
|
externalLink,
|
||||||
|
id = '',
|
||||||
|
suffix,
|
||||||
|
prefix,
|
||||||
|
showProgressIndicator,
|
||||||
|
size = 'small',
|
||||||
|
styleOverride,
|
||||||
|
text,
|
||||||
|
variant = 'solid',
|
||||||
|
onClick,
|
||||||
|
}: Props) => {
|
||||||
|
const Button = () => (
|
||||||
|
<button
|
||||||
|
id={id}
|
||||||
|
onClick={disabled ? () => {} : onClick}
|
||||||
|
style={styleOverride}
|
||||||
|
className={`${styles.button} ${styles[size]} ${styles[color]} ${
|
||||||
|
styles[variant]
|
||||||
|
} ${className} ${disabled ? `${styles.disabled}` : ''}`}
|
||||||
|
>
|
||||||
|
{prefix && !showProgressIndicator && (
|
||||||
|
<div className={styles.prefix}>{prefix}</div>
|
||||||
|
)}
|
||||||
|
{text && (
|
||||||
|
<div className={styles.text}>
|
||||||
|
{showProgressIndicator ? (
|
||||||
|
<CircularProgress
|
||||||
|
color='inherit'
|
||||||
|
size={
|
||||||
|
size === 'small'
|
||||||
|
? '10px'
|
||||||
|
: size === 'medium'
|
||||||
|
? '12px'
|
||||||
|
: '18px'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
text
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{suffix && !showProgressIndicator && (
|
||||||
|
<div className={styles.suffix}>{suffix}</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
return externalLink ? (
|
||||||
|
<a
|
||||||
|
href={externalLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
className={styles.link}
|
||||||
|
>
|
||||||
|
{Button()}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
Button()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Button
|
52
src/components/CollateralSwitch.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { createStyles, Switch, withStyles } from '@material-ui/core'
|
||||||
|
import colors from '../styles/_assets.module.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
switchCallback: (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
enabled: boolean
|
||||||
|
) => void
|
||||||
|
checked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollateralSwitch = ({ switchCallback, checked }: Props) => {
|
||||||
|
const CustomSwitch = withStyles(() =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: 28,
|
||||||
|
height: 16,
|
||||||
|
padding: 0,
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
switchBase: {
|
||||||
|
padding: 2,
|
||||||
|
color: colors.grey,
|
||||||
|
'&$checked': {
|
||||||
|
transform: 'translateX(12px)',
|
||||||
|
color: colors.white,
|
||||||
|
'& + $track': {
|
||||||
|
opacity: 1,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderColor: colors.primary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thumb: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
border: `1px solid ${colors.grey}`,
|
||||||
|
borderRadius: 16 / 2,
|
||||||
|
opacity: 1,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
},
|
||||||
|
checked: {},
|
||||||
|
})
|
||||||
|
)(Switch)
|
||||||
|
|
||||||
|
return <CustomSwitch checked={checked} onChange={switchCallback} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CollateralSwitch
|
70
src/components/CollectionHover.module.scss
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include layoutTooltip;
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: rem-calc(184);
|
||||||
|
box-sizing: unset;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.valueItem {
|
||||||
|
margin-top: space(1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
border: 1px solid $colorWhite;
|
||||||
|
height: rem-calc(8);
|
||||||
|
border-radius: $borderRadiusRound;
|
||||||
|
width: rem-calc(8);
|
||||||
|
margin-inline-end: space(2);
|
||||||
|
margin-top: space(1.5);
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 rem-calc(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subHeadline {
|
||||||
|
@include typoXS;
|
||||||
|
@include margin(0, 0, 2);
|
||||||
|
@include padding(0);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.4;
|
||||||
|
height: rem-calc(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 rem-calc(168);
|
||||||
|
flex-direction: column;
|
||||||
|
.titleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
:first-child {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleText {
|
||||||
|
justify-content: start !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
:first-child {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
.subTitleText {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
src/components/CollectionHover.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { formatValue } from '../libs/parse'
|
||||||
|
import styles from './CollectionHover.module.scss'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface HoverItem {
|
||||||
|
color?: string
|
||||||
|
name: string
|
||||||
|
amount?: number
|
||||||
|
usdValue?: number
|
||||||
|
negative?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data?: HoverItem[]
|
||||||
|
title?: string
|
||||||
|
noPercent?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionHover = ({ data, title, noPercent }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
// -----------------
|
||||||
|
// CALCULATE
|
||||||
|
// -----------------
|
||||||
|
const totalUsdValue = data
|
||||||
|
? data.reduce((total, item) => total + (item.usdValue || 0), 0)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const producePercentage = (fraction: number, sum: number): string => {
|
||||||
|
if (fraction <= 0.01 || sum <= 0.01) {
|
||||||
|
return '0.00%'
|
||||||
|
} else {
|
||||||
|
return formatValue((fraction / sum) * 100, 2, 2, true, false, '%')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// PRESENTATION
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
const produceItem = (item: HoverItem, key: number) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.item} key={key}>
|
||||||
|
{item.usdValue ? (
|
||||||
|
<div className={styles.valueItem}>
|
||||||
|
{item.color && (
|
||||||
|
<div
|
||||||
|
className={styles.dot}
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.titleContainer}>
|
||||||
|
<span className={`body ${styles.titleText}`}>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
<span className={`body ${styles.titleText}`}>
|
||||||
|
{item.amount
|
||||||
|
? formatValue(item.amount)
|
||||||
|
: formatValue(
|
||||||
|
item.usdValue,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
item.negative ? '$-' : '$'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.subTitleContainer}>
|
||||||
|
<span
|
||||||
|
className={`caption ${styles.subTitleText}`}
|
||||||
|
>
|
||||||
|
{!noPercent &&
|
||||||
|
producePercentage(
|
||||||
|
item.usdValue,
|
||||||
|
totalUsdValue
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{item.amount && (
|
||||||
|
<span
|
||||||
|
className={`caption ${styles.subTitleText}`}
|
||||||
|
>
|
||||||
|
{formatValue(
|
||||||
|
item.usdValue,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
item.negative ? '$-' : '$'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.subHeadline}>{item.name}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data ? (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<p className='sub2'>{title ? title : t('common.summary')}</p>
|
||||||
|
{data.map((item: HoverItem, index: number) =>
|
||||||
|
produceItem(item, index)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CollectionHover
|
45
src/components/CommonContainer.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useConnectedWallet, useWallet } from '@terra-money/wallet-provider'
|
||||||
|
import { ReactNode, useEffect } from 'react'
|
||||||
|
import networks from '../networks'
|
||||||
|
import useBlockHeightQuery from '../queries-new/BlockHeightQuery'
|
||||||
|
import useStore from '../store'
|
||||||
|
|
||||||
|
interface CommonContainerProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommonContainer = ({ children }: CommonContainerProps) => {
|
||||||
|
/**
|
||||||
|
* Network configurations
|
||||||
|
*/
|
||||||
|
const { network: extNetwork, status } = useWallet()
|
||||||
|
const networkName = extNetwork.name
|
||||||
|
const network = networks[networkName]
|
||||||
|
const connectedWallet = useConnectedWallet()
|
||||||
|
|
||||||
|
const isNetworkLoaded = useStore((s) => s.isNetworkLoaded)
|
||||||
|
const setNetworkInfo = useStore((s) => s.setNetworkInfo)
|
||||||
|
const setUserWalletAddress = useStore((s) => s.setUserWalletAddress)
|
||||||
|
const setNetworkConfig = useStore((s) => s.setNetworkConfig)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNetworkConfig(network || extNetwork)
|
||||||
|
}, [network, extNetwork, setNetworkConfig])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUserWalletAddress(connectedWallet?.terraAddress ?? '')
|
||||||
|
}, [setUserWalletAddress, connectedWallet])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNetworkInfo(networkName, status)
|
||||||
|
}, [networkName, status, setNetworkInfo])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blockchain meta data
|
||||||
|
*/
|
||||||
|
useBlockHeightQuery()
|
||||||
|
|
||||||
|
return <>{isNetworkLoaded && children}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommonContainer
|
74
src/components/ErrorBanner.module.scss
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.network {
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 200;
|
||||||
|
position: fixed;
|
||||||
|
margin: -100% 0 0;
|
||||||
|
transition: margin 0.5s;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
@include margin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include margin(0);
|
||||||
|
@include padding(6, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
text-align: center;
|
||||||
|
background-color: $colorAccent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
@include typoScaps;
|
||||||
|
@include margin(0, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
letter-spacing: rem-calc(1);
|
||||||
|
@include typoXS;
|
||||||
|
@include margin(0, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $colorPrimary;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
right: space(3);
|
||||||
|
top: space(3);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
@include padding(0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: rem-calc(20);
|
||||||
|
width: rem-calc(20);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
src/components/ErrorBanner.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import styles from './ErrorBanner.module.scss'
|
||||||
|
import { CloseSVG } from './Svg'
|
||||||
|
|
||||||
|
interface ErrorBannerProps {
|
||||||
|
hasNetworkError: boolean
|
||||||
|
hasQueryError: boolean
|
||||||
|
hasServerError: boolean
|
||||||
|
isNetworkSupported: boolean | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrorBanner = memo(
|
||||||
|
({
|
||||||
|
hasNetworkError,
|
||||||
|
hasQueryError,
|
||||||
|
hasServerError,
|
||||||
|
isNetworkSupported,
|
||||||
|
}: ErrorBannerProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [hideError, setHideError] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasNetworkError || hasQueryError || hasServerError) {
|
||||||
|
setHideError(false)
|
||||||
|
}
|
||||||
|
}, [hasNetworkError, hasQueryError, hasServerError])
|
||||||
|
|
||||||
|
const getHeadline = (): string => {
|
||||||
|
return hasNetworkError
|
||||||
|
? t('common.appearToBeOffline')
|
||||||
|
: hasServerError
|
||||||
|
? t('error.serverOfflineTitle')
|
||||||
|
: t('error.failingRequest')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBody = (): string => {
|
||||||
|
return hasNetworkError
|
||||||
|
? t('error.youHaveAFailingNetworkRequest')
|
||||||
|
: hasServerError
|
||||||
|
? t('error.serverOfflineBody')
|
||||||
|
: t('error.failingRequestDescription')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Error banner is disabled for GQL errors currently */}
|
||||||
|
{(hasNetworkError || hasServerError) && isNetworkSupported && (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
!hideError
|
||||||
|
? `${styles.network} ${styles.show}`
|
||||||
|
: styles.network
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.close}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setHideError(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseSVG />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h3 className={styles.headline}>{getHeadline()}</h3>
|
||||||
|
<p>{getBody()}</p>
|
||||||
|
{!hasNetworkError && (
|
||||||
|
<p>
|
||||||
|
<Trans i18nKey={'error.problemPersists'}>
|
||||||
|
text
|
||||||
|
<a
|
||||||
|
className={styles.link}
|
||||||
|
href='https://discord.gg/marsprotocol'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
link
|
||||||
|
</a>
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ErrorBanner
|
54
src/components/LanguageSelect.module.scss
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $fontColorLightPrimary;
|
||||||
|
width: rem-calc(120);
|
||||||
|
height: rem-calc(37);
|
||||||
|
display: inline-block;
|
||||||
|
@include padding(0.5, 0, 0.5, 3);
|
||||||
|
appearance: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
@include typoM;
|
||||||
|
outline: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectWrapper {
|
||||||
|
border: 1px solid $buttonBorder;
|
||||||
|
color: $fontColorLightPrimary;
|
||||||
|
width: rem-calc(120);
|
||||||
|
height: rem-calc(37);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
border-radius: $borderRadiusXXS;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 rem-calc(37);
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
width: rem-calc(12);
|
||||||
|
height: rem-calc(8);
|
||||||
|
background-color: $fontColorLightPrimary;
|
||||||
|
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
||||||
|
justify-self: end;
|
||||||
|
position: absolute;
|
||||||
|
right: rem-calc(8);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
39
src/components/LanguageSelect.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import i18n from 'i18next'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import styles from './LanguageSelect.module.scss'
|
||||||
|
|
||||||
|
const LanguageSelect = () => {
|
||||||
|
const [currentLanguage, setCurrentLanguage] = useState('en')
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const lang = i18n.language.substring(0, 2) || 'en'
|
||||||
|
if (currentLanguage !== lang) {
|
||||||
|
setCurrentLanguage(lang)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[i18n.language, currentLanguage]
|
||||||
|
)
|
||||||
|
|
||||||
|
const changeLanguage = (lng: string) => {
|
||||||
|
setCurrentLanguage(lng)
|
||||||
|
i18n.changeLanguage(lng)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.selectWrapper}>
|
||||||
|
<select
|
||||||
|
className={styles.select}
|
||||||
|
value={currentLanguage}
|
||||||
|
onChange={(e) => changeLanguage(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value='de'>Deutsch</option>
|
||||||
|
<option value='en'>English</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LanguageSelect
|
65
src/components/Notification.module.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
width: 100%;
|
||||||
|
min-height: rem-calc(48);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
@include layoutPopover;
|
||||||
|
margin-bottom: space(8) !important;
|
||||||
|
@include padding(2, 3);
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
@include margin(0);
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.info {
|
||||||
|
color: $colorAccent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: $colorInfoWarning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: $colorInfoWarning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closeBtnSpace {
|
||||||
|
@include padding(0, 10, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
@include margin(0, 1);
|
||||||
|
color: $colorSecondary;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeNotification {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
@include margin(0, 4, 0, 0);
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
@include padding(0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: rem-calc(14);
|
||||||
|
height: rem-calc(13);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
src/components/Notification.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { ReactNode, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import styles from './Notification.module.scss'
|
||||||
|
import { SmallCloseSVG } from './Svg'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { NotificationType } from '../types/enums'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showNotification: boolean
|
||||||
|
content: ReactNode
|
||||||
|
type: NotificationType
|
||||||
|
hideCloseBtn?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Notification = ({
|
||||||
|
showNotification,
|
||||||
|
content,
|
||||||
|
type,
|
||||||
|
hideCloseBtn = false,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [closeNotification, setCloseNotification] = useState(false)
|
||||||
|
|
||||||
|
const typeClass = useMemo(() => {
|
||||||
|
return type === NotificationType.Warning
|
||||||
|
? styles.warning
|
||||||
|
: type === NotificationType.Error
|
||||||
|
? styles.error
|
||||||
|
: styles.info
|
||||||
|
}, [type])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showNotification && !closeNotification ? (
|
||||||
|
<div
|
||||||
|
className={`${styles.notification} ${typeClass} ${
|
||||||
|
!hideCloseBtn ? styles.closeBtnSpace : null
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p>{content}</p>
|
||||||
|
|
||||||
|
{!hideCloseBtn && (
|
||||||
|
<button
|
||||||
|
title={t('common.close')}
|
||||||
|
className={styles.closeNotification}
|
||||||
|
onClick={() => {
|
||||||
|
setCloseNotification(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SmallCloseSVG />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notification
|
78
src/components/PositionBar.module.scss
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.position {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: rem-calc(61);
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: rem-calc(200);
|
||||||
|
flex: 0 0 rem-calc(200);
|
||||||
|
|
||||||
|
.value {
|
||||||
|
word-wrap: normal;
|
||||||
|
word-break: normal;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
span {
|
||||||
|
@include typoM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: flex-start;
|
||||||
|
@include margin(2, 0, 0);
|
||||||
|
height: rem-calc(8);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
> .fraction {
|
||||||
|
border-radius: $borderRadiusXXS;
|
||||||
|
display: inline-block;
|
||||||
|
@include margin(0, 0, 0, -1);
|
||||||
|
@include padding(0, 0, 0, 1);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
@include margin(0);
|
||||||
|
@include padding(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
box-sizing: unset;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) {
|
||||||
|
.compact {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.position {
|
||||||
|
.container {
|
||||||
|
width: rem-calc(120);
|
||||||
|
flex: 0 0 rem-calc(120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
src/components/PositionBar.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import Tippy from '@tippyjs/react'
|
||||||
|
import styles from './PositionBar.module.scss'
|
||||||
|
import colors from '../styles/_assets.module.scss'
|
||||||
|
import { formatValue, lookup } from '../libs/parse'
|
||||||
|
import CollectionHover, { HoverItem } from './CollectionHover'
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
import { UST_DECIMALS, UST_DENOM } from '../constants/appConstants'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
const PositionBar = ({
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
bars,
|
||||||
|
total,
|
||||||
|
compactView = false,
|
||||||
|
}: StrategyBarProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const produceData = (data: StrategyBarItem[]): HoverItem[] => {
|
||||||
|
const items: HoverItem[] = []
|
||||||
|
|
||||||
|
data.forEach((asset: StrategyBarItem) => {
|
||||||
|
if (asset.value !== 0) {
|
||||||
|
items.push({
|
||||||
|
color: asset.color || '',
|
||||||
|
name: t(`strategy.${asset.name}`) || '',
|
||||||
|
usdValue: lookup(asset.value, UST_DENOM, UST_DECIMALS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderBars = (bars: StrategyBarItem[]): ReactElement[] => {
|
||||||
|
const barParts: ReactElement[] = []
|
||||||
|
bars.forEach((item: StrategyBarItem, index: number) => {
|
||||||
|
barParts.push(
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={styles.fraction}
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
item.value === 0
|
||||||
|
? '0%'
|
||||||
|
: ((item.value / total) * 100).toFixed(2) + '%',
|
||||||
|
zIndex: 50 - index,
|
||||||
|
backgroundColor: item.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return barParts
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
compactView
|
||||||
|
? `${styles.position} ${styles.compact}`
|
||||||
|
: `${styles.position}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.value}>
|
||||||
|
<h4>
|
||||||
|
<span>$</span>
|
||||||
|
{formatValue(value)}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<span className={'sub2'}>{title}</span>
|
||||||
|
</div>
|
||||||
|
{bars.length === 0 ? (
|
||||||
|
<div className={styles.bar}>
|
||||||
|
<div
|
||||||
|
className={styles.fraction}
|
||||||
|
style={{
|
||||||
|
width: '33%',
|
||||||
|
zIndex: 50 - 1,
|
||||||
|
backgroundColor: colors.transparentWhite,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{!(bars.length === 1 && bars[0].value === 0) && (
|
||||||
|
<Tippy
|
||||||
|
className={styles.box}
|
||||||
|
content={
|
||||||
|
<CollectionHover
|
||||||
|
title={title}
|
||||||
|
data={produceData(bars)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.bar}>{renderBars(bars)}</div>
|
||||||
|
</Tippy>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PositionBar
|
763
src/components/Svg.tsx
Normal file
17
src/components/Title.module.scss
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
@include margin(8, 0);
|
||||||
|
opacity: 0.6;
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontalLine {
|
||||||
|
flex: auto;
|
||||||
|
@include devider60;
|
||||||
|
@include margin(0, 0, 3);
|
||||||
|
}
|
||||||
|
}
|
18
src/components/Title.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import styles from './Title.module.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
text: string
|
||||||
|
margin?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Title = ({ text, margin }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.title}>
|
||||||
|
<div className={styles.horizontalLine} />
|
||||||
|
<h6 style={{ margin: margin || '0 40px' }}>{text}</h6>
|
||||||
|
<div className={styles.horizontalLine} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Title
|
22
src/components/TxFee.module.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@use '../styles/master' as *;
|
||||||
|
|
||||||
|
.txFee {
|
||||||
|
@include padding(0, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.3;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
@include margin(0, 1.5, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/components/TxFee.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import styles from './TxFee.module.scss'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { formatValue } from '../libs/parse'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
txFee: string
|
||||||
|
styleOverride?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const TxFee = ({ txFee, styleOverride = {} }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className={styles.txFee}>
|
||||||
|
<div className={`overline ${styles.label}`} style={styleOverride}>
|
||||||
|
<span>{t('common.txFee')}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`overline ${styles.value}`} style={styleOverride}>
|
||||||
|
{formatValue(txFee, 2, 2, true, false, ' UST', true)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TxFee
|
41
src/components/TxFeeTooltip.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { formatValue } from '../libs/parse'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
gasFeeFormatted: string
|
||||||
|
taxFormatted: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TxFeeToolTip = ({ gasFeeFormatted, taxFormatted }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div style={{ fontSize: '0.8rem' }}>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<span style={{ flex: 'auto', marginRight: '6px' }}>
|
||||||
|
{t('common.gas')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{formatValue(
|
||||||
|
gasFeeFormatted,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
' UST',
|
||||||
|
true
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<span style={{ flex: 'auto', marginRight: '6px' }}>
|
||||||
|
{t('common.stability')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{formatValue(taxFormatted, 2, 2, true, false, ' UST', true)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TxFeeToolTip
|
113
src/components/bargraph/BarGraph.module.scss
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
@include margin(12.5, 25, 4);
|
||||||
|
width: rem-calc(230);
|
||||||
|
height: rem-calc(265);
|
||||||
|
border: 1px solid $graphAxis;
|
||||||
|
border-right: none;
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
.scale {
|
||||||
|
@include typoXXS;
|
||||||
|
opacity: 0.6;
|
||||||
|
line-height: rem-calc(12);
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(95);
|
||||||
|
text-align: end;
|
||||||
|
left: rem-calc(-100);
|
||||||
|
letter-spacing: rem-calc(2);
|
||||||
|
|
||||||
|
&.maxY {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.minY {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
@include typoXXScaps;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
bottom: rem-calc(-20);
|
||||||
|
|
||||||
|
&.position {
|
||||||
|
left: rem-calc(6);
|
||||||
|
width: rem-calc(120);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.debt {
|
||||||
|
right: rem-calc(-10);
|
||||||
|
width: rem-calc(95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
max-height: 100%;
|
||||||
|
width: rem-calc(36);
|
||||||
|
border-radius: $borderRadiusXS;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
&.supply {
|
||||||
|
left: rem-calc(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.borrow {
|
||||||
|
left: rem-calc(75);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.debt {
|
||||||
|
left: rem-calc(172);
|
||||||
|
transition: height 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(34);
|
||||||
|
bottom: rem-calc(12);
|
||||||
|
left: rem-calc(2);
|
||||||
|
text-align: center;
|
||||||
|
@include typoXXScaps;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liquidation {
|
||||||
|
@include typoXXScaps;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(105);
|
||||||
|
margin-bottom: space(-3);
|
||||||
|
text-align: end;
|
||||||
|
left: rem-calc(-110);
|
||||||
|
transition: bottom 1s ease-out;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
word-wrap: normal;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liquidationLine {
|
||||||
|
display: block;
|
||||||
|
width: rem-calc(110);
|
||||||
|
height: 1px;
|
||||||
|
border-bottom: 2px dashed $graphLiquidationsLine;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
transition: bottom 1s ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
112
src/components/bargraph/BarGraph.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import styles from './BarGraph.module.scss'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
|
||||||
|
interface barGraphData {
|
||||||
|
bars: number[]
|
||||||
|
labels: string[]
|
||||||
|
classNames: string[]
|
||||||
|
range: number[]
|
||||||
|
liquidation: number
|
||||||
|
legend: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: barGraphData
|
||||||
|
}
|
||||||
|
|
||||||
|
const BarGraph = ({ data }: Props) => {
|
||||||
|
const liquidationPosition = data.liquidation / (data.range[1] / 100)
|
||||||
|
|
||||||
|
const getBarHeightPercentage = (barIndex: number): number => {
|
||||||
|
return Math.floor(
|
||||||
|
(data.bars[barIndex] /
|
||||||
|
Math.max(
|
||||||
|
data.bars[0] || 0,
|
||||||
|
data.bars[1] || 0,
|
||||||
|
data.bars[2] || 0
|
||||||
|
)) *
|
||||||
|
100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<span className={`${styles.scale} ${styles.maxY}`}>
|
||||||
|
{formatValue(data.range[1], 2, 2, true, '$')}
|
||||||
|
</span>
|
||||||
|
<span className={`${styles.scale} ${styles.minY}`}>
|
||||||
|
{formatValue(data.range[0], 0, 0, true, '$')}
|
||||||
|
</span>
|
||||||
|
<span className={`${styles.legend} ${styles.position}`}>
|
||||||
|
{data.legend[0]}
|
||||||
|
</span>
|
||||||
|
{data.bars[2] > 0 && (
|
||||||
|
<span className={`${styles.legend} ${styles.debt}`}>
|
||||||
|
{data.legend[1]}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{data.bars[0] > 0 && (
|
||||||
|
<div
|
||||||
|
className={`${styles.bar} ${styles.supply} ${data.classNames[0]}`}
|
||||||
|
style={
|
||||||
|
data.bars[0] === 0
|
||||||
|
? { opacity: 0, height: '0%' }
|
||||||
|
: { height: `${getBarHeightPercentage(0)}%` }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className={styles.label}>{data.labels[0]}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.bars[1] > 0 && (
|
||||||
|
<div
|
||||||
|
className={`${styles.bar} ${styles.borrow} ${data.classNames[1]}`}
|
||||||
|
style={
|
||||||
|
data.bars[1] === 0
|
||||||
|
? { opacity: 0, height: '0%' }
|
||||||
|
: { height: `${getBarHeightPercentage(1)}%` }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className={styles.label}>{data.labels[1]}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.bars[2] > 0 && (
|
||||||
|
<div
|
||||||
|
className={`${styles.bar} ${styles.debt} ${data.classNames[2]}`}
|
||||||
|
style={
|
||||||
|
data.bars[2] === 0
|
||||||
|
? { height: '0%' }
|
||||||
|
: { height: `${getBarHeightPercentage(2)}%` }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className={styles.label}>{data.labels[2]}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{data.liquidation > 0 && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={styles.liquidation}
|
||||||
|
style={{
|
||||||
|
bottom: `${
|
||||||
|
liquidationPosition < 13
|
||||||
|
? 13
|
||||||
|
: liquidationPosition
|
||||||
|
}%`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>Liquidation threshold</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.liquidationLine}
|
||||||
|
style={{
|
||||||
|
bottom: `${liquidationPosition}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BarGraph
|
118
src/components/borrowlimit/BorrowLimit.module.scss
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include margin(0, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressbarContainer {
|
||||||
|
position: relative;
|
||||||
|
height: rem-calc(22);
|
||||||
|
|
||||||
|
.progressbar {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
@include bgHatched;
|
||||||
|
box-shadow: $shadowInset;
|
||||||
|
border-radius: $borderRadiusXXXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limitLine {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 1px;
|
||||||
|
background: $colorInfoWarning;
|
||||||
|
transition: left 2s;
|
||||||
|
box-shadow: $shadowInset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
background: $colorGreyDark;
|
||||||
|
box-shadow: $shadowInset;
|
||||||
|
border-top-left-radius: $borderRadiusXXXL;
|
||||||
|
border-bottom-left-radius: $borderRadiusXXXL;
|
||||||
|
transition: width 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(3);
|
||||||
|
height: rem-calc(3);
|
||||||
|
background: $colorInfoWarning;
|
||||||
|
margin-top: space(-1.75);
|
||||||
|
margin-inline-start: space(-0.25);
|
||||||
|
border-radius: $borderRadiusRound;
|
||||||
|
transition: left 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dotGlow {
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(7);
|
||||||
|
height: rem-calc(7);
|
||||||
|
background: $colorInfoWarning;
|
||||||
|
margin-top: space(-2.25);
|
||||||
|
margin-inline-start: space(-1.25);
|
||||||
|
border-radius: $borderRadiusXS;
|
||||||
|
@include glowM;
|
||||||
|
transition: left 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ltvContainer {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
overflow-x: hidden;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: $borderRadiusL;
|
||||||
|
transition: width 2s;
|
||||||
|
color: $fontColorLtv;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
transition: left 2s;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
@include bgLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
@include bgLimit;
|
||||||
|
opacity: 0.4;
|
||||||
|
transition: width 2s;
|
||||||
|
@include glowXXL;
|
||||||
|
@include margin(-5.5, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.values {
|
||||||
|
display: flex;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-top: space(1.25);
|
||||||
|
|
||||||
|
.zero {
|
||||||
|
text-align: start;
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit {
|
||||||
|
flex: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
172
src/components/borrowlimit/BorrowLimit.tsx
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import { formatValue, lookup } from '../../libs/parse'
|
||||||
|
import styles from './BorrowLimit.module.scss'
|
||||||
|
import { addDecimals } from '../../libs/math'
|
||||||
|
import { UST_DECIMALS, UST_DENOM } from '../../constants/appConstants'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width: string
|
||||||
|
ltv: number
|
||||||
|
maxLtv: number
|
||||||
|
liquidationThreshold: number
|
||||||
|
barHeight: string
|
||||||
|
showPercentageText: boolean
|
||||||
|
showTitleText: boolean
|
||||||
|
showLegend?: boolean
|
||||||
|
top?: number
|
||||||
|
percentageThreshold?: number
|
||||||
|
percentageOffset?: number
|
||||||
|
title?: string
|
||||||
|
mode?: string
|
||||||
|
criticalIndicator?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const BorrowLimit = ({
|
||||||
|
width,
|
||||||
|
ltv,
|
||||||
|
maxLtv,
|
||||||
|
liquidationThreshold,
|
||||||
|
barHeight = '22px',
|
||||||
|
showPercentageText = true,
|
||||||
|
showLegend = true,
|
||||||
|
top = 4,
|
||||||
|
showTitleText = true,
|
||||||
|
percentageThreshold = 15,
|
||||||
|
percentageOffset = 45,
|
||||||
|
title = 'Borrowing Capacity',
|
||||||
|
mode = 'default',
|
||||||
|
criticalIndicator,
|
||||||
|
}: Props) => {
|
||||||
|
const ltvPercent =
|
||||||
|
+(((ltv || 0) / (liquidationThreshold || 0)) * 100).toFixed(2) || 0
|
||||||
|
|
||||||
|
const ltvPercentRounded = +(Math.round(ltvPercent * 100) / 100).toFixed(1)
|
||||||
|
const ltvPercentRestrained =
|
||||||
|
ltvPercent > 100 ? 100 : ltvPercent < 0 ? 0 : ltvPercent
|
||||||
|
const ltvPercentMargin =
|
||||||
|
ltvPercent > percentageThreshold
|
||||||
|
? `-${percentageOffset + 15}px`
|
||||||
|
: '10px'
|
||||||
|
const maxBorrowPercent =
|
||||||
|
criticalIndicator || (maxLtv / liquidationThreshold) * 100
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div style={{ width: width }}>
|
||||||
|
{showTitleText ? (
|
||||||
|
<div className={`overline ${styles.title}`}>{title}</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ height: barHeight }}
|
||||||
|
className={styles.progressbarContainer}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ width: width }}
|
||||||
|
className={styles.progressbar}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ left: `${maxBorrowPercent}%` }}
|
||||||
|
className={styles.limitLine}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${maxBorrowPercent}%`,
|
||||||
|
maxWidth: width,
|
||||||
|
}}
|
||||||
|
className={styles.limit}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{ left: `${maxBorrowPercent}%` }}
|
||||||
|
className={styles.dot}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{ left: `${maxBorrowPercent}%` }}
|
||||||
|
className={styles.dotGlow}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.ltvContainer}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${ltvPercentRestrained}%`,
|
||||||
|
maxWidth: width,
|
||||||
|
}}
|
||||||
|
className={styles.mask}
|
||||||
|
>
|
||||||
|
{showPercentageText ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${ltvPercentRestrained}%`,
|
||||||
|
top: `${top}px`,
|
||||||
|
marginLeft: ltvPercentMargin,
|
||||||
|
width: `${percentageOffset + 8}px`,
|
||||||
|
textAlign:
|
||||||
|
ltvPercent > percentageThreshold
|
||||||
|
? 'right'
|
||||||
|
: 'left',
|
||||||
|
}}
|
||||||
|
className='overline'
|
||||||
|
>
|
||||||
|
{ltv < 0 ? (
|
||||||
|
'0%'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{`${
|
||||||
|
mode === 'default'
|
||||||
|
? ltvPercentRounded
|
||||||
|
: addDecimals(ltv)
|
||||||
|
}%`}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
style={{ width: width }}
|
||||||
|
className={styles.indicator}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${ltvPercentRestrained}%`,
|
||||||
|
maxWidth: width,
|
||||||
|
}}
|
||||||
|
className={styles.glow}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showLegend && (
|
||||||
|
<div className={`overline ${styles.values}`}>
|
||||||
|
<div className={styles.zero}>
|
||||||
|
{mode === 'default' ? '$0' : '0%'}
|
||||||
|
</div>
|
||||||
|
<div className={styles.limit}>
|
||||||
|
{mode === 'default' ? (
|
||||||
|
<span>
|
||||||
|
{formatValue(
|
||||||
|
lookup(maxLtv, UST_DENOM, UST_DECIMALS),
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
'$'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
formatValue(
|
||||||
|
liquidationThreshold,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorrowLimit
|
5
src/components/card/Card.module.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include layoutTile;
|
||||||
|
}
|
17
src/components/card/Card.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
import styles from './Card.module.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: ReactNode
|
||||||
|
styleOverride?: object
|
||||||
|
}
|
||||||
|
|
||||||
|
const Card = ({ children, styleOverride }: Props) => {
|
||||||
|
return (
|
||||||
|
<div style={styleOverride} className={styles.container}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card
|
48
src/components/donutgraph/DonutGraph.module.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
margin-inline-start: 0;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.innerText {
|
||||||
|
position: absolute;
|
||||||
|
top: 47.17%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
top: 58.66%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: rem-calc(110);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) and (min-width: $bpMediumLow) {
|
||||||
|
.container {
|
||||||
|
@include padding(3, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
266
src/components/donutgraph/DonutGraph.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import { Doughnut, defaults } from 'react-chartjs-2'
|
||||||
|
|
||||||
|
import styles from './DonutGraph.module.scss'
|
||||||
|
import DonutHover from './charts/DonutHover'
|
||||||
|
import { producePercentData } from '../../libs/assetInfo'
|
||||||
|
import Tippy from '@tippyjs/react'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
defaults.global.defaultFontFamily = 'Inter'
|
||||||
|
|
||||||
|
function hexToRgb(hex: any) {
|
||||||
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
|
return result
|
||||||
|
? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16),
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a gradient to use on our chart. This is essentially a circular border around the slice(s)
|
||||||
|
function createGrad(
|
||||||
|
ctx: any,
|
||||||
|
canvasWidth: number,
|
||||||
|
canvasHeight: number,
|
||||||
|
colorOne: string,
|
||||||
|
colorTwo = '#1b1d24',
|
||||||
|
opacityTwo = 0.35
|
||||||
|
) {
|
||||||
|
let opacityOne = 1,
|
||||||
|
colorStop = 92
|
||||||
|
var rgbOne = hexToRgb(colorOne)
|
||||||
|
var rgbTwo = hexToRgb(colorTwo)
|
||||||
|
colorStop = colorStop / 100
|
||||||
|
|
||||||
|
var grd = ctx
|
||||||
|
.getContext('2d')
|
||||||
|
.createRadialGradient(
|
||||||
|
canvasWidth / 2,
|
||||||
|
canvasHeight / 2,
|
||||||
|
0.0,
|
||||||
|
canvasWidth / 2,
|
||||||
|
canvasHeight / 2,
|
||||||
|
canvasHeight / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rgbOne == null || rgbTwo == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add color stops to the gradient
|
||||||
|
grd.addColorStop(
|
||||||
|
0.0,
|
||||||
|
'rgba(' +
|
||||||
|
rgbOne.r +
|
||||||
|
', ' +
|
||||||
|
rgbOne.g +
|
||||||
|
', ' +
|
||||||
|
rgbOne.b +
|
||||||
|
', ' +
|
||||||
|
opacityOne +
|
||||||
|
')'
|
||||||
|
)
|
||||||
|
grd.addColorStop(
|
||||||
|
colorStop,
|
||||||
|
'rgba(' +
|
||||||
|
rgbOne.r +
|
||||||
|
', ' +
|
||||||
|
rgbOne.g +
|
||||||
|
', ' +
|
||||||
|
rgbOne.b +
|
||||||
|
', ' +
|
||||||
|
opacityOne +
|
||||||
|
')'
|
||||||
|
)
|
||||||
|
grd.addColorStop(
|
||||||
|
0.94,
|
||||||
|
'rgba(' +
|
||||||
|
rgbTwo.r +
|
||||||
|
', ' +
|
||||||
|
rgbTwo.g +
|
||||||
|
', ' +
|
||||||
|
rgbTwo.b +
|
||||||
|
', ' +
|
||||||
|
opacityTwo +
|
||||||
|
')'
|
||||||
|
) // required to prevent jagged edges
|
||||||
|
grd.addColorStop(
|
||||||
|
1.0,
|
||||||
|
'rgba(' +
|
||||||
|
rgbTwo.r +
|
||||||
|
', ' +
|
||||||
|
rgbTwo.g +
|
||||||
|
', ' +
|
||||||
|
rgbTwo.b +
|
||||||
|
', ' +
|
||||||
|
opacityTwo +
|
||||||
|
')'
|
||||||
|
)
|
||||||
|
|
||||||
|
return grd
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateColors(mychart: any, defaultColors: any[]) {
|
||||||
|
if (mychart == null) {
|
||||||
|
return defaultColors
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColors.map((color) =>
|
||||||
|
createGrad(
|
||||||
|
mychart.chartInstance.ctx.canvas,
|
||||||
|
mychart.chartInstance.width,
|
||||||
|
mychart.chartInstance.height,
|
||||||
|
color,
|
||||||
|
'#1b1d24',
|
||||||
|
0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateHoverColors(mychart: any, defaultColors: any[]) {
|
||||||
|
if (mychart == null) {
|
||||||
|
return defaultColors
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColors.map((color) =>
|
||||||
|
createGrad(
|
||||||
|
mychart.chartInstance.ctx.canvas,
|
||||||
|
mychart.chartInstance.width,
|
||||||
|
mychart.chartInstance.height,
|
||||||
|
color,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
placeholderTitle?: string
|
||||||
|
value: number
|
||||||
|
cutoutPercentage: number
|
||||||
|
labels: string[]
|
||||||
|
data: AssetInfo[]
|
||||||
|
colors: string[]
|
||||||
|
expandableOnHover?: boolean
|
||||||
|
labelsOnHover?: boolean
|
||||||
|
styleOverride: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
const DonutGraph = ({
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
cutoutPercentage,
|
||||||
|
labels,
|
||||||
|
data,
|
||||||
|
colors,
|
||||||
|
expandableOnHover = true,
|
||||||
|
labelsOnHover = true,
|
||||||
|
styleOverride,
|
||||||
|
}: Props) => {
|
||||||
|
const [showToolTip, setShowToolTip] = useState(false)
|
||||||
|
const [myChart, setMyChart] = useState()
|
||||||
|
const _chartRef: any = React.createRef()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMyChart(_chartRef.current)
|
||||||
|
}, [_chartRef])
|
||||||
|
const colorsToAdd = generateColors(myChart, colors)
|
||||||
|
const hoverColors = generateHoverColors(myChart, colors)
|
||||||
|
|
||||||
|
const percentData = useMemo(() => producePercentData(data), [data])
|
||||||
|
|
||||||
|
// Set up our dataset based on whether we require expandable sections on hover
|
||||||
|
const chartHasData = useMemo(() => data.some((d) => !!d), [data])
|
||||||
|
|
||||||
|
const datasetToAdd = chartHasData
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
data: percentData,
|
||||||
|
backgroundColor: expandableOnHover ? colorsToAdd : colors,
|
||||||
|
hoverBackgroundColor: expandableOnHover
|
||||||
|
? hoverColors
|
||||||
|
: colors,
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
data: [100],
|
||||||
|
backgroundColor: expandableOnHover
|
||||||
|
? generateColors(myChart, ['#3a3c49'])
|
||||||
|
: ['#3a3c49'],
|
||||||
|
hoverBackgroundColor: expandableOnHover
|
||||||
|
? generateHoverColors(myChart, ['#3a3c49'])
|
||||||
|
: ['#3a3c49'],
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartData = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: datasetToAdd,
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
tooltips: {
|
||||||
|
enabled: false,
|
||||||
|
custom: (tooltipModel: any) => {
|
||||||
|
if (!chartHasData || !labelsOnHover) return
|
||||||
|
// if chart is not defined, return early
|
||||||
|
var chart: any = _chartRef.current
|
||||||
|
if (!chart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the tooltip when chartjs determines you've hovered out
|
||||||
|
if (tooltipModel.opacity <= 0) {
|
||||||
|
setShowToolTip(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setShowToolTip(true)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
cutoutPercentage: cutoutPercentage,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
circumference: Math.PI * 2,
|
||||||
|
rotation: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
{showToolTip && expandableOnHover && (
|
||||||
|
<Tippy content={<DonutHover title={title} data={data} />}>
|
||||||
|
<div className={styles.overlay} />
|
||||||
|
</Tippy>
|
||||||
|
)}
|
||||||
|
<div style={styleOverride}>
|
||||||
|
<Doughnut
|
||||||
|
data={chartData}
|
||||||
|
options={chartOptions}
|
||||||
|
ref={_chartRef}
|
||||||
|
/>
|
||||||
|
<div className={styles.innerText}>
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<span>$</span>
|
||||||
|
<span className={'h4'}>
|
||||||
|
{formatValue(Number(value), 2, 2, true, false)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<p className={'sub2'}>{title}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DonutGraph
|
5
src/components/donutgraph/charts/DonutHover.module.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@use '../../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
}
|
38
src/components/donutgraph/charts/DonutHover.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { UST_DENOM, UST_DECIMALS } from '../../../constants/appConstants'
|
||||||
|
import { lookup } from '../../../libs/parse'
|
||||||
|
import CollectionHover, { HoverItem } from '../../CollectionHover'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: AssetInfo[]
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DonutHover = ({ data, title }: Props) => {
|
||||||
|
const produceData = (data: AssetInfo[]): HoverItem[] => {
|
||||||
|
const items: HoverItem[] = []
|
||||||
|
|
||||||
|
data.forEach((asset: AssetInfo) => {
|
||||||
|
if (Number(asset.uusdBalance) > 0) {
|
||||||
|
items.push({
|
||||||
|
color: asset.color || '',
|
||||||
|
name: asset.symbol || '',
|
||||||
|
amount: lookup(
|
||||||
|
Number(asset.balance),
|
||||||
|
asset.denom,
|
||||||
|
asset.decimals
|
||||||
|
),
|
||||||
|
usdValue: lookup(
|
||||||
|
asset.uusdBalance || 0,
|
||||||
|
UST_DENOM,
|
||||||
|
UST_DECIMALS
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CollectionHover title={title} data={produceData(data)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DonutHover
|
17
src/components/grid/Asset.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
interface Props {
|
||||||
|
symbol: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Asset = ({ symbol, name }: Props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{symbol}</div>
|
||||||
|
<div style={{ opacity: 0.6 }} className='caption'>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Asset
|
47
src/components/grid/CellAmount.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { UST_DECIMALS, UST_DENOM } from '../../constants/appConstants'
|
||||||
|
import { formatValue, lookup } from '../../libs/parse'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
denom: string
|
||||||
|
decimals: number
|
||||||
|
amount: number
|
||||||
|
uusdAmount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const CellAmount = ({ denom, decimals, amount, uusdAmount }: Props) => {
|
||||||
|
const usdAmount = lookup(uusdAmount, UST_DENOM, UST_DECIMALS)
|
||||||
|
const assetAmount = lookup(amount, denom, decimals)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{denom !== 'uusd'
|
||||||
|
? formatValue(
|
||||||
|
assetAmount > 0 && assetAmount < 0.01
|
||||||
|
? 0.01
|
||||||
|
: assetAmount,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
assetAmount > 0 && assetAmount < 0.01 ? '< ' : false
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
opacity: denom !== 'uusd' ? 0.6 : 1,
|
||||||
|
}}
|
||||||
|
className={denom !== 'uusd' ? 'caption' : ''}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{formatValue(
|
||||||
|
usdAmount > 0 && usdAmount < 0.01 ? 0.01 : usdAmount,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
usdAmount > 0 && usdAmount < 0.01 ? '< $' : '$'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CellAmount
|
232
src/components/grid/Grid.jsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { useTable, useSortBy } from 'react-table'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import colors from '../../styles/_assets.module.scss'
|
||||||
|
const Grid = ({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
initialState,
|
||||||
|
hideheader = false,
|
||||||
|
updateSort,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
columns: instanceColumns,
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
prepareRow,
|
||||||
|
toggleSortBy,
|
||||||
|
} = useTable(
|
||||||
|
{
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
initialState,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
disableMultiSort: true,
|
||||||
|
sortDescFirst: true,
|
||||||
|
},
|
||||||
|
useSortBy
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
instanceColumns.forEach((column) => {
|
||||||
|
if (column.isSorted) {
|
||||||
|
if (
|
||||||
|
initialState.sortBy[0].id !== column.id ||
|
||||||
|
initialState.sortBy[0].desc !== column.isSortedDesc
|
||||||
|
)
|
||||||
|
updateSort({
|
||||||
|
sortBy: [
|
||||||
|
{
|
||||||
|
id: column.id,
|
||||||
|
desc: column.isSortedDesc,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[rows]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
instanceColumns.forEach((column) => {
|
||||||
|
let cleared = false
|
||||||
|
if (column.isSorted) {
|
||||||
|
if (
|
||||||
|
initialState.sortBy[0].id !== column.id ||
|
||||||
|
initialState.sortBy[0].desc !== column.isSortedDesc
|
||||||
|
) {
|
||||||
|
cleared = true
|
||||||
|
column.clearSortBy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleared) {
|
||||||
|
toggleSortBy(
|
||||||
|
initialState.sortBy[0].id,
|
||||||
|
initialState.sortBy[0].desc,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[initialState]
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<table
|
||||||
|
{...getTableProps()}
|
||||||
|
style={{
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
borderSpacing: '0px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{headerGroups[0].headers.map((column) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
{...column.getHeaderProps(
|
||||||
|
column.getSortByToggleProps()
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
borderTop: hideheader
|
||||||
|
? 'none'
|
||||||
|
: `1px solid ${colors.tableBorder}`,
|
||||||
|
borderBottom: `1px solid ${colors.tableBorder}`,
|
||||||
|
cursor: column.disableSortBy
|
||||||
|
? 'default'
|
||||||
|
: 'pointer',
|
||||||
|
width: column.width,
|
||||||
|
color: colors.tableHeader, // Overwrite the .caption color to be 50% transparent
|
||||||
|
}}
|
||||||
|
className='caption'
|
||||||
|
>
|
||||||
|
{column.hideHeader || hideheader ? null : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding:
|
||||||
|
column.headingPaddingOverride
|
||||||
|
? column.headingPaddingOverride
|
||||||
|
: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
whiteSpace:
|
||||||
|
column.whiteSpace ||
|
||||||
|
'nowrap',
|
||||||
|
textAlign:
|
||||||
|
column.textAlign ||
|
||||||
|
'inherit',
|
||||||
|
justifyContent:
|
||||||
|
column.textAlign === 'right'
|
||||||
|
? 'flex-end'
|
||||||
|
: '',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{column.textAlign !== 'right' &&
|
||||||
|
column.render('Header')}
|
||||||
|
{!column.disableSortBy ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
margin:
|
||||||
|
column.textAlign ===
|
||||||
|
'right'
|
||||||
|
? 'auto 7px auto 0'
|
||||||
|
: 'auto 0 auto 7px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderLeft:
|
||||||
|
'4px solid transparent',
|
||||||
|
borderRight:
|
||||||
|
'4px solid transparent',
|
||||||
|
borderBottom:
|
||||||
|
!column.disableSortBy
|
||||||
|
? !column.isSortedDesc &&
|
||||||
|
column.isSorted
|
||||||
|
? `4px solid ${colors.tableSortActive}`
|
||||||
|
: `4px solid ${colors.tableSort}`
|
||||||
|
: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginTop: '3px',
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderLeft:
|
||||||
|
'4px solid transparent',
|
||||||
|
borderRight:
|
||||||
|
'4px solid transparent',
|
||||||
|
borderTop:
|
||||||
|
!column.disableSortBy
|
||||||
|
? column.isSortedDesc &&
|
||||||
|
column.isSorted
|
||||||
|
? `4px solid ${colors.tableSortActive}`
|
||||||
|
: `4px solid ${colors.tableSort}`
|
||||||
|
: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{column.textAlign === 'right' &&
|
||||||
|
column.render('Header')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody {...getTableBodyProps()}>
|
||||||
|
{rows.map((row) => {
|
||||||
|
prepareRow(row)
|
||||||
|
return (
|
||||||
|
<tr {...row.getRowProps()}>
|
||||||
|
{row.cells.map((cell) => {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
{...cell.getCellProps()}
|
||||||
|
style={{
|
||||||
|
textAlign: cell.column.textAlign,
|
||||||
|
height: '53px',
|
||||||
|
width: cell.column.width,
|
||||||
|
padding: cell.column.paddingOverride
|
||||||
|
? cell.column.paddingOverride
|
||||||
|
: '0 8px',
|
||||||
|
borderBottom: `1px solid ${colors.tableBorder}`,
|
||||||
|
overflow: cell.column.showOverflow
|
||||||
|
? 'visable'
|
||||||
|
: 'hidden',
|
||||||
|
}}
|
||||||
|
className='body2'
|
||||||
|
>
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Grid
|
191
src/components/grid/GridActions.tsx
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import OverflowDropdown from '../../components/grid/dropdown/OverflowDropdown'
|
||||||
|
import useActionButtonClickHandler from '../../hooks/useActionButtonClickHandler'
|
||||||
|
import { useRedBank, useAccountBalance } from '../../hooks'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { DropdownItemProps } from '../../types/components'
|
||||||
|
import { useHistory, useLocation } from 'react-router'
|
||||||
|
import { ASTROPORT_URL } from '../../constants/appConstants'
|
||||||
|
import useStore from '../../store'
|
||||||
|
import Button from '../Button'
|
||||||
|
import { ExternalSVG } from '../Svg'
|
||||||
|
import { getRoute } from '../../libs/parse'
|
||||||
|
import { ActionType } from '../../types/enums'
|
||||||
|
|
||||||
|
export enum GridActionType {
|
||||||
|
BorrowAction,
|
||||||
|
DepositAction,
|
||||||
|
WithdrawAction,
|
||||||
|
ManageAction,
|
||||||
|
RepayAction,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
denom: string
|
||||||
|
menuItems: DropdownItemProps[]
|
||||||
|
actionType: GridActionType
|
||||||
|
strategy?: StrategyObject
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const GridActions = ({
|
||||||
|
denom,
|
||||||
|
menuItems,
|
||||||
|
actionType,
|
||||||
|
strategy,
|
||||||
|
disabled = false,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const newMenuItems = [...menuItems]
|
||||||
|
const mainButton = newMenuItems[0]
|
||||||
|
const whitelistedAssets = useStore((s) => s.whitelistedAssets)
|
||||||
|
|
||||||
|
const { find, findDeposit } = useAccountBalance()
|
||||||
|
const { findLiquidity, findMarketInfo } = useRedBank()
|
||||||
|
const provideClickHandler = useActionButtonClickHandler()
|
||||||
|
const history = useHistory()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
const swapUrl =
|
||||||
|
denom === 'uusd'
|
||||||
|
? `${ASTROPORT_URL}?from=uluna&to=uusd`
|
||||||
|
: denom === 'uluna'
|
||||||
|
? `${ASTROPORT_URL}?from=uusd&to=uluna`
|
||||||
|
: `${ASTROPORT_URL}?from=uusd&to=${
|
||||||
|
whitelistedAssets?.find((asset) => asset.denom === denom)
|
||||||
|
?.contract_addr
|
||||||
|
}`
|
||||||
|
|
||||||
|
const marketInfo = findMarketInfo(denom)
|
||||||
|
|
||||||
|
const buyClickHandler = provideClickHandler(
|
||||||
|
ActionType.ExternalLink,
|
||||||
|
denom,
|
||||||
|
GridActionType.None,
|
||||||
|
swapUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
let dropdownMenuItems = newMenuItems.slice(1, menuItems.length)
|
||||||
|
|
||||||
|
let useBuyButton = false
|
||||||
|
if (actionType === GridActionType.DepositAction) {
|
||||||
|
const assetWallet = find(denom)
|
||||||
|
|
||||||
|
if (Number(assetWallet?.amount || 0) <= 0) useBuyButton = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let disabledDepositButton: boolean = false
|
||||||
|
// If the market has had deposits disabled then disable deposit button
|
||||||
|
if (
|
||||||
|
actionType === GridActionType.DepositAction &&
|
||||||
|
!marketInfo?.deposit_enabled
|
||||||
|
) {
|
||||||
|
disabledDepositButton = true
|
||||||
|
}
|
||||||
|
// Remove deposit from submenu if it's disabled (applicable to My Station's grid)
|
||||||
|
if (
|
||||||
|
actionType === GridActionType.WithdrawAction &&
|
||||||
|
!marketInfo?.deposit_enabled
|
||||||
|
) {
|
||||||
|
dropdownMenuItems = dropdownMenuItems.filter(
|
||||||
|
(item) => item.gridAction !== GridActionType.DepositAction
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let disableBorrowButton: boolean = false
|
||||||
|
if (actionType === GridActionType.BorrowAction) {
|
||||||
|
// If the market has had borrows disabled then disable borrow button
|
||||||
|
if (!marketInfo?.borrow_enabled) {
|
||||||
|
disableBorrowButton = true
|
||||||
|
}
|
||||||
|
// If the user has no collateral then disable borrow button
|
||||||
|
if (!disableBorrowButton && whitelistedAssets?.length) {
|
||||||
|
let depositSum: number = 0
|
||||||
|
whitelistedAssets.forEach((asset) => {
|
||||||
|
depositSum += Number(findDeposit(asset.denom)?.amount || 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (depositSum <= 0) disableBorrowButton = true
|
||||||
|
}
|
||||||
|
// if the market has no liquidity available then disable borrow button
|
||||||
|
if (!disableBorrowButton) {
|
||||||
|
const assetLiquidity = findLiquidity(denom)
|
||||||
|
|
||||||
|
if (Number(assetLiquidity?.amount || 0) <= 0)
|
||||||
|
disableBorrowButton = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove borrow from submenu if it's disabled (applicable to My Station's grid)
|
||||||
|
if (
|
||||||
|
actionType === GridActionType.RepayAction &&
|
||||||
|
!marketInfo?.borrow_enabled
|
||||||
|
) {
|
||||||
|
dropdownMenuItems = dropdownMenuItems.filter(
|
||||||
|
(item) => item.gridAction !== GridActionType.BorrowAction
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled prop is used to disable all actions of a market on the UI, i.e. on the not connected screens where we show a dummie grid
|
||||||
|
// The !marketInfo?.active flag determines if all actions against the market has been disabled on the SC level
|
||||||
|
const isAllActionsDisabled: boolean = disabled || !marketInfo?.active
|
||||||
|
const isDropdownDisabled: boolean =
|
||||||
|
useBuyButton &&
|
||||||
|
dropdownMenuItems.length === 1 &&
|
||||||
|
dropdownMenuItems[0].title === 'Swap'
|
||||||
|
const isPrimaryActionDisabled: boolean =
|
||||||
|
disabledDepositButton || disableBorrowButton
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{useBuyButton ? (
|
||||||
|
<Button
|
||||||
|
text={t('common.buy')}
|
||||||
|
suffix={<ExternalSVG />}
|
||||||
|
color='secondary'
|
||||||
|
onClick={buyClickHandler}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{strategy ? (
|
||||||
|
<Button
|
||||||
|
text={mainButton?.title || ''}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(`/fields/${actionType}`)
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
text={mainButton?.title || ''}
|
||||||
|
disabled={
|
||||||
|
isAllActionsDisabled || isPrimaryActionDisabled
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(
|
||||||
|
`${location.pathname}/${getRoute(
|
||||||
|
actionType
|
||||||
|
)}/${denom}`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<OverflowDropdown
|
||||||
|
menuItems={dropdownMenuItems}
|
||||||
|
denom={denom}
|
||||||
|
strategy={strategy}
|
||||||
|
disabled={isAllActionsDisabled || isDropdownDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridActions
|
13
src/components/grid/dropdown/DropdownItem.module.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
@use '../../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include padding(1, 4);
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $alphaWhite10;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
38
src/components/grid/dropdown/DropdownItem.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useHistory } from 'react-router'
|
||||||
|
import useActionButtonClickHandler from '../../../hooks/useActionButtonClickHandler'
|
||||||
|
import { DropdownItemProps } from '../../../types/components'
|
||||||
|
import styles from './DropdownItem.module.scss'
|
||||||
|
|
||||||
|
const DropdownItem = ({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
denom,
|
||||||
|
actionType,
|
||||||
|
gridAction,
|
||||||
|
url,
|
||||||
|
close,
|
||||||
|
strategy,
|
||||||
|
}: DropdownItemProps) => {
|
||||||
|
const provideClickHandler = useActionButtonClickHandler()
|
||||||
|
const history = useHistory()
|
||||||
|
const clickHandler = strategy
|
||||||
|
? () => {
|
||||||
|
history.push(`/fields/strategy/${strategy.key}`)
|
||||||
|
}
|
||||||
|
: provideClickHandler(actionType, denom, gridAction, url)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.container}
|
||||||
|
onClick={() => {
|
||||||
|
clickHandler()
|
||||||
|
close()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<span className='body2'>{title}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropdownItem
|
@ -0,0 +1,9 @@
|
|||||||
|
@use '../../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include layoutTooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
margin-inline-start: space(1);
|
||||||
|
}
|
88
src/components/grid/dropdown/OverflowDropdown.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import Tippy, { TippyProps } from '@tippyjs/react'
|
||||||
|
import DropdownItem from './DropdownItem'
|
||||||
|
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
|
||||||
|
|
||||||
|
import styles from './OverflowDropdown.module.scss'
|
||||||
|
import { DropdownItemProps } from '../../../types/components'
|
||||||
|
import Button from '../../Button'
|
||||||
|
|
||||||
|
export const DefaultTippyProps: TippyProps = {
|
||||||
|
animation: false,
|
||||||
|
interactive: true,
|
||||||
|
appendTo: document.body,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropdownTippyProps: TippyProps = {
|
||||||
|
...DefaultTippyProps,
|
||||||
|
placement: 'bottom-end',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OverflowDropdownProps {
|
||||||
|
menuItems: DropdownItemProps[]
|
||||||
|
denom: string
|
||||||
|
strategy?: StrategyObject
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const OverflowDropdown = ({
|
||||||
|
menuItems,
|
||||||
|
denom,
|
||||||
|
strategy,
|
||||||
|
disabled = false,
|
||||||
|
}: OverflowDropdownProps) => {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const show = () => setVisible(true)
|
||||||
|
const hide = () => setVisible(false)
|
||||||
|
const renderDropdown = () => (
|
||||||
|
<div className={styles.container}>
|
||||||
|
{menuItems.map((menuItem, index) => (
|
||||||
|
<DropdownItem
|
||||||
|
key={index}
|
||||||
|
icon={menuItem.icon}
|
||||||
|
title={menuItem.title}
|
||||||
|
denom={denom}
|
||||||
|
actionType={menuItem.actionType}
|
||||||
|
gridAction={menuItem.gridAction}
|
||||||
|
url={menuItem.url}
|
||||||
|
close={hide}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (menuItems.length) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tippy
|
||||||
|
{...DropdownTippyProps}
|
||||||
|
render={renderDropdown}
|
||||||
|
visible={visible}
|
||||||
|
onClickOutside={hide}
|
||||||
|
>
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<Button
|
||||||
|
color='tertiary'
|
||||||
|
variant='round'
|
||||||
|
prefix={
|
||||||
|
<MoreHorizIcon
|
||||||
|
style={{
|
||||||
|
width: '1.2rem',
|
||||||
|
height: '1.2rem',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={visible ? hide : show}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tippy>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OverflowDropdown
|
156
src/components/header/ConnectButton.module.scss
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.disabledButton,
|
||||||
|
.secondary {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
border-radius: $borderRadiusXXL;
|
||||||
|
height: rem-calc(31);
|
||||||
|
@include padding(0.5, 4, 0);
|
||||||
|
@include typoButton;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $colorWhite;
|
||||||
|
border: 1px solid $alphaWhite60;
|
||||||
|
outline: none;
|
||||||
|
background: $alphaBlack10;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
@include margin(0, 0, 0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $alphaWhite60;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
@include margin(-0.5, 0, 0);
|
||||||
|
height: rem-calc(16);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabledButton:hover {
|
||||||
|
background: $alphaWhite30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: $colorSecondaryHighlight;
|
||||||
|
border: none;
|
||||||
|
@include padding(0.5, 5.5);
|
||||||
|
height: rem-calc(36);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorSecondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
top: rem-calc(38);
|
||||||
|
@include padding(6, 6, 5.5);
|
||||||
|
@include layoutPopover;
|
||||||
|
width: rem-calc(360);
|
||||||
|
max-width: 100vw;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $colorAccent;
|
||||||
|
border: none;
|
||||||
|
appearance: none;
|
||||||
|
@include typoL;
|
||||||
|
@include padding(2, 0);
|
||||||
|
font-weight: $fontWeightRegular;
|
||||||
|
text-align: start;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
opacity: 0.6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
color: $colorSecondaryHighlight;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
@include margin(0, 2, 0, 0);
|
||||||
|
|
||||||
|
svg,
|
||||||
|
img {
|
||||||
|
width: rem-calc(24);
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
@include margin(-1.5, 0, 0, 1);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $colorAccent;
|
||||||
|
opacity: 0.5;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) {
|
||||||
|
.dropdown {
|
||||||
|
&:not(.centered) {
|
||||||
|
right: 0;
|
||||||
|
transform: none;
|
||||||
|
left: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
+ .dropdown {
|
||||||
|
right: unset;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.dropdown {
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
}
|
||||||
|
}
|
146
src/components/header/ConnectButton.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { ReactNode, useCallback, useState } from 'react'
|
||||||
|
import styles from './ConnectButton.module.scss'
|
||||||
|
import { WalletSVG } from '../Svg'
|
||||||
|
import { ClickAwayListener } from '@material-ui/core'
|
||||||
|
import { ConnectType, useWallet } from '@terra-money/wallet-provider'
|
||||||
|
import * as rdd from 'react-device-detect'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
textOverride?: string | ReactNode
|
||||||
|
disabled?: boolean
|
||||||
|
color?: string
|
||||||
|
centered?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectButton = ({
|
||||||
|
textOverride,
|
||||||
|
disabled = false,
|
||||||
|
color,
|
||||||
|
centered = false,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [openConnectList, setOpenConnectList] = useState(false)
|
||||||
|
|
||||||
|
const { connect, availableConnections, availableInstallations } =
|
||||||
|
useWallet()
|
||||||
|
|
||||||
|
const onClickAway = useCallback(() => {
|
||||||
|
setOpenConnectList(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
disabled
|
||||||
|
? styles.disabledButton
|
||||||
|
: color
|
||||||
|
? styles[color]
|
||||||
|
: styles.button
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
rdd.isMobile
|
||||||
|
? connect(ConnectType.WALLETCONNECT)
|
||||||
|
: setOpenConnectList(!openConnectList)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WalletSVG />
|
||||||
|
<span className='overline'>
|
||||||
|
{textOverride || t('common.connectWallet')}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{openConnectList && (
|
||||||
|
<ClickAwayListener onClickAway={onClickAway}>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
centered
|
||||||
|
? `${styles.dropdown} ${styles.centered}`
|
||||||
|
: styles.dropdown
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!rdd.isMobile && (
|
||||||
|
<>
|
||||||
|
{availableConnections
|
||||||
|
.filter(
|
||||||
|
({ type }) =>
|
||||||
|
type !== ConnectType.READONLY
|
||||||
|
)
|
||||||
|
.map(({ type, name, identifier }) => {
|
||||||
|
if (
|
||||||
|
(type === ConnectType.EXTENSION &&
|
||||||
|
identifier === 'station') ||
|
||||||
|
type === ConnectType.WALLETCONNECT
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={
|
||||||
|
'connection' +
|
||||||
|
type +
|
||||||
|
identifier
|
||||||
|
}
|
||||||
|
className={styles.option}
|
||||||
|
onClick={() => {
|
||||||
|
connect(
|
||||||
|
type,
|
||||||
|
identifier
|
||||||
|
)
|
||||||
|
setOpenConnectList(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
|
{availableInstallations
|
||||||
|
.filter(
|
||||||
|
({ type }) =>
|
||||||
|
type === ConnectType.EXTENSION
|
||||||
|
)
|
||||||
|
.map(({ type, identifier, name, url }) => {
|
||||||
|
if (
|
||||||
|
type === ConnectType.EXTENSION &&
|
||||||
|
identifier === 'station'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={
|
||||||
|
'installation' +
|
||||||
|
type +
|
||||||
|
identifier
|
||||||
|
}
|
||||||
|
className={styles.option}
|
||||||
|
href={url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
onClick={() => {
|
||||||
|
setOpenConnectList(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.installName', {
|
||||||
|
name: name,
|
||||||
|
})}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectButton
|
227
src/components/header/ConnectedButton.module.scss
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
border-radius: $borderRadiusXXL;
|
||||||
|
height: rem-calc(31);
|
||||||
|
@include padding(0.5, 2, 0, 3);
|
||||||
|
@include typoS;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $colorWhite;
|
||||||
|
border: 1px solid $alphaWhite60;
|
||||||
|
outline: none;
|
||||||
|
background: $alphaBlack10;
|
||||||
|
|
||||||
|
.walletIcon {
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-top: space(-1);
|
||||||
|
height: rem-calc(16);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
margin-inline-start: space(1.5);
|
||||||
|
font-weight: $fontWeightRegular;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
|
font-weight: $fontWeightRegular;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
margin-inline-start: space(2);
|
||||||
|
padding-inline-start: rem-calc(8);
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
left: 0;
|
||||||
|
border-left: 1px solid $alphaWhite60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid $colorWhite;
|
||||||
|
background-color: $alphaWhite10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circularProgress {
|
||||||
|
margin-inline-end: space(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
top: rem-calc(38);
|
||||||
|
@include padding(6, 6, 5.5);
|
||||||
|
@include layoutPopover;
|
||||||
|
width: rem-calc(420);
|
||||||
|
left: rem-calc(-233);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsHeader {
|
||||||
|
display: flex;
|
||||||
|
flex: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
@include margin(0, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsBalance {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
@include margin(-1, 4, 0, 0);
|
||||||
|
height: space(6);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@include margin(0);
|
||||||
|
@include typoH4;
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsButton {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 rem-calc(116);
|
||||||
|
width: rem-calc(116);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsBody {
|
||||||
|
flex: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.address,
|
||||||
|
.addressMobile,
|
||||||
|
.addressLabel,
|
||||||
|
.tnsAddress {
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
opacity: 1;
|
||||||
|
@include margin(0, 0, 1);
|
||||||
|
@include typoS;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addressMobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: rem-calc(16);
|
||||||
|
width: auto;
|
||||||
|
@include margin(-1, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
@include padding(1, 0, 0);
|
||||||
|
|
||||||
|
> button {
|
||||||
|
font-weight: $fontWeightRegular;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
width: rem-calc(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: auto;
|
||||||
|
align-items: center;
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
@include padding(2, 0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
background-color: $colorSecondaryHighlight;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: $borderRadiusL;
|
||||||
|
@include padding(0, 2);
|
||||||
|
@include margin(0);
|
||||||
|
@include typoNetwork;
|
||||||
|
position: absolute;
|
||||||
|
top: space(-4);
|
||||||
|
right: space(-4);
|
||||||
|
cursor: default;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) {
|
||||||
|
.details {
|
||||||
|
right: 0;
|
||||||
|
transform: none;
|
||||||
|
left: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsButton {
|
||||||
|
@include margin(4, 0, 0);
|
||||||
|
flex: 1 0 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsHeader {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
right: rem-calc(-12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.details {
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsBody {
|
||||||
|
.addressMobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.address {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/components/header/ConnectedButton.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { useAccountBalance, useTNS } from '../../hooks'
|
||||||
|
import { truncate } from '../../libs/text'
|
||||||
|
import styles from './ConnectedButton.module.scss'
|
||||||
|
import { formatValue, lookup } from '../../libs/parse'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import Button from '../Button'
|
||||||
|
import useClipboard from 'react-use-clipboard'
|
||||||
|
import { CheckSVG, CopySVG, ExternalSVG, WalletSVG } from '../Svg'
|
||||||
|
import colors from '../../styles/_assets.module.scss'
|
||||||
|
import { CircularProgress, ClickAwayListener } from '@material-ui/core'
|
||||||
|
import { useWallet } from '@terra-money/wallet-provider'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
FINDER_URL,
|
||||||
|
UST_DECIMALS,
|
||||||
|
UST_DENOM,
|
||||||
|
} from '../../constants/appConstants'
|
||||||
|
import { State } from '../../types/enums'
|
||||||
|
import useStore from '../../store'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectedButton = ({ address }: Props) => {
|
||||||
|
const { find, state } = useAccountBalance()
|
||||||
|
const chainID = useStore((s) => s.networkConfig?.chainID)
|
||||||
|
const name = useStore((s) => s.networkConfig?.name)
|
||||||
|
const { disconnect } = useWallet()
|
||||||
|
const uusdAmount = Number(find(UST_DENOM)?.amount || 0)
|
||||||
|
const [showDetails, setShowDetails] = useState(false)
|
||||||
|
|
||||||
|
const [isCopied, setCopied] = useClipboard(address, {
|
||||||
|
successDuration: 1000 * 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
const viewOnFinder = useCallback(() => {
|
||||||
|
window.open(`${FINDER_URL}/${chainID}/account/${address}`, '_blank')
|
||||||
|
}, [chainID, address])
|
||||||
|
|
||||||
|
const onClickAway = useCallback(() => {
|
||||||
|
setShowDetails(false)
|
||||||
|
}, [])
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const walletAddress = useTNS(address, true)
|
||||||
|
const tnsAddress = useTNS(address, false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{name !== 'mainnet' && (
|
||||||
|
<span className={styles.network}>{name}</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className={styles.button}
|
||||||
|
onClick={() => {
|
||||||
|
setShowDetails(!showDetails)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={styles.walletIcon}>
|
||||||
|
<WalletSVG className={styles.walletIcon} />
|
||||||
|
</span>
|
||||||
|
<span className={styles.address}>
|
||||||
|
{walletAddress ? walletAddress : truncate(address, [2, 4])}
|
||||||
|
</span>
|
||||||
|
<div className={styles.balance}>
|
||||||
|
{state === State.READY ? (
|
||||||
|
`${formatValue(
|
||||||
|
lookup(uusdAmount, UST_DENOM, UST_DECIMALS)
|
||||||
|
)}`
|
||||||
|
) : (
|
||||||
|
<CircularProgress
|
||||||
|
color='inherit'
|
||||||
|
size={'0.9rem'}
|
||||||
|
className={styles.circularProgress}
|
||||||
|
/>
|
||||||
|
)}{' '}
|
||||||
|
UST
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{showDetails && (
|
||||||
|
<ClickAwayListener onClickAway={onClickAway}>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<div className={styles.detailsHeader}>
|
||||||
|
<div className={styles.detailsBalance}>
|
||||||
|
<WalletSVG color={colors.secondaryDark} />
|
||||||
|
<p>
|
||||||
|
{formatValue(
|
||||||
|
lookup(
|
||||||
|
uusdAmount,
|
||||||
|
UST_DENOM,
|
||||||
|
UST_DECIMALS
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)}{' '}
|
||||||
|
UST
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.detailsButton}>
|
||||||
|
<Button
|
||||||
|
text={t('common.disconnect')}
|
||||||
|
color='secondary'
|
||||||
|
onClick={disconnect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.detailsBody}>
|
||||||
|
<p className={`sub2 ${styles.addressLabel}`}>
|
||||||
|
{tnsAddress
|
||||||
|
? tnsAddress
|
||||||
|
: t('common.yourAddress')}
|
||||||
|
</p>
|
||||||
|
<p className={styles.address}>{address}</p>
|
||||||
|
<p className={styles.addressMobile}>
|
||||||
|
{truncate(address, [14, 14])}
|
||||||
|
</p>
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<button
|
||||||
|
className={styles.copy}
|
||||||
|
onClick={setCopied}
|
||||||
|
>
|
||||||
|
<CopySVG color={colors.secondaryDark} />
|
||||||
|
{isCopied ? (
|
||||||
|
<>
|
||||||
|
{t('common.copied')}{' '}
|
||||||
|
<CheckSVG
|
||||||
|
color={colors.secondaryDark}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>{t('common.copy')}</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={styles.external}
|
||||||
|
onClick={viewOnFinder}
|
||||||
|
>
|
||||||
|
<ExternalSVG color={colors.secondaryDark} />{' '}
|
||||||
|
{t('common.viewOnFinder')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectedButton
|
247
src/components/header/IncentivesButton.module.scss
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
border-radius: $borderRadiusXXL;
|
||||||
|
height: rem-calc(31);
|
||||||
|
@include padding(0.5, 3, 0);
|
||||||
|
@include margin(0, 2, 0, 0);
|
||||||
|
@include typoS;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $colorWhite;
|
||||||
|
border: 1px solid $alphaWhite60;
|
||||||
|
outline: none;
|
||||||
|
background: $alphaBlack10;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-top: space(-1);
|
||||||
|
height: rem-calc(19);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
|
font-weight: $fontWeightRegular;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
padding-inline-start: rem-calc(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid $colorWhite;
|
||||||
|
background-color: $alphaWhite10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonHighlight {
|
||||||
|
@include layoutIncentiveButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
top: rem-calc(38);
|
||||||
|
width: rem-calc(390);
|
||||||
|
left: rem-calc(-155);
|
||||||
|
z-index: 100;
|
||||||
|
@include layoutPopover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: rem-calc(12);
|
||||||
|
right: rem-calc(16);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $colorSecondaryDark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsHeader {
|
||||||
|
display: flex;
|
||||||
|
flex: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
@include padding(4, 0);
|
||||||
|
@include margin(0);
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 1px solid $alphaBlack20;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsHead {
|
||||||
|
@include margin(0);
|
||||||
|
@include typoScaps;
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsBody {
|
||||||
|
flex: 0;
|
||||||
|
width: 100%;
|
||||||
|
@include padding(4, 8);
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
|
||||||
|
.successContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.successTitle {
|
||||||
|
text-align: center;
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
@include margin(0, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.succcessTxHash {
|
||||||
|
display: flex;
|
||||||
|
@include margin(0, 0, 4);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include margin(0, 2, 0, 0);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container,
|
||||||
|
.total {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
@include padding(4, 0, 0);
|
||||||
|
border-bottom: 1px solid $colorSecondaryDark;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.position {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
@include padding(0, 0, 4);
|
||||||
|
|
||||||
|
.head {
|
||||||
|
@include typoScaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
width: 100%;
|
||||||
|
@include margin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1;
|
||||||
|
min-height: rem-calc(12);
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.subhead {
|
||||||
|
color: $colorSecondaryDark;
|
||||||
|
opacity: 0.6;
|
||||||
|
@include margin(1, 0);
|
||||||
|
@include typoScaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token {
|
||||||
|
@include typoS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
min-height: rem-calc(12);
|
||||||
|
flex: 0 0 rem-calc(76);
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
@include margin(0, 0, 0, 3);
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
@include typoXXScaps;
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokenAmount {
|
||||||
|
@include typoS;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokenValue {
|
||||||
|
@include margin(1, 0);
|
||||||
|
@include typoXXS;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
.position {
|
||||||
|
.label {
|
||||||
|
.subhead {
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 rem-calc(200);
|
||||||
|
max-width: rem-calc(200);
|
||||||
|
margin: 0 auto;
|
||||||
|
@include padding(6, 0, 0);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
@include padding(2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) {
|
||||||
|
.details {
|
||||||
|
right: 0;
|
||||||
|
top: rem-calc(46);
|
||||||
|
transform: none;
|
||||||
|
left: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@include margin(2, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.details {
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes moveGradient {
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
}
|
1010
src/components/header/IncentivesButton.tsx
Normal file
118
src/components/radialgauge/RadialGauge.module.scss
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: rem-calc(550);
|
||||||
|
@include margin(-5, -10, 0);
|
||||||
|
|
||||||
|
.glow {
|
||||||
|
width: rem-calc(550);
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.6;
|
||||||
|
@include glowXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialContainer {
|
||||||
|
width: rem-calc(550);
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dotContainer {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: space(17.5);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(4);
|
||||||
|
height: rem-calc(4);
|
||||||
|
background: $colorInfoWarning;
|
||||||
|
margin-top: space(13);
|
||||||
|
margin-inline-start: space(59);
|
||||||
|
border-radius: $borderRadiusXS;
|
||||||
|
transition: left 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dotGlow {
|
||||||
|
position: absolute;
|
||||||
|
width: rem-calc(7);
|
||||||
|
height: rem-calc(7);
|
||||||
|
background: $colorInfoWarning;
|
||||||
|
margin-top: space(13);
|
||||||
|
margin-inline-start: space(59);
|
||||||
|
border-radius: $borderRadiusXS;
|
||||||
|
@include glowM;
|
||||||
|
transition: left 2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.borrowLimitTextContainer {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.percent {
|
||||||
|
margin-bottom: space(-1.25);
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warn {
|
||||||
|
color: $colorInfoWarning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.maxText {
|
||||||
|
margin-top: space(19);
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.4;
|
||||||
|
margin-bottom: space(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpMediumHigh) {
|
||||||
|
.container {
|
||||||
|
@include margin(10, 0);
|
||||||
|
width: rem-calc(360);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.glow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maxText {
|
||||||
|
width: 100%;
|
||||||
|
@include margin(19, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $bpSmallHigh) {
|
||||||
|
.container {
|
||||||
|
width: rem-calc(282);
|
||||||
|
|
||||||
|
.dialContainer {
|
||||||
|
@include margin(0);
|
||||||
|
|
||||||
|
+ div {
|
||||||
|
margin-top: space(6) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/components/radialgauge/RadialGauge.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import 'create-conical-gradient'
|
||||||
|
import ChartComponent from 'react-chartjs-2'
|
||||||
|
import styles from './RadialGauge.module.scss'
|
||||||
|
import Chart from 'chart.js'
|
||||||
|
|
||||||
|
import produceValueArc, { GradientValue } from './elements/ValueArc'
|
||||||
|
import {
|
||||||
|
setUpDefaults,
|
||||||
|
RadialGaugeController,
|
||||||
|
} from './controllers/RadialGaugeController'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import produceTrackArc from './elements/TrackArc'
|
||||||
|
import { GAUGE_SCALE } from '../../constants/appConstants'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentLtv: number
|
||||||
|
maxLtv: number
|
||||||
|
maxValue: number
|
||||||
|
generateText: (limit: number) => ReactNode
|
||||||
|
showMaxValue: boolean
|
||||||
|
colorStops: Array<GradientValue>
|
||||||
|
showDot: boolean
|
||||||
|
maxTextPrefix?: string
|
||||||
|
maxTextTitle?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const RadialGauge = ({
|
||||||
|
currentLtv,
|
||||||
|
maxLtv,
|
||||||
|
maxValue,
|
||||||
|
generateText,
|
||||||
|
showMaxValue,
|
||||||
|
colorStops,
|
||||||
|
showDot = true,
|
||||||
|
maxTextPrefix = '$',
|
||||||
|
maxTextTitle = 'MAX',
|
||||||
|
}: Props) => {
|
||||||
|
Chart.defaults.radialGauge = Chart.defaults.doughnut
|
||||||
|
|
||||||
|
setUpDefaults()
|
||||||
|
// @ts-ignore : Chart.elements is not exposed on the d.ts file for chart.js
|
||||||
|
Chart.elements.RoundedOverArc = produceValueArc(colorStops)
|
||||||
|
//@ts-ignore
|
||||||
|
Chart.elements.RoundedArc = produceTrackArc(currentLtv > 0)
|
||||||
|
var controller = RadialGaugeController()
|
||||||
|
|
||||||
|
// todo enable tooltips when we have the finalised specification for them
|
||||||
|
Chart.defaults.global.tooltips.enabled = false
|
||||||
|
Chart.controllers.radialGauge = controller
|
||||||
|
|
||||||
|
const chartData = {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
// This is the value we want to show
|
||||||
|
data: [(currentLtv > 100 ? 100 : currentLtv) * GAUGE_SCALE],
|
||||||
|
borderWidth: 0,
|
||||||
|
maxLtv: maxLtv,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.glow}>
|
||||||
|
<ChartComponent
|
||||||
|
data={chartData}
|
||||||
|
// We can do this because of https://github.com/jerairrest/react-chartjs-2/blob/master/src/index.js#L25
|
||||||
|
// where it is checking the Chart.js instance for a controller that matches the type.
|
||||||
|
// Typescript will complain however because it is not one of the preconfigured charts in react-chart-js.
|
||||||
|
//@ts-ignore
|
||||||
|
type='radialGauge'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.dialContainer}>
|
||||||
|
<ChartComponent
|
||||||
|
data={chartData}
|
||||||
|
//@ts-ignore - see comment above
|
||||||
|
type='radialGauge'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{generateText(currentLtv ?? 0)}
|
||||||
|
|
||||||
|
{showMaxValue ? (
|
||||||
|
<div className={styles.maxText}>
|
||||||
|
<span className={`caption ${styles.caption}`}>
|
||||||
|
{maxTextTitle}
|
||||||
|
</span>
|
||||||
|
<span className={'sub2'} style={{ opacity: '1' }}>
|
||||||
|
{maxTextPrefix}
|
||||||
|
{formatValue(maxValue, 2, 2, true, false, true, true)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RadialGauge
|
279
src/components/radialgauge/controllers/RadialGaugeController.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
import Chart, { helpers } from 'chart.js'
|
||||||
|
import { GAUGE_SCALE } from '../../../constants/appConstants'
|
||||||
|
|
||||||
|
const setUpDefaults = () => {
|
||||||
|
Chart.defaults._set('radialGauge', {
|
||||||
|
animation: {
|
||||||
|
// Boolean - Whether we animate the rotation of the radialGauge
|
||||||
|
animateRotate: true,
|
||||||
|
// Boolean - Whether we animate scaling the radialGauge from the centre
|
||||||
|
animateScale: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Todo remove these out of the table
|
||||||
|
// The percentage of the chart that is the center area
|
||||||
|
centerPercentage: 95.5,
|
||||||
|
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
circumference: Math.PI + 2,
|
||||||
|
rotation: -Math.PI - 1,
|
||||||
|
|
||||||
|
// the color of the radial gauge's track
|
||||||
|
trackColor: '#1c1c1c',
|
||||||
|
// whether arc for the gauge should have rounded corners
|
||||||
|
roundedCorners: true,
|
||||||
|
|
||||||
|
hover: {
|
||||||
|
mode: 'disable',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
tooltips: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// the domain of the metric
|
||||||
|
domain: [0, 100],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomRadialGaugeController = () => {
|
||||||
|
return Chart.controllers.doughnut.extend({
|
||||||
|
// @ts-ignore
|
||||||
|
dataElementType: Chart.elements.RoundedOverArc,
|
||||||
|
linkScales: helpers.noop,
|
||||||
|
|
||||||
|
draw: function (ease: any) {
|
||||||
|
this.drawTrack()
|
||||||
|
Chart.controllers.doughnut.prototype.draw.call(this, ease)
|
||||||
|
this.drawDot()
|
||||||
|
},
|
||||||
|
|
||||||
|
drawTrack() {
|
||||||
|
//@ts-ignore
|
||||||
|
new Chart.elements.RoundedArc({
|
||||||
|
_view: {
|
||||||
|
backgroundColor: this.chart.options.trackColor,
|
||||||
|
borderColor: this.chart.options.trackColor,
|
||||||
|
borderWidth: 0,
|
||||||
|
startAngle: this.chart.options.rotation,
|
||||||
|
endAngle: Math.PI * 0.33,
|
||||||
|
x: this.centerX,
|
||||||
|
y: this.centerY,
|
||||||
|
innerRadius: this.innerRadius,
|
||||||
|
outerRadius: this.outerRadius,
|
||||||
|
roundedCorners: true,
|
||||||
|
},
|
||||||
|
_chart: this.chart,
|
||||||
|
}).draw()
|
||||||
|
},
|
||||||
|
|
||||||
|
drawDot() {
|
||||||
|
const maxLtv = this.getDataset()['maxLtv']
|
||||||
|
if (!maxLtv || maxLtv <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Calculate the angle of our borrowing capacity
|
||||||
|
const [domainStart, domainEnd] = this.getDomain() || [0, 100]
|
||||||
|
const value = maxLtv * GAUGE_SCALE
|
||||||
|
const domainSize = domainEnd - domainStart
|
||||||
|
const limitAngle =
|
||||||
|
domainSize > 0
|
||||||
|
? Math.PI *
|
||||||
|
2.0 *
|
||||||
|
(Math.abs(value - domainStart) / domainSize)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
// Get the center of the arc
|
||||||
|
const arcCentre = (this.outerRadius + this.innerRadius) / 2
|
||||||
|
|
||||||
|
// Calculate offset from the centre of our circle
|
||||||
|
const x =
|
||||||
|
arcCentre * Math.cos(limitAngle + this.chart.options.rotation)
|
||||||
|
const y =
|
||||||
|
arcCentre * Math.sin(limitAngle + this.chart.options.rotation)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
new Chart.elements.Point({
|
||||||
|
_view: {
|
||||||
|
radius: 2,
|
||||||
|
pointStyle: 'circle',
|
||||||
|
backgroundColor: '#c83333',
|
||||||
|
borderColor: '#c83333',
|
||||||
|
borderWidth: 1,
|
||||||
|
// Hover
|
||||||
|
hitRadius: 1,
|
||||||
|
hoverRadius: 4,
|
||||||
|
hoverBorderWidth: 1,
|
||||||
|
x: this.centerX + x,
|
||||||
|
y: this.centerY + y,
|
||||||
|
},
|
||||||
|
_chart: this.chart,
|
||||||
|
}).draw()
|
||||||
|
},
|
||||||
|
|
||||||
|
update(reset: any) {
|
||||||
|
const chart = this.chart
|
||||||
|
const chartArea = chart.chartArea
|
||||||
|
const opts = chart.options
|
||||||
|
const arcOpts = opts.elements.arc
|
||||||
|
const availableWidth =
|
||||||
|
chartArea.right - chartArea.left - arcOpts.borderWidth
|
||||||
|
const availableHeight =
|
||||||
|
chartArea.bottom - chartArea.top - arcOpts.borderWidth
|
||||||
|
const availableSize = Math.min(availableWidth, availableHeight)
|
||||||
|
|
||||||
|
const meta = this.getMeta()
|
||||||
|
const centerPercentage = opts.centerPercentage
|
||||||
|
|
||||||
|
this.borderWidth = this.getMaxBorderWidth(meta.data)
|
||||||
|
this.outerRadius = Math.max(
|
||||||
|
(availableSize - this.borderWidth) / 2,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
this.innerRadius = Math.max(
|
||||||
|
centerPercentage
|
||||||
|
? (this.outerRadius / 100) * centerPercentage
|
||||||
|
: 0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
meta.total = this.getMetricValue()
|
||||||
|
this.centerX = (chartArea.left + chartArea.right) / 2
|
||||||
|
this.centerY = (chartArea.top + chartArea.bottom) / 2
|
||||||
|
|
||||||
|
helpers.each(meta.data, (arc: any, index: any) => {
|
||||||
|
this.updateElement(arc, index, reset)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateElement(arc: any, index: any, reset: any) {
|
||||||
|
const chart = this.chart
|
||||||
|
const chartArea = chart.chartArea
|
||||||
|
const opts = chart.options
|
||||||
|
const animationOpts = opts.animation
|
||||||
|
const centerX = (chartArea.left + chartArea.right) / 2
|
||||||
|
const centerY = (chartArea.top + chartArea.bottom) / 2
|
||||||
|
const startAngle = opts.rotation // non reset case handled later
|
||||||
|
const dataset = this.getDataset()
|
||||||
|
const arcAngle =
|
||||||
|
reset && animationOpts.animateRotate
|
||||||
|
? 0
|
||||||
|
: this.calculateArcAngle(dataset.data[index])
|
||||||
|
const value =
|
||||||
|
reset && animationOpts.animateScale ? 0 : this.getMetricValue()
|
||||||
|
const endAngle = startAngle + arcAngle // arcAngle
|
||||||
|
const innerRadius = this.innerRadius
|
||||||
|
const outerRadius = this.outerRadius
|
||||||
|
const valueAtIndexOrDefault = helpers.valueAtIndexOrDefault
|
||||||
|
|
||||||
|
helpers.extend(arc, {
|
||||||
|
// Utility
|
||||||
|
_datasetIndex: this.index,
|
||||||
|
_index: index,
|
||||||
|
|
||||||
|
// Desired view properties
|
||||||
|
_model: {
|
||||||
|
x: centerX,
|
||||||
|
y: centerY,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
outerRadius,
|
||||||
|
innerRadius,
|
||||||
|
label: valueAtIndexOrDefault(
|
||||||
|
dataset.label,
|
||||||
|
index,
|
||||||
|
chart.data.labels[index]
|
||||||
|
),
|
||||||
|
roundedCorners: opts.roundedCorners,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const model = arc._model
|
||||||
|
|
||||||
|
// Resets the visual styles
|
||||||
|
const custom = arc.custom || {}
|
||||||
|
const valueOrDefault = helpers.valueAtIndexOrDefault
|
||||||
|
const elementOpts = this.chart.options.elements.arc
|
||||||
|
model.backgroundColor = custom.backgroundColor
|
||||||
|
? custom.backgroundColor
|
||||||
|
: valueOrDefault(
|
||||||
|
dataset.backgroundColor,
|
||||||
|
index,
|
||||||
|
elementOpts.backgroundColor
|
||||||
|
)
|
||||||
|
model.borderColor = custom.borderColor
|
||||||
|
? custom.borderColor
|
||||||
|
: valueOrDefault(
|
||||||
|
dataset.borderColor,
|
||||||
|
index,
|
||||||
|
elementOpts.borderColor
|
||||||
|
)
|
||||||
|
model.borderWidth = custom.borderWidth
|
||||||
|
? custom.borderWidth
|
||||||
|
: valueOrDefault(
|
||||||
|
dataset.borderWidth,
|
||||||
|
index,
|
||||||
|
elementOpts.borderWidth
|
||||||
|
)
|
||||||
|
|
||||||
|
arc.pivot()
|
||||||
|
},
|
||||||
|
|
||||||
|
getMetricValue() {
|
||||||
|
let value = this.getDataset().data[0]
|
||||||
|
if (value == null) {
|
||||||
|
value = this.chart.options.domain[0] || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
|
||||||
|
getDomain() {
|
||||||
|
return this.chart.options.domain
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateArcAngle() {
|
||||||
|
const [domainStart, domainEnd] = this.getDomain() || [0, 100]
|
||||||
|
const value = this.getMetricValue()
|
||||||
|
const domainSize = domainEnd - domainStart
|
||||||
|
return domainSize > 0
|
||||||
|
? Math.PI * 2.0 * (Math.abs(value - domainStart) / domainSize)
|
||||||
|
: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// gets the max border or hover width to properly scale pie charts
|
||||||
|
getMaxBorderWidth(arcs: any) {
|
||||||
|
let max = 0
|
||||||
|
const index = this.index
|
||||||
|
const length = arcs.length
|
||||||
|
let borderWidth
|
||||||
|
let hoverWidth
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0
|
||||||
|
hoverWidth = arcs[i]._chart
|
||||||
|
? arcs[i]._chart.config.data.datasets[index]
|
||||||
|
.hoverBorderWidth
|
||||||
|
: 0
|
||||||
|
|
||||||
|
max = borderWidth > max ? borderWidth : max
|
||||||
|
max = hoverWidth > max ? hoverWidth : max
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getCustomRadialGaugeController as RadialGaugeController,
|
||||||
|
setUpDefaults,
|
||||||
|
}
|
49
src/components/radialgauge/elements/TrackArc.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import Chart from 'chart.js'
|
||||||
|
|
||||||
|
const produceTrackArc = (hasValue: boolean) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return Chart.elements.Arc.extend({
|
||||||
|
draw() {
|
||||||
|
const noDataTrackColor = '#3a3c49'
|
||||||
|
const ctx = this._chart.ctx
|
||||||
|
const vm = this._view
|
||||||
|
const { startAngle, endAngle } = vm
|
||||||
|
const cornerRadius = (vm.outerRadius - vm.innerRadius) / 2
|
||||||
|
const cornerX = (vm.outerRadius + vm.innerRadius) / 2
|
||||||
|
|
||||||
|
// translate + rotate to make drawing the corners simpler
|
||||||
|
ctx.translate(vm.x, vm.y)
|
||||||
|
ctx.rotate(startAngle)
|
||||||
|
const angle = endAngle - startAngle
|
||||||
|
ctx.beginPath()
|
||||||
|
if (vm.roundedCorners) {
|
||||||
|
ctx.arc(cornerX, 0, cornerRadius, Math.PI, 0)
|
||||||
|
}
|
||||||
|
ctx.arc(0, 0, vm.outerRadius, 0, angle)
|
||||||
|
|
||||||
|
const x = cornerX * Math.cos(angle)
|
||||||
|
const y = cornerX * Math.sin(angle)
|
||||||
|
|
||||||
|
if (vm.roundedCorners) {
|
||||||
|
ctx.arc(x, y, cornerRadius, angle, angle + Math.PI)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.arc(0, 0, vm.innerRadius, angle, 0, true)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.rotate(-startAngle)
|
||||||
|
ctx.translate(-vm.x, -vm.y)
|
||||||
|
ctx.strokeStyle = hasValue ? vm.borderColor : noDataTrackColor
|
||||||
|
ctx.lineWidth = vm.borderWidth
|
||||||
|
ctx.fillStyle = hasValue ? vm.backgroundColor : noDataTrackColor
|
||||||
|
|
||||||
|
ctx.fill()
|
||||||
|
ctx.lineJoin = 'bevel'
|
||||||
|
|
||||||
|
if (vm.borderWidth) {
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default produceTrackArc
|
75
src/components/radialgauge/elements/ValueArc.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
export interface GradientValue {
|
||||||
|
color: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const produceValueArc = (colorStops: Array<GradientValue>) => {
|
||||||
|
//@ts-ignore
|
||||||
|
return Chart.elements.Arc.extend({
|
||||||
|
draw() {
|
||||||
|
const ctx = this._chart.ctx
|
||||||
|
const vm = this._view
|
||||||
|
|
||||||
|
const { startAngle, endAngle } = vm
|
||||||
|
if (vm.value === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cornerRadius = (vm.outerRadius - vm.innerRadius) / 2
|
||||||
|
const cornerX = (vm.outerRadius + vm.innerRadius) / 2
|
||||||
|
|
||||||
|
// translate + rotate to make drawing the corners simpler
|
||||||
|
ctx.translate(vm.x, vm.y)
|
||||||
|
ctx.rotate(startAngle)
|
||||||
|
const angle = endAngle - startAngle
|
||||||
|
ctx.beginPath()
|
||||||
|
if (vm.roundedCorners) {
|
||||||
|
ctx.arc(cornerX, 0, cornerRadius, Math.PI, 0)
|
||||||
|
}
|
||||||
|
ctx.arc(0, 0, vm.outerRadius, 0, angle)
|
||||||
|
|
||||||
|
const x = cornerX * Math.cos(angle)
|
||||||
|
const y = cornerX * Math.sin(angle)
|
||||||
|
|
||||||
|
if (vm.roundedCorners) {
|
||||||
|
ctx.arc(x, y, cornerRadius, angle, angle + Math.PI)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.arc(0, 0, vm.innerRadius, angle, 0, true)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.rotate(-startAngle)
|
||||||
|
ctx.translate(-vm.x, -vm.y)
|
||||||
|
|
||||||
|
ctx.strokeStyle = vm.borderColor
|
||||||
|
ctx.lineWidth = vm.borderWidth
|
||||||
|
ctx.strokeStyle = vm.borderColor
|
||||||
|
// create gradient and fill it
|
||||||
|
const rotationOffSet = Math.PI * 0.5
|
||||||
|
const gradient = ctx.createConicalGradient(
|
||||||
|
vm.x,
|
||||||
|
vm.y - 10,
|
||||||
|
0 + rotationOffSet,
|
||||||
|
Math.PI * 2 + rotationOffSet
|
||||||
|
)
|
||||||
|
|
||||||
|
for (var index = 0; index < colorStops.length; index += 1) {
|
||||||
|
const colorStop = colorStops[index]
|
||||||
|
gradient.addColorStop(colorStop.value, colorStop.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = gradient.pattern
|
||||||
|
|
||||||
|
ctx.strokeStyle = gradient
|
||||||
|
ctx.fillStyle = gradient
|
||||||
|
|
||||||
|
ctx.fill()
|
||||||
|
ctx.lineJoin = 'bevel'
|
||||||
|
|
||||||
|
if (vm.borderWidth) {
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default produceValueArc
|
50
src/components/tooltip/Apr.module.scss
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include layoutTooltip;
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: rem-calc(184);
|
||||||
|
.item {
|
||||||
|
@include margin(1, 0, 0);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
.dot {
|
||||||
|
border: 1px solid $colorWhite;
|
||||||
|
height: space(2);
|
||||||
|
border-radius: $borderRadiusXXS;
|
||||||
|
width: space(2);
|
||||||
|
@include margin(1.5, 2, 0, 0);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
width: rem-calc(168);
|
||||||
|
flex-direction: column;
|
||||||
|
.titleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
:first-child {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleText {
|
||||||
|
justify-content: start !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
:first-child {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
.subTitleText {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
src/components/tooltip/Apr.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import styles from './Apr.module.scss'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: AssetInfo
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HoverItem {
|
||||||
|
color?: string
|
||||||
|
symbol?: string
|
||||||
|
apr?: number
|
||||||
|
subtitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Apr = ({ data }: Props) => {
|
||||||
|
const [aprData, setAprData] = useState<HoverItem[]>([])
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const produceData = (data: AssetInfo[]): HoverItem[] => {
|
||||||
|
const items: HoverItem[] = []
|
||||||
|
|
||||||
|
data.forEach((asset: AssetInfo, key: number) => {
|
||||||
|
items.push({
|
||||||
|
color: asset.color,
|
||||||
|
symbol: asset.symbol,
|
||||||
|
apr: asset.apy,
|
||||||
|
subtitle:
|
||||||
|
key === 0 ? t('fields.baseApy') : t('common.incentiveApr'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const baseData = data.incentive ? [data, data.incentive] : [data]
|
||||||
|
setAprData(produceData(baseData))
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[data]
|
||||||
|
)
|
||||||
|
|
||||||
|
const produceItem = (item: HoverItem, key: number) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.item} key={key}>
|
||||||
|
<div
|
||||||
|
className={styles.dot}
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
/>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.titleContainer}>
|
||||||
|
<span className={`body ${styles.titleText}`}>
|
||||||
|
{item.symbol}
|
||||||
|
</span>
|
||||||
|
<span className={`body ${styles.titleText}`}>
|
||||||
|
{formatValue(
|
||||||
|
item?.apr || 0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.subTitleContainer}>
|
||||||
|
<span className={`caption ${styles.subTitleText}`}>
|
||||||
|
{item.subtitle}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<p className='sub2'>{t('fields.apyBreakdown')}</p>
|
||||||
|
{aprData.map((item: HoverItem, index: number) =>
|
||||||
|
produceItem(item, index)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Apr
|
38
src/components/tooltip/Apy.module.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include layoutTooltip;
|
||||||
|
min-width: rem-calc(184);
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-top: space(2);
|
||||||
|
display: flex;
|
||||||
|
width: rem-calc(175);
|
||||||
|
flex-direction: row;
|
||||||
|
@include typoXS;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
@include devider20;
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 rem-calc(115);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 0 0 rem-calc(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.leverage {
|
||||||
|
@include margin(2, 0, 0);
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
159
src/components/tooltip/Apy.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import styles from './Apy.module.scss'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useRedBank } from '../../hooks'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
trueApy: number
|
||||||
|
apyData: StrategyRate
|
||||||
|
aprData: StrategyRate
|
||||||
|
token: string
|
||||||
|
leverage?: string
|
||||||
|
borrowDenom?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Apy = ({
|
||||||
|
trueApy,
|
||||||
|
apyData,
|
||||||
|
aprData,
|
||||||
|
token,
|
||||||
|
leverage,
|
||||||
|
borrowDenom = 'uusd',
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { findMarketInfo } = useRedBank()
|
||||||
|
const [checkedApyData, setApyData] = useState<StrategyRate>(apyData)
|
||||||
|
const [checkedAprData, setAprData] = useState<StrategyRate>(aprData)
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (apyData?.total !== checkedApyData?.total) {
|
||||||
|
setApyData(apyData)
|
||||||
|
}
|
||||||
|
if (aprData?.total !== checkedAprData?.total) {
|
||||||
|
setAprData(aprData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[apyData, aprData]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<p className='sub2'>{t('fields.apyBreakdown')}</p>
|
||||||
|
{checkedAprData?.trading > 0 && (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.tradingFeesApr')}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(
|
||||||
|
checkedAprData.trading,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{checkedAprData?.astro > 0 && (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.protocolApr', { protocol: 'ASTRO' })}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(
|
||||||
|
checkedAprData.astro,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{checkedAprData?.protocol > 0 && (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.protocolApr', { protocol: token })}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(
|
||||||
|
checkedAprData.protocol,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{checkedApyData?.total > 0 && (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.totalRewardsApy')}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(
|
||||||
|
checkedApyData.total,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{leverage && Number(leverage) > 0 && trueApy > 0 && (
|
||||||
|
<>
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.borrowRateApr')}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(
|
||||||
|
Number(
|
||||||
|
findMarketInfo(borrowDenom)?.borrow_rate
|
||||||
|
) * 100,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
'-',
|
||||||
|
'%'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.item} ${styles.leverage}`}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{`${t('fields.leverage')} ${formatValue(
|
||||||
|
leverage || 0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'x',
|
||||||
|
true
|
||||||
|
)}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{t('fields.leveragedApy')}
|
||||||
|
</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(trueApy, 2, 2, true, false, '%')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Apy
|
38
src/components/tooltip/PnL.module.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include layoutTooltip;
|
||||||
|
min-width: rem-calc(184);
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-top: space(2);
|
||||||
|
display: flex;
|
||||||
|
width: rem-calc(175);
|
||||||
|
flex-direction: row;
|
||||||
|
@include typoXS;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
@include devider20;
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 rem-calc(115);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 0 0 rem-calc(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.leverage {
|
||||||
|
@include margin(2, 0, 0);
|
||||||
|
font-weight: $fontWeightSemibold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/components/tooltip/PnL.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import styles from './PnL.module.scss'
|
||||||
|
import { formatValue } from '../../libs/parse'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initial: string
|
||||||
|
current: string
|
||||||
|
pnlValue: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const PnL = ({ initial, current, pnlValue }: Props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<p className='sub2'>{t('fields.pnlBreakdown')}</p>
|
||||||
|
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>{t('fields.initialValue')}</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(initial, 2, 2, true, '$')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>{t('fields.currentValue')}</span>
|
||||||
|
<span className={styles.value}>
|
||||||
|
{formatValue(current, 2, 2, true, '$')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.label}>
|
||||||
|
{pnlValue >= 0 ? t('fields.profit') : t('fields.loss')}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`${styles.value} ${
|
||||||
|
pnlValue > 0
|
||||||
|
? 'colorInfoProfit'
|
||||||
|
: pnlValue < 0
|
||||||
|
? 'colorInfoLoss'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{formatValue(pnlValue, 2, 2, true, '$')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PnL
|
8
src/components/tooltip/Tooltip.module.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@use '../../styles/master' as *;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: $tooltipIconColor;
|
||||||
|
fill: $tooltipIconColor !important;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
24
src/components/tooltip/Tooltip.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
import Tippy from '@tippyjs/react'
|
||||||
|
|
||||||
|
import HelpOutlineIcon from '@material-ui/icons/HelpOutline'
|
||||||
|
|
||||||
|
import styles from './Tooltip.module.scss'
|
||||||
|
|
||||||
|
interface TooltipProps {
|
||||||
|
content: string | ReactNode
|
||||||
|
iconWidth: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tooltip = ({ content, iconWidth }: TooltipProps) => (
|
||||||
|
<Tippy
|
||||||
|
className='tippyContainer'
|
||||||
|
content={<span className='body2'>{content}</span>}
|
||||||
|
interactive={true}
|
||||||
|
appendTo={() => document.body}
|
||||||
|
>
|
||||||
|
<HelpOutlineIcon style={{ width: iconWidth }} className={styles.info} />
|
||||||
|
</Tippy>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Tooltip
|
86
src/configs/assets/mainnet.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import colors from '../../styles/_assets.module.scss'
|
||||||
|
|
||||||
|
// Native asset images
|
||||||
|
import luna from '../../images/LUNA.svg'
|
||||||
|
import ust from '../../images/UST.svg'
|
||||||
|
|
||||||
|
// CW20 asset images
|
||||||
|
import mars from '../../images/MARS-COLORED.svg'
|
||||||
|
import astro from '../../images/ASTRO.png'
|
||||||
|
import anc from '../../images/ANC.svg'
|
||||||
|
import mir from '../../images/MIR.svg'
|
||||||
|
|
||||||
|
const Assets: Assets = {
|
||||||
|
luna: {
|
||||||
|
symbol: 'LUNA',
|
||||||
|
name: 'Terra',
|
||||||
|
logo: luna,
|
||||||
|
denom: 'uluna',
|
||||||
|
maToken: 'maluna',
|
||||||
|
color: colors.luna,
|
||||||
|
native: true,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
ust: {
|
||||||
|
symbol: 'UST',
|
||||||
|
name: 'Terra USD',
|
||||||
|
logo: ust,
|
||||||
|
denom: 'uusd',
|
||||||
|
maToken: 'mausd',
|
||||||
|
color: colors.ust,
|
||||||
|
native: true,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
mars: {
|
||||||
|
symbol: 'MARS',
|
||||||
|
name: 'Mars',
|
||||||
|
logo: mars,
|
||||||
|
denom: 'MARS',
|
||||||
|
contract_addr: 'terra12hgwnpupflfpuual532wgrxu2gjp0tcagzgx4n',
|
||||||
|
maToken: 'mamars',
|
||||||
|
color: colors.mars,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: false,
|
||||||
|
},
|
||||||
|
astro: {
|
||||||
|
symbol: 'ASTRO',
|
||||||
|
name: 'Astroport',
|
||||||
|
logo: astro,
|
||||||
|
denom: 'ASTRO',
|
||||||
|
contract_addr: 'terra1xj49zyqrwpv5k928jwfpfy2ha668nwdgkwlrg3',
|
||||||
|
maToken: 'maastro',
|
||||||
|
color: colors.astro,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: false,
|
||||||
|
},
|
||||||
|
anc: {
|
||||||
|
symbol: 'ANC',
|
||||||
|
name: 'Anchor',
|
||||||
|
logo: anc,
|
||||||
|
denom: 'ANC',
|
||||||
|
contract_addr: 'terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76',
|
||||||
|
maToken: 'maanc',
|
||||||
|
color: colors.anc,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
mir: {
|
||||||
|
symbol: 'MIR',
|
||||||
|
name: 'Mirror',
|
||||||
|
logo: mir,
|
||||||
|
denom: 'MIR',
|
||||||
|
contract_addr: 'terra15gwkyepfc6xgca5t5zefzwy42uts8l2m4g40k6',
|
||||||
|
maToken: 'mamir',
|
||||||
|
color: colors.mir,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Assets
|
125
src/configs/assets/testnet.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import colors from '../../styles/_assets.module.scss'
|
||||||
|
|
||||||
|
// Native asset images
|
||||||
|
import luna from '../../images/LUNA.svg'
|
||||||
|
import ust from '../../images/UST.svg'
|
||||||
|
|
||||||
|
// CW20 asset images
|
||||||
|
import mars from '../../images/MARS-COLORED.svg'
|
||||||
|
import astro from '../../images/ASTRO.png'
|
||||||
|
import anc from '../../images/ANC.svg'
|
||||||
|
import mir from '../../images/MIR.svg'
|
||||||
|
import stLuna from '../../images/STLUNA.svg'
|
||||||
|
// import mine from '../../images/MINE.svg'
|
||||||
|
// import ornb from '../../images/ORNb.svg'
|
||||||
|
|
||||||
|
const Assets: Assets = {
|
||||||
|
luna: {
|
||||||
|
symbol: 'LUNA',
|
||||||
|
name: 'Terra',
|
||||||
|
logo: luna,
|
||||||
|
denom: 'uluna',
|
||||||
|
maToken: 'maluna',
|
||||||
|
color: colors.luna,
|
||||||
|
native: true,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
ust: {
|
||||||
|
symbol: 'UST',
|
||||||
|
name: 'Terra USD',
|
||||||
|
logo: ust,
|
||||||
|
denom: 'uusd',
|
||||||
|
maToken: 'mausd',
|
||||||
|
color: colors.ust,
|
||||||
|
native: true,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
stLuna: {
|
||||||
|
symbol: 'stLUNA',
|
||||||
|
name: 'Lido Staked LUNA',
|
||||||
|
logo: stLuna,
|
||||||
|
denom: 'stLuna',
|
||||||
|
contract_addr: 'terra1e42d7l5z5u53n7g990ry24tltdphs9vugap8cd',
|
||||||
|
maToken: 'mastluna',
|
||||||
|
color: colors.stLuna,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
mars: {
|
||||||
|
symbol: 'MARS',
|
||||||
|
name: 'Mars',
|
||||||
|
logo: mars,
|
||||||
|
denom: 'MARS',
|
||||||
|
contract_addr: 'terra1qs7h830ud0a4hj72yr8f7jmlppyx7z524f7gw6',
|
||||||
|
maToken: 'mamars',
|
||||||
|
color: colors.mars,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: false,
|
||||||
|
},
|
||||||
|
astro: {
|
||||||
|
symbol: 'ASTRO',
|
||||||
|
name: 'Astroport',
|
||||||
|
logo: astro,
|
||||||
|
denom: 'ASTRO',
|
||||||
|
contract_addr: 'terra1cc2up8erdqn2l7nz37qjgvnqy56sr38aj9vqry',
|
||||||
|
maToken: 'maastro',
|
||||||
|
color: colors.astro,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: false,
|
||||||
|
},
|
||||||
|
anc: {
|
||||||
|
symbol: 'ANC',
|
||||||
|
name: 'Anchor',
|
||||||
|
logo: anc,
|
||||||
|
denom: 'TTN',
|
||||||
|
contract_addr: 'terra1747mad58h0w4y589y3sk84r5efqdev9q4r02pc',
|
||||||
|
maToken: 'mattn',
|
||||||
|
color: colors.anc,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
fanc: {
|
||||||
|
symbol: 'FANC',
|
||||||
|
name: 'Anchor',
|
||||||
|
logo: anc,
|
||||||
|
denom: 'FTTN',
|
||||||
|
contract_addr: 'terra12n6pf6rj3z86s90594d57nmmh65q4pyh502c8z',
|
||||||
|
maToken: 'mafttn',
|
||||||
|
color: colors.anc,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
mir: {
|
||||||
|
symbol: 'MIR',
|
||||||
|
name: 'Mirror',
|
||||||
|
logo: mir,
|
||||||
|
denom: 'MIR',
|
||||||
|
contract_addr: 'terra10llyp6v3j3her8u3ce66ragytu45kcmd9asj3u',
|
||||||
|
maToken: 'mamir',
|
||||||
|
color: colors.mir,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
fmir: {
|
||||||
|
symbol: 'FMIR',
|
||||||
|
name: 'Mirror',
|
||||||
|
logo: mir,
|
||||||
|
denom: 'FMIR',
|
||||||
|
contract_addr: 'terra1swlg8vmfajfw6j0ay57wpa533dywwqz9zqd7cj',
|
||||||
|
maToken: 'mafmir',
|
||||||
|
color: colors.mir,
|
||||||
|
native: false,
|
||||||
|
decimals: 6,
|
||||||
|
hasOraclePrice: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Assets
|
5
src/configs/basecamp/mainnet.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"basecampAddress": "terra1685de0sx5px80d47ec2xjln224phshysqxxeje"
|
||||||
|
}
|
||||||
|
}
|
5
src/configs/basecamp/testnet.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"basecampAddress": "terra1jtdz9fhrrwd8yak6e3z7utmkypvx0qf0n393c6"
|
||||||
|
}
|
||||||
|
}
|
127
src/configs/fields/mainnet.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { StrategyProviders } from '../../constants/strategyProviders'
|
||||||
|
import Assets from '../assets/mainnet'
|
||||||
|
|
||||||
|
const FieldsStrategies: FieldsStrategy[] = [
|
||||||
|
{
|
||||||
|
key: 'lunaBullStrategy',
|
||||||
|
name: 'LUNA-UST LP',
|
||||||
|
description: 'LUNA-UST Bull Leveraged Yield Farm (Max 2x)',
|
||||||
|
minter: 'terra1m6ywlgn6wrjuagcmmezzz2a029gtldhey5k552',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.luna.color,
|
||||||
|
astroportGenerator: 'terra1zgrx9jjqrfye8swykfgmd6hpde60j0nszzupp9',
|
||||||
|
contract_addr: 'terra1kztywx50wv38r58unxj9p6k3pgr2ux6w5x68md',
|
||||||
|
lpToken: 'terra1m24f7k4g66gnh9f7uncp32p722v0kyt3q4l3u5',
|
||||||
|
assets: [Assets.luna, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
lunaBullStrategy: pool(address:"terra1m6ywlgn6wrjuagcmmezzz2a029gtldhey5k552") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ancBullStrategy',
|
||||||
|
name: 'ANC-UST LP',
|
||||||
|
description: 'ANC-UST Bull Leveraged Yield Farm (Max 2x)',
|
||||||
|
minter: 'terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.anc.color,
|
||||||
|
contract_addr: 'terra1vapq79y9cqghqny7zt72g4qukndz282uvqwtz6',
|
||||||
|
astroportGenerator: 'terra1zgrx9jjqrfye8swykfgmd6hpde60j0nszzupp9',
|
||||||
|
lpToken: 'terra1wmaty65yt7mjw6fjfymkd9zsm6atsq82d9arcd',
|
||||||
|
assets: [Assets.anc, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
ancBullStrategy: pool(address:"terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'mirBullStrategy',
|
||||||
|
name: 'MIR-UST LP',
|
||||||
|
description: 'MIR-UST Bull Leveraged Yield Farm (Max 2x)',
|
||||||
|
minter: 'terra143xxfw5xf62d5m32k3t4eu9s82ccw80lcprzl9',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.mir.color,
|
||||||
|
contract_addr: 'terra12dq4wmfcsnz6ycep6ek4umtuaj6luhfp256hyu',
|
||||||
|
astroportGenerator: 'terra1zgrx9jjqrfye8swykfgmd6hpde60j0nszzupp9',
|
||||||
|
lpToken: 'terra17trxzqjetl0q6xxep0s2w743dhw2cay0x47puc',
|
||||||
|
assets: [Assets.mir, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
mirBullStrategy: pool(address:"terra143xxfw5xf62d5m32k3t4eu9s82ccw80lcprzl9") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default FieldsStrategies
|
239
src/configs/fields/testnet.ts
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import { StrategyProviders } from '../../constants/strategyProviders'
|
||||||
|
import Assets from '../assets/testnet'
|
||||||
|
|
||||||
|
const FieldsStrategies: FieldsStrategy[] = [
|
||||||
|
{
|
||||||
|
key: 'apolloAncBullStrategy',
|
||||||
|
externalLink: 'https://app.apollo.farm/',
|
||||||
|
minter: 'terra13r3vngakfw457dwhw9ef36mc8w6agggefe70d9',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.luna.color,
|
||||||
|
astroportGenerator: 'terra1gjm7d9nmewn27qzrvqyhda8zsfl40aya7tvaw5',
|
||||||
|
contract_addr: 'terra1lanxgewats337t49mnkaeh5g098j2g7e87k87s',
|
||||||
|
lpToken: 'terra1agu2qllktlmf0jdkuhcheqtchnkppzrl4759y6',
|
||||||
|
assets: [Assets.anc, Assets.ust],
|
||||||
|
maxLeverage: 2.5,
|
||||||
|
provider: StrategyProviders.APOLLO,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
apolloAncBullStrategy: pool(address:"terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lunaBullStrategy',
|
||||||
|
minter: 'terra1e49fv4xm3c2znzpxmxs0z2z6y74xlwxspxt38s',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.luna.color,
|
||||||
|
astroportGenerator: 'terra1gjm7d9nmewn27qzrvqyhda8zsfl40aya7tvaw5',
|
||||||
|
contract_addr: 'terra1pkpgcqy38gyr978xfh9fx0ttq0jllzyfl05k4f',
|
||||||
|
lpToken: 'terra1dqjpcqej9nxej80u0p56rhkrzlr6w8tp7txkmj',
|
||||||
|
assets: [Assets.luna, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
lunaBullStrategy: pool(address:"terra1m6ywlgn6wrjuagcmmezzz2a029gtldhey5k552") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'ancBullStrategy',
|
||||||
|
minter: 'terra13r3vngakfw457dwhw9ef36mc8w6agggefe70d9',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.anc.color,
|
||||||
|
contract_addr: 'terra1x3tu0tgsa3wuz97w2nm29fvhnhjnag00nxsgmy',
|
||||||
|
astroportGenerator: 'terra1gjm7d9nmewn27qzrvqyhda8zsfl40aya7tvaw5',
|
||||||
|
lpToken: 'terra1agu2qllktlmf0jdkuhcheqtchnkppzrl4759y6',
|
||||||
|
assets: [Assets.anc, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
ancBullStrategy: pool(address:"terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'mirBullStrategy',
|
||||||
|
minter: 'terra1xrt4j56mkefvhnyqqd5pgk7pfxullnkvsje7wx',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.mir.color,
|
||||||
|
contract_addr: 'terra1wrj7lrrxzdmcmpask6y48eudq7huvu8eylssjs',
|
||||||
|
astroportGenerator: 'terra1gjm7d9nmewn27qzrvqyhda8zsfl40aya7tvaw5',
|
||||||
|
lpToken: 'terra1efmcf22aweaj3zzjhzgyghv88dda0yk4j9jp29',
|
||||||
|
assets: [Assets.mir, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
mirBullStrategy: pool(address:"terra143xxfw5xf62d5m32k3t4eu9s82ccw80lcprzl9") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fakeMirStrat',
|
||||||
|
minter: 'terra1408najmjzueu9ktg4rw7a8yge3z280d5yp9nay',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.fmir.color,
|
||||||
|
contract_addr: 'terra16htwvqxqkygazqxmgt9wpqr0vtf6jekm6mf44n',
|
||||||
|
astroportGenerator: 'terra1f7awhwwqcjkxlk83qvgzrpnnnzm7577u5ffsx7',
|
||||||
|
lpToken: 'terra1r5q4cepddwn9sklkm4x0jhspv09w8rwsfszx5u',
|
||||||
|
assets: [Assets.fmir, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
fakeMirStrat: pool(address:"terra1m6ywlgn6wrjuagcmmezzz2a029gtldhey5k552") {
|
||||||
|
trading_fees {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'fancBullStrategy',
|
||||||
|
minter: 'terra1q2kv38ldy03g8ql6l8vfhc0sf5a2ypur7ee50c',
|
||||||
|
logo: Assets.ust.logo,
|
||||||
|
borrow: Assets.ust.symbol,
|
||||||
|
color: Assets.fanc.color,
|
||||||
|
contract_addr: 'terra1gq03knyygm3v0fu2cvc3nptdhndl9cr4vekxkv',
|
||||||
|
astroportGenerator: 'terra1f7awhwwqcjkxlk83qvgzrpnnnzm7577u5ffsx7',
|
||||||
|
lpToken: 'terra1llys4hxu8wkt0msnm0nz328ymyrk3j3nz65rkx',
|
||||||
|
assets: [Assets.fanc, Assets.ust],
|
||||||
|
maxLeverage: 2,
|
||||||
|
provider: StrategyProviders.MARS,
|
||||||
|
apyQuery: {
|
||||||
|
url: 'https://api.astroport.fi/graphql',
|
||||||
|
query: `
|
||||||
|
fancBullStrategy: pool(address:"terra1qr2k6yjjd5p2kaewqvg93ag74k6gyjr7re37fs") {
|
||||||
|
trading_fees {
|
||||||
|
apr
|
||||||
|
apy
|
||||||
|
}
|
||||||
|
astro_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
protocol_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
total_rewards {
|
||||||
|
apy
|
||||||
|
apr
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
target: ['pool', 'total_rewards', 'apy'],
|
||||||
|
apr: true,
|
||||||
|
absolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default FieldsStrategies
|
19
src/configs/liquiditypools/mainnet.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"redBankContractAddress": "terra19dtgj9j5j7kyf3pmejqv8vzfpxtejaypgzkz5u"
|
||||||
|
},
|
||||||
|
"whitelist": {
|
||||||
|
"terra1dj9jfrrjc0yckz8l5g3mmqd86e67msvfh3q30f": {
|
||||||
|
"denom": "ANC",
|
||||||
|
"decimals": 6
|
||||||
|
},
|
||||||
|
"terra1x4rrkxx5pyuce32wsdn8ypqnpx8n27klnegv0d": {
|
||||||
|
"denom": "uluna",
|
||||||
|
"decimals": 6
|
||||||
|
},
|
||||||
|
"terra1cuku0vggplpgfxegdrenp302km26symjk4xxaf": {
|
||||||
|
"denom": "uusd",
|
||||||
|
"decimals": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/configs/liquiditypools/testnet.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"redBankContractAddress": "terra1avkm5w0gzwm92h0dlxymsdhx4l2rm7k0lxnwq7"
|
||||||
|
},
|
||||||
|
"whitelist": {
|
||||||
|
"terra1l7dzcwhz24prg3dsuzrl9s7l6cpc2l72lesacd": {
|
||||||
|
"denom": "stLuna",
|
||||||
|
"decimals": 6
|
||||||
|
},
|
||||||
|
"terra1gwl5srdgdy2r24dxpdzn7m2t7509yhnzjyc36w": {
|
||||||
|
"denom": "uluna",
|
||||||
|
"decimals": 6
|
||||||
|
},
|
||||||
|
"terra1xmx2e3eyj9d0mcycpak4fa95z0wdvykp7zesqt": {
|
||||||
|
"denom": "uusd",
|
||||||
|
"decimals": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/configs/lockdrop/mainnet.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"lockdropAddress": "terra1n38982txtv2yygtcfv3e9wp2ktmjyxl6z88rma",
|
||||||
|
"airdropAddress": "terra106htgurux879drvjy5tzp8l7ydcnytmhpj7yr3",
|
||||||
|
"auctionAddress": "terra1hgyamk2kcy3stqx82wrnsklw9aq7rask5dxfds",
|
||||||
|
"astroportMarsUstPoolAddress": "terra19wauh79y42u5vt62c5adt2g5h4exgh26t3rpds"
|
||||||
|
}
|
5
src/configs/lockdrop/testnet.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"lockdropAddress": "terra1eqpcgsa7750r8rjqhxzcmpzvw46fn7gquywzfe",
|
||||||
|
"airdropAddress": "terra1xn9079ly7qfxf3mspf3pny3w4q4hl6agxwlnps",
|
||||||
|
"auctionAddress": "terra1myp6y5869dngaczu943pqej56r8at3as2ehqje"
|
||||||
|
}
|
5
src/configs/oracles/mainnet.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"astroportFactoryAddress": "terra1fnywlw4edny3vw44x04xd67uzkdqluymgreu7g"
|
||||||
|
}
|
||||||
|
}
|
5
src/configs/oracles/testnet.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"contracts": {
|
||||||
|
"astroportFactoryAddress": "terra15jsahkaf9p0qu8ye873p0u5z6g07wdad0tdq43"
|
||||||
|
}
|
||||||
|
}
|
14
src/configs/whitelists/mainnet.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Assets from '../assets/mainnet'
|
||||||
|
|
||||||
|
const nativeAssetWhitelist: WhitelistAsset[] = [Assets.luna, Assets.ust]
|
||||||
|
|
||||||
|
const cw20AssetWhitelist: WhitelistAsset[] = [Assets.anc]
|
||||||
|
|
||||||
|
// These assets are supported by the red bank and should use the Mars oracle
|
||||||
|
export const Whitelist: WhitelistAsset[] = [
|
||||||
|
...nativeAssetWhitelist,
|
||||||
|
...cw20AssetWhitelist,
|
||||||
|
]
|
||||||
|
|
||||||
|
// These assets are not supported by red bank but will be displayed in the app where they need an exchange rate
|
||||||
|
export const Other: OtherAsset[] = [Assets.mars, Assets.astro, Assets.mir]
|
25
src/configs/whitelists/testnet.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Assets from '../assets/testnet'
|
||||||
|
|
||||||
|
const nativeAssetWhitelist: WhitelistAsset[] = [
|
||||||
|
Assets.luna,
|
||||||
|
Assets.ust,
|
||||||
|
Assets.stLuna,
|
||||||
|
]
|
||||||
|
|
||||||
|
const cw20AssetWhitelist: WhitelistAsset[] = []
|
||||||
|
|
||||||
|
// These assets are supported by the red bank and should use the Mars oracle
|
||||||
|
export const Whitelist: WhitelistAsset[] = [
|
||||||
|
...nativeAssetWhitelist,
|
||||||
|
...cw20AssetWhitelist,
|
||||||
|
]
|
||||||
|
|
||||||
|
// These assets are not supported by red bank but will be displayed in the app where they need an exchange rate
|
||||||
|
export const Other: OtherAsset[] = [
|
||||||
|
Assets.mars,
|
||||||
|
Assets.astro,
|
||||||
|
Assets.mir,
|
||||||
|
Assets.anc,
|
||||||
|
Assets.fanc,
|
||||||
|
Assets.fmir,
|
||||||
|
]
|
42
src/constants/appConstants.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* terra:network */
|
||||||
|
export const FINDER_URL = 'https://terrascope.info/'
|
||||||
|
export const EXTENSION = 'https://terra.money/extension'
|
||||||
|
export const CHROME = 'https://google.com/chrome'
|
||||||
|
export const ASTROPORT_URL = 'https://app.astroport.fi/swap'
|
||||||
|
export const FORUM_URL = 'https://forum.marsprotocol.io/'
|
||||||
|
|
||||||
|
/* contract deploy block heights */
|
||||||
|
export const STAKING_CONTRACT_DEPLOY_HEIGHT = 6531019
|
||||||
|
export const MARS_CONTRACT_DEPLOY_HEIGHT = 6530880
|
||||||
|
export const XMARS_CONTRACT_DEPLOY_HEIGHT = 6531023
|
||||||
|
export const AUCTION_CONTRACT_END_TIMESTAMP = 1646046000 + 432000 + 172800 // init_timestamp + ust_deposit_window + withdrawal_window
|
||||||
|
export const AUCTION_LP_TOKENS_VESTING_DURATION =
|
||||||
|
AUCTION_CONTRACT_END_TIMESTAMP + 7776000 // lp_tokens_vesting_duration
|
||||||
|
|
||||||
|
/* mars:unit */
|
||||||
|
export const MARS_DENOM = 'MARS'
|
||||||
|
export const MARS_DECIMALS = 6
|
||||||
|
export const XMARS_DENOM = 'XMARS'
|
||||||
|
export const XMARS_DECIMALS = 6
|
||||||
|
export const ASTRO_DENOM = 'ASTRO'
|
||||||
|
export const ASTRO_DECIMALS = 6
|
||||||
|
export const PROPOSAL_LIMIT = 5
|
||||||
|
export const UST_DENOM = 'uusd'
|
||||||
|
export const UST_DECIMALS = 6
|
||||||
|
|
||||||
|
export const BLOCK_TIME = 7500
|
||||||
|
export const BLOCKS_PER_DAY = (86400 * 1000) / BLOCK_TIME
|
||||||
|
export const COOLDOWN_BUFFER = BLOCK_TIME * 2
|
||||||
|
|
||||||
|
/* borrowLimit */
|
||||||
|
export const GAUGE_SCALE = 0.825
|
||||||
|
export const DEFAULT_SLIPPAGE = 0.01
|
||||||
|
|
||||||
|
/* other */
|
||||||
|
export const VOLATILITY_THRESHOLD = 0.05
|
||||||
|
|
||||||
|
/* feature flags */
|
||||||
|
export const FIELDS_FEATURE: Boolean = true
|
||||||
|
export const MY_LOCKDROP_FEATURE: Boolean = true
|
||||||
|
export const PROPOSAL_ACTION_BUTTONS_FEATURE: Boolean = false
|
||||||
|
export const AIRDROP_CLAIM_FEATURE: Boolean = true
|
15
src/constants/strategyProviders.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import mars from '../images/MARS-COLORED.svg'
|
||||||
|
import apollo from '../images/Apollo.svg'
|
||||||
|
|
||||||
|
import { StrategyProvider } from '../types/interfaces/strategyProvider'
|
||||||
|
|
||||||
|
export const StrategyProviders: StrategyProvider = {
|
||||||
|
MARS: {
|
||||||
|
logo: mars,
|
||||||
|
name: 'Mars',
|
||||||
|
},
|
||||||
|
APOLLO: {
|
||||||
|
logo: apollo,
|
||||||
|
name: 'Apollo',
|
||||||
|
},
|
||||||
|
}
|
3
src/constants/timeConstants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const DAY_IN_SECONDS = 60 * 60 * 24
|
||||||
|
export const HOUR_IN_SECONDS = 60 * 60
|
||||||
|
export const MINUTE_IN_SECONDS = 60
|