Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c3f867f06 | ||
|
|
1a95814925 | ||
|
|
a6d7a5cb4e |
@@ -4,11 +4,7 @@ VITE_ALCHEMY_API_KEY=
|
||||
|
||||
VITE_PK_ENCRYPTION_KEY=
|
||||
|
||||
VITE_V3_TOKEN_ADDRESS=
|
||||
VITE_TOKEN_MIGRATION_URI=
|
||||
VITE_WALLETCONNECT2_PROJECT_ID=
|
||||
|
||||
AMPLITUDE_API_KEY=
|
||||
BUGSNAG_API_KEY=
|
||||
IOS_APP_ID=
|
||||
INTERCOM_APP_ID=
|
||||
STATUS_PAGE_SCRIPT_URI=
|
||||
VITE_V3_TOKEN_ADDRESS=
|
||||
VITE_TOKEN_MIGRATION_URI=
|
||||
@@ -1,9 +1,6 @@
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
|
||||
Subject to your compliance with applicable law and the v4 Terms of Use, available at dydx.exchange/legal, you are granted the right to use the Program or Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Program or Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Program or Licensed Work will automatically and immediately terminate.
|
||||
|
||||
|
||||
The “Program” or “Licensed Work” shall mean any of the following: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. repository reflecting a copy of, or link to, this license.
|
||||
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
|
||||
|
||||
|
||||
The GNU Affero General Public License
|
||||
|
||||
@@ -69,21 +69,6 @@ You'll need to provide a Wallet Connect project id to enable onboarding and wall
|
||||
- Create a project on https://cloud.walletconnect.com/app
|
||||
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
|
||||
|
||||
## Part 4: Set Enviornment variables
|
||||
Set environment variables via `.env`.
|
||||
|
||||
- `VITE_BASE_URL` (required): The base URL of the deployment (e.g., `https://www.example.com`).
|
||||
- `VITE_ALCHEMY_API_KEY` (optional): Add an Alchemy API key for EVM interactions; the app will fall back to public RPCs if not provided.
|
||||
- `VITE_PK_ENCRYPTION_KEY` (optional): AES encryption key used for signature obfuscation; necessary for enabling the "Remember Me" feature.
|
||||
- `VITE_V3_TOKEN_ADDRESS` (optional): Address of the V3 $DYDX token.
|
||||
- `VITE_TOKEN_MIGRATION_URI` (optional): The URL of the token migration website.
|
||||
- `AMPLITUDE_API_KEY` (optional): Amplitude API key for enabling Amplitude; used with `pnpm run build:inject-amplitude`.
|
||||
- `BUGSNAG_API_KEY` (optional): API key for enabling Bugsnag; used with `pnpm run build:inject-bugsnag`.
|
||||
- `IOS_APP_ID` (optional): iOS app ID used for enabling deep linking to the iOS app; used with `pnpm run build:inject-app-deeplinks`.
|
||||
- `INTERCOM_APP_ID` (optional): Used for enabling Intercom; utilized with `pnpm run build:inject-intercom`.
|
||||
- `STATUS_PAGE_SCRIPT_URI` (optional): Used for enabling the status page; used with `pnpm run build:inject-statuspage`.
|
||||
|
||||
|
||||
# Deployments
|
||||
|
||||
## Deploying with Vercel
|
||||
@@ -98,11 +83,6 @@ For the "Build & Development Settings", we recommend the following:
|
||||
- Framework Preset: `Vite`
|
||||
- Build Command (override): `pnpm run build`
|
||||
|
||||
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
|
||||
```
|
||||
pnpm run build --mode testnet
|
||||
```
|
||||
|
||||
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
|
||||
`pnpm run build:inject-amplitude` and `pnpm run build:inject-bugsnag`. You will need to provide your own API keys for these services. In the Environment Variables section, name the variables as `AMPLITUDE_API_KEY` and `BUGSNAG_API_KEY` and provide the respective keys as their values.
|
||||
|
||||
|
||||
@@ -39,16 +39,14 @@
|
||||
"@cosmjs/proto-signing": "^0.31.0",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||
"@dydxprotocol/v4-abacus": "^1.0.24",
|
||||
"@dydxprotocol/v4-abacus": "^1.0.3",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.0",
|
||||
"@dydxprotocol/v4-localization": "^1.0.5",
|
||||
"@dydxprotocol/v4-localization": "^1.0.0",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-navigation-menu": "^1.1.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
@@ -98,7 +96,6 @@
|
||||
"long": "^5.2.3",
|
||||
"luxon": "^3.3.0",
|
||||
"qr-code-styling": "1.6.0-rc.1",
|
||||
"query-string": "^8.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-aria": "^3.25.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -111,7 +108,7 @@
|
||||
"styled-components": "^5.3.11",
|
||||
"use-latest": "^1.2.1",
|
||||
"viem": "^1.12.2",
|
||||
"wagmi": "^1.4.5"
|
||||
"wagmi": "^1.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
|
||||
@@ -23,23 +23,20 @@ dependencies:
|
||||
specifier: ^0.31.0
|
||||
version: 0.31.0
|
||||
'@dydxprotocol/v4-abacus':
|
||||
specifier: ^1.0.24
|
||||
version: 1.0.24
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@dydxprotocol/v4-client-js':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
'@dydxprotocol/v4-localization':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
'@ethersproject/providers':
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
'@js-joda/core':
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-collapsible':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -49,9 +46,6 @@ dependencies:
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-icons':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(react@18.2.0)
|
||||
'@radix-ui/react-navigation-menu':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -199,9 +193,6 @@ dependencies:
|
||||
qr-code-styling:
|
||||
specifier: 1.6.0-rc.1
|
||||
version: 1.6.0-rc.1
|
||||
query-string:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
@@ -239,8 +230,8 @@ dependencies:
|
||||
specifier: ^1.12.2
|
||||
version: 1.12.2(typescript@5.1.3)
|
||||
wagmi:
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
specifier: ^1.3.7
|
||||
version: 1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
@@ -984,8 +975,8 @@ packages:
|
||||
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
||||
dev: true
|
||||
|
||||
/@dydxprotocol/v4-abacus@1.0.24:
|
||||
resolution: {integrity: sha512-wDGSjkrc3Se6Ev7UTjPgJV7PiyzZSz2mJwOTZikLwH7W3k1iPYGQmaCd7TudFk8h2aSdlEwNsQBCf0sLoyvHaQ==}
|
||||
/@dydxprotocol/v4-abacus@1.0.3:
|
||||
resolution: {integrity: sha512-GuX37/DMMNSke8ZFcw52II7IVJfUgVGHqoi1Uw2cUb+hgIjjSG3OtbyRWedm5r6nqWq8BNLOexI0VVFB6OMVYg==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-client-js@1.0.0:
|
||||
@@ -1016,8 +1007,8 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-localization@1.0.5:
|
||||
resolution: {integrity: sha512-zZm3GFp/ORz9YjigQFVBlcWmaxYY+XEdIw8AWN5nEbsuiA2Jay+/OA7lO9+nV1J1p1T+TgLo9iczIHJsAo3IsA==}
|
||||
/@dydxprotocol/v4-localization@1.0.0:
|
||||
resolution: {integrity: sha512-U7QjOlzpfSAVEchkNDB3xYRx7okzdeYi83XsnHwI+EHa2quFm/2Y2dhrBkT5fqpZmhcwxb/vG8AZmFSToOf9hA==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-proto@0.4.1:
|
||||
@@ -2183,34 +2174,6 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.10
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.14)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.14)(react@18.2.0)
|
||||
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.14)(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.14)(react@18.2.0)
|
||||
'@types/react': 18.2.14
|
||||
'@types/react-dom': 18.2.6
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
|
||||
peerDependencies:
|
||||
@@ -2512,14 +2475,6 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-icons@1.3.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==}
|
||||
peerDependencies:
|
||||
react: ^16.x || ^17.x || ^18.x
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-id@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==}
|
||||
peerDependencies:
|
||||
@@ -5975,12 +5930,26 @@ packages:
|
||||
pretty-format: 27.5.1
|
||||
dev: true
|
||||
|
||||
/@wagmi/connectors@3.1.3(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-UgwsQKQDFObJVJMf9pDfFoXTv710o4zrTHyhIWKBTMMkLpCMsMxN5+ZaDhBYt/BgoRinfRYQo8uwuwLhxE6Log==}
|
||||
/@wagmi/chains@1.4.0(typescript@5.1.3):
|
||||
resolution: {integrity: sha512-9HwJrhcZ1TxyrCbE10y7s1eSiSiyfGam7AHIOLYExaOX+vpOZ+MNTt4orFEDbEpz1fxwJDPPI38lanAUix1OSA==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.4'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 5.1.3
|
||||
dev: false
|
||||
|
||||
/@wagmi/connectors@2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-klF31togMDd0qQqEcLl5cCGxjMbL0RRXQ8I1vxmEa3KdGzw6Z3ICVzX7/bDfnNEZcOW7BKyAnZDq7rCt5jTOiw==}
|
||||
peerDependencies:
|
||||
'@wagmi/chains': '>=1.3.0'
|
||||
typescript: '>=5.0.4'
|
||||
viem: '>=0.3.35'
|
||||
peerDependenciesMeta:
|
||||
'@wagmi/chains':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
@@ -5988,17 +5957,17 @@ packages:
|
||||
'@ledgerhq/connect-kit-loader': 1.1.2
|
||||
'@safe-global/safe-apps-provider': 0.17.1(typescript@5.1.3)
|
||||
'@safe-global/safe-apps-sdk': 8.1.0(typescript@5.1.3)
|
||||
'@walletconnect/ethereum-provider': 2.10.2(@walletconnect/modal@2.6.2)
|
||||
'@wagmi/chains': 1.4.0(typescript@5.1.3)
|
||||
'@walletconnect/ethereum-provider': 2.8.6(@walletconnect/modal@2.5.9)
|
||||
'@walletconnect/legacy-provider': 2.0.0
|
||||
'@walletconnect/modal': 2.6.2(@types/react@18.2.14)(react@18.2.0)
|
||||
'@walletconnect/utils': 2.10.2
|
||||
'@walletconnect/modal': 2.5.9(react@18.2.0)
|
||||
'@walletconnect/utils': 2.8.6
|
||||
abitype: 0.8.7(typescript@5.1.3)
|
||||
eventemitter3: 4.0.7
|
||||
typescript: 5.1.3
|
||||
viem: 1.12.2(typescript@5.1.3)
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
- '@types/react'
|
||||
- bufferutil
|
||||
- encoding
|
||||
- lokijs
|
||||
@@ -6008,8 +5977,8 @@ packages:
|
||||
- zod
|
||||
dev: false
|
||||
|
||||
/@wagmi/core@1.4.5(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-N9luRb1Uk4tBN9kaYcQSWKE9AsRt/rvZaFt5IZech4JPzNN2sQlfhKd9GEjOXYRDqEPHdDvos7qyBKiDNTz4GA==}
|
||||
/@wagmi/core@1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-TXv9ZlRR5aySfERFuWMuo+lKXC/CoqtxVJZVHPqhK1jY+nldMx3AvrWzzF4CccRaMYcVdvPFepvmxzq2A2VvWg==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.4'
|
||||
viem: '>=0.3.35'
|
||||
@@ -6017,7 +5986,8 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@wagmi/connectors': 3.1.3(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
'@wagmi/chains': 1.4.0(typescript@5.1.3)
|
||||
'@wagmi/connectors': 2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
abitype: 0.8.7(typescript@5.1.3)
|
||||
eventemitter3: 4.0.7
|
||||
typescript: 5.1.3
|
||||
@@ -6062,8 +6032,8 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@walletconnect/core@2.10.2:
|
||||
resolution: {integrity: sha512-JQz/xp3SLEpTeRQctdck2ugSBVEpMxoSE+lFi2voJkZop1hv6P+uqr6E4PzjFluAjeAnKlT1xvra0aFWjPWVcw==}
|
||||
/@walletconnect/core@2.8.6:
|
||||
resolution: {integrity: sha512-rnSqm1KJLcww/v6+UH8JeibQkJ3EKgyUDPfEK0stSEkrIUIcXaFlq3Et8S+vgV8bPhI0MVUhAhFL5OJZ3t2ryg==}
|
||||
dependencies:
|
||||
'@walletconnect/heartbeat': 1.2.1
|
||||
'@walletconnect/jsonrpc-provider': 1.0.13
|
||||
@@ -6076,8 +6046,8 @@ packages:
|
||||
'@walletconnect/relay-auth': 1.0.4
|
||||
'@walletconnect/safe-json': 1.0.2
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.10.2
|
||||
'@walletconnect/utils': 2.10.2
|
||||
'@walletconnect/types': 2.8.6
|
||||
'@walletconnect/utils': 2.8.6
|
||||
events: 3.3.0
|
||||
lodash.isequal: 4.5.0
|
||||
uint8arrays: 3.1.1
|
||||
@@ -6113,8 +6083,8 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: false
|
||||
|
||||
/@walletconnect/ethereum-provider@2.10.2(@walletconnect/modal@2.6.2):
|
||||
resolution: {integrity: sha512-QMYFZ6+rVq2CJLdIPdKK0j1Qm66UA27oQU5V2SrL8EVwl7wFfm0Bq7fnL+qAWeDpn612dNeNErpk/ROa1zWlWg==}
|
||||
/@walletconnect/ethereum-provider@2.8.6(@walletconnect/modal@2.5.9):
|
||||
resolution: {integrity: sha512-wUvJEsXTLmMihrOhQxAs1k9hrWEOT03QBn54P9r9GpJbJ1zEfIjQaXFfi8uup6gldhH+vN38PsbOiLyv/6d3qQ==}
|
||||
peerDependencies:
|
||||
'@walletconnect/modal': '>=2'
|
||||
peerDependenciesMeta:
|
||||
@@ -6125,11 +6095,11 @@ packages:
|
||||
'@walletconnect/jsonrpc-provider': 1.0.13
|
||||
'@walletconnect/jsonrpc-types': 1.0.3
|
||||
'@walletconnect/jsonrpc-utils': 1.0.8
|
||||
'@walletconnect/modal': 2.6.2(@types/react@18.2.14)(react@18.2.0)
|
||||
'@walletconnect/sign-client': 2.10.2
|
||||
'@walletconnect/types': 2.10.2
|
||||
'@walletconnect/universal-provider': 2.10.2
|
||||
'@walletconnect/utils': 2.10.2
|
||||
'@walletconnect/modal': 2.5.9(react@18.2.0)
|
||||
'@walletconnect/sign-client': 2.8.6
|
||||
'@walletconnect/types': 2.8.6
|
||||
'@walletconnect/universal-provider': 2.8.6
|
||||
'@walletconnect/utils': 2.8.6
|
||||
events: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
@@ -6280,34 +6250,32 @@ packages:
|
||||
tslib: 1.14.1
|
||||
dev: false
|
||||
|
||||
/@walletconnect/modal-core@2.6.2(@types/react@18.2.14)(react@18.2.0):
|
||||
resolution: {integrity: sha512-cv8ibvdOJQv2B+nyxP9IIFdxvQznMz8OOr/oR/AaUZym4hjXNL/l1a2UlSQBXrVjo3xxbouMxLb3kBsHoYP2CA==}
|
||||
/@walletconnect/modal-core@2.5.9(react@18.2.0):
|
||||
resolution: {integrity: sha512-isIebwF9hOknGouhS/Ob4YJ9Sa/tqNYG2v6Ua9EkCqIoLimepkG5eC53tslUWW29SLSfQ9qqBNG2+iE7yQXqgw==}
|
||||
dependencies:
|
||||
valtio: 1.11.2(@types/react@18.2.14)(react@18.2.0)
|
||||
buffer: 6.0.3
|
||||
valtio: 1.10.6(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
dev: false
|
||||
|
||||
/@walletconnect/modal-ui@2.6.2(@types/react@18.2.14)(react@18.2.0):
|
||||
resolution: {integrity: sha512-rbdstM1HPGvr7jprQkyPggX7rP4XiCG85ZA+zWBEX0dVQg8PpAgRUqpeub4xQKDgY7pY/xLRXSiCVdWGqvG2HA==}
|
||||
/@walletconnect/modal-ui@2.5.9(react@18.2.0):
|
||||
resolution: {integrity: sha512-nfBaAT9Ls7RZTBBgAq+Nt/3AoUcinIJ9bcq5UHXTV3lOPu/qCKmUC/0HY3GvUK8ykabUAsjr0OAGmcqkB91qug==}
|
||||
dependencies:
|
||||
'@walletconnect/modal-core': 2.6.2(@types/react@18.2.14)(react@18.2.0)
|
||||
lit: 2.8.0
|
||||
'@walletconnect/modal-core': 2.5.9(react@18.2.0)
|
||||
lit: 2.7.5
|
||||
motion: 10.16.2
|
||||
qrcode: 1.5.3
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
dev: false
|
||||
|
||||
/@walletconnect/modal@2.6.2(@types/react@18.2.14)(react@18.2.0):
|
||||
resolution: {integrity: sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA==}
|
||||
/@walletconnect/modal@2.5.9(react@18.2.0):
|
||||
resolution: {integrity: sha512-Zs2RvPwbBNRdBhb50FuJCxi3FJltt1KSpI7odjU/x9GTpTOcSOkmR66PBCy2JvNA0+ztnS1Xs0LVEr3lu7/Jzw==}
|
||||
dependencies:
|
||||
'@walletconnect/modal-core': 2.6.2(@types/react@18.2.14)(react@18.2.0)
|
||||
'@walletconnect/modal-ui': 2.6.2(@types/react@18.2.14)(react@18.2.0)
|
||||
'@walletconnect/modal-core': 2.5.9(react@18.2.0)
|
||||
'@walletconnect/modal-ui': 2.5.9(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
dev: false
|
||||
|
||||
@@ -6363,17 +6331,17 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@walletconnect/sign-client@2.10.2:
|
||||
resolution: {integrity: sha512-vviSLV3f92I0bReX+OLr1HmbH0uIzYEQQFd1MzIfDk9PkfFT/LLAHhUnDaIAMkIdippqDcJia+5QEtT4JihL3Q==}
|
||||
/@walletconnect/sign-client@2.8.6:
|
||||
resolution: {integrity: sha512-rOFTKTHP7oJfXgYHX7+SdB8VbcsEE3ZFG/bMdmZboWaBim1mrY3vUyDdKrNr0VgI3AwBiEQezQDfKxBX0pMSQQ==}
|
||||
dependencies:
|
||||
'@walletconnect/core': 2.10.2
|
||||
'@walletconnect/core': 2.8.6
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/heartbeat': 1.2.1
|
||||
'@walletconnect/jsonrpc-utils': 1.0.8
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.10.2
|
||||
'@walletconnect/utils': 2.10.2
|
||||
'@walletconnect/types': 2.8.6
|
||||
'@walletconnect/utils': 2.8.6
|
||||
events: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
@@ -6402,8 +6370,8 @@ packages:
|
||||
- lokijs
|
||||
dev: false
|
||||
|
||||
/@walletconnect/types@2.10.2:
|
||||
resolution: {integrity: sha512-luNV+07Wdla4STi9AejseCQY31tzWKQ5a7C3zZZaRK/di+rFaAAb7YW04OP4klE7tw/mJRGPTlekZElmHxO8kQ==}
|
||||
/@walletconnect/types@2.8.6:
|
||||
resolution: {integrity: sha512-Z/PFa3W1XdxeTcCtdR6lUsFgZfU/69wWJBPyclPwn7cu1+eriuCr6XZXQpJjib3flU+HnwHiXeUuqZaheehPxw==}
|
||||
dependencies:
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/heartbeat': 1.2.1
|
||||
@@ -6416,17 +6384,17 @@ packages:
|
||||
- lokijs
|
||||
dev: false
|
||||
|
||||
/@walletconnect/universal-provider@2.10.2:
|
||||
resolution: {integrity: sha512-wFgI0LbQ3D56sgaUMsgOHCM5m8WLxiC71BGuCKQfApgsbNMVKugYVy2zWHyUyi8sqTQHI+uSaVpDev4UHq9LEw==}
|
||||
/@walletconnect/universal-provider@2.8.6:
|
||||
resolution: {integrity: sha512-ln1RVv8+oHu9enOJ/oVkjiarneB+4vJCk16znOklIN2JtDHwB8iObDHlQH3UE6ynNTw1iRvaGuPR4g+YdIfB6w==}
|
||||
dependencies:
|
||||
'@walletconnect/jsonrpc-http-connection': 1.0.7
|
||||
'@walletconnect/jsonrpc-provider': 1.0.13
|
||||
'@walletconnect/jsonrpc-types': 1.0.3
|
||||
'@walletconnect/jsonrpc-utils': 1.0.8
|
||||
'@walletconnect/logger': 2.0.1
|
||||
'@walletconnect/sign-client': 2.10.2
|
||||
'@walletconnect/types': 2.10.2
|
||||
'@walletconnect/utils': 2.10.2
|
||||
'@walletconnect/sign-client': 2.8.6
|
||||
'@walletconnect/types': 2.8.6
|
||||
'@walletconnect/utils': 2.8.6
|
||||
events: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-async-storage/async-storage'
|
||||
@@ -6458,8 +6426,8 @@ packages:
|
||||
- lokijs
|
||||
dev: false
|
||||
|
||||
/@walletconnect/utils@2.10.2:
|
||||
resolution: {integrity: sha512-syxXRpc2yhSknMu3IfiBGobxOY7fLfLTJuw+ppKaeO6WUdZpIit3wfuGOcc0Ms3ZPFCrGfyGOoZsCvgdXtptRg==}
|
||||
/@walletconnect/utils@2.8.6:
|
||||
resolution: {integrity: sha512-wcy6e5+COYo7tfNnW8YqidnATdJDIW6vDiWWE7A1F78Sl/VflkaevB9cIgyn8eLdxC1SxXgGoeC2oLP90nnHJg==}
|
||||
dependencies:
|
||||
'@stablelib/chacha20poly1305': 1.0.1
|
||||
'@stablelib/hkdf': 1.0.1
|
||||
@@ -6469,7 +6437,7 @@ packages:
|
||||
'@walletconnect/relay-api': 1.0.9
|
||||
'@walletconnect/safe-json': 1.0.2
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.10.2
|
||||
'@walletconnect/types': 2.8.6
|
||||
'@walletconnect/window-getters': 1.0.1
|
||||
'@walletconnect/window-metadata': 1.0.1
|
||||
detect-browser: 5.3.0
|
||||
@@ -7684,7 +7652,7 @@ packages:
|
||||
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
|
||||
dependencies:
|
||||
cosmos-directory-types: 0.0.6
|
||||
node-fetch-native: 1.4.1
|
||||
node-fetch-native: 1.4.0
|
||||
dev: false
|
||||
|
||||
/cosmos-directory-types@0.0.6:
|
||||
@@ -7903,6 +7871,7 @@ packages:
|
||||
/decode-uri-component@0.4.1:
|
||||
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: true
|
||||
|
||||
/deep-eql@4.1.3:
|
||||
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
||||
@@ -8972,6 +8941,7 @@ packages:
|
||||
/filter-obj@5.1.0:
|
||||
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: true
|
||||
|
||||
/find-cache-dir@3.3.2:
|
||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||
@@ -10398,14 +10368,6 @@ packages:
|
||||
lit-html: 2.8.0
|
||||
dev: false
|
||||
|
||||
/lit@2.8.0:
|
||||
resolution: {integrity: sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==}
|
||||
dependencies:
|
||||
'@lit/reactive-element': 1.6.3
|
||||
lit-element: 3.3.3
|
||||
lit-html: 2.8.0
|
||||
dev: false
|
||||
|
||||
/loader-runner@4.3.0:
|
||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||
engines: {node: '>=6.11.5'}
|
||||
@@ -11321,8 +11283,8 @@ packages:
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: true
|
||||
|
||||
/node-fetch-native@1.4.1:
|
||||
resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==}
|
||||
/node-fetch-native@1.4.0:
|
||||
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.6.12:
|
||||
@@ -11910,6 +11872,7 @@ packages:
|
||||
decode-uri-component: 0.4.1
|
||||
filter-obj: 5.1.0
|
||||
split-on-first: 3.0.0
|
||||
dev: true
|
||||
|
||||
/queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
@@ -12734,6 +12697,7 @@ packages:
|
||||
/split-on-first@3.0.0:
|
||||
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
@@ -13539,19 +13503,15 @@ packages:
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/valtio@1.11.2(@types/react@18.2.14)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==}
|
||||
/valtio@1.10.6(react@18.2.0):
|
||||
resolution: {integrity: sha512-SxN1bHUmdhW6V8qsQTpCgJEwp7uHbntuH0S9cdLQtiohuevwBksbpXjwj5uDMA7bLwg1WKyq9sEpZrx3TIMrkA==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8'
|
||||
react: '>=16.8'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.14
|
||||
proxy-compare: 2.5.1
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
@@ -13774,8 +13734,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/wagmi@1.4.5(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-Ph62E6cO5n2Z8Z5LTyZrkaNprxTsbC4w0qZJT4OJdXrEELziI8z/b4FO6amVFXdu2rDp/wpvF56e4mhKC8/Kdw==}
|
||||
/wagmi@1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
|
||||
resolution: {integrity: sha512-n4Qwby0QB8/JSGjc0gGn4MWZ0BT229agEg6saPGS6VswmPjJLdkLq9qxKn1JptXxPREmb/ZvwHxU++3Z+gQJVA==}
|
||||
peerDependencies:
|
||||
react: '>=17.0.0'
|
||||
typescript: '>=5.0.4'
|
||||
@@ -13787,7 +13747,7 @@ packages:
|
||||
'@tanstack/query-sync-storage-persister': 4.32.6
|
||||
'@tanstack/react-query': 4.32.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@tanstack/react-query-persist-client': 4.32.6(@tanstack/react-query@4.32.6)
|
||||
'@wagmi/core': 1.4.5(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
'@wagmi/core': 1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
|
||||
abitype: 0.8.7(typescript@5.1.3)
|
||||
react: 18.2.0
|
||||
typescript: 5.1.3
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"apps": {
|
||||
"ios": {
|
||||
"scheme": "dydxv4"
|
||||
}
|
||||
},
|
||||
"deployments": {
|
||||
"MAINNET": {
|
||||
"environments": [
|
||||
@@ -43,7 +38,6 @@
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -116,7 +110,6 @@
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -188,7 +181,6 @@
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -208,8 +200,8 @@
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4dev4.dydx.exchange",
|
||||
"socket": "wss://indexer.v4dev4.dydx.exchange"
|
||||
"api": "http://indexer.v4dev4.dydx.exchange",
|
||||
"socket": "ws://indexer.v4dev4.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
@@ -260,7 +252,6 @@
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -332,7 +323,6 @@
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -400,84 +390,12 @@
|
||||
"signTypedDataDomainName": "dYdX Chain"
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-forced-update": {
|
||||
"name": "v4 Staging Forced Update",
|
||||
"ethereumChainId": "5",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4staging.dydx.exchange",
|
||||
"socket": "wss://indexer.v4staging.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"faucet": "https://faucet.v4staging.dydx.exchange",
|
||||
"validators": [
|
||||
"https://validator.v4staging.dydx.exchange"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com"
|
||||
},
|
||||
"links": {
|
||||
"tos": "https://dydx.exchange/v4-terms",
|
||||
"privacy": "https://dydx.exchange/privacy",
|
||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||
"documentation": "https://v4-teacher.vercel.app/",
|
||||
"community": "https://discord.com/invite/dydx",
|
||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||
"blogs": "https://www.dydx.foundation/blog"
|
||||
},
|
||||
"wallets": {
|
||||
"walletconnect": {
|
||||
"client": {
|
||||
"name": "dYdX v4",
|
||||
"description": "dYdX v4 App",
|
||||
"iconUrl": "/logos/dydx-x.png"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||
"signTypedDataDomainName": "dYdX Chain"
|
||||
},
|
||||
"apps": {
|
||||
"ios": {
|
||||
"minimalVersion": "1.0",
|
||||
"build":40000,
|
||||
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-west": {
|
||||
"name": "v4 Staging West",
|
||||
"ethereumChainId": "5",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -551,7 +469,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -629,7 +546,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -704,7 +620,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -779,7 +694,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -854,7 +768,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -929,7 +842,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -964,7 +876,6 @@
|
||||
"privacy": "https://dydx.exchange/privacy",
|
||||
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||
"documentation": "https://docs.dydx.exchange/",
|
||||
"community": "https://discord.com/invite/dydx",
|
||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||
@@ -995,7 +906,6 @@
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
@@ -1070,7 +980,6 @@
|
||||
"dydxChainId": "[mainnet chain id]",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"squidIntegratorId": "[mainnet squid integrator id]",
|
||||
"isMainNet": true,
|
||||
"tokens": {
|
||||
"comment": "Change according to mainnet release",
|
||||
@@ -1105,7 +1014,7 @@
|
||||
"tos": "[HTTP link to TOS]",
|
||||
"privacy": "[HTTP link to Privacy Policy]",
|
||||
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
|
||||
"mintscanBase": "[HTTP link to TOS mintscan base url]",
|
||||
"mintscanBase": "[HTTP link to TOS mintsacn base url]",
|
||||
"feedback": "[HTTP link to feedback form, can be null]",
|
||||
"blogs": "[HTTP link to blogs, can be null]",
|
||||
"foundation": "[HTTP link to foundation, can be null]",
|
||||
|
||||
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 817 B |
@@ -23,7 +23,6 @@ import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
||||
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||
import { SquidProvider } from '@/hooks/useSquid';
|
||||
import { TestFlagsProvider } from '@/hooks/useTestFlags';
|
||||
|
||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||
|
||||
@@ -124,7 +123,6 @@ const providers = [
|
||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(TestFlagsProvider),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(RestrictionProvider),
|
||||
wrapProvider(DydxProvider),
|
||||
|
||||
@@ -13,9 +13,7 @@ export const Checkboxes: Story<CheckboxProps> = (args) => {
|
||||
<Checkbox
|
||||
{...args}
|
||||
checked={isChecked}
|
||||
onCheckedChange={setIsChecked}
|
||||
id="story-checkbox"
|
||||
label="example label"
|
||||
onClick={(e: React.ChangeEvent) => setIsChecked((e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { Root, Indicator } from '@radix-ui/react-checkbox';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { breakpoints } from '@/styles';
|
||||
|
||||
type ElementProps = {
|
||||
checked: boolean;
|
||||
onCheckedChange: (checked: boolean) => void;
|
||||
id?: string;
|
||||
label?: React.ReactNode;
|
||||
onClick: (e: React.ChangeEvent) => void;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
@@ -17,54 +13,85 @@ type StyleProps = {
|
||||
|
||||
export type CheckboxProps = ElementProps & StyleProps;
|
||||
|
||||
export const Checkbox: React.FC<CheckboxProps> = ({
|
||||
checked,
|
||||
className,
|
||||
onCheckedChange,
|
||||
id,
|
||||
label,
|
||||
}) => (
|
||||
<Styled.Container>
|
||||
<Styled.Root className={className} checked={checked} onCheckedChange={onCheckedChange} id={id}>
|
||||
<Styled.Indicator>
|
||||
<CheckIcon />
|
||||
</Styled.Indicator>
|
||||
</Styled.Root>
|
||||
{label && <label htmlFor={id}>{label}</label>}
|
||||
</Styled.Container>
|
||||
);
|
||||
export const Checkbox: React.FC<CheckboxProps> = ({ checked, className, onClick }) => {
|
||||
return (
|
||||
<Styled.CheckboxWrapper className={className}>
|
||||
<Styled.Checkbox
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={onClick}
|
||||
/>
|
||||
<Styled.CustomCheckbox />
|
||||
</Styled.CheckboxWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Container = styled.div`
|
||||
${layoutMixins.row}
|
||||
gap: 1ch;
|
||||
font: var(--font-small-book);
|
||||
Styled.CustomCheckbox = styled.span`
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
--checkbox-backgroundColor: var(--color-layer-0);
|
||||
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
|
||||
border-radius: 0.375rem;
|
||||
border: var(--border-width) solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--checkbox-backgroundColor);
|
||||
|
||||
&[data-state='checked'] {
|
||||
--checkbox-backgroundColor: var(--color-accent);
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 0.25em;
|
||||
left: 0.4375em;
|
||||
width: 0.3125em;
|
||||
height: 0.5em;
|
||||
border: solid var(--checkbox-checkColor);
|
||||
border-width: 0 0.125em 0.125em 0;
|
||||
border-radius: 0.0625em;
|
||||
opacity: 0;
|
||||
transform: rotate(0deg) scale(0);
|
||||
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
|
||||
&::after {
|
||||
top: 0.25em;
|
||||
left: 0.4375em;
|
||||
width: 0.4375em;
|
||||
height: 0.625em;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Indicator = styled(Indicator)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
Styled.CheckboxWrapper = styled.div`
|
||||
--checkbox-backgroundColor: var(--color-layer-1);
|
||||
--checkbox-checkColor: var(--color-text-1);
|
||||
|
||||
color: var(--color-text-2);
|
||||
display: flex;
|
||||
border-radius: 0.25em;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
> input:checked ~ ${Styled.CustomCheckbox}::after {
|
||||
opacity: 1;
|
||||
transform: rotate(40deg) scale(1);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Checkbox = styled.input`
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { Story } from '@ladle/react';
|
||||
|
||||
import { Collapsible, type CollapsibleProps } from '@/components/Collapsible';
|
||||
import { Collapsible } from '@/components/Collapsible';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
import { IconName } from './Icon';
|
||||
|
||||
export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
|
||||
export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) => (
|
||||
<StoryWrapper>
|
||||
<Collapsible {...args}>
|
||||
<Collapsible
|
||||
label="Collapsible List of Items"
|
||||
{...args}
|
||||
>
|
||||
<ul>
|
||||
<li>Collapsible Item 1</li>
|
||||
<li>Collapsible Item 2</li>
|
||||
@@ -20,8 +23,6 @@ export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
|
||||
|
||||
CollapsibleStory.args = {
|
||||
disabled: false,
|
||||
withTrigger: true,
|
||||
label: 'Collapsible List of Items',
|
||||
};
|
||||
|
||||
CollapsibleStory.argTypes = {
|
||||
|
||||
@@ -14,7 +14,6 @@ type ElementProps = {
|
||||
label: React.ReactNode;
|
||||
triggerIcon?: IconName;
|
||||
children: React.ReactNode;
|
||||
withTrigger?: boolean;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
@@ -24,8 +23,6 @@ type StyleProps = {
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export type CollapsibleProps = ElementProps & StyleProps;
|
||||
|
||||
export const Collapsible = ({
|
||||
defaultOpen = false,
|
||||
disabled,
|
||||
@@ -38,29 +35,26 @@ export const Collapsible = ({
|
||||
triggerIconSide = 'left',
|
||||
fullWidth,
|
||||
className,
|
||||
withTrigger = true,
|
||||
}: CollapsibleProps) => (
|
||||
}: ElementProps & StyleProps) => (
|
||||
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
|
||||
{withTrigger && (
|
||||
<Styled.Trigger className={className} disabled={disabled}>
|
||||
{triggerIconSide === 'right' && (
|
||||
<>
|
||||
{label}
|
||||
{fullWidth && <HorizontalSeparatorFiller />}
|
||||
</>
|
||||
)}
|
||||
<Styled.Trigger className={className} disabled={disabled}>
|
||||
{triggerIconSide === 'right' && (
|
||||
<>
|
||||
{label}
|
||||
{fullWidth && <HorizontalSeparatorFiller />}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Styled.TriggerIcon>
|
||||
<Icon iconName={triggerIcon} />
|
||||
</Styled.TriggerIcon>
|
||||
{triggerIconSide === 'left' && (
|
||||
<>
|
||||
{fullWidth && <HorizontalSeparatorFiller />}
|
||||
{label}
|
||||
</>
|
||||
)}
|
||||
</Styled.Trigger>
|
||||
)}
|
||||
<Styled.TriggerIcon>
|
||||
<Icon iconName={triggerIcon} />
|
||||
</Styled.TriggerIcon>
|
||||
{triggerIconSide === 'left' && (
|
||||
<>
|
||||
{fullWidth && <HorizontalSeparatorFiller />}
|
||||
{label}
|
||||
</>
|
||||
)}
|
||||
</Styled.Trigger>
|
||||
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
|
||||
</Styled.Root>
|
||||
);
|
||||
|
||||
@@ -225,7 +225,6 @@ Styled.CollapsibleContent = styled(CollapsibleContent)`
|
||||
`;
|
||||
|
||||
Styled.IconButton = styled(IconButton)`
|
||||
--button-icon-size: 1em;
|
||||
${Styled.CollapsibleRoot}[data-state='closed'] & {
|
||||
rotate: -0.5turn;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,7 @@ type PickComboxMenuProps<
|
||||
MenuGroupValue extends string | number
|
||||
> = Pick<
|
||||
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
|
||||
| 'inputPlaceholder'
|
||||
| 'onItemSelected'
|
||||
| 'slotEmpty'
|
||||
| 'withItemBorders'
|
||||
| 'withSearch'
|
||||
| 'withStickyLayout'
|
||||
'inputPlaceholder' | 'onItemSelected' | 'slotEmpty' | 'withSearch' | 'withStickyLayout'
|
||||
>;
|
||||
|
||||
type PickDialogProps = Pick<
|
||||
@@ -39,7 +34,6 @@ type PickDialogProps = Pick<
|
||||
| 'slotHeaderInner'
|
||||
| 'slotTrigger'
|
||||
| 'slotFooter'
|
||||
| 'preventClose'
|
||||
>;
|
||||
|
||||
export const ComboboxDialogMenu = <
|
||||
@@ -58,13 +52,11 @@ export const ComboboxDialogMenu = <
|
||||
onItemSelected,
|
||||
inputPlaceholder,
|
||||
slotEmpty,
|
||||
withItemBorders,
|
||||
withSearch,
|
||||
withStickyLayout = true,
|
||||
children,
|
||||
|
||||
placement = DialogPlacement.Default,
|
||||
preventClose,
|
||||
className,
|
||||
}: ElementProps<MenuItemValue, MenuGroupValue> &
|
||||
PickComboxMenuProps<MenuItemValue, MenuGroupValue> &
|
||||
@@ -80,7 +72,6 @@ export const ComboboxDialogMenu = <
|
||||
slotTrigger={slotTrigger}
|
||||
slotFooter={slotFooter}
|
||||
placement={placement}
|
||||
preventClose={preventClose}
|
||||
className={className}
|
||||
>
|
||||
<Styled.ComboboxMenu
|
||||
@@ -89,7 +80,6 @@ export const ComboboxDialogMenu = <
|
||||
title={title}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
slotEmpty={slotEmpty}
|
||||
withItemBorders={withItemBorders}
|
||||
withSearch={withSearch}
|
||||
withStickyLayout={withStickyLayout}
|
||||
/>
|
||||
@@ -102,8 +92,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.Dialog = styled(Dialog)`
|
||||
/* Params */
|
||||
--comboboxDialogMenu-backgroundColor: var(--color-layer-2);
|
||||
--comboboxDialogMenu-item-gap: 0.5rem;
|
||||
--comboboxDialogMenu-item-padding: 0.5rem 1rem;
|
||||
|
||||
/* Overrides */
|
||||
& {
|
||||
@@ -127,6 +115,5 @@ Styled.Dialog = styled(Dialog)`
|
||||
|
||||
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
|
||||
--comboboxMenu-item-gap: var(--comboboxDialogMenu-item-gap);
|
||||
--comboboxMenu-item-padding: var(--comboboxDialogMenu-item-padding);
|
||||
--comboboxMenu-item-gap: var(--comboxDialogMenu-item-gap, 0.5rem);
|
||||
`;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Fragment, type ReactNode, useState } from 'react';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { Command } from 'cmdk';
|
||||
|
||||
import { MenuItem, type MenuConfig } from '@/constants/menus';
|
||||
import { type MenuConfig } from '@/constants/menus';
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
@@ -20,7 +20,6 @@ type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
withItemBorders?: boolean;
|
||||
withStickyLayout?: boolean;
|
||||
};
|
||||
|
||||
@@ -39,7 +38,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
withSearch = true,
|
||||
|
||||
className,
|
||||
withItemBorders,
|
||||
withStickyLayout,
|
||||
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
|
||||
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
|
||||
@@ -82,7 +80,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
<Styled.Group
|
||||
key={group.group}
|
||||
heading={group.groupLabel}
|
||||
$withItemBorders={withItemBorders}
|
||||
$withStickyLayout={withStickyLayout}
|
||||
>
|
||||
{group.items.map((item) => (
|
||||
@@ -102,31 +99,22 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
}
|
||||
}}
|
||||
disabled={item.disabled}
|
||||
$withItemBorders={withItemBorders}
|
||||
>
|
||||
{
|
||||
<>
|
||||
{item.slotBefore}
|
||||
{item.slotCustomContent ?? (
|
||||
<Styled.ItemLabel>
|
||||
<span>
|
||||
{typeof item.label === 'string'
|
||||
? `${item.label}${item.subitems?.length ? '…' : ''}`
|
||||
: item.label}
|
||||
{item.tag && (
|
||||
<>
|
||||
{' '}
|
||||
<Tag>{item.tag}</Tag>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
{item.description && <span>{item.description}</span>}
|
||||
</Styled.ItemLabel>
|
||||
{item.slotBefore}
|
||||
<Styled.ItemLabel>
|
||||
<span>
|
||||
{`${item.label}${item.subitems?.length ? '…' : ''}`}
|
||||
{item.tag && (
|
||||
<>
|
||||
{' '}
|
||||
<Tag>{item.tag}</Tag>
|
||||
</>
|
||||
)}
|
||||
{item.slotAfter}
|
||||
{item.subitems && '→'}
|
||||
</>
|
||||
}
|
||||
</span>
|
||||
{item.description && <span>{item.description}</span>}
|
||||
</Styled.ItemLabel>
|
||||
{item.slotAfter}
|
||||
{item.subitems && '→'}
|
||||
</Styled.Item>
|
||||
|
||||
{searchValue &&
|
||||
@@ -149,7 +137,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
onItemSelected?.();
|
||||
}}
|
||||
disabled={subitem.disabled}
|
||||
$withItemBorders={withItemBorders}
|
||||
>
|
||||
{subitem.slotBefore}
|
||||
<Styled.ItemLabel>
|
||||
@@ -182,17 +169,14 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
||||
--comboboxMenu-backgroundColor: var(--color-layer-2);
|
||||
|
||||
--comboboxMenu-input-backgroundColor: var(--color-layer-3);
|
||||
--comboboxMenu-input-height: 2.5rem;
|
||||
|
||||
--comboboxMenu-item-checked-backgroundColor: ;
|
||||
--comboboxMenu-item-checked-textColor: ;
|
||||
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
|
||||
--comboboxMenu-item-highlighted-textColor: var(--color-text-1);
|
||||
--comboboxMenu-item-backgroundColor: ;
|
||||
--comboboxMenu-item-gap: 0.5rem;
|
||||
--comboboxMenu-item-padding: 0.5em 1em;
|
||||
|
||||
display: grid;
|
||||
align-content: start;
|
||||
@@ -241,7 +225,7 @@ Styled.Input = styled(Command.Input)`
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLayout?: boolean }>`
|
||||
Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>`
|
||||
color: var(--color-text-0);
|
||||
|
||||
> [cmdk-group-heading] {
|
||||
@@ -264,14 +248,6 @@ Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLa
|
||||
${layoutMixins.stickyArea3}
|
||||
}
|
||||
`}
|
||||
|
||||
${({ $withItemBorders }) =>
|
||||
$withItemBorders &&
|
||||
css`
|
||||
> [cmdk-group-items] {
|
||||
padding: var(--border-width) 0;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
||||
@@ -298,14 +274,13 @@ Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
|
||||
Styled.Item = styled(Command.Item)`
|
||||
${layoutMixins.scrollSnapItem}
|
||||
${popoverMixins.item}
|
||||
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
|
||||
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
|
||||
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
|
||||
--item-gap: var(--comboboxMenu-item-gap);
|
||||
--item-padding: var(--comboboxMenu-item-padding);
|
||||
|
||||
background-color: var(--comboboxMenu-backgroundColor, inherit);
|
||||
|
||||
@@ -316,12 +291,6 @@ Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
|
||||
opacity: 0.75;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
${({ $withItemBorders }) =>
|
||||
$withItemBorders &&
|
||||
css`
|
||||
${layoutMixins.withOuterBorder}
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.ItemLabel = styled.div`
|
||||
@@ -340,8 +309,6 @@ Styled.ItemLabel = styled.div`
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
Styled.Empty = styled(Command.Empty)`
|
||||
|
||||
@@ -15,10 +15,10 @@ CopyButtonStory.args = {
|
||||
};
|
||||
|
||||
CopyButtonStory.argTypes = {
|
||||
buttonType: {
|
||||
options: ["text", "icon", "default"],
|
||||
shownAsText: {
|
||||
options: [true, false],
|
||||
control: { type: 'select' },
|
||||
defaultValue: "default",
|
||||
defaultValue: false,
|
||||
},
|
||||
children: {
|
||||
options: ['some text to copy'],
|
||||
|
||||
@@ -9,20 +9,14 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Button, ButtonProps } from './Button';
|
||||
import { Icon, IconName } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
|
||||
export type CopyButtonProps = {
|
||||
value?: string;
|
||||
buttonType?: 'text' | 'icon' | 'default';
|
||||
shownAsText?: boolean;
|
||||
children?: React.ReactNode;
|
||||
} & ButtonProps;
|
||||
|
||||
export const CopyButton = ({
|
||||
value,
|
||||
buttonType = 'default',
|
||||
children,
|
||||
...buttonProps
|
||||
}: CopyButtonProps) => {
|
||||
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@@ -34,19 +28,11 @@ export const CopyButton = ({
|
||||
setTimeout(() => setCopied(false), 500);
|
||||
};
|
||||
|
||||
return buttonType === 'text' ? (
|
||||
return shownAsText ? (
|
||||
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
||||
{children}
|
||||
<Styled.Icon copied={copied} iconName={copied ? IconName.Check : IconName.Copy} />
|
||||
<Icon iconName={IconName.Copy} />
|
||||
</Styled.InlineRow>
|
||||
) : buttonType === 'icon' ? (
|
||||
<Styled.IconButton
|
||||
{...buttonProps}
|
||||
copied={copied}
|
||||
action={ButtonAction.Base}
|
||||
iconName={copied ? IconName.Check : IconName.Copy}
|
||||
onClick={onCopy}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
{...buttonProps}
|
||||
@@ -77,21 +63,3 @@ Styled.InlineRow = styled.div<{ copied: boolean }>`
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.Icon = styled(Icon)<{ copied: boolean }>`
|
||||
${({ copied }) =>
|
||||
copied &&
|
||||
css`
|
||||
color: var(--color-positive);
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.IconButton = styled(IconButton)<{ copied: boolean }>`
|
||||
${({ copied }) =>
|
||||
copied &&
|
||||
css`
|
||||
svg {
|
||||
color: var(--color-positive);
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -38,7 +38,6 @@ type ElementProps = {
|
||||
slotTrigger?: React.ReactNode;
|
||||
slotHeaderInner?: React.ReactNode;
|
||||
slotFooter?: React.ReactNode;
|
||||
withClose?: boolean;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
@@ -47,7 +46,6 @@ type StyleProps = {
|
||||
hasHeaderBorder?: boolean;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
withAnimation?: boolean;
|
||||
};
|
||||
|
||||
export type DialogProps = ElementProps & StyleProps;
|
||||
@@ -81,11 +79,9 @@ export const Dialog = ({
|
||||
slotTrigger,
|
||||
slotHeaderInner,
|
||||
slotFooter,
|
||||
withClose = true,
|
||||
placement = DialogPlacement.Default,
|
||||
portalContainer,
|
||||
hasHeaderBorder = false,
|
||||
withAnimation = false,
|
||||
children,
|
||||
className,
|
||||
}: DialogProps) => {
|
||||
@@ -109,7 +105,6 @@ export const Dialog = ({
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
$withAnimation={withAnimation}
|
||||
>
|
||||
<Styled.Header $withBorder={hasHeaderBorder}>
|
||||
<Styled.HeaderTopRow>
|
||||
@@ -119,7 +114,7 @@ export const Dialog = ({
|
||||
|
||||
{title && <Styled.Title>{title}</Styled.Title>}
|
||||
|
||||
{!preventClose && withClose && (
|
||||
{!preventClose && (
|
||||
<Styled.Close ref={closeButtonRef}>
|
||||
<Icon iconName={IconName.Close} />
|
||||
</Styled.Close>
|
||||
@@ -173,7 +168,7 @@ Styled.Overlay = styled(Overlay)`
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?: boolean }>`
|
||||
Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
|
||||
/* Params */
|
||||
--dialog-inset: 1rem;
|
||||
--dialog-width: 30rem;
|
||||
@@ -229,7 +224,7 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
||||
|
||||
outline: none;
|
||||
|
||||
${({ placement, $withAnimation }) =>
|
||||
${({ placement }) =>
|
||||
({
|
||||
[DialogPlacement.Default]: css`
|
||||
inset: var(--dialog-inset);
|
||||
@@ -265,11 +260,9 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
||||
padding-bottom: var(--dialog-radius); */
|
||||
}
|
||||
|
||||
${$withAnimation &&
|
||||
css`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -277,19 +270,18 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
||||
max-height: 0;
|
||||
}
|
||||
`} 0.15s var(--ease-out-expo);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
to {
|
||||
opacity: 0;
|
||||
scale: 0.9;
|
||||
max-height: 0;
|
||||
}
|
||||
`} 0.15s;
|
||||
}
|
||||
}
|
||||
`}
|
||||
}
|
||||
`,
|
||||
[DialogPlacement.Sidebar]: css`
|
||||
--dialog-width: var(--sidebar-width);
|
||||
@@ -299,52 +291,50 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
${$withAnimation &&
|
||||
css`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
from {
|
||||
translate: 100% 0;
|
||||
opacity: 0;
|
||||
}
|
||||
`} 0.15s var(--ease-out-expo);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
to {
|
||||
translate: 100% 0;
|
||||
opacity: 0;
|
||||
}
|
||||
`} 0.15s var(--ease-out-expo);
|
||||
}
|
||||
}
|
||||
`}
|
||||
}
|
||||
`,
|
||||
[DialogPlacement.Inline]: css`
|
||||
${$withAnimation &&
|
||||
css`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
&[data-state='open'] {
|
||||
animation: ${keyframes`
|
||||
from {
|
||||
scale: 0.99;
|
||||
opacity: 0;
|
||||
/* filter: blur(2px); */
|
||||
/* backdrop-filter: none; */
|
||||
}
|
||||
`} 0.15s var(--ease-out-expo);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
&[data-state='closed'] {
|
||||
animation: ${keyframes`
|
||||
to {
|
||||
scale: 0.99;
|
||||
opacity: 0;
|
||||
/* filter: blur(2px); */
|
||||
/* backdrop-filter: none; */
|
||||
}
|
||||
`} 0.15s var(--ease-out-expo);
|
||||
}
|
||||
}
|
||||
`}
|
||||
}
|
||||
`,
|
||||
[DialogPlacement.FullScreen]: css`
|
||||
--dialog-width: 100vw;
|
||||
|
||||
@@ -33,11 +33,9 @@ export const DropdownHeaderMenu = <MenuItemValue extends string>({
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<Styled.Trigger className={className} asChild>
|
||||
<div>
|
||||
<Styled.Trigger className={className}>
|
||||
{children}
|
||||
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
|
||||
</div>
|
||||
</Styled.Trigger>
|
||||
<Portal>
|
||||
<Styled.Content
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
RadioItem,
|
||||
Portal,
|
||||
} from '@radix-ui/react-dropdown-menu';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
|
||||
import { type MenuItem } from '@/constants/menus';
|
||||
|
||||
@@ -91,9 +90,7 @@ export const DropdownSelectMenu = <MenuItemValue extends string>({
|
||||
|
||||
{slotAfter}
|
||||
|
||||
<Styled.ItemIndicator>
|
||||
<CheckIcon />
|
||||
</Styled.ItemIndicator>
|
||||
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
||||
</Styled.RadioItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
|
||||
@@ -1,16 +1,43 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
|
||||
export const GreenCheckCircle = ({ className }: { className?: string }) => (
|
||||
<Styled.Icon className={className} iconName={IconName.CheckCircle} />
|
||||
<Styled.GreenCheckCircle className={className}>
|
||||
<Icon iconName={IconName.Check} />
|
||||
</Styled.GreenCheckCircle>
|
||||
);
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Icon = styled(Icon)`
|
||||
Styled.GreenCheckCircle = styled.div`
|
||||
${layoutMixins.stack}
|
||||
|
||||
--icon-size: 1.25rem;
|
||||
--icon-border-width: 3px;
|
||||
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
border-radius: 50%;
|
||||
|
||||
align-items: center;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
||||
height: calc(var(--icon-size) - var(--icon-border-width) * 2);
|
||||
border: solid var(--icon-border-width) var(--color-positive);
|
||||
border-radius: 50%;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: calc(var(--icon-size) / 3);
|
||||
height: calc(var(--icon-size) / 3);
|
||||
|
||||
color: var(--color-positive);
|
||||
justify-self: center;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
CautionCircleIcon,
|
||||
ChatIcon,
|
||||
CheckIcon,
|
||||
CheckCircleIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
ClockIcon,
|
||||
@@ -25,10 +24,9 @@ import {
|
||||
CommentIcon,
|
||||
CopyIcon,
|
||||
CubeIcon,
|
||||
DepositIcon,
|
||||
DepthChartIcon,
|
||||
DiscordIcon,
|
||||
EtherscanIcon,
|
||||
DepthChartIcon,
|
||||
ExportKeysIcon,
|
||||
FeedbackIcon,
|
||||
FileIcon,
|
||||
@@ -71,7 +69,6 @@ import {
|
||||
WarningIcon,
|
||||
WebsiteIcon,
|
||||
WhitepaperIcon,
|
||||
WithdrawIcon,
|
||||
} from '@/icons';
|
||||
|
||||
export enum IconName {
|
||||
@@ -87,7 +84,6 @@ export enum IconName {
|
||||
CautionCircleStroked = 'CautionCircleStroked',
|
||||
Chat = 'Chat',
|
||||
Check = 'Check',
|
||||
CheckCircle = 'CheckCircle',
|
||||
ChevronLeft = 'ChevronLeft',
|
||||
ChevronRight = 'ChevronRight',
|
||||
Clock = 'Clock',
|
||||
@@ -97,7 +93,6 @@ export enum IconName {
|
||||
Comment = 'Comment',
|
||||
Copy = 'Copy',
|
||||
Cube = 'Cube',
|
||||
Deposit = 'Deposit',
|
||||
DepthChart = 'DepthChart',
|
||||
Discord = 'Discord',
|
||||
Etherscan = 'Etherscan',
|
||||
@@ -144,7 +139,6 @@ export enum IconName {
|
||||
Warning = 'Warning',
|
||||
Website = 'Website',
|
||||
Whitepaper = 'Whitepaper',
|
||||
Withdraw = 'Withdraw',
|
||||
}
|
||||
|
||||
const icons = {
|
||||
@@ -160,7 +154,6 @@ const icons = {
|
||||
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
|
||||
[IconName.Chat]: ChatIcon,
|
||||
[IconName.Check]: CheckIcon,
|
||||
[IconName.CheckCircle]: CheckCircleIcon,
|
||||
[IconName.ChevronLeft]: ChevronLeftIcon,
|
||||
[IconName.ChevronRight]: ChevronRightIcon,
|
||||
[IconName.Clock]: ClockIcon,
|
||||
@@ -170,7 +163,6 @@ const icons = {
|
||||
[IconName.Comment]: CommentIcon,
|
||||
[IconName.Copy]: CopyIcon,
|
||||
[IconName.Cube]: CubeIcon,
|
||||
[IconName.Deposit]: DepositIcon,
|
||||
[IconName.DepthChart]: DepthChartIcon,
|
||||
[IconName.Discord]: DiscordIcon,
|
||||
[IconName.Etherscan]: EtherscanIcon,
|
||||
@@ -216,7 +208,6 @@ const icons = {
|
||||
[IconName.Warning]: WarningIcon,
|
||||
[IconName.Website]: WebsiteIcon,
|
||||
[IconName.Whitepaper]: WhitepaperIcon,
|
||||
[IconName.Withdraw]: WithdrawIcon,
|
||||
} as Record<IconName, ElementType>;
|
||||
|
||||
type ElementProps = {
|
||||
|
||||
@@ -67,7 +67,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
const buttonMixin = css`
|
||||
// Params
|
||||
--button-icon-size: 1.125em;
|
||||
--button-padding: 0;
|
||||
|
||||
// Rules
|
||||
> * {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, Ref } from 'react';
|
||||
import { forwardRef, Ref, useEffect, useRef, useState } from 'react';
|
||||
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||
import { NavLink, matchPath, useLocation } from 'react-router-dom';
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
|
||||
import { Tag } from './Tag';
|
||||
import { Icon, IconName } from './Icon';
|
||||
|
||||
@@ -59,6 +61,7 @@ const NavItem = forwardRef(
|
||||
<>
|
||||
{slotBefore}
|
||||
<span>
|
||||
{/* {`${label}${subitems?.length ? ' ' : ''}`} */}
|
||||
{label}
|
||||
{tag && (
|
||||
<>
|
||||
@@ -68,7 +71,7 @@ const NavItem = forwardRef(
|
||||
)}
|
||||
</span>
|
||||
{slotAfter}
|
||||
{subitems?.length && <Styled.Icon iconName={IconName.Triangle} />}
|
||||
{subitems?.length && <Styled.Icon iconName={IconName.Caret} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -97,6 +100,8 @@ const NavItem = forwardRef(
|
||||
}
|
||||
);
|
||||
|
||||
type TriggerRef = HTMLAnchorElement | HTMLDivElement | HTMLButtonElement | null;
|
||||
|
||||
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
|
||||
onSelectItem,
|
||||
onSelectGroup,
|
||||
@@ -107,6 +112,33 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
||||
dir = 'ltr',
|
||||
className,
|
||||
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
|
||||
// Disable click (close) in the first 500ms after hover (open)
|
||||
// https://github.com/radix-ui/primitives/issues/1630#issuecomment-1545995075
|
||||
const [clickIsDisabled, setClickIsDisabled] = useState(false);
|
||||
const triggerRefs = useRef({} as { [value: string]: TriggerRef });
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (
|
||||
mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'data-state' &&
|
||||
(mutation.target as unknown as HTMLOrSVGElement).dataset.state === 'open' &&
|
||||
mutation.target !== document.activeElement
|
||||
) {
|
||||
setClickIsDisabled(true);
|
||||
setTimeout(() => setClickIsDisabled(false), 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const element of Object.values(triggerRefs.current).filter(isTruthy)) {
|
||||
observer.observe(element, { attributes: true });
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const renderSubitems = ({
|
||||
item,
|
||||
depth = 0,
|
||||
@@ -116,18 +148,18 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
|
||||
}) => (
|
||||
<>
|
||||
<Styled.SubMenuTrigger
|
||||
asChild
|
||||
onPointerMove={(e: MouseEvent) => e.preventDefault()}
|
||||
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
||||
asChild={depth > 0}
|
||||
ref={(ref: TriggerRef) => (triggerRefs.current[item.value] = ref)}
|
||||
onClick={(e: MouseEvent) => {
|
||||
if (clickIsDisabled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
|
||||
</Styled.SubMenuTrigger>
|
||||
|
||||
<Styled.Content
|
||||
onPointerEnter={(e: MouseEvent) => e.preventDefault()}
|
||||
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
|
||||
data-placement={submenuPlacement}
|
||||
>
|
||||
<Styled.Content data-placement={submenuPlacement}>
|
||||
<Styled.Sub data-placement={submenuPlacement}>
|
||||
<Styled.List
|
||||
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
|
||||
@@ -378,12 +410,25 @@ Styled.List = styled(List)`
|
||||
`;
|
||||
|
||||
Styled.ListItem = styled(Item)`
|
||||
/* display: contents; */
|
||||
display: grid;
|
||||
position: relative;
|
||||
|
||||
${Styled.List}[data-orientation="horizontal"] > & {
|
||||
gap: var(--submenu-side-offset);
|
||||
}
|
||||
|
||||
/* ${Styled.List}[data-orientation="menu"] > & {
|
||||
grid-template-columns: 1fr 0;
|
||||
align-items: start;
|
||||
gap: 2rem;
|
||||
} */
|
||||
|
||||
/* &:has([data-state="open"]) {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
right: 0;
|
||||
} */
|
||||
`;
|
||||
|
||||
Styled.SubMenuTrigger = styled(Trigger)`
|
||||
@@ -391,6 +436,11 @@ Styled.SubMenuTrigger = styled(Trigger)`
|
||||
outline-offset: -2px;
|
||||
|
||||
&[data-state='open'] {
|
||||
div {
|
||||
background-color: var(--navigationMenu-item-checked-backgroundColor);
|
||||
color: var(--navigationMenu-item-checked-textColor);
|
||||
}
|
||||
|
||||
svg {
|
||||
rotate: 0.5turn;
|
||||
}
|
||||
@@ -402,10 +452,11 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
||||
subitems?.length
|
||||
? css`
|
||||
${popoverMixins.trigger}
|
||||
--trigger-open-backgroundColor: var(--navigationMenu-item-checked-backgroundColor);
|
||||
--trigger-backgroundColor: transparent;
|
||||
--trigger-open-backgroundColor: var(--color-layer-3);
|
||||
`
|
||||
: css`
|
||||
&:hover:not(:active) {
|
||||
&:hover:not(.active) {
|
||||
background-color: var(--navigationMenu-item-highlighted-backgroundColor);
|
||||
color: var(--navigationMenu-item-highlighted-textColor);
|
||||
}
|
||||
@@ -419,6 +470,8 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
||||
--item-radius: var(--navigationMenu-item-radius);
|
||||
--item-padding: var(--navigationMenu-item-padding);
|
||||
|
||||
/* ${popoverMixins.backdropOverlay} */
|
||||
|
||||
${layoutMixins.scrollSnapItem}
|
||||
|
||||
min-height: var(--navigationMenu-item-height);
|
||||
@@ -472,7 +525,7 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
|
||||
`;
|
||||
|
||||
Styled.Icon = styled(Icon)`
|
||||
font-size: 0.375em;
|
||||
height: 0.75em;
|
||||
transition: rotate 0.3s var(--ease-out-expo);
|
||||
|
||||
${Styled.List}[data-orientation="menu"] & {
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import React, { type MouseEvent } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
|
||||
import {
|
||||
NotificationStatus,
|
||||
type Notification as NotificationDataType,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { IconName } from '@/components/Icon';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
|
||||
type ElementProps = {
|
||||
notification: NotificationDataType;
|
||||
onClick?: () => void;
|
||||
slotIcon?: React.ReactNode;
|
||||
slotTitle?: React.ReactNode;
|
||||
slotTitleLeft?: React.ReactNode;
|
||||
slotTitleRight?: React.ReactNode;
|
||||
slotDescription?: React.ReactNode;
|
||||
slotCustomContent?: React.ReactNode;
|
||||
slotAction?: React.ReactNode;
|
||||
withClose?: boolean;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
isToast?: boolean;
|
||||
};
|
||||
|
||||
export type NotificationProps = ElementProps & StyleProps;
|
||||
|
||||
export const Notification = ({
|
||||
className,
|
||||
isToast,
|
||||
notification,
|
||||
onClick,
|
||||
slotIcon,
|
||||
slotTitle,
|
||||
slotTitleLeft,
|
||||
slotTitleRight,
|
||||
slotDescription,
|
||||
slotCustomContent,
|
||||
slotAction,
|
||||
withClose = !isToast,
|
||||
}: NotificationProps) => {
|
||||
const { markCleared, markSeen } = useNotifications();
|
||||
|
||||
return (
|
||||
<$Container className={className} isToast={isToast} onClick={onClick}>
|
||||
<$Header>
|
||||
{slotTitleLeft ?? (slotIcon && <$Icon>{slotIcon}</$Icon>)}
|
||||
<$Title>{slotTitle}</$Title>
|
||||
{slotTitleRight}
|
||||
{withClose && !isToast && (
|
||||
<$ActionItems>
|
||||
<$Output
|
||||
type={OutputType.RelativeTime}
|
||||
value={notification.timestamps[NotificationStatus.Triggered]}
|
||||
/>
|
||||
|
||||
{notification.status < NotificationStatus.Seen ? <$UnreadIndicator /> : null}
|
||||
|
||||
{notification.status < NotificationStatus.Cleared ? (
|
||||
<$IconButton
|
||||
iconName={IconName.Close}
|
||||
shape={ButtonShape.Square}
|
||||
size={ButtonSize.XSmall}
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (notification.status < NotificationStatus.Seen) {
|
||||
markSeen(notification);
|
||||
} else if (notification.status < NotificationStatus.Cleared) {
|
||||
markCleared(notification);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</$ActionItems>
|
||||
)}
|
||||
</$Header>
|
||||
<$Description>{slotCustomContent ?? slotDescription}</$Description>
|
||||
{slotAction && <$Action>{slotAction}</$Action>}
|
||||
</$Container>
|
||||
);
|
||||
};
|
||||
|
||||
const $Container = styled.div<{ isToast?: boolean }>`
|
||||
// Params
|
||||
--toast-icon-size: 1.75em;
|
||||
|
||||
// Rules
|
||||
${popoverMixins.popover}
|
||||
overflow: visible;
|
||||
padding: 1rem 1.25rem;
|
||||
|
||||
${({ isToast }) =>
|
||||
isToast
|
||||
? css`
|
||||
padding: 1rem;
|
||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||
// border
|
||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||
`
|
||||
: css`
|
||||
flex: 1;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
backdrop-filter: none;
|
||||
`}
|
||||
`;
|
||||
|
||||
const $Header = styled.header`
|
||||
${layoutMixins.row}
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const $Icon = styled.div`
|
||||
${layoutMixins.row}
|
||||
float: left;
|
||||
|
||||
margin-right: 0.5rem;
|
||||
|
||||
line-height: 1;
|
||||
|
||||
> svg,
|
||||
img {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const $Title = styled.div`
|
||||
flex: 1;
|
||||
|
||||
font: var(--font-base-medium);
|
||||
color: var(--color-text-2);
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const $Description = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
const $Action = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
const $ActionItems = styled.div`
|
||||
min-width: fit-content;
|
||||
padding-left: 0.5rem;
|
||||
height: 100%;
|
||||
${layoutMixins.row};
|
||||
justify-content: end;
|
||||
gap: 0.5rem;
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
background-color: var(--color-layer-2);
|
||||
font: var(--font-mini-book);
|
||||
|
||||
display: none;
|
||||
z-index: 1;
|
||||
|
||||
${$Container}:hover & {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
const $UnreadIndicator = styled.div`
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-accent);
|
||||
border: 1px solid var(--color-layer-2);
|
||||
`;
|
||||
|
||||
const $Output = styled(Output)`
|
||||
color: var(--color-text-0);
|
||||
`;
|
||||
|
||||
const $IconButton = styled(IconButton)`
|
||||
--button-border: none;
|
||||
--button-textColor: var(--color-text-0);
|
||||
--button-hover-textColor: var(--color-text-1);
|
||||
`;
|
||||
@@ -31,7 +31,7 @@ export const Panel = ({
|
||||
hasSeparator,
|
||||
className,
|
||||
}: PanelProps & PanelStyleProps) => (
|
||||
<Styled.Panel onClick={onClick} className={className}>
|
||||
<Styled.Panel onClick={onClick}>
|
||||
<Styled.Left>
|
||||
{href ? (
|
||||
<Link to={href}>
|
||||
@@ -53,7 +53,7 @@ export const Panel = ({
|
||||
</Styled.Header>
|
||||
)
|
||||
)}
|
||||
<Styled.Content>{children}</Styled.Content>
|
||||
<Styled.Content className={className}>{children}</Styled.Content>
|
||||
</Styled.Left>
|
||||
{slotRight}
|
||||
</Styled.Panel>
|
||||
@@ -61,29 +61,11 @@ export const Panel = ({
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled.section<{ onClick?: () => void }>`
|
||||
--panel-paddingY: 1rem;
|
||||
--panel-paddingX: 1rem;
|
||||
--panel-content-paddingY: var(--panel-paddingY);
|
||||
--panel-content-paddingX: var(--panel-paddingX);
|
||||
|
||||
Styled.Panel = styled.section`
|
||||
${layoutMixins.row}
|
||||
|
||||
background-color: var(--color-layer-3);
|
||||
border-radius: 0.875rem;
|
||||
|
||||
${({ onClick }) =>
|
||||
onClick &&
|
||||
css`
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
button:not(:disabled) {
|
||||
color: var(--button-hover-textColor);
|
||||
filter: var(--button-hover-filter);
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.Left = styled.div`
|
||||
@@ -93,7 +75,7 @@ Styled.Left = styled.div`
|
||||
|
||||
Styled.Header = styled.header<{ hasSeparator?: boolean }>`
|
||||
${layoutMixins.spacedRow}
|
||||
padding: var(--panel-paddingY) var(--panel-paddingX);
|
||||
padding: 1rem 1rem;
|
||||
|
||||
${({ hasSeparator }) =>
|
||||
hasSeparator &&
|
||||
@@ -112,5 +94,5 @@ Styled.Content = styled.div`
|
||||
${layoutMixins.scrollArea}
|
||||
${layoutMixins.stickyArea0}
|
||||
--stickyArea0-background: transparent;
|
||||
padding: var(--panel-content-paddingY) var(--panel-content-paddingX);
|
||||
padding: 1rem 1rem;
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
@@ -22,11 +22,6 @@ export const SearchInput = ({
|
||||
}: SearchInputProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [value, setValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) inputRef?.current?.focus();
|
||||
}, [inputRef, isOpen]);
|
||||
|
||||
return (
|
||||
<Styled.Search>
|
||||
@@ -46,7 +41,6 @@ export const SearchInput = ({
|
||||
/>
|
||||
<Styled.Input
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
isOpen={isOpen}
|
||||
type={type}
|
||||
|
||||
@@ -12,8 +12,6 @@ import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
||||
import { WithLabel } from '@/components/WithLabel';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
import breakpoints from '@/styles/breakpoints';
|
||||
|
||||
type ElementProps = {
|
||||
asChild?: boolean;
|
||||
@@ -86,30 +84,27 @@ export const SearchSelectMenu = ({
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.SearchSelectMenu = styled.div`
|
||||
${layoutMixins.column}
|
||||
`;
|
||||
|
||||
Styled.MenuTrigger = styled.div`
|
||||
height: var(--form-input-height);
|
||||
|
||||
${layoutMixins.spacedRow}
|
||||
align-items: center;
|
||||
padding: var(--form-input-paddingY) var(--form-input-paddingX);
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
height: var(--form-input-height-mobile);
|
||||
}
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
Styled.WithLabel = styled(WithLabel)`
|
||||
${formMixins.inputLabel}
|
||||
--label-textColor: var(--color-text-0);
|
||||
gap: 0.25rem;
|
||||
|
||||
label {
|
||||
font: var(--font-mini-book);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.SearchSelectMenu = styled.div`
|
||||
${layoutMixins.column}
|
||||
`;
|
||||
|
||||
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
||||
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import {
|
||||
Root,
|
||||
@@ -13,13 +13,11 @@ import {
|
||||
Portal,
|
||||
Icon as SelectIcon,
|
||||
} from '@radix-ui/react-select';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
|
||||
import { WithLabel } from '@/components/WithLabel';
|
||||
import { Icon, IconName } from './Icon';
|
||||
|
||||
export const SelectMenu = <T extends string>({
|
||||
children,
|
||||
@@ -46,9 +44,7 @@ export const SelectMenu = <T extends string>({
|
||||
) : (
|
||||
<Value />
|
||||
)}
|
||||
{React.Children.toArray(children).length > 1 && (
|
||||
<Styled.TriggerIcon iconName={IconName.Triangle} />
|
||||
)}
|
||||
{React.Children.toArray(children).length > 1 && <Styled.DropdownIcon />}
|
||||
</Styled.Trigger>
|
||||
<Portal>
|
||||
<Styled.Content className={className}>
|
||||
@@ -72,9 +68,7 @@ export const SelectItem = <T extends string>({
|
||||
}) => (
|
||||
<Styled.Item className={className} value={value}>
|
||||
<ItemText>{label}</ItemText>
|
||||
<Styled.ItemIndicator>
|
||||
<CheckIcon />
|
||||
</Styled.ItemIndicator>
|
||||
<Styled.ItemIndicator>✔{/* <CheckIcon /> */}</Styled.ItemIndicator>
|
||||
</Styled.Item>
|
||||
);
|
||||
|
||||
@@ -94,6 +88,12 @@ Styled.Trigger = styled(Trigger)<{ $withBlur?: boolean }>`
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.DropdownIcon = styled(SelectIcon)`
|
||||
font-size: 0.675em;
|
||||
color: var(--color-text-1);
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
--select-menu-content-maxWidth: ;
|
||||
max-width: var(--select-menu-content-maxWidth);
|
||||
@@ -116,9 +116,3 @@ Styled.WithLabel = styled(WithLabel)`
|
||||
${formMixins.inputLabel}
|
||||
border-radius: 0;
|
||||
`;
|
||||
|
||||
Styled.TriggerIcon = styled(Icon)`
|
||||
width: 0.625rem;
|
||||
height: 0.375rem;
|
||||
color: var(--color-text-0);
|
||||
`;
|
||||
|
||||
@@ -831,7 +831,6 @@ Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
|
||||
Styled.Thead = styled.thead<StyleProps>`
|
||||
${layoutMixins.stickyHeader}
|
||||
scroll-snap-align: none;
|
||||
font: var(--font-mini-book);
|
||||
|
||||
> * {
|
||||
height: var(--stickyArea-topHeight);
|
||||
@@ -840,17 +839,18 @@ Styled.Thead = styled.thead<StyleProps>`
|
||||
color: var(--tableHeader-textColor);
|
||||
background-color: var(--tableHeader-backgroundColor);
|
||||
|
||||
${({ withInnerBorders, withGradientCardRows }) =>
|
||||
withInnerBorders &&
|
||||
!withGradientCardRows &&
|
||||
css`
|
||||
${layoutMixins.withInnerHorizontalBorders}
|
||||
`}
|
||||
@media ${breakpoints.notTablet} {
|
||||
${({ withInnerBorders, withGradientCardRows }) =>
|
||||
withInnerBorders &&
|
||||
!withGradientCardRows &&
|
||||
css`
|
||||
${layoutMixins.withInnerHorizontalBorders}
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Tbody = styled.tbody<StyleProps>`
|
||||
${layoutMixins.stickyArea2}
|
||||
font: var(--font-small-book);
|
||||
|
||||
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
|
||||
&:before,
|
||||
@@ -869,10 +869,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
|
||||
--stickyArea2-paddingBottom: var(--border-width);
|
||||
--stickyArea2-paddingLeft: var(--border-width);
|
||||
--stickyArea2-paddingRight: var(--border-width);
|
||||
|
||||
tr:first-of-type {
|
||||
box-shadow: none;
|
||||
}
|
||||
`}
|
||||
|
||||
${({ withGradientCardRows }) =>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { type MouseEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { Root, Action, Close } from '@radix-ui/react-toast';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Root, Title, Description, Action, Close } from '@radix-ui/react-toast';
|
||||
|
||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
|
||||
import { Notification, type NotificationProps } from '@/components/Notification';
|
||||
|
||||
import { IconButton } from './IconButton';
|
||||
import { IconName } from './Icon';
|
||||
import { CloseIcon } from '@/icons';
|
||||
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
isOpen?: boolean;
|
||||
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
|
||||
slotIcon?: React.ReactNode;
|
||||
slotTitle?: React.ReactNode;
|
||||
slotDescription?: React.ReactNode;
|
||||
slotCustomContent?: React.ReactNode;
|
||||
slotAction?: React.ReactNode;
|
||||
actionDescription?: string;
|
||||
actionAltText?: string;
|
||||
sensitivity?: 'foreground' | 'background';
|
||||
@@ -20,22 +26,13 @@ type ElementProps = {
|
||||
lastUpdated?: number;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
type ToastProps = NotificationProps & ElementProps & StyleProps;
|
||||
type StyleProps = {};
|
||||
|
||||
export const Toast = ({
|
||||
className,
|
||||
isOpen = true,
|
||||
notification,
|
||||
onClick,
|
||||
setIsOpen,
|
||||
slotIcon,
|
||||
slotTitle,
|
||||
slotTitleLeft,
|
||||
slotTitleRight,
|
||||
slotDescription,
|
||||
slotCustomContent,
|
||||
slotAction,
|
||||
@@ -44,7 +41,7 @@ export const Toast = ({
|
||||
sensitivity = 'background',
|
||||
duration = Infinity,
|
||||
lastUpdated,
|
||||
}: ToastProps) => {
|
||||
}: ElementProps & StyleProps) => {
|
||||
// Timeout
|
||||
const timeout = useRef<number>();
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
@@ -61,45 +58,37 @@ export const Toast = ({
|
||||
|
||||
return (
|
||||
<$Root
|
||||
className={className}
|
||||
type={sensitivity}
|
||||
duration={Infinity}
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
onPause={() => setIsPaused(true)}
|
||||
onResume={() => setIsPaused(false)}
|
||||
onClick={() => {
|
||||
setIsOpen?.(false, false);
|
||||
onClick?.();
|
||||
}}
|
||||
>
|
||||
<Close asChild>
|
||||
<$CloseButton
|
||||
iconName={IconName.Close}
|
||||
shape={ButtonShape.Square}
|
||||
size={ButtonSize.XSmall}
|
||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||
/>
|
||||
</Close>
|
||||
<div>
|
||||
<$Container>
|
||||
<$Header>
|
||||
{slotIcon && <$Icon>{slotIcon}</$Icon>}
|
||||
|
||||
{slotCustomContent ?? (
|
||||
<$Notification
|
||||
isToast
|
||||
notification={notification}
|
||||
slotIcon={slotIcon}
|
||||
slotTitle={slotTitle}
|
||||
slotTitleLeft={slotTitleLeft}
|
||||
slotTitleRight={slotTitleRight}
|
||||
slotDescription={slotDescription}
|
||||
slotAction={
|
||||
actionDescription && (
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Close asChild>
|
||||
<$CloseButton
|
||||
iconComponent={CloseIcon}
|
||||
shape={ButtonShape.Square}
|
||||
size={ButtonSize.XSmall}
|
||||
/>
|
||||
</Close>
|
||||
|
||||
<$Title>{slotTitle}</$Title>
|
||||
</$Header>
|
||||
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
|
||||
{slotCustomContent}
|
||||
{actionDescription && (
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
)}
|
||||
</$Container>
|
||||
</div>
|
||||
</$Root>
|
||||
);
|
||||
};
|
||||
@@ -125,7 +114,7 @@ const $Root = styled(Root)`
|
||||
translate: var(--x) var(--y);
|
||||
will-change: translate, margin-top;
|
||||
|
||||
/* margin-bottom: var(--toasts-gap); */
|
||||
margin-bottom: var(--toasts-gap);
|
||||
|
||||
&[data-swipe-direction='right'] {
|
||||
&[data-state='open'] {
|
||||
@@ -174,10 +163,6 @@ const $Root = styled(Root)`
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
&:focus:not([data-swipe='end']) & {
|
||||
outline: var(--color-accent) 1px solid;
|
||||
}
|
||||
|
||||
&[data-swipe='move'] {
|
||||
transition-property: opacity;
|
||||
opacity: 0.98;
|
||||
@@ -186,10 +171,6 @@ const $Root = styled(Root)`
|
||||
* {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
& > * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-state='closed'],
|
||||
@@ -217,44 +198,69 @@ const $Root = styled(Root)`
|
||||
}
|
||||
`;
|
||||
|
||||
const $Notification = styled(Notification)`
|
||||
const $Container = styled.div`
|
||||
// Params
|
||||
--toast-icon-size: 1.75em;
|
||||
|
||||
// Rules
|
||||
${popoverMixins.popover}
|
||||
overflow: visible;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||
// border
|
||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||
|
||||
${$Root}:focus:not([data-swipe='end']) & {
|
||||
outline: var(--color-accent) 1px solid;
|
||||
}
|
||||
|
||||
> * {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
${$Root}[data-swipe='move'] & > * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`;
|
||||
|
||||
const $Header = styled.header`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const $Icon = styled.div`
|
||||
${layoutMixins.row}
|
||||
|
||||
float: left;
|
||||
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
margin-right: 0.4em;
|
||||
|
||||
line-height: 1;
|
||||
`;
|
||||
|
||||
const $CloseButton = styled(IconButton)`
|
||||
--button-textColor: var(--color-text-0);
|
||||
--button-border: none;
|
||||
--button-icon-size: 0.85em;
|
||||
display: none;
|
||||
|
||||
/* float: right; */
|
||||
float: right;
|
||||
margin: -0.42rem -0.42rem -0.42rem 0.42rem;
|
||||
`;
|
||||
|
||||
// Absolute
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
const $Title = styled(Title)`
|
||||
flex: 1;
|
||||
|
||||
border: solid var(--border-width) var(--color-border);
|
||||
border-radius: 50%;
|
||||
font: var(--font-base-medium);
|
||||
color: var(--color-text-2);
|
||||
|
||||
${$Root}:hover & {
|
||||
display: block;
|
||||
z-index: 2;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const $Description = styled(Description)`
|
||||
margin-top: 0.5rem;
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
const $Action = styled(Action)`
|
||||
|
||||
@@ -34,7 +34,6 @@ const $ToastArea = styled.aside`
|
||||
> * {
|
||||
display: contents;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
|
||||
> ol {
|
||||
/* display: block; */
|
||||
@@ -44,7 +43,6 @@ const $ToastArea = styled.aside`
|
||||
// Desired display order: latest to earliest (¯\_(ツ)_/¯)
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position: relative;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
|
||||
@@ -52,19 +52,15 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.BaseButton = styled(BaseButton)`
|
||||
--button-toggle-off-backgroundColor: var(--color-layer-3);
|
||||
--button-toggle-off-textColor: var(--color-text-0);
|
||||
--button-toggle-off-border: solid var(--border-width) var(--border-color);
|
||||
--button-toggle-on-backgroundColor: var(--color-layer-1);
|
||||
--button-toggle-on-textColor: var(--color-text-2);
|
||||
--button-toggle-on-border: solid var(--border-width) var(--border-color);
|
||||
|
||||
--button-backgroundColor: var(--button-toggle-off-backgroundColor);
|
||||
--button-textColor: var(--button-toggle-off-textColor);
|
||||
--button-border: var(--button-toggle-off-border);
|
||||
|
||||
&[data-state='on'],
|
||||
&[data-state='active'] {
|
||||
--button-backgroundColor: var(--button-toggle-on-backgroundColor);
|
||||
--button-textColor: var(--button-toggle-on-textColor);
|
||||
--button-border: var(--button-toggle-on-border);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -7,15 +7,13 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
import { useBreakpoints } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { type BaseButtonProps } from '@/components/BaseButton';
|
||||
import { type BaseButtonProps } from '@/components/BaseButton';
|
||||
import { ToggleButton } from '@/components/ToggleButton';
|
||||
|
||||
type ElementProps<MenuItemValue extends string> = {
|
||||
items: MenuItem<MenuItemValue>[];
|
||||
value: MenuItemValue;
|
||||
onValueChange: (value: any) => void;
|
||||
onInteraction?: () => void;
|
||||
ensureSelected?: boolean;
|
||||
onValueChange: (value: MenuItemValue) => void;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
@@ -27,14 +25,10 @@ export const ToggleGroup = forwardRef(
|
||||
{
|
||||
items,
|
||||
value,
|
||||
ensureSelected = true,
|
||||
onValueChange,
|
||||
onInteraction,
|
||||
|
||||
className,
|
||||
size,
|
||||
shape = ButtonShape.Pill,
|
||||
|
||||
...buttonProps
|
||||
}: ElementProps<MenuItemValue> & StyleProps & BaseButtonProps,
|
||||
ref: Ref<HTMLDivElement>
|
||||
@@ -46,17 +40,12 @@ export const ToggleGroup = forwardRef(
|
||||
ref={ref}
|
||||
type="single"
|
||||
value={value}
|
||||
onValueChange={(newValue: MenuItemValue) => {
|
||||
if ((ensureSelected && newValue) || !ensureSelected) {
|
||||
onValueChange(newValue);
|
||||
}
|
||||
onInteraction?.();
|
||||
}}
|
||||
onValueChange={onValueChange}
|
||||
className={className}
|
||||
loop
|
||||
>
|
||||
{items.map((item) => (
|
||||
<Item key={item.value} value={item.value} asChild>
|
||||
<Item key={item.value} value={item.value} aria-label={item.label} asChild>
|
||||
<ToggleButton
|
||||
size={size ? size : isTablet ? ButtonSize.Small : ButtonSize.XSmall}
|
||||
shape={shape}
|
||||
|
||||
@@ -124,8 +124,6 @@ export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPo
|
||||
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
||||
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
|
||||
const abacusOrderStatuses = [...AbacusOrderStatus.values()] as const;
|
||||
export type AbacusOrderStatuses = (typeof abacusOrderStatuses)[number];
|
||||
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
|
||||
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
||||
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
||||
@@ -211,13 +209,11 @@ type ReadonlyKeysOf<T> = {
|
||||
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
|
||||
}[keyof T];
|
||||
|
||||
export type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
||||
type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
|
||||
|
||||
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
|
||||
[AbacusOrderSide.buy.name]: OrderSide.BUY,
|
||||
[AbacusOrderSide.sell.name]: OrderSide.SELL,
|
||||
[AbacusOrderSide.buy.rawValue]: OrderSide.BUY,
|
||||
[AbacusOrderSide.sell.rawValue]: OrderSide.SELL,
|
||||
};
|
||||
|
||||
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
|
||||
@@ -238,25 +234,12 @@ export const HISTORICAL_PNL_PERIODS: Record<
|
||||
|
||||
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
||||
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
||||
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
|
||||
|
||||
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
|
||||
[AbacusOrderStatus.partiallyFilled.rawValue]: STRING_KEYS.PARTIALLY_FILLED,
|
||||
|
||||
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
|
||||
[AbacusOrderStatus.filled.rawValue]: STRING_KEYS.ORDER_FILLED,
|
||||
|
||||
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
|
||||
[AbacusOrderStatus.cancelled.rawValue]: STRING_KEYS.CANCELED,
|
||||
|
||||
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
|
||||
[AbacusOrderStatus.canceling.rawValue]: STRING_KEYS.CANCELING,
|
||||
|
||||
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
||||
[AbacusOrderStatus.pending.rawValue]: STRING_KEYS.PENDING,
|
||||
|
||||
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
||||
[AbacusOrderStatus.untriggered.rawValue]: STRING_KEYS.UNTRIGGERED,
|
||||
};
|
||||
|
||||
export const TRADE_TYPES: Record<
|
||||
@@ -264,31 +247,15 @@ export const TRADE_TYPES: Record<
|
||||
Nullable<TradeTypes>
|
||||
> = {
|
||||
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
||||
[AbacusOrderType.limit.rawValue]: TradeTypes.LIMIT,
|
||||
|
||||
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
||||
[AbacusOrderType.market.rawValue]: TradeTypes.MARKET,
|
||||
|
||||
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
||||
[AbacusOrderType.stopLimit.rawValue]: TradeTypes.STOP_LIMIT,
|
||||
|
||||
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
||||
[AbacusOrderType.stopMarket.rawValue]: TradeTypes.STOP_MARKET,
|
||||
|
||||
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
||||
[AbacusOrderType.takeProfitLimit.rawValue]: TradeTypes.TAKE_PROFIT,
|
||||
|
||||
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||
|
||||
[AbacusOrderType.liquidated.name]: null,
|
||||
[AbacusOrderType.liquidation.name]: null,
|
||||
|
||||
[AbacusOrderType.liquidation.name]: null,
|
||||
[AbacusOrderType.liquidation.rawValue]: null,
|
||||
|
||||
[AbacusOrderType.trailingStop.name]: null,
|
||||
[AbacusOrderType.trailingStop.rawValue]: null,
|
||||
};
|
||||
|
||||
// Custom types involving Abacus
|
||||
|
||||
@@ -10,7 +10,6 @@ export enum DialogTypes {
|
||||
MobileSignIn = 'MobileSignIn',
|
||||
Onboarding = 'Onboarding',
|
||||
OrderDetails = 'OrderDetails',
|
||||
Preferences = 'Preferences',
|
||||
RateLimit = 'RateLimit',
|
||||
Receive = 'Receive',
|
||||
RestrictedGeo = 'RestrictedGeo',
|
||||
@@ -18,7 +17,6 @@ export enum DialogTypes {
|
||||
Trade = 'Trade',
|
||||
Transfer = 'Transfer',
|
||||
Withdraw = 'Withdraw',
|
||||
ManageFunds = 'ManageFunds',
|
||||
}
|
||||
|
||||
export enum TradeBoxDialogTypes {
|
||||
|
||||
@@ -13,7 +13,6 @@ export enum LocalStorageKey {
|
||||
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
||||
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
||||
TransferNotifications = 'dydx.TransferNotifications',
|
||||
NotificationPreferences = 'dydx.NotificationPreferences',
|
||||
|
||||
// UI State
|
||||
LastViewedMarket = 'dydx.LastViewedMarket',
|
||||
@@ -26,8 +25,5 @@ export enum LocalStorageKey {
|
||||
|
||||
export const LOCAL_STORAGE_VERSIONS = {
|
||||
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
||||
[LocalStorageKey.NotificationPreferences]: 'v1',
|
||||
[LocalStorageKey.TransferNotifications]: 'v1',
|
||||
[LocalStorageKey.Notifications]: 'v1',
|
||||
// TODO: version all localStorage keys
|
||||
};
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
LOCALE_DATA,
|
||||
TOOLTIPS,
|
||||
WARNINGS_STRING_KEYS,
|
||||
NOTIFICATIONS_STRING_KEYS,
|
||||
NOTIFICATIONS,
|
||||
} from '@dydxprotocol/v4-localization';
|
||||
|
||||
import { type LinksConfigs } from '@/hooks/useURLConfigs';
|
||||
@@ -29,7 +27,6 @@ export enum SupportedLocales {
|
||||
|
||||
export const EN_LOCALE_DATA = {
|
||||
...LOCALE_DATA[SupportedLocales.EN],
|
||||
...NOTIFICATIONS[SupportedLocales.EN],
|
||||
TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
|
||||
};
|
||||
|
||||
@@ -37,13 +34,8 @@ export const STRING_KEYS = {
|
||||
...APP_STRING_KEYS,
|
||||
...ERRORS_STRING_KEYS,
|
||||
...WARNINGS_STRING_KEYS,
|
||||
...NOTIFICATIONS_STRING_KEYS,
|
||||
};
|
||||
|
||||
export const STRING_KEY_VALUES = Object.fromEntries(
|
||||
Object.values(STRING_KEYS).map((key) => [key, key])
|
||||
);
|
||||
|
||||
export type StringKey = keyof typeof STRING_KEYS;
|
||||
|
||||
export type LocaleData = typeof EN_LOCALE_DATA;
|
||||
|
||||
@@ -26,7 +26,6 @@ export type MenuItem<MenuItemValue, MenuItemTypes = string> = {
|
||||
tag?: React.ReactNode;
|
||||
slotAfter?: React.ReactNode;
|
||||
description?: string;
|
||||
slotCustomContent?: React.ReactNode;
|
||||
|
||||
href?: string;
|
||||
onSelect?: (key: MenuItemValue) => void;
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import environments from '../../public/configs/env.json';
|
||||
|
||||
const CURRENT_MODE = ({
|
||||
production: 'MAINNET',
|
||||
testnet: 'TESTNET',
|
||||
staging: 'DEV',
|
||||
development: 'DEV',
|
||||
}[import.meta.env.MODE] ?? 'MAINNET') as 'MAINNET' | 'TESTNET' | 'DEV';
|
||||
// TODO: Update for Mainnet deployment
|
||||
export const AVAILABLE_ENVIRONMENTS =
|
||||
import.meta.env.MODE === 'production'
|
||||
? environments.deployments.TESTNET
|
||||
: environments.deployments.DEV;
|
||||
|
||||
export const isMainnet = CURRENT_MODE === 'MAINNET';
|
||||
export const isTestnet = CURRENT_MODE === 'TESTNET';
|
||||
export const isDev = CURRENT_MODE === 'DEV';
|
||||
|
||||
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
|
||||
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
|
||||
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV';
|
||||
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
|
||||
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
||||
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
/** implemented in useNotificationTypes */
|
||||
export enum NotificationType {
|
||||
AbacusGenerated = 'AbacusGenerated',
|
||||
OrderStatusChanged = 'OrderStatusChanged',
|
||||
SquidTransfer = 'SquidTransfer',
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export enum NotificationStatus {
|
||||
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
|
||||
Seen,
|
||||
|
||||
/** Notification marked for deletion. Hidden in NotificationsMenu. */
|
||||
/** Notification marked for deletion. "Archived" in NotificationsMenu. */
|
||||
Cleared,
|
||||
}
|
||||
|
||||
@@ -80,20 +80,14 @@ export type Notifications = Record<NotificationId, Notification<any>>;
|
||||
/** Notification display data derived from app state at runtime. */
|
||||
export type NotificationDisplayData = {
|
||||
icon?: React.ReactNode;
|
||||
title: string; // Title for Toast, Notification, and Push Notification
|
||||
body?: string; // Description body for Toast, Notification, and Push Notification
|
||||
|
||||
slotTitleLeft?: React.ReactNode;
|
||||
slotTitleRight?: React.ReactNode;
|
||||
title?: string;
|
||||
|
||||
// Overrides title/body for Notification in NotificationMenu
|
||||
renderCustomBody?: ({
|
||||
isToast,
|
||||
notification,
|
||||
}: {
|
||||
isToast?: boolean;
|
||||
notification: Notification;
|
||||
}) => React.ReactNode; // Custom Notification
|
||||
description?: React.ReactNode;
|
||||
|
||||
customContent?: React.ReactNode;
|
||||
|
||||
customMenuContent?: React.ReactNode;
|
||||
|
||||
actionDescription?: string;
|
||||
|
||||
@@ -128,15 +122,5 @@ export type TransferNotifcation = {
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
errorCount?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Struct to store whether a NotificationType should be triggered
|
||||
*/
|
||||
export type NotificationPreferences = {
|
||||
[key in NotificationType]: boolean;
|
||||
} & { version: string };
|
||||
|
||||
export const DEFAULT_TOAST_AUTO_CLOSE_MS = 5000;
|
||||
|
||||
@@ -42,7 +42,6 @@ const useAccountsContext = () => {
|
||||
selectedWalletError,
|
||||
evmAddress,
|
||||
signerWagmi,
|
||||
publicClientWagmi,
|
||||
dydxAddress: connectedDydxAddress,
|
||||
signerGraz,
|
||||
} = useWalletConnection();
|
||||
@@ -136,8 +135,8 @@ const useAccountsContext = () => {
|
||||
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
||||
try {
|
||||
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
||||
setDydxSubaccounts(response?.subaccounts);
|
||||
return response?.subaccounts ?? [];
|
||||
setDydxSubaccounts(response.subaccounts);
|
||||
return response.subaccounts;
|
||||
} catch (error) {
|
||||
// 404 is expected if the user has no subaccounts
|
||||
if (error.status === 404) {
|
||||
@@ -297,7 +296,6 @@ const useAccountsContext = () => {
|
||||
// Wallet connection (EVM)
|
||||
evmAddress,
|
||||
signerWagmi,
|
||||
publicClientWagmi,
|
||||
|
||||
// Wallet connection (Cosmos)
|
||||
signerGraz,
|
||||
|
||||
@@ -22,7 +22,6 @@ export const useCurrentMarketId = () => {
|
||||
const dispatch = useDispatch();
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||
const hasMarketIds = marketIds.length > 0;
|
||||
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
||||
key: LocalStorageKey.LastViewedMarket,
|
||||
defaultValue: DEFAULT_MARKETID,
|
||||
@@ -49,5 +48,5 @@ export const useCurrentMarketId = () => {
|
||||
if (marketIds) {
|
||||
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||
}
|
||||
}, [selectedNetwork, hasMarketIds, marketId]);
|
||||
}, [selectedNetwork, marketIds, marketId]);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isDev } from '@/constants/networks';
|
||||
import { WalletType } from '@/constants/wallets';
|
||||
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
@@ -7,7 +6,7 @@ export const useDisplayedWallets = () => {
|
||||
return [
|
||||
WalletType.MetaMask,
|
||||
|
||||
isDev && WalletType.Keplr,
|
||||
import.meta.env.MODE !== 'production' && WalletType.Keplr,
|
||||
|
||||
WalletType.WalletConnect2,
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createContext, useContext, useCallback, useEffect, useMemo } from 'reac
|
||||
import { useQuery } from 'react-query';
|
||||
import type { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { type TransferNotifcation } from '@/constants/notifications';
|
||||
|
||||
import { useAccounts } from '@/hooks/useAccounts';
|
||||
import { STATUS_ERROR_GRACE_PERIOD, useSquid } from '@/hooks/useSquid';
|
||||
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
|
||||
const LocalNotificationsContext = createContext<
|
||||
@@ -22,32 +22,16 @@ export const LocalNotificationsProvider = ({ ...props }) => (
|
||||
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
||||
|
||||
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
||||
const ERROR_COUNT_THRESHOLD = 3;
|
||||
|
||||
const useLocalNotificationsContext = () => {
|
||||
// transfer notifications
|
||||
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
||||
[key: `dydx${string}`]: TransferNotifcation[];
|
||||
version: string;
|
||||
}>({
|
||||
key: LocalStorageKey.TransferNotifications,
|
||||
defaultValue: {
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
||||
},
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
// Ensure version matches, otherwise wipe
|
||||
useEffect(() => {
|
||||
if (
|
||||
allTransferNotifications?.version !==
|
||||
LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications]
|
||||
) {
|
||||
setAllTransferNotifications({
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
|
||||
});
|
||||
}
|
||||
}, [allTransferNotifications]);
|
||||
|
||||
const { dydxAddress } = useAccounts();
|
||||
|
||||
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
||||
@@ -70,61 +54,47 @@ const useLocalNotificationsContext = () => {
|
||||
|
||||
const squid = useSquid();
|
||||
|
||||
useQuery({
|
||||
queryKey: 'getTransactionStatus',
|
||||
const { data: transferStatuses } = useQuery({
|
||||
queryKey: ['getTransactionStatus', transferNotifications],
|
||||
queryFn: async () => {
|
||||
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
|
||||
const newTransferNotifications = await Promise.all(
|
||||
transferNotifications.map(async (transferNotification) => {
|
||||
const {
|
||||
txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
triggeredAt,
|
||||
errorCount,
|
||||
status: currentStatus,
|
||||
} = transferNotification;
|
||||
const statuses: { [key: string]: StatusResponse } = {};
|
||||
for (const {
|
||||
txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
triggeredAt,
|
||||
status: currentStatus,
|
||||
} of transferNotifications) {
|
||||
try {
|
||||
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
||||
|
||||
if (
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
!currentStatus?.errors &&
|
||||
!currentStatus?.error &&
|
||||
(!currentStatus?.squidTransactionStatus ||
|
||||
currentStatus?.squidTransactionStatus === 'ongoing')
|
||||
) {
|
||||
try {
|
||||
const status = await squid?.getStatus({
|
||||
transactionId: txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
});
|
||||
|
||||
if (status) {
|
||||
transferNotification.status = status;
|
||||
}
|
||||
} catch (error) {
|
||||
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
||||
if (errorCount && errorCount > ERROR_COUNT_THRESHOLD) {
|
||||
transferNotification.status = error;
|
||||
} else {
|
||||
transferNotification.errorCount = errorCount ? errorCount + 1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transferNotification;
|
||||
})
|
||||
);
|
||||
|
||||
return newTransferNotifications;
|
||||
};
|
||||
const newTransferNotifications = await processTransferNotifications(transferNotifications);
|
||||
setTransferNotifications(newTransferNotifications);
|
||||
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
||||
if (status) statuses[txHash] = status;
|
||||
} catch (error) {
|
||||
// ignore errors for the first 120s since the status might not be available yet
|
||||
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
|
||||
statuses[txHash] = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return statuses;
|
||||
},
|
||||
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!transferStatuses) return;
|
||||
const newTransferNotifications = transferNotifications.map((notification) => {
|
||||
const status = transferStatuses[notification.txHash];
|
||||
if (!status) return notification;
|
||||
return {
|
||||
...notification,
|
||||
status,
|
||||
};
|
||||
});
|
||||
setTransferNotifications(newTransferNotifications);
|
||||
}, [transferStatuses]);
|
||||
|
||||
return {
|
||||
transferNotifications,
|
||||
addTransferNotification,
|
||||
|
||||
@@ -6,10 +6,8 @@ import { MarketFilters, MARKET_FILTER_LABELS, type MarketData } from '@/constant
|
||||
import { getAssets } from '@/state/assetsSelectors';
|
||||
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
|
||||
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
|
||||
const filterFunctions = {
|
||||
[MarketFilters.ALL]: () => true,
|
||||
[MarketFilters.ALL]: (market: MarketData) => true,
|
||||
[MarketFilters.LAYER_1]: (market: MarketData) => {
|
||||
return market.asset.tags?.toArray().includes('Layer 1');
|
||||
},
|
||||
@@ -30,15 +28,13 @@ export const useMarketsData = (
|
||||
const allAssets = useSelector(getAssets, shallowEqual) || {};
|
||||
|
||||
const markets = useMemo(() => {
|
||||
return Object.values(allPerpetualMarkets)
|
||||
.filter(isTruthy)
|
||||
.map((marketData) => ({
|
||||
asset: allAssets[marketData.assetId],
|
||||
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
||||
...marketData,
|
||||
...marketData.perpetual,
|
||||
...marketData.configs,
|
||||
})) as MarketData[];
|
||||
return Object.values(allPerpetualMarkets).map((marketData) => ({
|
||||
asset: allAssets[marketData.assetId],
|
||||
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
||||
...marketData,
|
||||
...marketData.perpetual,
|
||||
...marketData.configs,
|
||||
})) as MarketData[];
|
||||
}, [allPerpetualMarkets, allAssets]);
|
||||
|
||||
const filteredMarkets = useMemo(() => {
|
||||
|
||||
@@ -1,213 +1,205 @@
|
||||
import { type ReactNode, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { isEqual, groupBy } from 'lodash';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
||||
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
||||
|
||||
import {
|
||||
STRING_KEYS,
|
||||
STRING_KEY_VALUES,
|
||||
type StringGetterFunction,
|
||||
type StringKey,
|
||||
} from '@/constants/localization';
|
||||
|
||||
import {
|
||||
type NotificationTypeConfig,
|
||||
NotificationType,
|
||||
DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { TradeNotification } from '@/views/notifications/TradeNotification';
|
||||
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { TransferStatusToast } from '@/views/TransferStatus';
|
||||
|
||||
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
import { getAbacusNotifications } from '@/state/notificationsSelectors';
|
||||
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
||||
|
||||
const parseStringParamsForNotification = ({
|
||||
stringGetter,
|
||||
value,
|
||||
}: {
|
||||
stringGetter: StringGetterFunction;
|
||||
value: unknown;
|
||||
}): ReactNode => {
|
||||
if (STRING_KEY_VALUES[value as StringKey]) {
|
||||
return stringGetter({ key: value as StringKey });
|
||||
}
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
return value as ReactNode;
|
||||
};
|
||||
|
||||
export const notificationTypes: NotificationTypeConfig[] = [
|
||||
export const notificationTypes = [
|
||||
{
|
||||
type: NotificationType.AbacusGenerated,
|
||||
useTrigger: ({ trigger }) => {
|
||||
type: NotificationType.OrderStatusChanged,
|
||||
|
||||
useTrigger: ({ trigger, lastUpdated }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const abacusNotifications = useSelector(getAbacusNotifications, isEqual);
|
||||
|
||||
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
||||
const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
|
||||
|
||||
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
||||
const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
|
||||
|
||||
const orderIds = useMemo(
|
||||
() => [...Object.keys(ordersByOrderId), ...Object.keys(fillsByOrderId)],
|
||||
[orders, fills]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
for (const abacusNotif of abacusNotifications) {
|
||||
const [abacusNotificationType = '', id = ''] = abacusNotif.id.split(':');
|
||||
const parsedData = abacusNotif.data ? JSON.parse(abacusNotif.data) : {};
|
||||
for (const orderId of orderIds) {
|
||||
const fills = fillsByOrderId[orderId];
|
||||
|
||||
const params = Object.fromEntries(
|
||||
Object.entries(parsedData).map(([key, value]) => {
|
||||
return [key, parseStringParamsForNotification({ stringGetter, value })];
|
||||
})
|
||||
);
|
||||
|
||||
switch (abacusNotificationType) {
|
||||
case 'order': {
|
||||
trigger(
|
||||
abacusNotif.id,
|
||||
{
|
||||
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
|
||||
title: stringGetter({ key: abacusNotif.title }),
|
||||
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
|
||||
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||
toastSensitivity: 'foreground',
|
||||
renderCustomBody: ({ isToast, notification }) => (
|
||||
<TradeNotification
|
||||
isToast={isToast}
|
||||
data={parsedData}
|
||||
notification={notification}
|
||||
/>
|
||||
const order =
|
||||
ordersByOrderId[orderId] ??
|
||||
(fills?.length
|
||||
? {
|
||||
...fills[fills.length - 1],
|
||||
id: orderId,
|
||||
createdAtMilliseconds: Math.max(
|
||||
...fills.map((fill) => fill.createdAtMilliseconds)
|
||||
),
|
||||
},
|
||||
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
|
||||
true
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
trigger(
|
||||
abacusNotif.id,
|
||||
{
|
||||
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
|
||||
title: stringGetter({ key: abacusNotif.title }),
|
||||
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
|
||||
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||
toastSensitivity: 'foreground',
|
||||
},
|
||||
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [abacusNotifications, stringGetter]);
|
||||
},
|
||||
useNotificationAction: () => {
|
||||
const dispatch = useDispatch();
|
||||
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
||||
const ordersById = groupBy(orders, 'id');
|
||||
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
||||
const fillsById = groupBy(fills, 'id');
|
||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||
const navigate = useNavigate();
|
||||
status: AbacusOrderStatus.filled,
|
||||
}
|
||||
: undefined);
|
||||
|
||||
return (notificationId: string) => {
|
||||
const [abacusNotificationType = '', id = ''] = notificationId.split(':');
|
||||
|
||||
if (ordersById[id]) {
|
||||
dispatch(
|
||||
openDialog({
|
||||
type: DialogTypes.OrderDetails,
|
||||
dialogProps: { orderId: id },
|
||||
})
|
||||
);
|
||||
} else if (fillsById[id]) {
|
||||
dispatch(
|
||||
openDialog({
|
||||
type: DialogTypes.FillDetails,
|
||||
dialogProps: { fillId: id },
|
||||
})
|
||||
);
|
||||
} else if (marketIds.includes(id)) {
|
||||
navigate(`${AppRoute.Trade}/${id}`, {
|
||||
replace: true,
|
||||
});
|
||||
if (order)
|
||||
trigger(
|
||||
order.id,
|
||||
{
|
||||
icon: (
|
||||
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
|
||||
),
|
||||
title: `${stringGetter({
|
||||
key: order.resources.typeStringKey as StringKey,
|
||||
})} ${
|
||||
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
|
||||
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
|
||||
: stringGetter({ key: order.resources.statusStringKey as StringKey })
|
||||
}`,
|
||||
description: `${stringGetter({
|
||||
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
|
||||
})} ${order.size} ${order.marketId} @ $${order.price}`,
|
||||
actionDescription: 'View Order',
|
||||
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
|
||||
toastSensitivity:
|
||||
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
|
||||
toastDuration: 5000,
|
||||
},
|
||||
[order.status.name, order.size],
|
||||
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [orderIds]);
|
||||
},
|
||||
},
|
||||
|
||||
// useNotificationAction: () => {
|
||||
// const dispatch = useDispatch();
|
||||
// const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
||||
// const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
|
||||
|
||||
// const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
||||
// const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
|
||||
|
||||
// return (id) => {
|
||||
// if (ordersByOrderId[id]) {
|
||||
// dispatch(
|
||||
// openDialog({
|
||||
// type: DialogTypes.OrderDetails,
|
||||
// dialogProps: { orderId: id },
|
||||
// })
|
||||
// );
|
||||
// } else if (fillsByOrderId[id]) {
|
||||
// const fillId = fillsByOrderId[id][0].id;
|
||||
|
||||
// dispatch(
|
||||
// openDialog({
|
||||
// type: DialogTypes.FillDetails,
|
||||
// dialogProps: { fillId },
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
// },
|
||||
} as NotificationTypeConfig<string, [string, number]>,
|
||||
{
|
||||
type: NotificationType.SquidTransfer,
|
||||
useTrigger: ({ trigger }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { transferNotifications } = useLocalNotifications();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
|
||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
|
||||
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
||||
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
|
||||
if (type === 'withdrawal' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
|
||||
return STRING_KEYS.WITHDRAW;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
for (const transfer of transferNotifications) {
|
||||
const { fromChainId, status, txHash, toAmount } = transfer;
|
||||
const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
|
||||
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||
const type = fromChainId === TESTNET_CHAIN_ID ? 'withdrawal' : 'deposit';
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||
|
||||
const type =
|
||||
fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
|
||||
? 'withdrawal'
|
||||
: 'deposit';
|
||||
// TODO: confirm with design what the description should be
|
||||
const description = (
|
||||
<div>
|
||||
<Styled.TransferText>
|
||||
{type === 'deposit' ? 'Deposit of ' : 'Withdraw of '}
|
||||
<Output type={OutputType.Fiat} value={toAmount} />
|
||||
</Styled.TransferText>
|
||||
|
||||
const title = stringGetter({
|
||||
key: {
|
||||
deposit: isFinished ? STRING_KEYS.DEPOSIT : STRING_KEYS.DEPOSIT_IN_PROGRESS,
|
||||
withdrawal: isFinished ? STRING_KEYS.WITHDRAW : STRING_KEYS.WITHDRAW_IN_PROGRESS,
|
||||
}[type],
|
||||
});
|
||||
|
||||
const toChainEta = status?.toChain?.chainData?.estimatedRouteDuration || 0;
|
||||
const estimatedDuration = formatSeconds(Math.max(toChainEta, 0));
|
||||
const body = stringGetter({
|
||||
key: STRING_KEYS.DEPOSIT_STATUS,
|
||||
params: {
|
||||
AMOUNT_USD: `${toAmount} ${DydxChainAsset.USDC.toUpperCase()}`,
|
||||
ESTIMATED_DURATION: estimatedDuration,
|
||||
},
|
||||
});
|
||||
{error && (
|
||||
<Styled.ErrorMessage type={AlertType.Error}>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
ERROR_MESSAGE:
|
||||
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
},
|
||||
})}
|
||||
</Styled.ErrorMessage>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
trigger(
|
||||
txHash,
|
||||
{
|
||||
icon,
|
||||
title,
|
||||
body,
|
||||
renderCustomBody: ({ isToast, notification }) => (
|
||||
<TransferStatusNotification
|
||||
isToast={isToast}
|
||||
slotIcon={icon}
|
||||
slotTitle={title}
|
||||
transfer={transfer}
|
||||
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
|
||||
title: stringGetter({ key: getTitleStringKey(type, finished) }),
|
||||
description: description,
|
||||
customContent: (
|
||||
<TransferStatusToast
|
||||
type={type}
|
||||
toAmount={transfer.toAmount}
|
||||
triggeredAt={transfer.triggeredAt}
|
||||
notification={notification}
|
||||
status={transfer.status}
|
||||
/>
|
||||
),
|
||||
customMenuContent: !finished && (
|
||||
<div>
|
||||
{description}
|
||||
<TransferStatusSteps status={transfer.status} type={type} />
|
||||
</div>
|
||||
),
|
||||
toastSensitivity: 'foreground',
|
||||
},
|
||||
[isFinished]
|
||||
[]
|
||||
);
|
||||
}
|
||||
}, [transferNotifications, stringGetter]);
|
||||
},
|
||||
useNotificationAction: () => {
|
||||
return () => {};
|
||||
}, [transferNotifications]);
|
||||
},
|
||||
},
|
||||
];
|
||||
] satisfies NotificationTypeConfig[];
|
||||
|
||||
const $Icon = styled.img`
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.TransferText = styled.span`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5ch;
|
||||
`;
|
||||
|
||||
Styled.ErrorMessage = styled.div`
|
||||
max-width: 13rem;
|
||||
`;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import {
|
||||
type Notification,
|
||||
type NotificationDisplayData,
|
||||
type NotificationPreferences,
|
||||
type Notifications,
|
||||
NotificationStatus,
|
||||
NotificationType,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
@@ -15,10 +13,8 @@ import { notificationTypes } from './useNotificationTypes';
|
||||
|
||||
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
||||
|
||||
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
|
||||
|
||||
const NotificationsContext = createContext<NotificationsContextType>(
|
||||
{} as NotificationsContextType
|
||||
const NotificationsContext = createContext<ReturnType<typeof useNotificationsContext> | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
NotificationsContext.displayName = 'Notifications';
|
||||
@@ -31,7 +27,6 @@ export const useNotifications = () => useContext(NotificationsContext)!;
|
||||
|
||||
const useNotificationsContext = () => {
|
||||
// Local storage
|
||||
// const [notifications, setNotifications] = useState<Notifications>({});
|
||||
const [notifications, setNotifications] = useLocalStorage<Notifications>({
|
||||
key: LocalStorageKey.Notifications,
|
||||
defaultValue: {},
|
||||
@@ -42,25 +37,12 @@ const useNotificationsContext = () => {
|
||||
defaultValue: Date.now(),
|
||||
});
|
||||
|
||||
const [notificationPreferences, setNotificationPreferences] =
|
||||
useLocalStorage<NotificationPreferences>({
|
||||
key: LocalStorageKey.NotificationPreferences,
|
||||
defaultValue: {
|
||||
[NotificationType.AbacusGenerated]: true,
|
||||
[NotificationType.SquidTransfer]: true,
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setNotificationsLastUpdated(Date.now());
|
||||
}, [notifications]);
|
||||
|
||||
const getKey = useCallback(
|
||||
<T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
|
||||
`${notification.type}/${notification.id}`,
|
||||
[]
|
||||
);
|
||||
const getKey = <T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
|
||||
`${notification.type}/${notification.id}`;
|
||||
|
||||
// Display data
|
||||
const [notificationsDisplayData, setNotificationsDisplayData] = useState(
|
||||
@@ -72,46 +54,29 @@ const useNotificationsContext = () => {
|
||||
[notificationsDisplayData]
|
||||
);
|
||||
|
||||
// Check for version changes
|
||||
useEffect(() => {
|
||||
if (
|
||||
notificationPreferences.version !==
|
||||
LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences]
|
||||
) {
|
||||
setNotificationPreferences({
|
||||
[NotificationType.AbacusGenerated]: true,
|
||||
[NotificationType.SquidTransfer]: true,
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Status changes
|
||||
const updateStatus = useCallback(
|
||||
(notification: Notification, status: NotificationStatus) => {
|
||||
notification.status = status;
|
||||
notification.timestamps[notification.status] = Date.now();
|
||||
setNotifications({ ...notifications, [getKey(notification)]: notification });
|
||||
setNotifications({ ...notifications });
|
||||
},
|
||||
[notifications, getKey]
|
||||
[notifications]
|
||||
);
|
||||
|
||||
const { markUnseen, markSeen, markCleared } = useMemo(
|
||||
() => ({
|
||||
markUnseen: (notification: Notification) => {
|
||||
if (notification.status < NotificationStatus.Unseen) {
|
||||
if (notification.status < NotificationStatus.Unseen)
|
||||
updateStatus(notification, NotificationStatus.Unseen);
|
||||
}
|
||||
},
|
||||
markSeen: (notification: Notification) => {
|
||||
if (notification.status < NotificationStatus.Seen) {
|
||||
if (notification.status < NotificationStatus.Seen)
|
||||
updateStatus(notification, NotificationStatus.Seen);
|
||||
}
|
||||
},
|
||||
markCleared: (notification: Notification) => {
|
||||
if (notification.status < NotificationStatus.Cleared) {
|
||||
if (notification.status < NotificationStatus.Cleared)
|
||||
updateStatus(notification, NotificationStatus.Cleared);
|
||||
}
|
||||
},
|
||||
}),
|
||||
[updateStatus]
|
||||
@@ -132,37 +97,33 @@ const useNotificationsContext = () => {
|
||||
|
||||
const notification = notifications[key];
|
||||
|
||||
// Filter out notifications that are not enabled
|
||||
if (notificationPreferences[type] !== false) {
|
||||
// New unique key - create new notification
|
||||
if (!notification) {
|
||||
const notification = (notifications[key] = {
|
||||
id,
|
||||
type,
|
||||
timestamps: {},
|
||||
updateKey,
|
||||
} as Notification);
|
||||
updateStatus(
|
||||
notification,
|
||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||
);
|
||||
} else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
|
||||
// updateKey changed - update existing notification
|
||||
// New unique key - create new notification
|
||||
if (!notification) {
|
||||
const notification = (notifications[key] = {
|
||||
id,
|
||||
type,
|
||||
timestamps: {},
|
||||
updateKey,
|
||||
} as Notification);
|
||||
|
||||
const notification = notifications[key];
|
||||
updateStatus(
|
||||
notification,
|
||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||
);
|
||||
}
|
||||
|
||||
notification.updateKey = updateKey;
|
||||
updateStatus(notification, NotificationStatus.Updated);
|
||||
}
|
||||
} else {
|
||||
// Notification is disabled - remove it
|
||||
delete notifications[key];
|
||||
// updateKey changed - update existing notification
|
||||
else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
|
||||
const notification = notifications[key];
|
||||
|
||||
notification.updateKey = updateKey;
|
||||
updateStatus(notification, NotificationStatus.Updated);
|
||||
}
|
||||
|
||||
notificationsDisplayData[key] = displayData;
|
||||
setNotificationsDisplayData({ ...notificationsDisplayData });
|
||||
},
|
||||
[notifications, updateStatus, notificationPreferences[type]]
|
||||
[notifications, updateStatus]
|
||||
),
|
||||
|
||||
lastUpdated: notificationsLastUpdated,
|
||||
@@ -175,8 +136,10 @@ const useNotificationsContext = () => {
|
||||
)
|
||||
);
|
||||
|
||||
const onNotificationAction = async (notification: Notification) =>
|
||||
await actions[notification.type]?.(notification.id);
|
||||
const onNotificationAction = useCallback(
|
||||
async (notification: Notification) => await actions[notification.type]?.(notification.id),
|
||||
[actions]
|
||||
);
|
||||
|
||||
// Push notifications
|
||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||
@@ -223,7 +186,7 @@ const useNotificationsContext = () => {
|
||||
renotify: true,
|
||||
tag: getKey(notification),
|
||||
data: notification,
|
||||
description: displayData.body,
|
||||
body: displayData.description,
|
||||
icon: iconUrl ?? '/favicon.svg',
|
||||
badge: iconUrl ?? '/favicon.svg',
|
||||
image: iconUrl ?? '/favicon.svg',
|
||||
@@ -245,7 +208,7 @@ const useNotificationsContext = () => {
|
||||
|
||||
setPushNotificationsLastUpdated(Date.now());
|
||||
})();
|
||||
}, [hasEnabledPush, notifications, markSeen]);
|
||||
}, [hasEnabledPush, notifications, onNotificationAction, markSeen]);
|
||||
|
||||
// Menu state
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
@@ -276,13 +239,5 @@ const useNotificationsContext = () => {
|
||||
// Menu state
|
||||
isMenuOpen,
|
||||
setIsMenuOpen,
|
||||
|
||||
// Notification Preferences
|
||||
notificationPreferences,
|
||||
setNotificationPreferences,
|
||||
getNotificationPreferenceForType: useCallback(
|
||||
(type: NotificationType) => notificationPreferences[type],
|
||||
[notificationPreferences]
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||
|
||||
export const STATUS_ERROR_GRACE_PERIOD = 300_000;
|
||||
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
|
||||
|
||||
const useSquidContext = () => {
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
@@ -22,11 +22,7 @@ const useSquidContext = () => {
|
||||
};
|
||||
|
||||
const squid = useMemo(
|
||||
() =>
|
||||
new Squid({
|
||||
baseUrl: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.endpoints['0xsquid'],
|
||||
integratorId: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.squidIntegratorId,
|
||||
}),
|
||||
() => new Squid({ baseUrl: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.endpoints['0xsquid'] }),
|
||||
[selectedNetwork]
|
||||
);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export const useSubaccount = () => useContext(SubaccountContext);
|
||||
|
||||
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { usdcDenom, usdcDecimals } = useTokenConfigs();
|
||||
const { usdcDenom } = useTokenConfigs();
|
||||
const { compositeClient, faucetClient } = useDydxClient();
|
||||
|
||||
const { getFaucetFunds } = useMemo(
|
||||
@@ -79,34 +79,16 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
subaccountClient: SubaccountClient;
|
||||
assetId?: number;
|
||||
amount: number;
|
||||
}) => {
|
||||
try {
|
||||
return await compositeClient?.depositToSubaccount(
|
||||
subaccountClient,
|
||||
amount.toFixed(usdcDecimals)
|
||||
);
|
||||
} catch (error) {
|
||||
log('useSubaccount/depositToSubaccount', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}) => await compositeClient?.depositToSubaccount(subaccountClient, amount.toString()),
|
||||
|
||||
withdrawFromSubaccount: async ({
|
||||
subaccountClient,
|
||||
amount,
|
||||
}: {
|
||||
subaccountClient: SubaccountClient;
|
||||
amount: number;
|
||||
}) => {
|
||||
try {
|
||||
return await compositeClient?.withdrawFromSubaccount(
|
||||
subaccountClient,
|
||||
amount.toFixed(usdcDecimals)
|
||||
);
|
||||
} catch (error) {
|
||||
log('useSubaccount/withdrawFromSubaccount', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()),
|
||||
|
||||
transferFromSubaccountToAddress: async ({
|
||||
subaccountClient,
|
||||
assetId = 0,
|
||||
@@ -117,33 +99,27 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
assetId?: number;
|
||||
amount: number;
|
||||
recipient: string;
|
||||
}) => {
|
||||
try {
|
||||
return await compositeClient?.validatorClient.post.send(
|
||||
subaccountClient?.wallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg =
|
||||
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
|
||||
subaccountClient.address,
|
||||
subaccountClient.subaccountNumber,
|
||||
assetId,
|
||||
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
|
||||
recipient
|
||||
);
|
||||
}) =>
|
||||
await compositeClient?.validatorClient.post.send(
|
||||
subaccountClient?.wallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg =
|
||||
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
|
||||
subaccountClient.address,
|
||||
subaccountClient.subaccountNumber,
|
||||
assetId,
|
||||
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
|
||||
recipient
|
||||
);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
Method.BroadcastTxCommit
|
||||
);
|
||||
} catch (error) {
|
||||
log('useSubaccount/transferFromSubaccountToAddress', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
resolve([msg]);
|
||||
}),
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
Method.BroadcastTxCommit
|
||||
),
|
||||
|
||||
transferNativeToken: async ({
|
||||
subaccountClient,
|
||||
@@ -153,30 +129,24 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
subaccountClient: SubaccountClient;
|
||||
amount: number;
|
||||
recipient: string;
|
||||
}) => {
|
||||
try {
|
||||
return await compositeClient?.validatorClient.post.send(
|
||||
subaccountClient.wallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg = compositeClient?.sendTokenMessage(
|
||||
subaccountClient.wallet,
|
||||
amount.toString(),
|
||||
recipient
|
||||
);
|
||||
}) =>
|
||||
await compositeClient?.validatorClient.post.send(
|
||||
subaccountClient.wallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg = compositeClient?.sendTokenMessage(
|
||||
subaccountClient.wallet,
|
||||
amount.toString(),
|
||||
recipient,
|
||||
);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
false,
|
||||
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||
undefined,
|
||||
Method.BroadcastTxCommit
|
||||
);
|
||||
} catch (error) {
|
||||
log('useSubaccount/transferNativeToken', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
resolve([msg]);
|
||||
}),
|
||||
false,
|
||||
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||
undefined,
|
||||
Method.BroadcastTxCommit
|
||||
),
|
||||
|
||||
sendSquidWithdrawFromSubaccount: async ({
|
||||
subaccountClient,
|
||||
@@ -188,27 +158,20 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
payload: string;
|
||||
}) => {
|
||||
if (!compositeClient) throw new Error('client not initialized');
|
||||
try {
|
||||
const transaction = JSON.parse(payload);
|
||||
|
||||
const msg = compositeClient.withdrawFromSubaccountMessage(
|
||||
subaccountClient,
|
||||
amount.toFixed(usdcDecimals)
|
||||
);
|
||||
const ibcMsg: EncodeObject = {
|
||||
typeUrl: transaction.msgTypeUrl,
|
||||
value: transaction.msg,
|
||||
};
|
||||
const transaction = JSON.parse(payload);
|
||||
|
||||
return await compositeClient.send(
|
||||
subaccountClient.wallet,
|
||||
() => Promise.resolve([msg, ibcMsg]),
|
||||
false
|
||||
);
|
||||
} catch (error) {
|
||||
log('useSubaccount/sendSquidWithdrawFromSubaccount', error);
|
||||
throw error;
|
||||
}
|
||||
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString());
|
||||
const ibcMsg: EncodeObject = {
|
||||
typeUrl: transaction.msgTypeUrl,
|
||||
value: transaction.msg,
|
||||
};
|
||||
|
||||
return await compositeClient.send(
|
||||
subaccountClient.wallet,
|
||||
() => Promise.resolve([msg, ibcMsg]),
|
||||
false
|
||||
);
|
||||
},
|
||||
}),
|
||||
[compositeClient]
|
||||
@@ -350,6 +313,11 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
placeOrderParams = abacusStateManager.placeOrder(callback);
|
||||
}
|
||||
|
||||
track(AnalyticsEvent.TradePlaceOrder, {
|
||||
...placeOrderParams,
|
||||
isClosePosition,
|
||||
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
|
||||
|
||||
return placeOrderParams;
|
||||
},
|
||||
[subaccountClient]
|
||||
@@ -378,6 +346,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
}) => {
|
||||
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
|
||||
if (success) {
|
||||
track(AnalyticsEvent.TradeCancelOrder);
|
||||
onSuccess?.();
|
||||
} else {
|
||||
onError?.({ errorStringKey: parsingError?.stringKey });
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
|
||||
class TestFlags {
|
||||
public queryParams: queryString.ParsedQuery<string>;
|
||||
|
||||
constructor() {
|
||||
this.queryParams = {};
|
||||
}
|
||||
|
||||
setQueryParams(flags: queryString.ParsedQuery<string>) {
|
||||
this.queryParams = flags;
|
||||
}
|
||||
|
||||
get displayInitializingMarkets() {
|
||||
return !!this.queryParams.displayInitializingMarkets;
|
||||
}
|
||||
}
|
||||
|
||||
export const testFlags = new TestFlags();
|
||||
|
||||
const useTestFlagsContext = () => {
|
||||
const [queryParams, setQueryParams] = useState<queryString.ParsedQuery<string>>({});
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const parsedQueryParams = queryString.parse(location.search.substring(-1));
|
||||
testFlags.setQueryParams(parsedQueryParams);
|
||||
setQueryParams(parsedQueryParams);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...queryParams,
|
||||
};
|
||||
};
|
||||
|
||||
type TestFlagsContextType = ReturnType<typeof useTestFlagsContext>;
|
||||
const TestFlagsContext = createContext<TestFlagsContextType>({} as TestFlagsContextType);
|
||||
TestFlagsContext.displayName = 'TestFlags';
|
||||
|
||||
export const TestFlagsProvider = ({ ...props }) => (
|
||||
<TestFlagsContext.Provider value={useTestFlagsContext()} {...props} />
|
||||
);
|
||||
|
||||
export const useTestFlags = () => useContext(TestFlagsContext);
|
||||
@@ -19,10 +19,8 @@ export const useTokenConfigs = (): {
|
||||
},
|
||||
};
|
||||
usdcDenom: string;
|
||||
usdcDecimals: number;
|
||||
usdcLabel: string;
|
||||
chainTokenDenom: string;
|
||||
chainTokenDecimals: number;
|
||||
chainTokenLabel: string;
|
||||
} => {
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
@@ -31,10 +29,8 @@ export const useTokenConfigs = (): {
|
||||
return {
|
||||
tokensConfigs,
|
||||
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
|
||||
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
@@ -90,21 +90,19 @@ export const useWalletConnection = () => {
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
|
||||
const wagmiConnector = useMemo(
|
||||
() =>
|
||||
walletType && walletConnectionType
|
||||
? resolveWagmiConnector({
|
||||
|
||||
const { connectAsync: connectWagmi } =
|
||||
walletType && walletConnectionType
|
||||
? useConnectWagmi({
|
||||
connector: resolveWagmiConnector({
|
||||
walletType,
|
||||
walletConnection: {
|
||||
type: walletConnectionType,
|
||||
},
|
||||
walletConnectConfig,
|
||||
})
|
||||
: undefined,
|
||||
[walletConnectConfig, walletType, walletConnectionType]
|
||||
);
|
||||
|
||||
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
|
||||
}),
|
||||
})
|
||||
: useConnectWagmi();
|
||||
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
||||
|
||||
const connectWallet = useCallback(
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="27" height="28" viewBox="0 0 27 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame">
|
||||
<path id="Vector" d="M9.5 15L12.5 18L17.5 11M25.5 14C25.5 15.5759 25.1896 17.1363 24.5866 18.5922C23.9835 20.0481 23.0996 21.371 21.9853 22.4853C20.871 23.5996 19.5481 24.4835 18.0922 25.0866C16.6363 25.6896 15.0759 26 13.5 26C11.9241 26 10.3637 25.6896 8.9078 25.0866C7.45189 24.4835 6.12902 23.5996 5.01472 22.4853C3.90042 21.371 3.0165 20.0481 2.41345 18.5922C1.81039 17.1363 1.5 15.5759 1.5 14C1.5 10.8174 2.76428 7.76516 5.01472 5.51472C7.26516 3.26428 10.3174 2 13.5 2C16.6826 2 19.7348 3.26428 21.9853 5.51472C24.2357 7.76516 25.5 10.8174 25.5 14Z" stroke="#3ED9A4" stroke-opacity="0.5" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M9.5 15L12.5 18L17.5 11" stroke="#3ED9A4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 914 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 6.39098L5.68182 10L11 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
<path d="M2 6.39098L5.68182 10L11 2" stroke="currentColor" stroke-width="3" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
@@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<g clip-path="url(#clip0_18204_36775)">
|
||||
<path d="M13.2426 6.25736C14.0817 7.09647 14.6531 8.16557 14.8846 9.32946C15.1162 10.4933 14.9973 11.6997 14.5432 12.7961C14.0891 13.8925 13.32 14.8295 12.3334 15.4888C11.3467 16.1481 10.1866 16.5 8.99993 16.5C7.81324 16.5 6.6532 16.1481 5.66651 15.4888C4.67981 14.8295 3.91078 13.8925 3.45665 12.7961C3.00253 11.6997 2.88371 10.4933 3.11522 9.32946C3.34673 8.16557 3.91817 7.09647 4.75729 6.25736" stroke="#C8C7D8" stroke-width="1.35" stroke-linecap="round"/>
|
||||
<path d="M11.25 9L9 11.25M9 11.25L6.75 9M9 11.25V1.6875" stroke="#C8C7D8" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_18204_36775">
|
||||
<rect width="18" height="18" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 886 B |
@@ -10,7 +10,6 @@ export { default as CautionCircleIcon } from './caution-circle.svg';
|
||||
export { default as CautionCircleStrokeIcon } from './caution-circle-stroke.svg';
|
||||
export { default as ChatIcon } from './chat-bubble.svg';
|
||||
export { default as CheckIcon } from './check.svg';
|
||||
export { default as CheckCircleIcon } from './check-circle.svg';
|
||||
export { default as ChevronLeftIcon } from './chevron-left.svg';
|
||||
export { default as ChevronRightIcon } from './chevron-right.svg';
|
||||
export { default as ClockIcon } from './clock.svg';
|
||||
@@ -20,7 +19,6 @@ export { default as CoinsIcon } from './coins.svg';
|
||||
export { default as CommentIcon } from './comment.svg';
|
||||
export { default as CopyIcon } from './copy.svg';
|
||||
export { default as CubeIcon } from './cube.svg';
|
||||
export { default as DepositIcon } from './deposit.svg';
|
||||
export { default as DepthChartIcon } from './depth-chart.svg';
|
||||
export { default as DiscordIcon } from './discord.svg';
|
||||
export { default as ExportKeysIcon } from './export-keys.svg';
|
||||
@@ -61,7 +59,6 @@ export { default as TransferIcon } from './transfer.svg';
|
||||
export { default as TriangleIcon } from './triangle.svg';
|
||||
export { default as TryAgainIcon } from './try-again.svg';
|
||||
export { default as WarningIcon } from './warning.svg';
|
||||
export { default as WithdrawIcon } from './withdraw.svg';
|
||||
|
||||
// Wallets
|
||||
export { default as BitkeepIcon } from './wallets/bitkeep.svg';
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Wallet" clip-path="url(#clip0_14122_34311)">
|
||||
<g id="Group 151">
|
||||
<path id="Exclude" fill-rule="evenodd" clip-rule="evenodd" d="M11.4979 10.9976C11.1764 11.3193 10.7404 11.5 10.2857 11.5L1.71429 11.5C1.25963 11.5 0.823594 11.3193 0.502102 10.9976C0.180612 10.6758 -4.76837e-07 10.2395 -4.76837e-07 9.78455L-4.76837e-07 2.06502C-4.76837e-07 2.51999 0.180612 2.95632 0.502102 3.27803C0.823594 3.59974 1.25963 3.78047 1.71429 3.78047L10.2857 3.78047C10.7404 3.78047 11.1764 3.96121 11.4979 4.28292C11.8194 4.60462 12 5.04096 12 5.49592V9.78455C12 10.2395 11.8194 10.6758 11.4979 10.9976ZM9.64294 8.7126C10.2347 8.7126 10.7144 8.23258 10.7144 7.64044C10.7144 7.0483 10.2347 6.56828 9.64294 6.56828C9.05121 6.56828 8.57151 7.0483 8.57151 7.64044C8.57151 8.23258 9.05121 8.7126 9.64294 8.7126Z" fill="#C8C7D8"/>
|
||||
<path id="Vector" d="M8.57031 2.92186V2.06413C8.57031 1.60917 8.3897 1.17284 8.06821 0.851128C7.74672 0.529419 7.31068 0.348684 6.85603 0.348684L2.14174 0.348684C1.80075 0.348684 1.47372 0.484235 1.2326 0.725517C0.991486 0.966799 0.856028 1.29405 0.856028 1.63527C0.856028 1.9765 0.991486 2.30374 1.2326 2.54503C1.47372 2.78631 1.80075 2.92186 2.14174 2.92186L8.57031 2.92186Z" fill="#C8C7D8"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_14122_34311">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<g fill="#FFFFFF">
|
||||
<path d="M461.2 128H80c-8.84 0-16-7.16-16-16s7.16-16 16-16h384c8.84 0 16-7.16 16-16 0-26.51-21.49-48-48-48H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h397.2c28.02 0 50.8-21.53 50.8-48V176c0-26.47-22.78-48-50.8-48zM416 336c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 405 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path d="M13.2426 6.2573C14.0817 7.09641 14.6531 8.16551 14.8846 9.3294C15.1162 10.4933 14.9973 11.6997 14.5432 12.796C14.0891 13.8924 13.32 14.8295 12.3334 15.4888C11.3467 16.148 10.1866 16.4999 8.99993 16.4999C7.81324 16.4999 6.6532 16.148 5.66651 15.4888C4.67981 14.8295 3.91078 13.8924 3.45665 12.796C3.00253 11.6997 2.88371 10.4933 3.11522 9.3294C3.34673 8.16551 3.91817 7.09641 4.75729 6.2573" stroke="#C8C7D8" stroke-width="1.35" stroke-linecap="round"/>
|
||||
<path d="M11.25 2.9375L9 0.6875M9 0.6875L6.75 2.9375M9 0.6875V10.25" stroke="#C8C7D8" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 723 B |
@@ -15,7 +15,6 @@ import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
|
||||
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
|
||||
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
|
||||
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
|
||||
import { PreferencesDialog } from '@/views/dialogs/PreferencesDialog';
|
||||
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
|
||||
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
|
||||
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
|
||||
@@ -23,7 +22,6 @@ import { TradeDialog } from '@/views/dialogs/TradeDialog';
|
||||
import { TransferDialog } from '@/views/dialogs/TransferDialog';
|
||||
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
|
||||
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
|
||||
import { ManageFundsDialog } from '@/views/dialogs/ManageFundsDialog';
|
||||
|
||||
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
|
||||
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
||||
@@ -58,7 +56,6 @@ export const DialogManager = () => {
|
||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
||||
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
|
||||
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
|
||||
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
|
||||
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
|
||||
@@ -66,6 +63,5 @@ export const DialogManager = () => {
|
||||
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
|
||||
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
|
||||
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
|
||||
[DialogTypes.ManageFunds]: <ManageFundsDialog {...modalProps} />,
|
||||
}[type];
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { AbacusApiStatus } from '@/constants/abacus';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||
import { ChatIcon, LinkOutIcon } from '@/icons';
|
||||
@@ -75,7 +75,7 @@ export const FooterDesktop = () => {
|
||||
)}
|
||||
</Styled.Row>
|
||||
|
||||
{isDev && (
|
||||
{import.meta.env.MODE !== 'production' && (
|
||||
<Styled.Details
|
||||
withSeparators
|
||||
items={[
|
||||
|
||||
@@ -236,5 +236,4 @@ Styled.IconButton = styled(IconButton)<{ size?: string }>`
|
||||
${headerMixins.button}
|
||||
--button-border: none;
|
||||
--button-icon-size: 1rem;
|
||||
--button-padding: 0 0.5em;
|
||||
`;
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { NotificationStatus } from '@/constants/notifications';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { useBreakpoints } from '@/hooks/useBreakpoints';
|
||||
|
||||
import { Button } from '@/components/Button';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { ToastArea } from '@/components/ToastArea';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { Button } from '@/components/Button';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const MAX_TOASTS = 10;
|
||||
|
||||
export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
const {
|
||||
notifications,
|
||||
@@ -29,9 +26,10 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
} = useNotifications();
|
||||
|
||||
const { isMobile } = useBreakpoints();
|
||||
const notificationMap = useMemo(() => {
|
||||
return (
|
||||
Object.values(notifications)
|
||||
|
||||
return (
|
||||
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
|
||||
{Object.values(notifications)
|
||||
// Sort by time of first trigger
|
||||
.sort(
|
||||
(n1, n2) =>
|
||||
@@ -44,69 +42,44 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
displayData: getDisplayData(notification),
|
||||
}))
|
||||
.filter(({ displayData }) => displayData)
|
||||
.slice(-MAX_TOASTS)
|
||||
);
|
||||
}, [notifications, getKey, getDisplayData]);
|
||||
|
||||
if (isMenuOpen) return null;
|
||||
|
||||
return (
|
||||
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
|
||||
{notificationMap.map(({ notification, key, displayData }, idx) => (
|
||||
<StyledToast
|
||||
key={key}
|
||||
layer={notificationMap.length - 1 - idx}
|
||||
isOpen={notification.status < NotificationStatus.Unseen}
|
||||
notification={notification}
|
||||
slotIcon={displayData.icon}
|
||||
slotTitle={displayData.title}
|
||||
slotTitleLeft={displayData.slotTitleLeft}
|
||||
slotTitleRight={displayData.slotTitleRight}
|
||||
slotDescription={displayData.body}
|
||||
slotCustomContent={displayData.renderCustomBody?.({ isToast: true, notification })}
|
||||
onClick={() => onNotificationAction(notification)}
|
||||
slotAction={
|
||||
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
||||
{displayData.actionDescription}
|
||||
</Button>
|
||||
}
|
||||
actionDescription={displayData.actionDescription}
|
||||
actionAltText={displayData.actionAltText}
|
||||
duration={displayData.toastDuration ?? Infinity}
|
||||
sensitivity={displayData.toastSensitivity}
|
||||
setIsOpen={(isOpen, isClosedFromTimeout) => {
|
||||
if (!isOpen)
|
||||
if (isClosedFromTimeout)
|
||||
// Toast timer expired without user interaction
|
||||
markUnseen(notification);
|
||||
// Toast interacted with or dismissed
|
||||
else markSeen(notification);
|
||||
}}
|
||||
lastUpdated={notification.timestamps[notification.status]}
|
||||
/>
|
||||
))}
|
||||
.map(({ notification, key, displayData }) => (
|
||||
<Toast
|
||||
key={key}
|
||||
isOpen={notification.status < NotificationStatus.Unseen}
|
||||
slotIcon={displayData.icon}
|
||||
slotTitle={displayData.title}
|
||||
slotDescription={displayData.description}
|
||||
slotCustomContent={displayData.customContent}
|
||||
slotAction={
|
||||
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
||||
{displayData.actionDescription}
|
||||
</Button>
|
||||
}
|
||||
actionDescription={displayData.actionDescription}
|
||||
actionAltText={displayData.actionAltText}
|
||||
duration={isMenuOpen ? Infinity : displayData.toastDuration}
|
||||
sensitivity={displayData.toastSensitivity}
|
||||
setIsOpen={(isOpen, isClosedFromTimeout) => {
|
||||
if (!isOpen)
|
||||
if (isClosedFromTimeout)
|
||||
// Toast timer expired without user interaction
|
||||
markUnseen(notification);
|
||||
// Toast interacted with or dismissed
|
||||
else markSeen(notification);
|
||||
}}
|
||||
lastUpdated={notification.timestamps[notification.status]}
|
||||
/>
|
||||
))}
|
||||
</StyledToastArea>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledToastArea = styled(ToastArea)`
|
||||
position: absolute;
|
||||
width: min(17.5rem, 100%);
|
||||
width: min(16rem, 100%);
|
||||
inset: 0 0 0 auto;
|
||||
|
||||
padding: 0.75rem 0.75rem 0.75rem 0;
|
||||
|
||||
mask-image: linear-gradient(to left, transparent, white 0.5rem);
|
||||
`;
|
||||
|
||||
const StyledToast = styled(Toast)<{ layer: number }>`
|
||||
// Stacked toast
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
${({ layer }) => css`
|
||||
right: calc(${layer} * -2px);
|
||||
top: calc(${layer} * 2px);
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Abacus, { type Nullable } from '@dydxprotocol/v4-abacus';
|
||||
import Long from 'long';
|
||||
import type { IndexedTx } from '@cosmjs/stargate';
|
||||
import { encodeJson } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import {
|
||||
CompositeClient,
|
||||
@@ -31,7 +30,7 @@ import {
|
||||
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
|
||||
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
|
||||
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
|
||||
import { RootStore } from '@/state/_store';
|
||||
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
|
||||
@@ -196,19 +195,14 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
throw new StatefulOrderError('Stateful order has failed to commit.', tx);
|
||||
}
|
||||
|
||||
const encodedTx = encodeJson(tx);
|
||||
const parsedTx = JSON.parse(encodedTx);
|
||||
const hash = parsedTx.hash.toUpperCase();
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
const hash = parsedTx?.hash;
|
||||
|
||||
if (isTestnet) {
|
||||
console.log(
|
||||
`${ENVIRONMENT_CONFIG_MAP[
|
||||
this.compositeClient.network.getString() as DydxNetwork
|
||||
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
|
||||
);
|
||||
if (import.meta.env.MODE === 'production') {
|
||||
console.log(`https://testnet.mintscan.io/dydx-testnet/txs/${hash}`);
|
||||
} else console.log(`txHash: ${hash}`);
|
||||
|
||||
return encodedTx;
|
||||
return JSON.stringify(parsedTx);
|
||||
} catch (error) {
|
||||
if (error?.name !== 'BroadcastError') {
|
||||
log('DydxChainTransactions/placeOrderTransaction', error);
|
||||
@@ -235,17 +229,12 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
orderFlags,
|
||||
clobPairId,
|
||||
goodTilBlock || undefined,
|
||||
goodTilBlockTime || undefined
|
||||
goodTilBlockTime || undefined,
|
||||
);
|
||||
|
||||
const encodedTx = encodeJson(tx);
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
const parsedTx = JSON.parse(encodedTx);
|
||||
console.log(parsedTx, parsedTx.hash.toUpperCase());
|
||||
}
|
||||
|
||||
return encodedTx;
|
||||
return JSON.stringify(parsedTx);
|
||||
} catch (error) {
|
||||
log('DydxChainTransactions/cancelOrderTransaction', error);
|
||||
|
||||
@@ -306,11 +295,15 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
if (!this.localWallet) {
|
||||
throw new Error('Missing compositeClient or localWallet');
|
||||
}
|
||||
const msg = compositeClient?.sendTokenMessage(this.localWallet, amount, recipient);
|
||||
const msg = compositeClient?.sendTokenMessage(
|
||||
this.localWallet,
|
||||
amount,
|
||||
recipient,
|
||||
);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice
|
||||
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||
);
|
||||
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
@@ -441,6 +434,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
const parseDelegations = this.parseToPrimitives(delegations);
|
||||
callback(JSON.stringify(parseDelegations));
|
||||
break;
|
||||
// Do not implement Transfers (yet)
|
||||
case QueryType.Transfers:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
|
||||
|
||||
export const ENDPOINTS_PATH =
|
||||
import.meta.env.MODE === 'production'
|
||||
? 'config/prod/endpoints.json'
|
||||
: 'config/staging/dev_endpoints.json';
|
||||
|
||||
class AbacusFileSystem implements AbacusFileSystemProtocol {
|
||||
readTextFile(location: FileLocation, path: string): Nullable<string> {
|
||||
return null;
|
||||
|
||||
@@ -41,7 +41,7 @@ import AbacusStateNotifier from './stateNotification';
|
||||
import AbacusLocalizer from './localizer';
|
||||
import AbacusFormatter from './formatter';
|
||||
import AbacusThreading from './threading';
|
||||
import AbacusFileSystem from './filesystem';
|
||||
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
|
||||
import { LocaleSeparators } from '../numbers';
|
||||
class AbacusStateManager {
|
||||
private store: RootStore | undefined;
|
||||
|
||||
@@ -56,8 +56,13 @@ class AbacusRest implements AbacusRestProtocol {
|
||||
.then(async (response) => {
|
||||
const data = await response.text();
|
||||
|
||||
callback(data, response.status);
|
||||
|
||||
if (response.ok) {
|
||||
callback(data, response.status);
|
||||
} else {
|
||||
// response not OK, call callback with null data and the status, this includes 400/500 status codes
|
||||
callback(null, response.status);
|
||||
}
|
||||
|
||||
try {
|
||||
lastSuccessfulRestRequestByOrigin[new URL(url).origin] = Date.now();
|
||||
} catch {}
|
||||
|
||||
@@ -35,7 +35,6 @@ import { setApiState } from '@/state/app';
|
||||
import { setAssets } from '@/state/assets';
|
||||
import { setConfigs } from '@/state/configs';
|
||||
import { setInputs } from '@/state/inputs';
|
||||
import { updateNotifications } from '@/state/notifications';
|
||||
import { setHistoricalFundings, setLiveTrades, setMarkets, setOrderbook } from '@/state/perpetuals';
|
||||
import { isTruthy } from '../isTruthy';
|
||||
|
||||
@@ -51,7 +50,6 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
||||
}
|
||||
|
||||
notificationsChanged(notifications: kollections.List<AbacusNotification>): void {
|
||||
this.store?.dispatch(updateNotifications(notifications.toArray()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AbacusWebsocketProtocol } from '@/constants/abacus';
|
||||
import type { TradingViewBar } from '@/constants/candles';
|
||||
import { isDev } from '@/constants/networks';
|
||||
|
||||
import {
|
||||
PING_INTERVAL_MS,
|
||||
@@ -9,12 +8,11 @@ import {
|
||||
PONG_MESSAGE_TYPE,
|
||||
} from '@/constants/websocket';
|
||||
|
||||
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
||||
import { testFlags } from '@/hooks/useTestFlags';
|
||||
|
||||
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
||||
import { mapCandle } from '@/lib/tradingView/utils';
|
||||
|
||||
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
||||
|
||||
import { log } from '../telemetry';
|
||||
|
||||
const RECONNECT_INTERVAL_MS = 10_000;
|
||||
@@ -145,23 +143,6 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
||||
|
||||
break;
|
||||
}
|
||||
case 'v4_markets': {
|
||||
if (testFlags.displayInitializingMarkets) {
|
||||
shouldProcess = false;
|
||||
const { contents } = parsedMessage;
|
||||
|
||||
Object.keys(contents.markets ?? {}).forEach((market: any) => {
|
||||
const status = contents.markets[market].status;
|
||||
if (status === 'INITIALIZING') {
|
||||
contents.markets[market].status = 'ONLINE';
|
||||
}
|
||||
});
|
||||
|
||||
this.receivedCallback?.(JSON.stringify(parsedMessage));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
@@ -180,13 +161,13 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
||||
|
||||
this.socket.onclose = (e) => {
|
||||
this.connectedCallback?.(false);
|
||||
if (!isDev) return;
|
||||
if (import.meta.env.MODE === 'production') return;
|
||||
console.warn('AbacusStateManager > WS > close > ', e);
|
||||
};
|
||||
|
||||
this.socket.onerror = (e) => {
|
||||
this.connectedCallback?.(false);
|
||||
if (!isDev) return;
|
||||
if (import.meta.env.MODE === 'production') return;
|
||||
console.error('AbacusStateManager > WS > error > ', e);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { isDev } from "@/constants/networks";
|
||||
|
||||
export const log = (location: string, error: Error, metadata?: any) => {
|
||||
if (isDev) {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn('telemetry/log:', { location, error, metadata });
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +51,11 @@ const Profile = () => {
|
||||
icon: { iconName: IconName.Gear },
|
||||
href: AppRoute.Settings,
|
||||
},
|
||||
isConnected && {
|
||||
key: 'transfers',
|
||||
label: stringGetter({ key: STRING_KEYS.MANAGE_FUNDS }),
|
||||
icon: { iconName: IconName.Transfer },
|
||||
onClick: () => {
|
||||
dispatch(openDialog({ type: DialogTypes.ManageFunds }));
|
||||
},
|
||||
},
|
||||
// {
|
||||
// key: 'tutorials',
|
||||
// label: stringGetter({ key: STRING_KEYS.TUTORIALS }),
|
||||
// icon: { iconName: IconName.Comment },
|
||||
// },
|
||||
isConnected
|
||||
? {
|
||||
key: 'sign-out',
|
||||
@@ -183,7 +180,7 @@ Styled.MobileProfileLayout = styled.div`
|
||||
${layoutMixins.contentContainerPage}
|
||||
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Header = styled.header`
|
||||
@@ -281,17 +278,13 @@ Styled.RewardsPanel = styled(Panel)`
|
||||
`;
|
||||
|
||||
Styled.TablePanel = styled(Panel)`
|
||||
--panel-content-paddingY: 0;
|
||||
--panel-content-paddingX: 0;
|
||||
|
||||
> div > div {
|
||||
margin-top: 0.5rem;
|
||||
--scrollArea-height: 10rem;
|
||||
border-radius: 0.875rem;
|
||||
}
|
||||
max-width: calc(100vw - 2rem);
|
||||
max-height: 10rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0;
|
||||
border-radius: 0.875rem;
|
||||
|
||||
table {
|
||||
max-height: 10rem;
|
||||
--tableCell-padding: 0.25rem 1rem;
|
||||
--tableRow-backgroundColor: var(--color-layer-3);
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
@@ -131,7 +131,7 @@ export const AccountDetailsAndHistory = () => {
|
||||
key: 'BuyingPower',
|
||||
labelKey: STRING_KEYS.BUYING_POWER,
|
||||
type: OutputType.Fiat,
|
||||
value: MustBigNumber(buyingPower?.current).lt(0) ? undefined : buyingPower?.current, // show '-' when buying power is negative
|
||||
value: buyingPower?.current,
|
||||
},
|
||||
].filter(isTruthy);
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Nullable } from '@dydxprotocol/v4-abacus';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import { FeeTier } from '@/constants/abacus';
|
||||
import { FEE_DECIMALS } from '@/constants/numbers';
|
||||
import { FEE_DECIMALS, QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { tableMixins } from '@/styles/tableMixins';
|
||||
@@ -29,8 +29,9 @@ const EQUALITY_SYMBOL_MAP = {
|
||||
};
|
||||
|
||||
export const Fees = () => {
|
||||
const { dydxAddress } = useAccounts();
|
||||
const stringGetter = useStringGetter();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const userFeeTier = useSelector(getUserFeeTier, shallowEqual);
|
||||
const userStats = useSelector(getUserStats, shallowEqual);
|
||||
const feeTiers = useSelector(getFeeTiers, shallowEqual);
|
||||
@@ -55,28 +56,28 @@ export const Fees = () => {
|
||||
<Styled.AdditionalConditions>
|
||||
{!isAdditional && !totalShare && !makerShare && <Output type={OutputType.Text} />}
|
||||
{!!totalShare && (
|
||||
<Styled.AdditionalConditionsText>
|
||||
{isAdditional && stringGetter({ key: STRING_KEYS.AND })}{' '}
|
||||
{stringGetter({ key: STRING_KEYS.EXCHANGE_MARKET_SHARE })}{' '}
|
||||
<Styled.Highlighted>{'>'}</Styled.Highlighted>{' '}
|
||||
<Styled.TextRow>
|
||||
{isAdditional && <span>{stringGetter({ key: STRING_KEYS.AND })}</span>}
|
||||
<span>{stringGetter({ key: STRING_KEYS.EXCHANGE_MARKET_SHARE })}</span>
|
||||
<Styled.Highlighted>{'>'}</Styled.Highlighted>
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.Percent}
|
||||
value={totalShare}
|
||||
fractionDigits={0}
|
||||
/>
|
||||
</Styled.AdditionalConditionsText>
|
||||
</Styled.TextRow>
|
||||
)}
|
||||
{!!makerShare && (
|
||||
<Styled.AdditionalConditionsText>
|
||||
{isAdditional && stringGetter({ key: STRING_KEYS.AND })}{' '}
|
||||
{stringGetter({ key: STRING_KEYS.MAKER_MARKET_SHARE })}{' '}
|
||||
<Styled.Highlighted>{'>'}</Styled.Highlighted>{' '}
|
||||
<Styled.TextRow>
|
||||
{isAdditional && <span>{stringGetter({ key: STRING_KEYS.AND })}</span>}
|
||||
<span>{stringGetter({ key: STRING_KEYS.MAKER_MARKET_SHARE })}</span>
|
||||
<Styled.Highlighted>{'>'}</Styled.Highlighted>
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.Percent}
|
||||
value={makerShare}
|
||||
fractionDigits={0}
|
||||
/>
|
||||
</Styled.AdditionalConditionsText>
|
||||
</Styled.TextRow>
|
||||
)}
|
||||
</Styled.AdditionalConditions>
|
||||
),
|
||||
@@ -102,90 +103,91 @@ export const Fees = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Styled.FeeTable
|
||||
label="Fee Tiers"
|
||||
data={feeTiers || []}
|
||||
getRowKey={(row: FeeTier) => row.tier}
|
||||
getRowAttributes={(row: FeeTier) => ({
|
||||
'data-yours': row.tier === userFeeTier,
|
||||
})}
|
||||
columns={(
|
||||
[
|
||||
{
|
||||
columnKey: 'tier',
|
||||
getCellValue: (row) => row.tier,
|
||||
label: stringGetter({ key: STRING_KEYS.TIER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ tier }) => (
|
||||
<Styled.Tier>
|
||||
<Styled.Output type={OutputType.Text} value={tier} />
|
||||
{tier === userFeeTier && (
|
||||
<Styled.YouTag size={TagSize.Medium}>
|
||||
{stringGetter({ key: STRING_KEYS.YOU })}
|
||||
</Styled.YouTag>
|
||||
)}
|
||||
</Styled.Tier>
|
||||
),
|
||||
},
|
||||
{
|
||||
columnKey: 'volume',
|
||||
getCellValue: (row) => row.volume,
|
||||
label: stringGetter({ key: STRING_KEYS.VOLUME_30D }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ symbol, volume, makerShare, totalShare }) => (
|
||||
<>
|
||||
<span>{`${
|
||||
symbol in EQUALITY_SYMBOL_MAP
|
||||
? EQUALITY_SYMBOL_MAP[symbol as keyof typeof EQUALITY_SYMBOL_MAP]
|
||||
: symbol
|
||||
} `}</span>
|
||||
<Styled.HighlightOutput type={OutputType.CompactFiat} value={volume} />
|
||||
{isTablet &&
|
||||
AdditionalConditions({ totalShare, makerShare, isAdditional: true })}
|
||||
</>
|
||||
),
|
||||
},
|
||||
isNotTablet && {
|
||||
columnKey: 'condition',
|
||||
getCellValue: (row) => row.volume,
|
||||
label: stringGetter({ key: STRING_KEYS.ADDITIONAL_CONDITION }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ totalShare, makerShare }) =>
|
||||
AdditionalConditions({ totalShare, makerShare }),
|
||||
},
|
||||
{
|
||||
columnKey: 'maker',
|
||||
getCellValue: (row) => row.maker,
|
||||
label: stringGetter({ key: STRING_KEYS.MAKER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ maker }) => (
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.SmallPercent}
|
||||
value={maker}
|
||||
fractionDigits={FEE_DECIMALS}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
columnKey: 'taker',
|
||||
getCellValue: (row) => row.taker,
|
||||
label: stringGetter({ key: STRING_KEYS.TAKER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ taker }) => (
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.SmallPercent}
|
||||
value={taker}
|
||||
fractionDigits={FEE_DECIMALS}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<FeeTier>[]
|
||||
).filter(isTruthy)}
|
||||
selectionBehavior="replace"
|
||||
withOuterBorder={isNotTablet}
|
||||
withInnerBorders
|
||||
/>
|
||||
<div>
|
||||
<Styled.FeeTable
|
||||
label="Fee Tiers"
|
||||
data={feeTiers || []}
|
||||
getRowKey={(row: FeeTier) => row.tier}
|
||||
getRowAttributes={(row: FeeTier) => ({
|
||||
'data-yours': row.tier === userFeeTier,
|
||||
})}
|
||||
columns={(
|
||||
[
|
||||
{
|
||||
columnKey: 'tier',
|
||||
getCellValue: (row) => row.tier,
|
||||
label: stringGetter({ key: STRING_KEYS.TIER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ tier }) => (
|
||||
<Styled.Tier>
|
||||
<Styled.Output type={OutputType.Text} value={tier} />
|
||||
{tier === userFeeTier && (
|
||||
<Styled.YouTag size={TagSize.Medium}>
|
||||
{stringGetter({ key: STRING_KEYS.YOU })}
|
||||
</Styled.YouTag>
|
||||
)}
|
||||
</Styled.Tier>
|
||||
),
|
||||
},
|
||||
{
|
||||
columnKey: 'volume',
|
||||
getCellValue: (row) => row.volume,
|
||||
label: stringGetter({ key: STRING_KEYS.VOLUME_30D }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ symbol, volume, makerShare, totalShare }) => (
|
||||
<>
|
||||
<span>{`${
|
||||
symbol in EQUALITY_SYMBOL_MAP
|
||||
? EQUALITY_SYMBOL_MAP[symbol as keyof typeof EQUALITY_SYMBOL_MAP]
|
||||
: symbol
|
||||
} `}</span>
|
||||
<Styled.HighlightOutput type={OutputType.CompactFiat} value={volume} />
|
||||
{!isNotTablet &&
|
||||
AdditionalConditions({ totalShare, makerShare, isAdditional: true })}
|
||||
</>
|
||||
),
|
||||
},
|
||||
isNotTablet && {
|
||||
columnKey: 'condition',
|
||||
getCellValue: (row) => row.volume,
|
||||
label: stringGetter({ key: STRING_KEYS.ADDITIONAL_CONDITION }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ totalShare, makerShare }) =>
|
||||
AdditionalConditions({ totalShare, makerShare }),
|
||||
},
|
||||
{
|
||||
columnKey: 'maker',
|
||||
getCellValue: (row) => row.maker,
|
||||
label: stringGetter({ key: STRING_KEYS.MAKER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ maker }) => (
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.SmallPercent}
|
||||
value={maker}
|
||||
fractionDigits={FEE_DECIMALS}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
columnKey: 'taker',
|
||||
getCellValue: (row) => row.taker,
|
||||
label: stringGetter({ key: STRING_KEYS.TAKER }),
|
||||
allowsSorting: false,
|
||||
renderCell: ({ taker }) => (
|
||||
<Styled.HighlightOutput
|
||||
type={OutputType.SmallPercent}
|
||||
value={taker}
|
||||
fractionDigits={FEE_DECIMALS}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<FeeTier>[]
|
||||
).filter(isTruthy)}
|
||||
selectionBehavior="replace"
|
||||
withOuterBorder={isNotTablet}
|
||||
withInnerBorders
|
||||
/>
|
||||
</div>
|
||||
</Styled.ContentWrapper>
|
||||
</AttachedExpandingSection>
|
||||
);
|
||||
@@ -196,43 +198,21 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.ContentWrapper = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1.5rem;
|
||||
max-width: 100vw;
|
||||
`;
|
||||
|
||||
Styled.AdditionalConditions = styled.div`
|
||||
${tableMixins.cellContentColumn}
|
||||
justify-content: end;
|
||||
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
|
||||
> :nth-child(2) {
|
||||
font: var(--font-small-book);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.AdditionalConditionsText = styled.span`
|
||||
display: flex;
|
||||
gap: 0.5ch;
|
||||
|
||||
@media ${breakpoints.mobile} {
|
||||
display: inline;
|
||||
max-width: 8rem;
|
||||
min-width: 0;
|
||||
|
||||
output {
|
||||
display: inline;
|
||||
}
|
||||
font: var(--font-base-book);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.FeesDetails = styled(Details)`
|
||||
gap: 1rem;
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
margin: 0 1.25rem;
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
--details-grid-numColumns: 1;
|
||||
@@ -257,7 +237,11 @@ Styled.FeesDetails = styled(Details)`
|
||||
}
|
||||
|
||||
output {
|
||||
font: var(--font-base-book);
|
||||
font: var(--font-medium-book);
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
font: var(--font-base-book);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -287,11 +271,6 @@ Styled.FeeTable = styled(Table)`
|
||||
|
||||
font: var(--font-base-book);
|
||||
|
||||
@media ${breakpoints.mobile} {
|
||||
--tableCell-padding: 1rem 1.25rem;
|
||||
font: var(--font-small-book);
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&[data-yours='true'] {
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { useEffect } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { OnboardingState } from '@/constants/account';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { HistoryRoute, PortfolioRoute } from '@/constants/routes';
|
||||
|
||||
import { useAccountBalance, useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
|
||||
import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
|
||||
|
||||
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
|
||||
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
|
||||
@@ -16,9 +14,6 @@ import { Icon, IconName } from '@/components/Icon';
|
||||
import { NavigationMenu } from '@/components/NavigationMenu';
|
||||
import { WithSidebar } from '@/components/WithSidebar';
|
||||
|
||||
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { PortfolioNavMobile } from './PortfolioNavMobile';
|
||||
import { Overview } from './Overview';
|
||||
import { Positions } from './Positions';
|
||||
@@ -27,20 +22,11 @@ import { Fees } from './Fees';
|
||||
import { History } from './History';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { Button } from '@/components/Button';
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
|
||||
export default () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
|
||||
const onboardingState = useSelector(getOnboardingState);
|
||||
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
|
||||
const usdcBalance = freeCollateral?.current || 0;
|
||||
|
||||
useDocumentTitle(stringGetter({ key: STRING_KEYS.PORTFOLIO }));
|
||||
|
||||
const routesComponent = (
|
||||
@@ -98,74 +84,46 @@ export default () => {
|
||||
<WithSidebar
|
||||
sidebar={
|
||||
isTablet ? null : (
|
||||
<Styled.SideBar>
|
||||
<Styled.NavigationMenu
|
||||
items={[
|
||||
{
|
||||
group: 'views',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
|
||||
items: [
|
||||
{
|
||||
value: PortfolioRoute.Overview,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
|
||||
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
|
||||
href: PortfolioRoute.Overview,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Positions,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
|
||||
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
|
||||
href: PortfolioRoute.Positions,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Orders,
|
||||
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
|
||||
label: stringGetter({ key: STRING_KEYS.ORDERS }),
|
||||
href: PortfolioRoute.Orders,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Fees,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
|
||||
label: stringGetter({ key: STRING_KEYS.FEES }),
|
||||
href: PortfolioRoute.Fees,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.History,
|
||||
slotBefore: <Styled.Icon iconName={IconName.History} />,
|
||||
label: stringGetter({ key: STRING_KEYS.HISTORY }),
|
||||
href: PortfolioRoute.History,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{onboardingState === OnboardingState.AccountConnected && (
|
||||
<Styled.Footer>
|
||||
<Button
|
||||
action={ButtonAction.Primary}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
||||
</Button>
|
||||
{usdcBalance > 0 && (
|
||||
<Button
|
||||
action={ButtonAction.Base}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||
</Button>
|
||||
)}
|
||||
{(usdcBalance > 0 || nativeTokenBalance.gt(0)) && (
|
||||
<Button
|
||||
action={ButtonAction.Base}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.TRANSFER })}
|
||||
</Button>
|
||||
)}
|
||||
</Styled.Footer>
|
||||
)}
|
||||
</Styled.SideBar>
|
||||
<Styled.NavigationMenu
|
||||
items={[
|
||||
{
|
||||
group: 'views',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
|
||||
items: [
|
||||
{
|
||||
value: PortfolioRoute.Overview,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
|
||||
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
|
||||
href: PortfolioRoute.Overview,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Positions,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
|
||||
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
|
||||
href: PortfolioRoute.Positions,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Orders,
|
||||
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
|
||||
label: stringGetter({ key: STRING_KEYS.ORDERS }),
|
||||
href: PortfolioRoute.Orders,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Fees,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
|
||||
label: stringGetter({ key: STRING_KEYS.FEES }),
|
||||
href: PortfolioRoute.Fees,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.History,
|
||||
slotBefore: <Styled.Icon iconName={IconName.History} />,
|
||||
label: stringGetter({ key: STRING_KEYS.HISTORY }),
|
||||
href: PortfolioRoute.History,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -181,26 +139,6 @@ Styled.PortfolioMobile = styled.div`
|
||||
${layoutMixins.expandingColumnWithHeader}
|
||||
`;
|
||||
|
||||
Styled.SideBar = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
justify-content: space-between;
|
||||
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
Styled.Footer = styled.div`
|
||||
${layoutMixins.row}
|
||||
flex-wrap: wrap;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
gap: 0.5rem;
|
||||
|
||||
> button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.NavigationMenu = styled(NavigationMenu)`
|
||||
padding: 0.5rem;
|
||||
padding-top: 0;
|
||||
|
||||
@@ -32,25 +32,34 @@ export const DYDXBalancePanel = () => {
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.Panel
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
<AssetIcon symbol={chainTokenLabel} />
|
||||
{/* <AssetIcon symbol="DYDX" /> */}
|
||||
{chainTokenLabel}
|
||||
</Styled.Title>
|
||||
<Styled.ReceiveAndTransferButtons>
|
||||
{!canAccountTrade ? (
|
||||
<OnboardingTriggerButton size={ButtonSize.Small} />
|
||||
) : (
|
||||
<Button
|
||||
slotLeft={<Icon iconName={IconName.Send} />}
|
||||
size={ButtonSize.Small}
|
||||
action={ButtonAction.Primary}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.TRANSFER })}
|
||||
</Button>
|
||||
<>
|
||||
<Styled.ReceiveButton
|
||||
slotLeft={<Icon iconName={IconName.Qr} />}
|
||||
size={ButtonSize.Small}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Receive }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.RECEIVE })}
|
||||
</Styled.ReceiveButton>
|
||||
<Button
|
||||
slotLeft={<Icon iconName={IconName.Send} />}
|
||||
size={ButtonSize.Small}
|
||||
action={ButtonAction.Primary}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.TRANSFER })}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Styled.ReceiveAndTransferButtons>
|
||||
</Styled.Header>
|
||||
@@ -108,19 +117,14 @@ export const DYDXBalancePanel = () => {
|
||||
]}
|
||||
/>
|
||||
</Styled.Content>
|
||||
</Styled.Panel>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
`;
|
||||
|
||||
Styled.Header = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem 0;
|
||||
`;
|
||||
|
||||
@@ -173,7 +177,7 @@ Styled.WalletAndStakedBalance = styled(Details)`
|
||||
--details-item-backgroundColor: var(--color-layer-6);
|
||||
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
> div {
|
||||
gap: 1rem;
|
||||
|
||||
@@ -71,7 +71,7 @@ export const MigratePanel = () => {
|
||||
</Styled.Description>
|
||||
</Styled.MigratePanel>
|
||||
) : (
|
||||
<Styled.MigratePanel
|
||||
<Styled.MobileMigratePanel
|
||||
slotHeader={
|
||||
<Styled.MobileMigrateHeader>
|
||||
<h3>
|
||||
@@ -88,52 +88,49 @@ export const MigratePanel = () => {
|
||||
</Styled.MobileMigrateHeader>
|
||||
}
|
||||
>
|
||||
<Styled.Column>
|
||||
<Styled.WithReceipt
|
||||
slotReceipt={
|
||||
<Styled.Details
|
||||
items={[
|
||||
{
|
||||
key: 'available-to-migrate',
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
{stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })}
|
||||
<Tag>DYDX</Tag>
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
value: <Output type={OutputType.Asset} value={tokenBalance} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{import.meta.env.VITE_TOKEN_MIGRATION_URI && (
|
||||
<Button
|
||||
action={tokenBalance ? ButtonAction.Primary : ButtonAction.Base}
|
||||
size={ButtonSize.Medium}
|
||||
slotRight={<Icon iconName={IconName.LinkOut} />}
|
||||
href={import.meta.env.VITE_TOKEN_MIGRATION_URI}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.MIGRATE_NOW })}
|
||||
</Button>
|
||||
)}
|
||||
</Styled.WithReceipt>
|
||||
<Styled.InlineRow>
|
||||
{stringGetter({ key: STRING_KEYS.WANT_TO_LEARN })}
|
||||
<Link href={TOKEN_MIGRATION_LEARN_MORE_LINK} withIcon>
|
||||
{stringGetter({ key: STRING_KEYS.CLICK_HERE })}
|
||||
</Link>
|
||||
</Styled.InlineRow>
|
||||
</Styled.Column>
|
||||
</Styled.MigratePanel>
|
||||
<Styled.WithReceipt
|
||||
slotReceipt={
|
||||
<Styled.Details
|
||||
items={[
|
||||
{
|
||||
key: 'available-to-migrate',
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
{stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })}
|
||||
<Tag>DYDX</Tag>
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
value: <Output type={OutputType.Asset} value={tokenBalance} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{import.meta.env.VITE_TOKEN_MIGRATION_URI && (
|
||||
<Button
|
||||
action={tokenBalance ? ButtonAction.Primary : ButtonAction.Base}
|
||||
size={ButtonSize.Medium}
|
||||
slotRight={<Icon iconName={IconName.LinkOut} />}
|
||||
href={import.meta.env.VITE_TOKEN_MIGRATION_URI}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.MIGRATE_NOW })}
|
||||
</Button>
|
||||
)}
|
||||
</Styled.WithReceipt>
|
||||
<Styled.InlineRow>
|
||||
{stringGetter({ key: STRING_KEYS.WANT_TO_LEARN })}
|
||||
<Link href={TOKEN_MIGRATION_LEARN_MORE_LINK} withIcon>
|
||||
{stringGetter({ key: STRING_KEYS.CLICK_HERE })}
|
||||
</Link>
|
||||
</Styled.InlineRow>
|
||||
</Styled.MobileMigratePanel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.MigratePanel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
width: 100%;
|
||||
padding: 1rem 1.5rem;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
@@ -166,9 +163,10 @@ Styled.Description = styled.div`
|
||||
--link-color: var(--color-text-1);
|
||||
`;
|
||||
|
||||
Styled.Column = styled.div`
|
||||
Styled.MobileMigratePanel = styled(Panel)`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
|
||||
@@ -40,7 +40,11 @@ export const RewardsPage = () => {
|
||||
<Styled.Page>
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
|
||||
<Styled.PanelRow>
|
||||
{isTablet && <DYDXBalancePanel />}
|
||||
{isTablet && (
|
||||
<Styled.BalancePanelContainer>
|
||||
<DYDXBalancePanel />
|
||||
</Styled.BalancePanelContainer>
|
||||
)}
|
||||
|
||||
<Styled.Panel
|
||||
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
|
||||
@@ -68,7 +72,11 @@ export const RewardsPage = () => {
|
||||
</Styled.Description>
|
||||
</Styled.Panel>
|
||||
|
||||
{isNotTablet && <DYDXBalancePanel />}
|
||||
{isNotTablet && (
|
||||
<Styled.BalancePanelContainer>
|
||||
<DYDXBalancePanel />
|
||||
</Styled.BalancePanelContainer>
|
||||
)}
|
||||
</Styled.PanelRow>
|
||||
</Styled.Page>
|
||||
);
|
||||
@@ -79,26 +87,10 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.Page = styled.div`
|
||||
${layoutMixins.contentContainerPage}
|
||||
gap: 1.5rem;
|
||||
padding: 2rem;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
--content-max-width: 70rem;
|
||||
max-width: min(calc(100vw - 4rem), var(--content-max-width));
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
padding: 1.25rem;
|
||||
|
||||
> * {
|
||||
max-width: calc(100vw - 2.5rem);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
--panel-paddingX: 1.5rem;
|
||||
padding: 1rem 1.5rem;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
@@ -113,14 +105,27 @@ Styled.Description = styled.div`
|
||||
`;
|
||||
|
||||
Styled.PanelRow = styled.div`
|
||||
${layoutMixins.gridEqualColumns}
|
||||
${layoutMixins.spacedRow}
|
||||
gap: 1.5rem;
|
||||
|
||||
max-width: min(100vw, var(--content-max-width));
|
||||
align-items: flex-start;
|
||||
|
||||
> section {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
grid-auto-flow: row;
|
||||
grid-template-columns: 1fr;
|
||||
max-width: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.BalancePanelContainer = styled.div`
|
||||
width: 21.25rem;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
width: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -130,5 +135,5 @@ Styled.IconButton = styled(IconButton)`
|
||||
`;
|
||||
|
||||
Styled.Arrow = styled.div`
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
`;
|
||||
|
||||
@@ -227,8 +227,8 @@ export const HorizontalPanel = ({ isOpen = true, setIsOpen }: ElementProps) => {
|
||||
},
|
||||
]}
|
||||
value={view}
|
||||
onValueChange={setView}
|
||||
onInteraction={() => {
|
||||
onValueChange={(newView: string) => {
|
||||
setView((newView || view) as PanelView);
|
||||
setIsOpen?.(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
import appMiddleware from './appMiddleware';
|
||||
import localizationMiddleware from './localizationMiddleware';
|
||||
|
||||
@@ -14,7 +13,6 @@ import { inputsSlice } from './inputs';
|
||||
import { layoutSlice } from './layout';
|
||||
import { localizationSlice } from './localization';
|
||||
import { perpetualsSlice } from './perpetuals';
|
||||
import { notificationsSlice } from './notifications';
|
||||
|
||||
export const commandMenuSlices = [layoutSlice, localizationSlice];
|
||||
|
||||
@@ -28,7 +26,6 @@ export const store = configureStore({
|
||||
inputs: inputsSlice.reducer,
|
||||
layout: layoutSlice.reducer,
|
||||
localization: localizationSlice.reducer,
|
||||
notifications: notificationsSlice.reducer,
|
||||
perpetuals: perpetualsSlice.reducer,
|
||||
},
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { AbacusApiStatus } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { isDev } from '@/constants/networks';
|
||||
|
||||
import { setApiState, setSelectedNetwork } from '@/state/app';
|
||||
import { resetPerpetualsState } from '@/state/perpetuals';
|
||||
@@ -31,7 +30,7 @@ export default (store: any) => (next: any) => async (action: PayloadAction<any>)
|
||||
store.dispatch(
|
||||
openDialog({
|
||||
type: DialogTypes.ExchangeOffline,
|
||||
dialogProps: { preventClose: !isDev },
|
||||
dialogProps: { preventClose: import.meta.env.MODE === 'production' },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,15 +48,6 @@ export const getTradeInputErrors = (state: RootState) => {
|
||||
return currentInput === 'trade' ? getInputErrors(state) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns input errors for Close position
|
||||
*/
|
||||
export const getClosePositionInputErrors = (state: RootState) => {
|
||||
const currentInput = state.inputs.current;
|
||||
return currentInput === 'closePosition' ? getInputErrors(state) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns input errors for Transfer
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
LOCALE_DATA,
|
||||
NOTIFICATIONS,
|
||||
SupportedLocale,
|
||||
TOOLTIPS,
|
||||
} from '@dydxprotocol/v4-localization';
|
||||
import { LOCALE_DATA, SupportedLocale, TOOLTIPS } from '@dydxprotocol/v4-localization';
|
||||
|
||||
import {
|
||||
type LocaleData,
|
||||
@@ -31,7 +26,6 @@ const getNewLocaleData = ({
|
||||
|
||||
const newLocaleData = {
|
||||
...LOCALE_DATA[locale],
|
||||
...NOTIFICATIONS[locale],
|
||||
TOOLTIPS: TOOLTIPS[locale],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { AbacusNotification } from '@/constants/abacus';
|
||||
|
||||
export interface NotificationsState {
|
||||
abacusNotifications: AbacusNotification[];
|
||||
}
|
||||
|
||||
const initialState: NotificationsState = {
|
||||
abacusNotifications: [],
|
||||
};
|
||||
|
||||
export const notificationsSlice = createSlice({
|
||||
name: 'Notifications',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateNotifications: (state, action: PayloadAction<AbacusNotification[]>) => {
|
||||
state.abacusNotifications = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateNotifications } = notificationsSlice.actions;
|
||||
@@ -1,3 +0,0 @@
|
||||
import { RootState } from './_store';
|
||||
|
||||
export const getAbacusNotifications = (state: RootState) => state.notifications.abacusNotifications;
|
||||
@@ -107,17 +107,12 @@ export const formMixins: Record<
|
||||
`,
|
||||
|
||||
inputLabel: css`
|
||||
--label-textColor: var(--color-text-1);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
gap: 0;
|
||||
|
||||
border-radius: inherit;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
`,
|
||||
|
||||
inputToggleGroup: css`
|
||||
@@ -135,28 +130,4 @@ export const formMixins: Record<
|
||||
--button-toggle-off-textColor: var(--color-text-1);
|
||||
}
|
||||
`,
|
||||
|
||||
footer: css`
|
||||
${layoutMixins.stickyFooter}
|
||||
${layoutMixins.withStickyFooterBackdrop}
|
||||
|
||||
margin-top: auto;
|
||||
`,
|
||||
|
||||
transfersForm: css`
|
||||
${() => formMixins.inputsColumn}
|
||||
--form-input-gap: 1.25rem;
|
||||
--form-input-height: 3.5rem;
|
||||
--form-input-height-mobile: 4rem;
|
||||
--form-input-paddingY: 0.5rem;
|
||||
--form-input-paddingX: 1rem;
|
||||
|
||||
label {
|
||||
--label-textColor: var(--color-text-0);
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--form-input-gap: 1rem;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -12,8 +12,8 @@ export const headerMixins = {
|
||||
--trigger-open-textColor: var(--color-text-2);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
--trigger-backgroundColor: var(--trigger-hover-backgroundColor);
|
||||
--trigger-textColor: var(--trigger-hover-textColor);
|
||||
background-color: var(--trigger-hover-backgroundColor);
|
||||
color: var(--trigger-hover-textColor);
|
||||
}
|
||||
`,
|
||||
|
||||
|
||||
@@ -18,5 +18,9 @@ export const tradeViewMixins: Record<
|
||||
tbody {
|
||||
font: var(--font-small-book);
|
||||
}
|
||||
|
||||
thead tr {
|
||||
box-shadow: none;
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -151,10 +151,7 @@ export const AccountInfoConnectedState = () => {
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.BUYING_POWER }),
|
||||
type: OutputType.Fiat,
|
||||
value:
|
||||
MustBigNumber(buyingPower?.current).lt(0) && buyingPower?.postOrder === null
|
||||
? undefined
|
||||
: buyingPower,
|
||||
value: buyingPower,
|
||||
},
|
||||
].map(
|
||||
({
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MarketFilter = ({
|
||||
<>
|
||||
{!isSearch && (
|
||||
<ToggleGroup
|
||||
items={Object.values(filters).map((value) => ({
|
||||
items={Object.entries(filters).map(([key, value]) => ({
|
||||
label: stringGetter({ key: MARKET_FILTER_LABELS[value] }),
|
||||
value,
|
||||
}))}
|
||||
|
||||
@@ -198,6 +198,8 @@ Styled.Details = styled(Details)`
|
||||
@media ${breakpoints.tablet} {
|
||||
${layoutMixins.withOuterAndInnerBorders}
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||
font: var(--font-small-book);
|
||||
|
||||
> * {
|
||||
|
||||
@@ -82,18 +82,18 @@ const MarketsDropdownContent = ({ onRowAction }: { onRowAction?: (market: string
|
||||
),
|
||||
},
|
||||
{
|
||||
columnKey: 'priceChange24HPercent',
|
||||
getCellValue: (row) => row.priceChange24HPercent,
|
||||
columnKey: 'priceChange24H',
|
||||
getCellValue: (row) => row.priceChange24H,
|
||||
label: stringGetter({ key: STRING_KEYS._24H }),
|
||||
renderCell: ({ priceChange24HPercent }) => (
|
||||
renderCell: ({ priceChange24H, priceChange24HPercent }) => (
|
||||
<Styled.InlineRow>
|
||||
{!priceChange24HPercent ? (
|
||||
{!priceChange24H ? (
|
||||
<Styled.Output type={OutputType.Text} value={null} />
|
||||
) : (
|
||||
<Styled.PriceChangeOutput
|
||||
type={OutputType.Percent}
|
||||
value={priceChange24HPercent}
|
||||
isNegative={MustBigNumber(priceChange24HPercent).isNegative()}
|
||||
isNegative={MustBigNumber(priceChange24H).isNegative()}
|
||||
/>
|
||||
)}
|
||||
</Styled.InlineRow>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AbacusOrderStatus } from '@/constants/abacus';
|
||||
import { type OrderStatus, AbacusOrderStatus } from '@/constants/abacus';
|
||||
|
||||
import { Icon } from '@/components/Icon';
|
||||
import {
|
||||
OrderCanceledIcon,
|
||||
OrderFilledIcon,
|
||||
@@ -10,57 +9,49 @@ import {
|
||||
OrderPendingIcon,
|
||||
} from '@/icons';
|
||||
|
||||
import { Icon } from '@/components/Icon';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type ElementProps = {
|
||||
status: string;
|
||||
status: OrderStatus;
|
||||
totalFilled: number;
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const OrderStatusIcon = ({ className, status, totalFilled }: ElementProps & StyleProps) => {
|
||||
const { iconComponent, color } = {
|
||||
[AbacusOrderStatus.open.rawValue]:
|
||||
totalFilled > 0
|
||||
? {
|
||||
iconComponent: OrderPartiallyFilledIcon,
|
||||
color: 'var(--color-warning)',
|
||||
}
|
||||
: {
|
||||
iconComponent: OrderOpenIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
[AbacusOrderStatus.partiallyFilled.rawValue]: {
|
||||
iconComponent: OrderPartiallyFilledIcon,
|
||||
color: 'var(--color-warning)',
|
||||
},
|
||||
[AbacusOrderStatus.filled.rawValue]: {
|
||||
iconComponent: OrderFilledIcon,
|
||||
color: 'var(--color-success)',
|
||||
},
|
||||
[AbacusOrderStatus.cancelled.rawValue]: {
|
||||
iconComponent: OrderCanceledIcon,
|
||||
color: 'var(--color-error)',
|
||||
},
|
||||
[AbacusOrderStatus.canceling.rawValue]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-error)',
|
||||
},
|
||||
[AbacusOrderStatus.pending.rawValue]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
[AbacusOrderStatus.untriggered.rawValue]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
}[status];
|
||||
|
||||
return <$Icon className={className} iconComponent={iconComponent} color={color} />;
|
||||
};
|
||||
export const OrderStatusIcon = ({ status, totalFilled }: ElementProps) => (
|
||||
<$Icon
|
||||
{...{
|
||||
[AbacusOrderStatus.open.name]:
|
||||
totalFilled > 0
|
||||
? {
|
||||
iconComponent: OrderPartiallyFilledIcon,
|
||||
color: 'var(--color-warning)',
|
||||
}
|
||||
: {
|
||||
iconComponent: OrderOpenIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
[AbacusOrderStatus.filled.name]: {
|
||||
iconComponent: OrderFilledIcon,
|
||||
color: 'var(--color-success)',
|
||||
},
|
||||
[AbacusOrderStatus.cancelled.name]: {
|
||||
iconComponent: OrderCanceledIcon,
|
||||
color: 'var(--color-error)',
|
||||
},
|
||||
[AbacusOrderStatus.canceling.name]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-error)',
|
||||
},
|
||||
[AbacusOrderStatus.pending.name]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
[AbacusOrderStatus.untriggered.name]: {
|
||||
iconComponent: OrderPendingIcon,
|
||||
color: 'var(--color-text-2)',
|
||||
},
|
||||
}[status.name]}
|
||||
/>
|
||||
);
|
||||
|
||||
const $Icon = styled(Icon)<{ color: string }>`
|
||||
color: ${({ color }) => color};
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { useInterval, useStringGetter } from '@/hooks';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
|
||||
import { AlertMessage } from '@/components/AlertMessage';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
type: 'withdrawal' | 'deposit';
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
export const TransferStatusToast = ({
|
||||
type,
|
||||
toAmount,
|
||||
triggeredAt = Date.now(),
|
||||
status,
|
||||
}: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
|
||||
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||
|
||||
const updateSecondsLeft = useCallback(() => {
|
||||
const fromChainEta = (status?.fromChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
const toChainEta = (status?.toChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
setSecondsLeft(Math.floor((triggeredAt + fromChainEta + toChainEta - Date.now()) / 1000));
|
||||
}, [status]);
|
||||
|
||||
useInterval({ callback: updateSecondsLeft });
|
||||
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
const statusString =
|
||||
type === 'deposit'
|
||||
? status?.squidTransactionStatus === 'success'
|
||||
? STRING_KEYS.DEPOSIT_COMPLETE
|
||||
: STRING_KEYS.DEPOSIT_STATUS
|
||||
: status?.squidTransactionStatus === 'success'
|
||||
? STRING_KEYS.WITHDRAW_COMPLETE
|
||||
: STRING_KEYS.WITHDRAW_STATUS;
|
||||
|
||||
return (
|
||||
<Styled.Root open={open} onOpenChange={setOpen}>
|
||||
<WithReceipt
|
||||
hideReceipt={!open}
|
||||
side="bottom"
|
||||
slotReceipt={
|
||||
<Styled.Receipt>
|
||||
<TransferStatusSteps status={status} type={type} />
|
||||
</Styled.Receipt>
|
||||
}
|
||||
>
|
||||
<Styled.BridgingStatus>
|
||||
<Styled.Status>
|
||||
{stringGetter({
|
||||
key: statusString,
|
||||
params: {
|
||||
AMOUNT_USD: <Styled.InlineOutput type={OutputType.Fiat} value={toAmount} />,
|
||||
ESTIMATED_DURATION: (
|
||||
<Styled.InlineOutput
|
||||
type={OutputType.Text}
|
||||
value={formatSeconds(Math.max(secondsLeft || 0, 0))}
|
||||
/>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</Styled.Status>
|
||||
{error && (
|
||||
<AlertMessage type={AlertType.Error}>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
},
|
||||
})}
|
||||
</AlertMessage>
|
||||
)}
|
||||
<Styled.Trigger>
|
||||
<Styled.TriggerIcon>
|
||||
<Icon iconName={IconName.Caret} />
|
||||
</Styled.TriggerIcon>
|
||||
{stringGetter({ key: open ? STRING_KEYS.HIDE_DETAILS : STRING_KEYS.VIEW_DETAILS })}
|
||||
</Styled.Trigger>
|
||||
</Styled.BridgingStatus>
|
||||
</WithReceipt>
|
||||
</Styled.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
margin: 0.5rem -1rem -1rem -1rem;
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
Styled.Receipt = styled.div`
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
Styled.BridgingStatus = styled.div`
|
||||
${layoutMixins.flexColumn};
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Status = styled.div`
|
||||
color: var(--color-text-0);
|
||||
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
|
||||
Styled.InlineOutput = styled(Output)`
|
||||
display: inline-block;
|
||||
|
||||
color: var(--color-text-1);
|
||||
`;
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
${layoutMixins.flexColumn};
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Step = styled.div`
|
||||
${layoutMixins.row};
|
||||
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Trigger = styled(Trigger)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
color: var(--color-accent);
|
||||
user-select: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TriggerIcon = styled.span`
|
||||
width: 0.75em;
|
||||
|
||||
display: inline-flex;
|
||||
transition: rotate 0.3s var(--ease-out-expo);
|
||||
|
||||
${Styled.Trigger}[data-state='open'] & {
|
||||
rotate: -0.5turn;
|
||||
}
|
||||
`;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { useStringGetter, useSelectedNetwork, useURLConfigs } from '@/hooks';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { Link } from '@/components/Link';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
@@ -11,17 +11,12 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
type ElementProps = {
|
||||
status?: StatusResponse;
|
||||
type: 'withdrawal' | 'deposit';
|
||||
};
|
||||
|
||||
type StyleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
enum TransferStatusStep {
|
||||
FromChain,
|
||||
Bridge,
|
||||
@@ -29,19 +24,14 @@ enum TransferStatusStep {
|
||||
Complete,
|
||||
}
|
||||
|
||||
export const TransferStatusSteps = ({ className, status, type }: ElementProps & StyleProps) => {
|
||||
export const TransferStatusSteps = ({ status, type }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const { mintscan: mintscanTxUrl } = useURLConfigs();
|
||||
const dydxChainId = ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId;
|
||||
|
||||
const { currentStep, steps } = useMemo(() => {
|
||||
const routeStatus = status?.routeStatus;
|
||||
const fromChain = status?.fromChain?.chainData?.chainId;
|
||||
const toChain = status?.toChain?.chainData?.chainId;
|
||||
|
||||
const currentStatus = routeStatus?.[routeStatus?.length - 1];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
label: stringGetter({
|
||||
@@ -49,12 +39,7 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
|
||||
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
|
||||
}),
|
||||
step: TransferStatusStep.FromChain,
|
||||
link:
|
||||
type === 'deposit'
|
||||
? status?.fromChain?.transactionUrl
|
||||
: routeStatus?.[0]?.chainId === dydxChainId && routeStatus[0].txHash
|
||||
? `${mintscanTxUrl?.replace('{tx_hash}', routeStatus[0].txHash.replace('0x', ''))}`
|
||||
: undefined,
|
||||
link: status?.fromChain?.transactionUrl,
|
||||
},
|
||||
{
|
||||
label: stringGetter({ key: STRING_KEYS.BRIDGING_TOKENS }),
|
||||
@@ -69,15 +54,12 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
|
||||
},
|
||||
}),
|
||||
step: TransferStatusStep.ToChain,
|
||||
link:
|
||||
type === 'withdrawal'
|
||||
? status?.toChain?.transactionUrl
|
||||
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
|
||||
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash.replace('0x', ''))}`
|
||||
: undefined,
|
||||
link: status?.toChain?.transactionUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const currentStatus = routeStatus?.[routeStatus?.length - 1];
|
||||
|
||||
let currentStep = TransferStatusStep.Bridge;
|
||||
|
||||
if (!routeStatus?.length) {
|
||||
@@ -105,7 +87,7 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
return (
|
||||
<Styled.BridgingStatus className={className}>
|
||||
<Styled.BridgingStatus>
|
||||
{steps.map((step) => (
|
||||
<Styled.Step key={step.step}>
|
||||
<Styled.row>
|
||||
@@ -246,7 +246,9 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
|
||||
}[rate] || '',
|
||||
}))}
|
||||
value={fundingRateView}
|
||||
onValueChange={setFundingRateView}
|
||||
onValueChange={(newRate: FundingRateResolution) => {
|
||||
if (newRate) setFundingRateView(newRate);
|
||||
}}
|
||||
size={ButtonSize.XSmall}
|
||||
/>
|
||||
</Styled.FundingRateToggle>
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { useState } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
||||
|
||||
import { DepositDialogContent } from './DepositDialog/DepositDialogContent';
|
||||
import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
|
||||
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
setIsOpen?: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const [showTestDeposit, setShowTestDeposit] = useState(true);
|
||||
const stringGetter = useStringGetter();
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
@@ -20,9 +27,39 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||
isOpen
|
||||
setIsOpen={setIsOpen}
|
||||
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
||||
description={showTestDeposit && 'Test funds will be sent directly to your dYdX account.'}
|
||||
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
||||
>
|
||||
<DepositDialogContent onDeposit={closeDialog} />
|
||||
<Styled.Content>
|
||||
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
|
||||
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
||||
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
||||
</Styled.TextToggle>
|
||||
</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.TextToggle = styled.div`
|
||||
${layoutMixins.stickyFooter}
|
||||
color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
|
||||
margin-top: auto;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.stickyArea0}
|
||||
--stickyArea0-bottomHeight: 2rem;
|
||||
--stickyArea0-bottomGap: 1rem;
|
||||
--stickyArea0-totalInsetBottom: 0.5rem;
|
||||
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
`;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { isMainnet } from '@/constants/networks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
|
||||
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
|
||||
|
||||
type ElementProps = {
|
||||
onDeposit?: () => void;
|
||||
};
|
||||
|
||||
export const DepositDialogContent = ({ onDeposit }: ElementProps) => {
|
||||
const [showFaucet, setShowFaucet] = useState(!isMainnet);
|
||||
|
||||
return (
|
||||
<Styled.Content>
|
||||
{isMainnet || !showFaucet ? (
|
||||
<DepositForm onDeposit={onDeposit} />
|
||||
) : (
|
||||
<TestnetDepositForm onDeposit={onDeposit} />
|
||||
)}
|
||||
{!isMainnet && (
|
||||
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
|
||||
{showFaucet ? 'Show deposit form' : 'Show test faucet'}
|
||||
</Styled.TextToggle>
|
||||
)}
|
||||
</Styled.Content>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TextToggle = styled.div`
|
||||
${layoutMixins.stickyFooter}
|
||||
--stickyArea-bottomHeight: 0;
|
||||
|
||||
color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
|
||||
margin-top: auto;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
@@ -5,7 +5,6 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { AbacusApiStatus } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { isDev } from '@/constants/networks';
|
||||
|
||||
import { useApiState, useStringGetter } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
@@ -46,7 +45,7 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
|
||||
>
|
||||
<Styled.Content>
|
||||
<p>{statusErrorMessage}</p>
|
||||
{isDev && <NetworkSelectMenu />}
|
||||
{import.meta.env.MODE !== 'production' && <NetworkSelectMenu />}
|
||||
</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { TransferInputField, TransferType } from '@/constants/abacus';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
||||
import { ToggleGroup } from '@/components/ToggleGroup';
|
||||
import { TransferForm } from '@/views/forms/TransferForm';
|
||||
import { WithdrawForm } from '@/views/forms/AccountManagementForms/WithdrawForm';
|
||||
|
||||
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
import { DepositDialogContent } from './DepositDialog/DepositDialogContent';
|
||||
|
||||
type ElementProps = {
|
||||
setIsOpen?: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const ManageFundsDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { type } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const currentType = type?.rawValue ?? TransferType.deposit.rawValue;
|
||||
|
||||
const closeDialog = () => setIsOpen?.(false);
|
||||
|
||||
const transferTypeConfig = {
|
||||
[TransferType.deposit.rawValue]: {
|
||||
value: TransferType.deposit.rawValue,
|
||||
label: stringGetter({ key: STRING_KEYS.DEPOSIT }),
|
||||
component: <DepositDialogContent />,
|
||||
},
|
||||
[TransferType.withdrawal.rawValue]: {
|
||||
value: TransferType.withdrawal.rawValue,
|
||||
label: stringGetter({ key: STRING_KEYS.WITHDRAW }),
|
||||
component: <WithdrawForm />,
|
||||
},
|
||||
[TransferType.transferOut.rawValue]: {
|
||||
value: TransferType.transferOut.rawValue,
|
||||
label: stringGetter({ key: STRING_KEYS.TRANSFER }),
|
||||
component: <TransferForm onDone={closeDialog} />,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled.Dialog
|
||||
isOpen
|
||||
setIsOpen={setIsOpen}
|
||||
placement={DialogPlacement.FullScreen}
|
||||
title={
|
||||
<Styled.ToggleGroup
|
||||
items={Object.values(transferTypeConfig)}
|
||||
value={currentType}
|
||||
size={ButtonSize.Medium}
|
||||
onValueChange={(value: string) =>
|
||||
abacusStateManager.setTransferValue({
|
||||
field: TransferInputField.type,
|
||||
value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
}
|
||||
hasHeaderBorder
|
||||
>
|
||||
{transferTypeConfig[currentType].component}
|
||||
</Styled.Dialog>
|
||||
);
|
||||
};
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Dialog = styled(Dialog)`
|
||||
--dialog-content-paddingTop: 1.5rem;
|
||||
`;
|
||||
|
||||
Styled.ToggleGroup = styled(ToggleGroup)`
|
||||
overflow-x: auto;
|
||||
|
||||
button {
|
||||
--button-toggle-off-border: none;
|
||||
--button-padding: 0 1rem;
|
||||
}
|
||||
`;
|
||||