draft implementation of governance page
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
a2a4b86ccc
commit
20f2e78ae7
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ghost-dao-interface",
|
"name": "ghost-dao-interface",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.4.4",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"@ethersproject/bignumber": "^5.8.0",
|
"@ethersproject/bignumber": "^5.8.0",
|
||||||
"@ethersproject/units": "^5.8.0",
|
"@ethersproject/units": "^5.8.0",
|
||||||
"@mui/icons-material": "^6.4.7",
|
"@mui/icons-material": "^6.4.7",
|
||||||
|
"@mui/lab": "6.0.1-beta.36",
|
||||||
"@mui/material": "^6.4.7",
|
"@mui/material": "^6.4.7",
|
||||||
"@mui/utils": "^6.4.6",
|
"@mui/utils": "^6.4.6",
|
||||||
"@polkadot-api/metadata-builders": "0.13.0",
|
"@polkadot-api/metadata-builders": "0.13.0",
|
||||||
|
|||||||
218
pnpm-lock.yaml
218
pnpm-lock.yaml
@ -26,6 +26,9 @@ importers:
|
|||||||
'@mui/icons-material':
|
'@mui/icons-material':
|
||||||
specifier: ^6.4.7
|
specifier: ^6.4.7
|
||||||
version: 6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
version: 6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@mui/lab':
|
||||||
|
specifier: 6.0.1-beta.36
|
||||||
|
version: 6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
'@mui/material':
|
'@mui/material':
|
||||||
specifier: ^6.4.7
|
specifier: ^6.4.7
|
||||||
version: 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@ -525,6 +528,21 @@ packages:
|
|||||||
'@ethersproject/units@5.8.0':
|
'@ethersproject/units@5.8.0':
|
||||||
resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==}
|
resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==}
|
||||||
|
|
||||||
|
'@floating-ui/core@1.7.4':
|
||||||
|
resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==}
|
||||||
|
|
||||||
|
'@floating-ui/dom@1.7.5':
|
||||||
|
resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==}
|
||||||
|
|
||||||
|
'@floating-ui/react-dom@2.1.7':
|
||||||
|
resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
|
'@floating-ui/utils@0.2.10':
|
||||||
|
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||||
|
|
||||||
'@humanfs/core@0.19.1':
|
'@humanfs/core@0.19.1':
|
||||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@ -638,6 +656,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==}
|
resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
'@mui/base@5.0.0-beta.70':
|
||||||
|
resolution: {integrity: sha512-Tb/BIhJzb0pa5zv/wu7OdokY9ZKEDqcu1BDFnohyvGCoHuSXbEr90rPq1qeNW3XvTBIbNWHEF7gqge+xpUo6tQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
deprecated: This package has been replaced by @base-ui/react
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/core-downloads-tracker@6.4.7':
|
'@mui/core-downloads-tracker@6.4.7':
|
||||||
resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==}
|
resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==}
|
||||||
|
|
||||||
@ -652,6 +682,27 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/lab@6.0.1-beta.36':
|
||||||
|
resolution: {integrity: sha512-af9lDmA9SZGEWF1XXk0EVBpfCITk9IKsvh9lLOZGdYaaHfQeCsqxGEDMvNO66j0P8EYoxpyry84LFCJYuLVtVw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.5.0
|
||||||
|
'@emotion/styled': ^11.3.0
|
||||||
|
'@mui/material': ^6.5.0
|
||||||
|
'@mui/material-pigment-css': ^6.5.0
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
'@mui/material-pigment-css':
|
||||||
|
optional: true
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/material@6.4.7':
|
'@mui/material@6.4.7':
|
||||||
resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==}
|
resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@ -682,6 +733,16 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/private-theming@6.4.9':
|
||||||
|
resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/styled-engine@6.4.6':
|
'@mui/styled-engine@6.4.6':
|
||||||
resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==}
|
resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@ -695,6 +756,19 @@ packages:
|
|||||||
'@emotion/styled':
|
'@emotion/styled':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/styled-engine@6.5.0':
|
||||||
|
resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.4.1
|
||||||
|
'@emotion/styled': ^11.3.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/system@6.4.7':
|
'@mui/system@6.4.7':
|
||||||
resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==}
|
resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@ -711,6 +785,22 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/system@6.5.0':
|
||||||
|
resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/react': ^11.5.0
|
||||||
|
'@emotion/styled': ^11.3.0
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/react':
|
||||||
|
optional: true
|
||||||
|
'@emotion/styled':
|
||||||
|
optional: true
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/types@7.2.21':
|
'@mui/types@7.2.21':
|
||||||
resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==}
|
resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -719,6 +809,14 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/types@7.2.24':
|
||||||
|
resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@mui/utils@6.4.6':
|
'@mui/utils@6.4.6':
|
||||||
resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==}
|
resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@ -729,6 +827,16 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@mui/utils@6.4.9':
|
||||||
|
resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@noble/ciphers@1.2.1':
|
'@noble/ciphers@1.2.1':
|
||||||
resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==}
|
resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==}
|
||||||
engines: {node: ^14.21.3 || >=16}
|
engines: {node: ^14.21.3 || >=16}
|
||||||
@ -3423,6 +3531,23 @@ snapshots:
|
|||||||
'@ethersproject/constants': 5.8.0
|
'@ethersproject/constants': 5.8.0
|
||||||
'@ethersproject/logger': 5.8.0
|
'@ethersproject/logger': 5.8.0
|
||||||
|
|
||||||
|
'@floating-ui/core@1.7.4':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/utils': 0.2.10
|
||||||
|
|
||||||
|
'@floating-ui/dom@1.7.5':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.7.4
|
||||||
|
'@floating-ui/utils': 0.2.10
|
||||||
|
|
||||||
|
'@floating-ui/react-dom@2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/dom': 1.7.5
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
|
||||||
|
'@floating-ui/utils@0.2.10': {}
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.6':
|
'@humanfs/node@0.16.6':
|
||||||
@ -3615,6 +3740,20 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@mui/base@5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@floating-ui/react-dom': 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||||
|
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@popperjs/core': 2.11.8
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@mui/core-downloads-tracker@6.4.7': {}
|
'@mui/core-downloads-tracker@6.4.7': {}
|
||||||
|
|
||||||
'@mui/icons-material@6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)':
|
'@mui/icons-material@6.4.7(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)':
|
||||||
@ -3625,6 +3764,23 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
|
'@mui/lab@6.0.1-beta.36(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/base': 5.0.0-beta.70(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
'@mui/material': 6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
'@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||||
|
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
'@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.26.9
|
||||||
@ -3648,16 +3804,38 @@ snapshots:
|
|||||||
|
|
||||||
'@mui/private-theming@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
'@mui/private-theming@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.27.6
|
||||||
'@mui/utils': 6.4.6(@types/react@19.0.10)(react@19.0.0)
|
'@mui/utils': 6.4.6(@types/react@19.0.10)(react@19.0.0)
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
|
'@mui/private-theming@6.4.9(@types/react@19.0.10)(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@mui/styled-engine@6.4.6(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)':
|
'@mui/styled-engine@6.4.6(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.27.6
|
||||||
|
'@emotion/cache': 11.14.0
|
||||||
|
'@emotion/serialize': 1.3.3
|
||||||
|
'@emotion/sheet': 1.4.0
|
||||||
|
csstype: 3.1.3
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
|
||||||
|
'@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
'@emotion/cache': 11.14.0
|
'@emotion/cache': 11.14.0
|
||||||
'@emotion/serialize': 1.3.3
|
'@emotion/serialize': 1.3.3
|
||||||
'@emotion/sheet': 1.4.0
|
'@emotion/sheet': 1.4.0
|
||||||
@ -3684,10 +3862,30 @@ snapshots:
|
|||||||
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
|
'@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/private-theming': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0))(react@19.0.0)
|
||||||
|
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||||
|
'@mui/utils': 6.4.9(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
clsx: 2.1.1
|
||||||
|
csstype: 3.1.3
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/react': 11.14.0(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.10)(react@19.0.0))(@types/react@19.0.10)(react@19.0.0)
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@mui/types@7.2.21(@types/react@19.0.10)':
|
'@mui/types@7.2.21(@types/react@19.0.10)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
|
'@mui/types@7.2.24(@types/react@19.0.10)':
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@mui/utils@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
'@mui/utils@6.4.6(@types/react@19.0.10)(react@19.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.26.9
|
||||||
@ -3700,6 +3898,18 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
|
'@mui/utils@6.4.9(@types/react@19.0.10)(react@19.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.6
|
||||||
|
'@mui/types': 7.2.24(@types/react@19.0.10)
|
||||||
|
'@types/prop-types': 15.7.14
|
||||||
|
clsx: 2.1.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
react-is: 19.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.0.10
|
||||||
|
|
||||||
'@noble/ciphers@1.2.1': {}
|
'@noble/ciphers@1.2.1': {}
|
||||||
|
|
||||||
'@noble/ciphers@1.3.0': {}
|
'@noble/ciphers@1.3.0': {}
|
||||||
@ -5042,7 +5252,7 @@ snapshots:
|
|||||||
|
|
||||||
babel-plugin-macros@3.1.0:
|
babel-plugin-macros@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.27.6
|
||||||
cosmiconfig: 7.1.0
|
cosmiconfig: 7.1.0
|
||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
|
|
||||||
@ -5238,7 +5448,7 @@ snapshots:
|
|||||||
|
|
||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.27.6
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
dot-case@3.0.4:
|
dot-case@3.0.4:
|
||||||
|
|||||||
@ -31,6 +31,8 @@ const Wrapper = lazy(() => import("./containers/WethWrapper/WethWrapper"));
|
|||||||
const Dex = lazy(() => import("./containers/Dex/Dex"));
|
const Dex = lazy(() => import("./containers/Dex/Dex"));
|
||||||
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
|
const Bridge = lazy(() => import("./containers/Bridge/Bridge"));
|
||||||
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
const NotFound = lazy(() => import("./containers/NotFound/NotFound"));
|
||||||
|
const Governance = lazy(() => import("./containers/Governance/Governance"));
|
||||||
|
const ProposalDetails = lazy(() => import("./containers/Governance/ProposalDetails"));
|
||||||
|
|
||||||
const PREFIX = "App";
|
const PREFIX = "App";
|
||||||
|
|
||||||
@ -213,6 +215,8 @@ function App() {
|
|||||||
}
|
}
|
||||||
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/bridge" element={<Bridge config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
<Route path="/dex/:name" element={<Dex connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
|
<Route path="/governance" element={<Governance config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
|
<Route path="/governance/:id" element={<ProposalDetails config={config} connect={tryConnectInjected} address={address} chainId={addressChainId ? addressChainId : chainId} />} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
<Route path="/empty" element={<NotFound
|
<Route path="/empty" element={<NotFound
|
||||||
|
|||||||
39
src/components/Progress/LinearProgressBar.jsx
Normal file
39
src/components/Progress/LinearProgressBar.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Box, LinearProgress as MuiLinearProgress } from "@mui/material";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
const PREFIX = "MuiLinearProgress";
|
||||||
|
|
||||||
|
const classes = {
|
||||||
|
chip: `${PREFIX}-bar`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMuiLinearProgress = styled(MuiLinearProgress, {
|
||||||
|
shouldForwardProp: (prop) => prop !== "barBackground" && prop !== 'barColor' && prop !== 'height'
|
||||||
|
})(({ theme, barColor, barBackground, height }) => ({
|
||||||
|
height: height || 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: barBackground || theme.palette.grey[300],
|
||||||
|
'& .MuiLinearProgress-bar': {
|
||||||
|
backgroundColor: barColor || theme.palette.primary.main
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LinearProgressBar = (props) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'relative', width: '100%', py: 1 }}>
|
||||||
|
<StyledMuiLinearProgress {...props} />
|
||||||
|
{props.target && <Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${props.target}%`,
|
||||||
|
top: props.targetTop || 0,
|
||||||
|
bottom: props.targetBottom || 0,
|
||||||
|
width: props.targetWidth || "2px",
|
||||||
|
backgroundColor: props.targetBackgroundColor || 'white',
|
||||||
|
}}
|
||||||
|
></Box>}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LinearProgressBar;
|
||||||
@ -24,6 +24,8 @@ import TelegramIcon from '@mui/icons-material/Telegram';
|
|||||||
import HowToVoteIcon from '@mui/icons-material/HowToVote';
|
import HowToVoteIcon from '@mui/icons-material/HowToVote';
|
||||||
import HubIcon from '@mui/icons-material/Hub';
|
import HubIcon from '@mui/icons-material/Hub';
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
|
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||||
|
import GavelIcon from '@mui/icons-material/Gavel';
|
||||||
import ForumIcon from '@mui/icons-material/Forum';
|
import ForumIcon from '@mui/icons-material/Forum';
|
||||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
||||||
import BookIcon from '@mui/icons-material/Book';
|
import BookIcon from '@mui/icons-material/Book';
|
||||||
@ -177,7 +179,8 @@ const NavContent = ({ chainId, addressChainId }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
|
<NavItem icon={StakeIcon} label={`Stake`} to="/stake" />
|
||||||
<NavItem icon={PublicIcon} label={`Bridge`} to="/bridge" />
|
<NavItem icon={ForkRightIcon} label={`Bridge`} to="/bridge" />
|
||||||
|
<NavItem icon={GavelIcon} label={`Governance`} to="/governance" />
|
||||||
<Box className="menu-divider">
|
<Box className="menu-divider">
|
||||||
<Divider />
|
<Divider />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material";
|
import { Box, Tab, Tabs, Container, useMediaQuery } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
@ -21,7 +20,6 @@ import { useTokenSymbol } from "../../hooks/tokens";
|
|||||||
|
|
||||||
const Bonds = ({ chainId, address, connect }) => {
|
const Bonds = ({ chainId, address, connect }) => {
|
||||||
const [isZoomed] = useState(false);
|
const [isZoomed] = useState(false);
|
||||||
const navigate = useNavigate();
|
|
||||||
const [secondsTo, setSecondsTo] = useState(0);
|
const [secondsTo, setSecondsTo] = useState(0);
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
@ -29,7 +27,7 @@ const Bonds = ({ chainId, address, connect }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.send({ hitType: "pageview", page: "/bonds" });
|
ReactGA.send({ hitType: "pageview", page: "/bonds" });
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { liveBonds } = useLiveBonds(chainId);
|
const { liveBonds } = useLiveBonds(chainId);
|
||||||
const totalReserves = useTotalReserves(chainId);
|
const totalReserves = useTotalReserves(chainId);
|
||||||
|
|||||||
92
src/containers/Governance/Governance.jsx
Normal file
92
src/containers/Governance/Governance.jsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
import { Box, Container, Grid, Divider, Typography, useMediaQuery } from "@mui/material";
|
||||||
|
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
|
import { PrimaryButton } from "../../components/Button";
|
||||||
|
|
||||||
|
import GovernanceInfoText from "./components/GovernanceInfoText";
|
||||||
|
import ProposalsList from "./components/ProposalsList";
|
||||||
|
import { ProposalsCount, MinQuorumPercentage, ProposalThreshold } from "./components/Metric";
|
||||||
|
|
||||||
|
import { useTokenSymbol } from "../../hooks/tokens";
|
||||||
|
|
||||||
|
const Governance = ({ connect, config, address, chainId }) => {
|
||||||
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
|
||||||
|
const handleModal = () => {
|
||||||
|
alert("proposal modal here");
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: "/governance" });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<PageTitle name="ghostDAO Governance" subtitle={`Vote for proposals and suggest new with $${ghstSymbol}`} />
|
||||||
|
<Container
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
minHeight: "calc(100vh - 128px)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ mt: "15px" }}>
|
||||||
|
<Paper
|
||||||
|
fullWidth
|
||||||
|
enableBackground
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||||
|
<Typography variant="h6">
|
||||||
|
Proposal Requirements
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<ProposalsCount chainId={chainId} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<MinQuorumPercentage chainId={chainId} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={isSmallScreen ? 12 : 4}>
|
||||||
|
<ProposalThreshold chainId={chainId} ghstSymbol={ghstSymbol} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Divider sx={{ marginTop: "30px" }} />
|
||||||
|
<Box display="flex" justifyContent="center">Claimes for locked funds could be here</Box>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box mt="15px" display="flex" flexDirection="column" alignItems="center" justifyContent="center">
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => handleModal(true)}
|
||||||
|
sx={{ maxWidth: isSemiSmallScreen ? "100%" : "350px" }}
|
||||||
|
>
|
||||||
|
Create Proposal
|
||||||
|
</PrimaryButton>
|
||||||
|
<Box textAlign="center" mt="15px">
|
||||||
|
<GovernanceInfoText />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<ProposalsList config={config} chainId={chainId} />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Governance;
|
||||||
244
src/containers/Governance/ProposalDetails.jsx
Normal file
244
src/containers/Governance/ProposalDetails.jsx
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
import { useEffect, useState, useMemo } from "react";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Box, Container, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
import PageTitle from "../../components/PageTitle/PageTitle";
|
||||||
|
import LinearProgressBar from "../../components/Progress/LinearProgressBar";
|
||||||
|
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
|
||||||
|
import Chip from "../../components/Chip/Chip";
|
||||||
|
import { SecondaryButton } from "../../components/Button";
|
||||||
|
|
||||||
|
import { formatNumber } from "../../helpers";
|
||||||
|
import { prettifySecondsInDays } from "../../helpers/timeUtil";
|
||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
|
||||||
|
import ProposalDiscussionModal from "./components/ProposalDiscussionModal";
|
||||||
|
import ProposalDiscussion from "./components/ProposalDiscussion";
|
||||||
|
import { convertStatusToTemplate } from "./helpers";
|
||||||
|
|
||||||
|
import { useTokenSymbol, useTotalSupply, useBalance } from "../../hooks/tokens";
|
||||||
|
import {
|
||||||
|
useProposalStatus,
|
||||||
|
useProposalProposer,
|
||||||
|
useProposalLocked,
|
||||||
|
useProposalQuorum,
|
||||||
|
useProposalVotes,
|
||||||
|
useProposalSnapshot,
|
||||||
|
useProposalDeadline,
|
||||||
|
useProposalVotingDelay
|
||||||
|
} from "../../hooks/governance";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
import Timeline from '@mui/lab/Timeline';
|
||||||
|
import TimelineItem from '@mui/lab/TimelineItem';
|
||||||
|
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
||||||
|
import TimelineConnector from '@mui/lab/TimelineConnector';
|
||||||
|
import TimelineContent from '@mui/lab/TimelineContent';
|
||||||
|
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';
|
||||||
|
import TimelineDot from '@mui/lab/TimelineDot';
|
||||||
|
///////////////////////////
|
||||||
|
import FastfoodIcon from '@mui/icons-material/Fastfood';
|
||||||
|
import LaptopMacIcon from '@mui/icons-material/LaptopMac';
|
||||||
|
import HotelIcon from '@mui/icons-material/Hotel';
|
||||||
|
import RepeatIcon from '@mui/icons-material/Repeat';
|
||||||
|
|
||||||
|
const HUNDRED = new DecimalBigNumber(100n, 0);
|
||||||
|
|
||||||
|
const ProposalDetails = ({ chainId, address }) => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||||
|
|
||||||
|
const isSemiSmallScreen = useMediaQuery("(max-width: 745px)");
|
||||||
|
const isSmallScreen = useMediaQuery("(max-width: 650px)");
|
||||||
|
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
|
||||||
|
const { balance } = useBalance(chainId, "GHST", address);
|
||||||
|
const { totalSupply } = useTotalSupply(chainId, "GHST"); // TODO: revisit
|
||||||
|
|
||||||
|
const { status: proposalStatus } = useProposalStatus(chainId, id);
|
||||||
|
const { proposer: proposalProposer } = useProposalProposer(chainId, id);
|
||||||
|
const { locked: proposalLocked } = useProposalLocked(chainId, id);
|
||||||
|
const { quorum: proposalQuorum } = useProposalQuorum(chainId, id);
|
||||||
|
const { forVotes, againstVotes } = useProposalVotes(chainId, id);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ReactGA.send({ hitType: "pageview", page: `/governance/${id}` });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isDiscussionModalOpened = useMemo(() => {
|
||||||
|
return selectedDiscussionUrl !== undefined;
|
||||||
|
}, [selectedDiscussionUrl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProposalDiscussionModal
|
||||||
|
url={selectedDiscussionUrl}
|
||||||
|
isOpened={isDiscussionModalOpened}
|
||||||
|
closeModal={() => setSelectedDiscussionUrl(undefined)}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<PageTitle name={`GBP: ${id} - NAME`} subtitle={`By: ${proposalProposer} | BONDED: $${proposalLocked} ${ghstSymbol}`} />
|
||||||
|
<Container
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
paddingRight: isSmallScreen || isVerySmallScreen ? "0" : "3.3rem",
|
||||||
|
minHeight: "calc(100vh - 128px)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ mt: "15px" }}>
|
||||||
|
<Box display="flex" justifyContent="space-between" gap="20px">
|
||||||
|
<Paper
|
||||||
|
fullWidth
|
||||||
|
enableBackground
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" alignItems="center" flexDirection="row" gap="10px">
|
||||||
|
<Typography variant="h6">
|
||||||
|
Progress
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
sx={{ marginTop: "4px", width: "88px" }}
|
||||||
|
label={proposalStatus}
|
||||||
|
template={convertStatusToTemplate(proposalStatus)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
topRight={
|
||||||
|
<ProposalDiscussion onClick={() => setSelectedDiscussionUrl("dicks")} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box height="220px" display="flex" flexDirection="column" justifyContent="space-between" gap="20px">
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
<Typography variant="body2" color={theme.colors.feedback.success}>
|
||||||
|
For: {formatNumber(forVotes.toString(), 2)} ({formatNumber(forVotes * HUNDRED / proposalQuorum, 1)}%)
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color={theme.colors.feedback.error}>
|
||||||
|
Against: {formatNumber(againstVotes.toString(), 2)} ({formatNumber(againstVotes * HUNDRED / proposalQuorum, 1)}%)
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<LinearProgressBar
|
||||||
|
barColor={theme.colors.feedback.success}
|
||||||
|
barBackground={theme.colors.feedback.error}
|
||||||
|
variant="determinate"
|
||||||
|
value={69}
|
||||||
|
target={Math.floor(Math.random() * 101)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column">
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<Typography>Quorum</Typography>
|
||||||
|
<InfoTooltip message="Minimum number of voting power required to be present to make proposal executable" />
|
||||||
|
</Box>
|
||||||
|
<Typography>{formatNumber(proposalQuorum.toString(), 4)}</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<Typography>Total</Typography>
|
||||||
|
<InfoTooltip message="Total number of votes available for proposal" />
|
||||||
|
</Box>
|
||||||
|
<Typography>{formatNumber(totalSupply.toString(), 4)}</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
<Box display="flex" flexDirection="row">
|
||||||
|
<Typography>Votes</Typography>
|
||||||
|
<InfoTooltip message="Voting power of the connected wallet" />
|
||||||
|
</Box>
|
||||||
|
<Typography>{formatNumber(balance.toString(), 4)}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" gap="20px">
|
||||||
|
<SecondaryButton fullWidth onClick={() => alert("For vote casted")}>For</SecondaryButton>
|
||||||
|
<SecondaryButton fullWidth onClick={() => alert("Against vote casted")}>Against</SecondaryButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
fullWidth
|
||||||
|
enableBackground
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||||
|
<Typography variant="h6">
|
||||||
|
Timeline
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<VotingTimeline chainId={chainId} proposalId={id} />
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
fullWidth
|
||||||
|
enableBackground
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" alignItems="center" flexDirection="row" gap="5px">
|
||||||
|
<Typography variant="h6">
|
||||||
|
Executable Code
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Here will be a list of decoded calldatas
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VotingTimeline = ({ proposalId, chainId }) => {
|
||||||
|
const { delay: propsalVotingDelay } = useProposalVotingDelay(chainId, proposalId);
|
||||||
|
const { snapshot: proposalSnapshot } = useProposalSnapshot(chainId, proposalId);
|
||||||
|
const { deadline: proposalDeadline } = useProposalDeadline(chainId, proposalId);
|
||||||
|
|
||||||
|
const voteStarted = useMemo(() => {
|
||||||
|
if (proposalSnapshot && propsalVotingDelay) {
|
||||||
|
return proposalSnapshot > propsalVotingDelay ? proposalSnapshot - propsalVotingDelay : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}, [proposalSnapshot, propsalVotingDelay]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Timeline sx={{ margin: 0, padding: 0 }}>
|
||||||
|
<VotingTimelineItem time={voteStarted} message="Proposed on:" isFirst />
|
||||||
|
<VotingTimelineItem time={proposalSnapshot} message="Voting started:" />
|
||||||
|
<VotingTimelineItem time={proposalDeadline} message="Voting ends:" />
|
||||||
|
</Timeline>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VotingTimelineItem = ({ isFirst, isLast, time, message }) => {
|
||||||
|
return (
|
||||||
|
<TimelineItem>
|
||||||
|
<TimelineOppositeContent sx={{ display: "none" }} />
|
||||||
|
<TimelineSeparator>
|
||||||
|
{!isFirst && <TimelineConnector sx={{ background: "#fff" }} />}
|
||||||
|
<TimelineDot sx={{ width: "15px", height: "15px", background: "#fff" }}></TimelineDot>
|
||||||
|
{!isLast && <TimelineConnector sx={{ background: "#fff" }} />}
|
||||||
|
</TimelineSeparator>
|
||||||
|
|
||||||
|
<TimelineContent>
|
||||||
|
<Typography>{message}</Typography>
|
||||||
|
<Typography component="span">{new Date(time * 1000).toLocaleString()}</Typography>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProposalDetails;
|
||||||
23
src/containers/Governance/components/GovernanceInfoText.jsx
Normal file
23
src/containers/Governance/components/GovernanceInfoText.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Link, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
const GovernanceInfoText = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
fontSize="0.875em"
|
||||||
|
lineHeight="15px"
|
||||||
|
>
|
||||||
|
ghostDAO's adaptive governance system algorithmically sets proposal threshold based on activity.
|
||||||
|
<Link
|
||||||
|
color={theme.colors.primary[300]}
|
||||||
|
href="https://ghostchain.io/ghostdao_litepaper"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>Learn more here.</Link>
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GovernanceInfoText;
|
||||||
53
src/containers/Governance/components/Metric.jsx
Normal file
53
src/containers/Governance/components/Metric.jsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Metric from "../../../components/Metric/Metric";
|
||||||
|
|
||||||
|
import { formatCurrency, formatNumber } from "../../../helpers";
|
||||||
|
import {
|
||||||
|
useMinQuorum,
|
||||||
|
useProposalThreshold,
|
||||||
|
useProposalCount
|
||||||
|
} from "../../../hooks/governance";
|
||||||
|
|
||||||
|
export const MinQuorumPercentage = props => {
|
||||||
|
const { percentage } = useMinQuorum(props.chainId);
|
||||||
|
|
||||||
|
const _props = {
|
||||||
|
...props,
|
||||||
|
label: `Min Quorum`,
|
||||||
|
tooltip: `Minimum quorum needed for proposal to be succeeded.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (percentage) _props.metric = `${formatNumber(percentage, 2)}%`;
|
||||||
|
else _props.isLoading = true;
|
||||||
|
|
||||||
|
return <Metric {..._props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProposalThreshold = props => {
|
||||||
|
const { threshold } = useProposalThreshold(props.chainId, props.ghstSymbol);
|
||||||
|
|
||||||
|
const _props = {
|
||||||
|
...props,
|
||||||
|
label: `$${props.ghstSymbol} Threshold`,
|
||||||
|
tooltip: `Minimum $${props.ghstSymbol} amount to be locked to create proposal.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (threshold) _props.metric = `${formatCurrency(threshold.toString(), 0, props.ghstSymbol)}`;
|
||||||
|
else _props.isLoading = true;
|
||||||
|
|
||||||
|
return <Metric {..._props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProposalsCount = props => {
|
||||||
|
const { proposalsCount } = useProposalCount(props.chainId);
|
||||||
|
|
||||||
|
const _props = {
|
||||||
|
...props,
|
||||||
|
label: `Proposals Count`,
|
||||||
|
tooltip: `How much proposals already passed.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (proposalsCount) _props.metric = proposalsCount.toString();
|
||||||
|
else _props.isLoading = true;
|
||||||
|
|
||||||
|
return <Metric {..._props} />;
|
||||||
|
}
|
||||||
29
src/containers/Governance/components/ProposalDiscussion.jsx
Normal file
29
src/containers/Governance/components/ProposalDiscussion.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Link } from "@mui/material";
|
||||||
|
import GhostStyledIcon from "../../../components/Icon/GhostIcon";
|
||||||
|
import ArrowUpIcon from "../../../assets/icons/arrow-up.svg?react";
|
||||||
|
|
||||||
|
const ProposalDiscussion = (linkProps) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
{...linkProps}
|
||||||
|
underline="hover"
|
||||||
|
sx={{ fontFamily: "Ubuntu" }}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
alignContent="center"
|
||||||
|
justifyContent={linkProps.isSmallScreen ? "start" : "center"}
|
||||||
|
className="link-container"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
<GhostStyledIcon
|
||||||
|
style={{ marginTop: "7px" }}
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
className="external-site-link-icon"
|
||||||
|
component={ArrowUpIcon}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProposalDiscussion;
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Typography, Link } from "@mui/material";
|
||||||
|
|
||||||
|
import Modal from "../../../components/Modal/Modal";
|
||||||
|
import { PrimaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
|
const ProposalDiscussionModal = ({ isOpened, closeModal, url }) => {
|
||||||
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
navigator.clipboard.writeText(url)
|
||||||
|
.then(() => {
|
||||||
|
isCopied(true);
|
||||||
|
setTimeout(() => setIsCopied(false), 2000);
|
||||||
|
})
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
headerContent={
|
||||||
|
<Box display="flex" justifyContent="center" alignItems="center" gap="15px">
|
||||||
|
<Typography variant="h4">Discussion URL</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
open={isOpened}
|
||||||
|
onClose={closeModal}
|
||||||
|
maxWidth="460px"
|
||||||
|
minHeight="200px"
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column">
|
||||||
|
<Box marginBottom="20px" display="flex" flexDirection="column" alignItems="center" gap="10px">
|
||||||
|
<Typography align="center">
|
||||||
|
You are leaving the ghost dao app. Check the link on your own, we are not in charge of your destiny.
|
||||||
|
</Typography>
|
||||||
|
<Link
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
underline="hover"
|
||||||
|
sx={{
|
||||||
|
maxWidth: '360px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'inline-block',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
fontStyle: 'italic'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => window.open(url, '_blank', 'noopener,noreferrer')}
|
||||||
|
>
|
||||||
|
Open
|
||||||
|
</PrimaryButton>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProposalDiscussionModal;
|
||||||
23
src/containers/Governance/components/ProposalInfoText.jsx
Normal file
23
src/containers/Governance/components/ProposalInfoText.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Link, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
const ProposalInfoText = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
fontSize="0.875em"
|
||||||
|
lineHeight="15px"
|
||||||
|
>
|
||||||
|
Important: We display only the 10 most recent proposals. Only one proposal can be active at a time.
|
||||||
|
<Link
|
||||||
|
color={theme.colors.primary[300]}
|
||||||
|
href="https://ghostchain.io/ghostdao_litepaper"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>Learn more here.</Link>
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProposalInfoText;
|
||||||
316
src/containers/Governance/components/ProposalsList.jsx
Normal file
316
src/containers/Governance/components/ProposalsList.jsx
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Link,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery
|
||||||
|
} from "@mui/material";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import { getBlockNumber } from "@wagmi/core";
|
||||||
|
|
||||||
|
import { networkAvgBlockSpeed } from "../../../constants";
|
||||||
|
import { prettifySecondsInDays } from "../../../helpers/timeUtil";
|
||||||
|
|
||||||
|
import Chip from "../../../components/Chip/Chip";
|
||||||
|
import Modal from "../../../components/Modal/Modal";
|
||||||
|
import Paper from "../../../components/Paper/Paper";
|
||||||
|
import LinearProgressBar from "../../../components/Progress/LinearProgressBar";
|
||||||
|
import { PrimaryButton, TertiaryButton } from "../../../components/Button";
|
||||||
|
|
||||||
|
import ProposalDiscussionModal from "./ProposalDiscussionModal";
|
||||||
|
import ProposalDiscussion from "./ProposalDiscussion";
|
||||||
|
import ProposalInfoText from "./ProposalInfoText";
|
||||||
|
import { convertStatusToTemplate } from "../helpers";
|
||||||
|
|
||||||
|
import { useScreenSize } from "../../../hooks/useScreenSize";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useProposals,
|
||||||
|
} from "../../../hooks/governance";
|
||||||
|
|
||||||
|
const MAX_PROPOSALS_TO_SHOW = 10;
|
||||||
|
|
||||||
|
const ProposalsList = ({ chainId, config }) => {
|
||||||
|
const isSmallScreen = useScreenSize("md");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [blockNumber, setBlockNumber] = useState(0n);
|
||||||
|
const [selectedDiscussionUrl, setSelectedDiscussionUrl] = useState(undefined);
|
||||||
|
const [proposalsFilter, setProposalFilter] = useState("active");
|
||||||
|
const { proposals } = useProposals(chainId, MAX_PROPOSALS_TO_SHOW);
|
||||||
|
|
||||||
|
getBlockNumber(config).then(block => setBlockNumber(block));
|
||||||
|
|
||||||
|
const isDiscussionModalOpened = useMemo(() => {
|
||||||
|
return selectedDiscussionUrl !== undefined;
|
||||||
|
}, [selectedDiscussionUrl]);
|
||||||
|
|
||||||
|
const filteredProposals = useMemo(() => {
|
||||||
|
switch (proposalsFilter) {
|
||||||
|
case "voted":
|
||||||
|
return proposals.filter(obj => obj.status === "Succeeded" || obj.status === "Defeated");
|
||||||
|
case "created":
|
||||||
|
return proposals.filter(obj => obj.status === "Executed");
|
||||||
|
default:
|
||||||
|
return proposals;
|
||||||
|
}
|
||||||
|
}, [proposals, proposalsFilter]);
|
||||||
|
|
||||||
|
if (proposals?.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="center">
|
||||||
|
<Typography variant="h4">No proposals yet</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProposalDiscussionModal
|
||||||
|
url={selectedDiscussionUrl}
|
||||||
|
isOpened={isDiscussionModalOpened}
|
||||||
|
closeModal={() => setSelectedDiscussionUrl(undefined)}
|
||||||
|
/>
|
||||||
|
<Paper headerText="Proposals" fullWidth enableBackground>
|
||||||
|
<Box my="24px" textAlign="center">
|
||||||
|
<ProposalInfoText />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" flexDirection="column" gap="40px">
|
||||||
|
{filteredProposals?.map(proposal => (
|
||||||
|
<ProposalCard
|
||||||
|
key={proposal.id}
|
||||||
|
proposal={proposal}
|
||||||
|
setActive={setSelectedDiscussionUrl}
|
||||||
|
blockNumber={blockNumber}
|
||||||
|
chainId={chainId}
|
||||||
|
openProposal={() => navigate(`/governance/${proposal.id}`)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProposalDiscussionModal
|
||||||
|
url={selectedDiscussionUrl}
|
||||||
|
isOpened={isDiscussionModalOpened}
|
||||||
|
closeModal={() => setSelectedDiscussionUrl(undefined)}
|
||||||
|
/>
|
||||||
|
<Paper headerText="Proposals" fullWidth enableBackground>
|
||||||
|
<ProposalFilterTrigger trigger={proposalsFilter} setTrigger={setProposalFilter} />
|
||||||
|
|
||||||
|
<ProposalTable>
|
||||||
|
{filteredProposals?.map(proposal => (
|
||||||
|
<ProposalRow
|
||||||
|
key={proposal.id}
|
||||||
|
proposal={proposal}
|
||||||
|
setActive={setSelectedDiscussionUrl}
|
||||||
|
blockNumber={blockNumber}
|
||||||
|
chainId={chainId}
|
||||||
|
openProposal={() => navigate(`/governance/${proposal.id}`)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ProposalTable>
|
||||||
|
|
||||||
|
<Box mt="24px" textAlign="center" width="70%" mx="auto">
|
||||||
|
<ProposalInfoText />
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProposalTable = ({ children }) => (
|
||||||
|
<TableContainer>
|
||||||
|
<Table aria-label="Available bonds" style={{ tableLayout: "fixed" }}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={{ width: "100px", padding: "8px 0" }}>Proposal ID</TableCell>
|
||||||
|
<TableCell align="center" style={{ width: "120px", padding: "8px 0" }}>Status</TableCell>
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>Discussion</TableCell>
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>Vote Ends</TableCell>
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>Voting Stats</TableCell>
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
<TableBody>{children}</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProposalRow = ({ proposal, setActive, blockNumber, openProposal, chainId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow id={proposal.id + `--proposal`} data-testid={proposal.id + `--proposal`}>
|
||||||
|
<TableCell style={{ padding: "8px 0" }}>
|
||||||
|
<Typography>GDP-{proposal.id}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
|
<Chip
|
||||||
|
sx={{ width: "88px" }}
|
||||||
|
label={proposal.status}
|
||||||
|
template={convertStatusToTemplate(proposal.status)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
|
<ProposalDiscussion onClick={() => setActive(proposal.discussion)} />
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
|
<Typography>
|
||||||
|
{convertVoteEnds(
|
||||||
|
proposal.id % 2n === 0n,
|
||||||
|
proposal.voteEnds,
|
||||||
|
blockNumber,
|
||||||
|
chainId
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
|
<Box marginLeft="15px" marginRight="15px">
|
||||||
|
<LinearProgressBar
|
||||||
|
barColor={theme.colors.feedback.success}
|
||||||
|
barBackground={theme.colors.feedback.error}
|
||||||
|
variant="determinate"
|
||||||
|
value={69}
|
||||||
|
target={Math.floor(Math.random() * 101)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center" style={{ padding: "8px 0" }}>
|
||||||
|
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => openProposal()}
|
||||||
|
sx={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
|
||||||
|
</PrimaryButton>}
|
||||||
|
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => openProposal()}
|
||||||
|
sx={{ maxWidth: "150px" }}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</TertiaryButton>}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProposalCard = ({ proposal, setActive, blockNumber, openProposal, chainId }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery('(max-width: 450px)');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box id={proposal.id + `--proposal`} data-testid={proposal.id + `--proposal`}>
|
||||||
|
<Box display="flex" flexDirection={isSmallScreen ? "column" : "row"} justifyContent="space-between">
|
||||||
|
<Box display="flex" flexDirection="column" width="100%">
|
||||||
|
<Box display="flex" flexDirection="row" alignItems="center" width="100%" gap="10px">
|
||||||
|
<Typography variant="h3">GIP-{proposal.id}</Typography>
|
||||||
|
<Chip
|
||||||
|
sx={{ width: "88px" }}
|
||||||
|
label={proposal.status}
|
||||||
|
template={convertStatusToTemplate(proposal.status)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Typography>
|
||||||
|
{convertVoteEnds(
|
||||||
|
proposal.id % 2n === 0n,
|
||||||
|
proposal.voteEnds,
|
||||||
|
blockNumber,
|
||||||
|
chainId
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box width="150px">
|
||||||
|
<ProposalDiscussion
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
onClick={() => setActive(proposal.discussion)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box marginTop="15px" marginBottom="15px">
|
||||||
|
<LinearProgressBar
|
||||||
|
barColor={theme.colors.feedback.success}
|
||||||
|
barBackground={theme.colors.feedback.error}
|
||||||
|
variant="determinate"
|
||||||
|
value={69}
|
||||||
|
target={Math.floor(Math.random() * 101)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box marginBottom="20px">
|
||||||
|
{(proposal.status === "Active" || proposal.status === "Succeeded") && <PrimaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => openProposal()}
|
||||||
|
>
|
||||||
|
{proposal.status === "Succeeded" ? "Execute" : "Vote"}
|
||||||
|
</PrimaryButton>}
|
||||||
|
{(proposal.status !== "Active" && proposal.status !== "Succeeded") && <TertiaryButton
|
||||||
|
fullWidth
|
||||||
|
onClick={() => openProposal()}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</TertiaryButton>}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProposalFilterTrigger = ({ trigger, setTrigger }) => {
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
centered
|
||||||
|
textColor="primary"
|
||||||
|
indicatorColor="primary"
|
||||||
|
value={trigger}
|
||||||
|
aria-label="Proposal filter tabs"
|
||||||
|
onChange={(_, view) => setTrigger(view)}
|
||||||
|
TabIndicatorProps={{ style: { display: "none" } }}
|
||||||
|
>
|
||||||
|
<Tab aria-label="proposal-filter-active-button" value="active" label="Active" style={{ fontSize: "1rem" }} />
|
||||||
|
<Tab aria-label="proposal-filter-voted-button" value="voted" label="Voted" style={{ fontSize: "1rem" }} />
|
||||||
|
<Tab aria-label="proposal-filter-created-button" value="created" label="Created" style={{ fontSize: "1rem" }} />
|
||||||
|
</Tabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertVoteEnds = (tmp, voteEnds, blockNumber, chainId) => {
|
||||||
|
const tmpVoteSeconds = Number(voteEnds * networkAvgBlockSpeed(chainId));
|
||||||
|
const tmpSeconds = (tmp ? tmpVoteSeconds : -tmpVoteSeconds);
|
||||||
|
|
||||||
|
const result = prettifySecondsInDays(tmpSeconds);
|
||||||
|
if (result === "now") {
|
||||||
|
return new Date(Date.now()).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return `in ${result}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProposalsList;
|
||||||
14
src/containers/Governance/helpers.js
Normal file
14
src/containers/Governance/helpers.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const convertStatusToTemplate = (status) => {
|
||||||
|
switch (status.toUpperCase()) {
|
||||||
|
case "EXECUTED":
|
||||||
|
return 'info';
|
||||||
|
case "CANCELED":
|
||||||
|
return 'warning';
|
||||||
|
case "SUCCEEDED":
|
||||||
|
return 'success';
|
||||||
|
case "DEFEATED":
|
||||||
|
return 'error';
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
import { Dispatch, SetStateAction, useState, useEffect } from "react";
|
||||||
import { Box, Container, Grid, Divider, Typography, Link, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Container, Grid, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
import Paper from "../../components/Paper/Paper";
|
import Paper from "../../components/Paper/Paper";
|
||||||
|
|||||||
108
src/hooks/governance/index.js
Normal file
108
src/hooks/governance/index.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
|
||||||
|
import { getTokenDecimals } from "../helpers";
|
||||||
|
|
||||||
|
export const useMinQuorum = (chainId) => {
|
||||||
|
const numerator = 69n;
|
||||||
|
const denominator = 100n;
|
||||||
|
|
||||||
|
let percentage = 0;
|
||||||
|
|
||||||
|
if (numerator && denominator && denominator > 0n) {
|
||||||
|
percentage = Number(100n * numerator / denominator) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { numerator, denominator, percentage }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalThreshold = (chainId, name) => {
|
||||||
|
const decimals = getTokenDecimals(name);
|
||||||
|
const threshold = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
|
||||||
|
|
||||||
|
return { threshold };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalCount = (chainId) => {
|
||||||
|
const proposalsCount = 1337n;
|
||||||
|
return { proposalsCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalStatus = (chainId, proposalId) => {
|
||||||
|
const status = "Succeeded";
|
||||||
|
return { status };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalProposer = (chainId, proposalId) => {
|
||||||
|
const proposer = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F";
|
||||||
|
return { proposer };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalLocked = (chainId, proposalId) => {
|
||||||
|
const decimals = getTokenDecimals(name);
|
||||||
|
const locked = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
|
||||||
|
return { locked }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalQuorum = (chainId, proposalId) => {
|
||||||
|
const decimals = getTokenDecimals(name);
|
||||||
|
const quorum = new DecimalBigNumber(1337_000_000_000_000_000_000n, decimals);
|
||||||
|
return { quorum }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalVotes = (chainId, proposalId) => {
|
||||||
|
const decimals = getTokenDecimals(name);
|
||||||
|
const forVotes = new DecimalBigNumber(420_000_000_000_000_000_000n, decimals);
|
||||||
|
const againstVotes = new DecimalBigNumber(69_000_000_000_000_000_000n, decimals);
|
||||||
|
return { forVotes, againstVotes }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalSnapshot = (chainId, proposalId) => {
|
||||||
|
const snapshot = Math.floor((Date.now() - (3 * 24 * 60 * 60 * 1000)) / 1000);
|
||||||
|
return { snapshot };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalDeadline = (chainId, proposalId) => {
|
||||||
|
const deadline = Math.floor(Date.now() / 1000);
|
||||||
|
return { deadline };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposalVotingDelay = (chainId, proposalId) => {
|
||||||
|
const delay = 1;
|
||||||
|
return { delay };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProposals = (chainId, depth) => {
|
||||||
|
const decimals = getTokenDecimals(name);
|
||||||
|
const { proposalsCount } = useProposalCount(chainId);
|
||||||
|
|
||||||
|
let iterator = proposalsCount ? proposalsCount : 0n;
|
||||||
|
const bigIntDepth = BigInt(depth);
|
||||||
|
const edgeProposalId = iterator > bigIntDepth ? iterator - bigIntDepth : 0n;
|
||||||
|
|
||||||
|
const statuses = [
|
||||||
|
"Active",
|
||||||
|
"Executed",
|
||||||
|
"Canceled",
|
||||||
|
"Succeeded",
|
||||||
|
"Defeated"
|
||||||
|
];
|
||||||
|
let proposals = [];
|
||||||
|
|
||||||
|
while (iterator > proposalsCount - bigIntDepth) {
|
||||||
|
iterator -= 1n;
|
||||||
|
|
||||||
|
const voteEnds = 50n;
|
||||||
|
const yesVotes = new DecimalBigNumber(1337_000_000_000_000_000_000, decimals);
|
||||||
|
const noVotes = new DecimalBigNumber(420_000_000_000_000_000_000, decimals);
|
||||||
|
|
||||||
|
proposals.push({
|
||||||
|
id: iterator,
|
||||||
|
discussion: "https://google.com",
|
||||||
|
status: statuses[Number(iterator) % statuses.length],
|
||||||
|
voteEnds,
|
||||||
|
yesVotes,
|
||||||
|
noVotes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { proposals };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user