Compare commits
11 Commits
enable-cex
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d24a02f7b9 | ||
|
|
caaea7e475 | ||
|
|
9d3a124485 | ||
|
|
038eb08e2e | ||
|
|
e1d2a5eda4 | ||
|
|
56be9ba9cc | ||
|
|
859e7dde80 | ||
|
|
d9daf9f583 | ||
|
|
5be9d2b1b2 | ||
|
|
d2c5b7945b | ||
|
|
fc5d8538fe |
@ -3,6 +3,7 @@ VITE_BASE_URL=
|
||||
VITE_ALCHEMY_API_KEY=
|
||||
|
||||
VITE_PK_ENCRYPTION_KEY=
|
||||
VITE_ROUTER_TYPE=
|
||||
|
||||
VITE_V3_TOKEN_ADDRESS=
|
||||
VITE_TOKEN_MIGRATION_URI=
|
||||
|
||||
26
README.md
26
README.md
@ -66,10 +66,12 @@ This will automatically open your default browser at `http://localhost:61000`.
|
||||
|
||||
Add or modify the relevant endpoints, links and options in `/public/configs/env.json`.
|
||||
You'll need to provide a Wallet Connect project id to enable onboarding and wallet connection:
|
||||
|
||||
- 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`).
|
||||
@ -96,10 +98,12 @@ Select "Import Git Repository" from your dashboard, and provide the URL of this
|
||||
### Step 2: Configure your project
|
||||
|
||||
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
|
||||
```
|
||||
@ -114,6 +118,14 @@ For more details, check out Vercel's [official documentation](https://vercel.com
|
||||
|
||||
## Deploying to IPFS
|
||||
|
||||
### Must Enable HashRouting
|
||||
|
||||
Add the following to `.env` file
|
||||
|
||||
```
|
||||
VITE_ROUTER_TYPE=hash
|
||||
```
|
||||
|
||||
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
||||
|
||||
Export the API token as an environment variable (replace `your_token` with the generated token), and run the script to build and deploy to IPFS:
|
||||
@ -150,12 +162,14 @@ Replace `your_cid` with the actual CID.
|
||||
We recommend that you add your website to Cloudflare for additional security settings.
|
||||
|
||||
To block OFAC Sanctioned countries:
|
||||
|
||||
1. Navigate Websites > Domain > Security > WAF
|
||||
|
||||
2. Create Rule with the following settings:
|
||||
* If incoming requests match
|
||||
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
|
||||
* This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
|
||||
1. Custom Text
|
||||
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
|
||||
2. Default Cloudflare WAF block page
|
||||
|
||||
- If incoming requests match
|
||||
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
|
||||
- This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
|
||||
1. Custom Text
|
||||
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
|
||||
2. Default Cloudflare WAF block page
|
||||
|
||||
@ -40,9 +40,9 @@
|
||||
"@cosmjs/proto-signing": "^0.32.1",
|
||||
"@cosmjs/stargate": "^0.32.1",
|
||||
"@cosmjs/tendermint-rpc": "^0.32.1",
|
||||
"@dydxprotocol/v4-abacus": "^1.4.6",
|
||||
"@dydxprotocol/v4-abacus": "^1.4.12",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.20",
|
||||
"@dydxprotocol/v4-localization": "^1.1.31",
|
||||
"@dydxprotocol/v4-localization": "^1.1.35",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -1,5 +1,9 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
follow-redirects: 1.15.3
|
||||
|
||||
@ -26,14 +30,14 @@ dependencies:
|
||||
specifier: ^0.32.1
|
||||
version: 0.32.2
|
||||
'@dydxprotocol/v4-abacus':
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.6
|
||||
specifier: ^1.4.12
|
||||
version: 1.4.12
|
||||
'@dydxprotocol/v4-client-js':
|
||||
specifier: ^1.0.20
|
||||
version: 1.0.20
|
||||
'@dydxprotocol/v4-localization':
|
||||
specifier: ^1.1.31
|
||||
version: 1.1.31
|
||||
specifier: ^1.1.35
|
||||
version: 1.1.35
|
||||
'@ethersproject/providers':
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
@ -1286,8 +1290,8 @@ packages:
|
||||
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-abacus@1.4.6:
|
||||
resolution: {integrity: sha512-qYq+4TizcMMxYVXckn0LCucWBe5N9ZNtD1XnowCAuBUUifHSgMGvao5OeZIKMgNM/udSKOXLss4zLy6dH/G2SA==}
|
||||
/@dydxprotocol/v4-abacus@1.4.12:
|
||||
resolution: {integrity: sha512-dNQFaNrLTujyViMH2JEtf9Vn2ew2le+qTWexktK5h7AeADBbS9uxQknMIFhfwzGkbS1QqqszBT1plrDmQ+E6nQ==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-client-js@1.0.20:
|
||||
@ -1319,8 +1323,8 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-localization@1.1.31:
|
||||
resolution: {integrity: sha512-plJVIgFAKq9/hA/gk5GgKgCQFsH3pNtDWfG/yHLDXyiGX0M0mMEi1bTNVs4podFVoHJu1nSL9YPFlpJ00FteGw==}
|
||||
/@dydxprotocol/v4-localization@1.1.35:
|
||||
resolution: {integrity: sha512-q5JFYoL/QanHXOtFqRa2owBZJibi1sMpSm3dAcxs9x0/xe8mo6fWcnbQfhl8k7g0/tv7PsBc+e3rbWD0EfvGiA==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-proto@4.0.0-dev.0:
|
||||
@ -16049,7 +16053,3 @@ packages:
|
||||
/zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
dev: true
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
@ -725,7 +725,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
@ -812,7 +812,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
@ -899,7 +899,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
@ -986,7 +986,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
@ -1073,7 +1073,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
@ -1151,7 +1151,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
@ -1238,7 +1238,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-mainnet": {
|
||||
|
||||
@ -71,7 +71,8 @@
|
||||
"keplrDashboard": "https://testnet.keplr.app/",
|
||||
"strideZoneApp": "https://testnet.stride.zone",
|
||||
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
|
||||
"launchIncentive": "https://cloud.chaoslabs.co"
|
||||
},
|
||||
"dydx-testnet-4": {
|
||||
"tos": "https://dydx.exchange/v4-terms",
|
||||
@ -92,7 +93,8 @@
|
||||
"keplrDashboard": "https://testnet.keplr.app/",
|
||||
"strideZoneApp": "https://testnet.stride.zone",
|
||||
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
|
||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
|
||||
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
|
||||
"launchIncentive": "https://cloud.chaoslabs.co"
|
||||
},
|
||||
"[mainnet chain id]": {
|
||||
"tos": "[HTTP link to TOS]",
|
||||
@ -113,7 +115,8 @@
|
||||
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
|
||||
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
|
||||
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
|
||||
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
|
||||
"walletLearnMore": "[HTTP link to wallet learn more, can be null]",
|
||||
"launchIncentive": "[HTTP link to launch incentive host, can be null]"
|
||||
}
|
||||
},
|
||||
"wallets": {
|
||||
@ -258,7 +261,8 @@
|
||||
"faucet": "https://faucet.v4dev.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-2": {
|
||||
@ -283,7 +287,8 @@
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-4": {
|
||||
@ -309,7 +314,8 @@
|
||||
"faucet": "https://faucet.v4dev4.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-5": {
|
||||
@ -334,7 +340,8 @@
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging": {
|
||||
@ -360,7 +367,8 @@
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-forced-update": {
|
||||
@ -393,7 +401,8 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-west": {
|
||||
@ -419,7 +428,8 @@
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet": {
|
||||
@ -449,7 +459,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
@ -475,7 +486,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
@ -501,7 +513,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
@ -527,7 +540,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
@ -553,7 +567,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
@ -579,7 +594,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
@ -605,7 +621,8 @@
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-mainnet": {
|
||||
@ -631,7 +648,8 @@
|
||||
"nobleValidator": "[noble validator endpoint for mainnet]"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": false,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/wallets/okx-wallet.png
Normal file
BIN
public/wallets/okx-wallet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
21
src/App.tsx
21
src/App.tsx
@ -1,5 +1,5 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { lazy, Suspense, useMemo } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { WagmiConfig } from 'wagmi';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
@ -26,6 +26,7 @@ import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||
|
||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
||||
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
||||
@ -34,12 +35,12 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
|
||||
import { DialogManager } from '@/layout/DialogManager';
|
||||
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
||||
|
||||
import { parseLocationHash } from '@/lib/urlUtils';
|
||||
import { config } from '@/lib/wagmi';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { GlobalStyle } from '@/styles/globalStyle';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
||||
|
||||
import '@/styles/constants.css';
|
||||
import '@/styles/fonts.css';
|
||||
@ -68,6 +69,14 @@ const Content = () => {
|
||||
const isShowingHeader = isNotTablet;
|
||||
const isShowingFooter = useShouldShowFooter();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
const location = useLocation();
|
||||
|
||||
const pathFromHash = useMemo(() => {
|
||||
if (location.hash === '') {
|
||||
return '';
|
||||
}
|
||||
return parseLocationHash(location.hash);
|
||||
}, [location.hash]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -102,8 +111,10 @@ const Content = () => {
|
||||
|
||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||
|
||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate to={pathFromHash || DEFAULT_TRADE_ROUTE} replace />}
|
||||
/>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</Styled.Main>
|
||||
|
||||
@ -81,6 +81,9 @@ export enum AnalyticsEvent {
|
||||
TradePlaceOrderConfirmed = 'TradePlaceOrderConfirmed',
|
||||
TradeCancelOrder = 'TradeCancelOrder',
|
||||
TradeCancelOrderConfirmed = 'TradeCancelOrderConfirmed',
|
||||
|
||||
// Notification
|
||||
NotificationAction = 'NotificationAction',
|
||||
}
|
||||
|
||||
export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
@ -180,6 +183,12 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
/** URL/IP of node the order was sent to */
|
||||
validatorUrl: string;
|
||||
}
|
||||
: // Notifcation
|
||||
T extends AnalyticsEvent.NotificationAction
|
||||
? {
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
: never;
|
||||
|
||||
export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';
|
||||
|
||||
@ -11,6 +11,7 @@ export enum DialogTypes {
|
||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||
MnemonicExport = 'MnemonicExport',
|
||||
MobileSignIn = 'MobileSignIn',
|
||||
MobileDownload = 'MobileDownload',
|
||||
Onboarding = 'Onboarding',
|
||||
OrderDetails = 'OrderDetails',
|
||||
Preferences = 'Preferences',
|
||||
|
||||
@ -143,6 +143,11 @@ export type TransferNotifcation = {
|
||||
isExchange?: boolean;
|
||||
};
|
||||
|
||||
export enum ReleaseUpdateNotificationIds {
|
||||
RewardsAndFullTradingLive = 'rewards-and-full-trading-live',
|
||||
IncentivesS3 = 'incentives-s3',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Struct to store whether a NotificationType should be triggered
|
||||
*/
|
||||
|
||||
@ -41,6 +41,7 @@ export enum MobileSettingsRoute {
|
||||
Network = 'network',
|
||||
}
|
||||
|
||||
export const BASE_ROUTE = import.meta.env.VITE_ROUTER_TYPE === 'hash' ? '/#' : '';
|
||||
export const TRADE_ROUTE = `${AppRoute.Trade}/:market`;
|
||||
export const PORTFOLIO_ROUTE = `${AppRoute.Portfolio}/:subroute`;
|
||||
export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:subroute`;
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
KeplrIcon,
|
||||
MathWalletIcon,
|
||||
MetaMaskIcon,
|
||||
OkxWalletIcon,
|
||||
RainbowIcon,
|
||||
TokenPocketIcon,
|
||||
TrustWalletIcon,
|
||||
@ -89,6 +90,7 @@ export enum WalletType {
|
||||
// Ledger = 'LEDGER',
|
||||
MathWallet = 'MATH_WALLET',
|
||||
MetaMask = 'METAMASK',
|
||||
OkxWallet = 'OKX_WALLET',
|
||||
Rainbow = 'RAINBOW_WALLET',
|
||||
TokenPocket = 'TOKEN_POCKET',
|
||||
TrustWallet = 'TRUST_WALLET',
|
||||
@ -102,6 +104,7 @@ const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||
imToken: 'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
|
||||
TokenPocket: '20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
|
||||
Trust: '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
|
||||
OkxWallet: '971e689d0a5be527bac79629b4ee9b925e82208e5168b733496a09c0faed0709',
|
||||
Rainbow: '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369',
|
||||
Zerion: 'ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18',
|
||||
Ledger: '19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927',
|
||||
@ -212,6 +215,14 @@ export const wallets: Record<WalletType, WalletConfig> = {
|
||||
matchesInjectedEip1193: isMetaMask,
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.Metamask,
|
||||
},
|
||||
[WalletType.OkxWallet]: {
|
||||
type: WalletType.OkxWallet,
|
||||
stringKey: STRING_KEYS.OKX_WALLET,
|
||||
icon: OkxWalletIcon,
|
||||
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
|
||||
matchesInjectedEip1193: (provider) => provider.isOkxWallet,
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.OkxWallet,
|
||||
},
|
||||
[WalletType.Rainbow]: {
|
||||
type: WalletType.Rainbow,
|
||||
stringKey: STRING_KEYS.RAINBOW_WALLET,
|
||||
@ -278,6 +289,10 @@ export type WithInjectedWeb3Provider = {
|
||||
};
|
||||
};
|
||||
|
||||
export type WithInjectedOkxWalletProvider = {
|
||||
okxwallet: InjectedWeb3Provider;
|
||||
};
|
||||
|
||||
// Wallet connections
|
||||
|
||||
export type WalletConnection = {
|
||||
|
||||
@ -57,13 +57,15 @@ export const useChartLines = ({
|
||||
useEffect(() => {
|
||||
if (tvWidget && isChartReady) {
|
||||
tvWidget.onChartReady(() => {
|
||||
tvWidget.chart().dataReady(() => {
|
||||
if (showOrderLines) {
|
||||
drawOrderLines();
|
||||
drawPositionLine();
|
||||
} else {
|
||||
deleteChartLines();
|
||||
}
|
||||
tvWidget.headerReady().then(() => {
|
||||
tvWidget.chart().dataReady(() => {
|
||||
if (showOrderLines) {
|
||||
drawOrderLines();
|
||||
drawPositionLine();
|
||||
} else {
|
||||
deleteChartLines();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -10,9 +10,10 @@ export const useDisplayedWallets = () => {
|
||||
isDev && WalletType.Keplr,
|
||||
|
||||
WalletType.WalletConnect2,
|
||||
|
||||
|
||||
WalletType.CoinbaseWallet,
|
||||
|
||||
|
||||
WalletType.OkxWallet,
|
||||
// Hide these wallet options until they can be properly tested on mainnet
|
||||
// WalletType.ImToken,
|
||||
// WalletType.Rainbow,
|
||||
|
||||
@ -5,7 +5,7 @@ import { isEqual, groupBy } from 'lodash';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, TokenRoute } from '@/constants/routes';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import {
|
||||
@ -20,9 +20,10 @@ import {
|
||||
NotificationType,
|
||||
DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||
TransferNotificationTypes,
|
||||
ReleaseUpdateNotificationIds,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { useStringGetter, useTokenConfigs } from '@/hooks';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
@ -238,13 +239,16 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
{
|
||||
type: NotificationType.ReleaseUpdates,
|
||||
useTrigger: ({ trigger }) => {
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
const stringGetter = useStringGetter();
|
||||
const expirationDate = new Date('2024-03-08T23:59:59');
|
||||
const currentDate = new Date();
|
||||
|
||||
useEffect(() => {
|
||||
trigger(
|
||||
'rewards-and-full-trading-live',
|
||||
ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
|
||||
{
|
||||
icon: <AssetIcon symbol="DYDX" />,
|
||||
icon: <AssetIcon symbol={chainTokenLabel} />,
|
||||
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
|
||||
body: stringGetter({
|
||||
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
|
||||
@ -270,14 +274,41 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
},
|
||||
}),
|
||||
toastSensitivity: 'foreground',
|
||||
groupKey: NotificationType.ReleaseUpdates,
|
||||
groupKey: ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
|
||||
},
|
||||
[]
|
||||
);
|
||||
if (currentDate <= expirationDate) {
|
||||
trigger(
|
||||
ReleaseUpdateNotificationIds.IncentivesS3,
|
||||
{
|
||||
icon: <AssetIcon symbol={chainTokenLabel} />,
|
||||
title: stringGetter({ key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.TITLE' }),
|
||||
body: stringGetter({
|
||||
key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.BODY',
|
||||
params: {
|
||||
SEASON_NUMBER: '3',
|
||||
PREV_SEASON_NUMBER: '1',
|
||||
DYDX_AMOUNT: '34',
|
||||
USDC_AMOUNT: '100',
|
||||
},
|
||||
}),
|
||||
toastSensitivity: 'foreground',
|
||||
groupKey: ReleaseUpdateNotificationIds.IncentivesS3,
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
}, [stringGetter]);
|
||||
},
|
||||
useNotificationAction: () => {
|
||||
return () => {};
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
const navigate = useNavigate();
|
||||
return (notificationId: string) => {
|
||||
if (notificationId === ReleaseUpdateNotificationIds.IncentivesS3) {
|
||||
navigate(`${chainTokenLabel}/${TokenRoute.TradingRewards}`);
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { AnalyticsEvent } from '@/constants/analytics';
|
||||
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
|
||||
import {
|
||||
type Notification,
|
||||
@ -13,7 +14,8 @@ import {
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
import { notificationTypes } from './useNotificationTypes';
|
||||
|
||||
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
||||
import { track } from '@/lib/analytics';
|
||||
import { renderSvgToDataUrl } from '@/lib/renderSvgToDataUrl';
|
||||
|
||||
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
|
||||
|
||||
@ -48,6 +50,7 @@ const useNotificationsContext = () => {
|
||||
defaultValue: {
|
||||
[NotificationType.AbacusGenerated]: true,
|
||||
[NotificationType.SquidTransfer]: true,
|
||||
[NotificationType.ReleaseUpdates]: true,
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||
},
|
||||
});
|
||||
@ -176,8 +179,10 @@ const useNotificationsContext = () => {
|
||||
)
|
||||
);
|
||||
|
||||
const onNotificationAction = async (notification: Notification) =>
|
||||
await actions[notification.type]?.(notification.id);
|
||||
const onNotificationAction = async (notification: Notification) => {
|
||||
track(AnalyticsEvent.NotificationAction, { type: notification.type, id: notification.id });
|
||||
return await actions[notification.type]?.(notification.id);
|
||||
};
|
||||
|
||||
// Push notifications
|
||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||
|
||||
@ -83,6 +83,7 @@ export { default as LedgerIcon } from './wallets/ledger.svg';
|
||||
export { default as MagicIcon } from './wallets/magic.svg';
|
||||
export { default as MathWalletIcon } from './wallets/mathwallet.svg';
|
||||
export { default as MetaMaskIcon } from './wallets/metamask.svg';
|
||||
export { default as OkxWalletIcon } from './wallets/okx-wallet.svg';
|
||||
export { default as RainbowIcon } from './wallets/rainbow-wallet.svg';
|
||||
export { default as TestWalletIcon } from './wallets/test-wallet.svg';
|
||||
export { default as TokenPocketIcon } from './wallets/tokenpocket.svg';
|
||||
|
||||
1
src/icons/wallets/okx-wallet.svg
Normal file
1
src/icons/wallets/okx-wallet.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0, 0, 400,400"><g id="svgg"><path id="path0" d="M85.200 76.394 C 80.665 76.883,78.308 78.483,76.929 82.006 C 76.085 84.161,75.592 142.346,76.360 149.217 C 76.910 154.145,78.020 156.078,81.141 157.541 L 83.400 158.600 117.600 158.600 C 154.653 158.600,154.238 158.623,156.431 156.431 C 158.623 154.238,158.600 154.653,158.600 117.600 L 158.600 83.400 157.541 81.141 C 156.025 77.909,154.089 76.841,148.902 76.377 C 144.325 75.968,89.012 75.983,85.200 76.394 M249.890 76.409 C 245.967 76.882,243.812 78.255,242.459 81.141 L 241.400 83.400 241.400 117.600 C 241.400 154.653,241.377 154.238,243.569 156.431 C 245.762 158.623,245.347 158.600,282.400 158.600 L 316.600 158.600 318.859 157.541 C 324.048 155.108,324.091 154.742,323.907 114.679 C 323.739 78.113,323.917 79.248,317.999 76.930 C 316.018 76.154,255.815 75.693,249.890 76.409 M163.813 159.382 C 158.668 160.777,158.800 159.708,158.800 200.000 C 158.800 240.779,158.632 239.561,164.419 240.762 C 168.838 241.680,235.468 241.166,237.395 240.200 C 241.459 238.161,241.400 238.753,241.400 200.000 C 241.400 161.247,241.459 161.839,237.395 159.800 C 235.329 158.764,167.515 158.378,163.813 159.382 M84.192 241.586 C 75.955 242.833,75.887 243.209,76.093 286.273 C 76.252 319.496,76.167 318.455,78.918 321.120 C 81.744 323.860,80.445 323.758,114.679 323.911 C 154.743 324.090,155.109 324.047,157.541 318.859 L 158.600 316.600 158.600 282.400 C 158.600 242.256,158.762 243.612,153.768 241.955 C 151.993 241.366,87.894 241.026,84.192 241.586 M248.251 241.633 C 241.106 242.699,241.228 241.990,241.215 282.600 C 241.202 321.601,241.152 321.289,247.760 323.301 C 250.708 324.198,315.213 324.005,317.830 323.091 C 323.901 320.971,323.748 321.925,323.911 285.321 C 324.090 245.267,324.075 245.137,319.000 242.600 L 316.600 241.400 283.600 241.341 C 265.450 241.309,249.543 241.440,248.251 241.633 " stroke="none" fill="#fafafa" fill-rule="evenodd"></path><path id="path1" d="M0.000 200.000 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 200.000 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 200.000 M148.902 76.377 C 154.089 76.841,156.025 77.909,157.541 81.141 L 158.600 83.400 158.600 117.600 C 158.600 154.653,158.623 154.238,156.431 156.431 C 154.238 158.623,154.653 158.600,117.600 158.600 L 83.400 158.600 81.141 157.541 C 75.953 155.109,75.910 154.743,76.089 114.679 C 76.250 78.643,76.131 79.509,81.200 77.288 C 83.883 76.112,137.816 75.387,148.902 76.377 M317.999 76.930 C 323.917 79.248,323.739 78.113,323.907 114.679 C 324.091 154.742,324.048 155.108,318.859 157.541 L 316.600 158.600 282.400 158.600 C 245.347 158.600,245.762 158.623,243.569 156.431 C 241.377 154.238,241.400 154.653,241.400 117.600 L 241.400 83.400 242.459 81.141 C 243.812 78.255,245.967 76.882,249.890 76.409 C 255.815 75.693,316.018 76.154,317.999 76.930 M237.395 159.800 C 241.459 161.839,241.400 161.247,241.400 200.000 C 241.400 238.753,241.459 238.161,237.395 240.200 C 235.468 241.166,168.838 241.680,164.419 240.762 C 158.632 239.561,158.800 240.779,158.800 200.000 C 158.800 159.708,158.668 160.777,163.813 159.382 C 167.515 158.378,235.329 158.764,237.395 159.800 M153.768 241.955 C 158.762 243.612,158.600 242.256,158.600 282.400 L 158.600 316.600 157.541 318.859 C 155.109 324.047,154.743 324.090,114.679 323.911 C 80.445 323.758,81.744 323.860,78.918 321.120 C 76.167 318.455,76.252 319.496,76.093 286.273 C 75.887 243.209,75.955 242.833,84.192 241.586 C 87.894 241.026,151.993 241.366,153.768 241.955 M319.000 242.600 C 324.075 245.137,324.090 245.267,323.911 285.321 C 323.748 321.925,323.901 320.971,317.830 323.091 C 315.213 324.005,250.708 324.198,247.760 323.301 C 244.032 322.166,242.292 320.037,241.590 315.751 C 241.084 312.662,241.101 252.756,241.608 248.844 C 242.629 240.980,241.034 241.265,283.600 241.341 L 316.600 241.400 319.000 242.600 " stroke="none" fill="#040404" fill-rule="evenodd"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -31,6 +31,7 @@ import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDial
|
||||
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
|
||||
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
|
||||
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
|
||||
import { MobileDownloadDialog } from '@/views/dialogs/MobileDownloadDialog';
|
||||
|
||||
export const DialogManager = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -63,6 +64,7 @@ export const DialogManager = () => {
|
||||
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
|
||||
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||
[DialogTypes.MobileDownload]: <MobileDownloadDialog {...modalProps} />,
|
||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
||||
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
|
||||
|
||||
14
src/lib/__test__/urlUtils.spec.ts
Normal file
14
src/lib/__test__/urlUtils.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { parseLocationHash } from '@/lib/urlUtils';
|
||||
|
||||
describe('parseLocationHash', () => {
|
||||
it('returns the path separated from hash', () => {
|
||||
const hash = '#/markets';
|
||||
expect(parseLocationHash(hash)).toEqual('/markets');
|
||||
});
|
||||
it('returns the path and query string separated from hash', () => {
|
||||
const hash = '#/markets?displayinitializingmarkets=true';
|
||||
expect(parseLocationHash(hash)).toEqual('/markets?displayinitializingmarkets=true');
|
||||
});
|
||||
});
|
||||
@ -26,10 +26,6 @@ class TestFlags {
|
||||
get showTradingRewards() {
|
||||
return !!this.queryParams.tradingrewards;
|
||||
}
|
||||
|
||||
get showCexWithdrawal() {
|
||||
return !!this.queryParams.cexwithdrawal;
|
||||
}
|
||||
}
|
||||
|
||||
export const testFlags = new TestFlags();
|
||||
|
||||
13
src/lib/urlUtils.ts
Normal file
13
src/lib/urlUtils.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @param hash location.hash
|
||||
* @returns path and query string if hash parameter is not empty
|
||||
*/
|
||||
export const parseLocationHash = (hash: string) => {
|
||||
if (!hash || hash.length === 0) return '';
|
||||
|
||||
// Remove '#' and split by '?'
|
||||
const [path, queryString] = hash.substring(1).split('?');
|
||||
|
||||
// Reconstruct path and query string
|
||||
return `${path}${queryString ? `?${queryString}` : ''}`;
|
||||
};
|
||||
@ -5,6 +5,7 @@ import {
|
||||
type InjectedCoinbaseWalletExtensionProvider,
|
||||
type WithInjectedEthereumProvider,
|
||||
type WithInjectedWeb3Provider,
|
||||
type WithInjectedOkxWalletProvider,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
import { isTruthy } from '../isTruthy';
|
||||
@ -18,15 +19,6 @@ export const isMetaMask = (provider: ExternalProvider) => (
|
||||
&& (
|
||||
!(provider as InjectedCoinbaseWalletExtensionProvider).overrideIsMetaMask
|
||||
)
|
||||
|
||||
/* not a MetaMask wannabe! */
|
||||
&& (
|
||||
Reflect.ownKeys(provider).filter((key) =>
|
||||
typeof key === 'string'
|
||||
&& key.match(/^is/)
|
||||
&& !['isConnected', 'isMetaMask'].includes(key)
|
||||
).length === 0
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
@ -54,5 +46,10 @@ export const detectInjectedEip1193Providers = (): ExternalProvider[] => {
|
||||
? ethereumProvider.providers
|
||||
: [];
|
||||
|
||||
return [...displacedProviders, ethereumProvider, web3Provider].filter(isTruthy);
|
||||
const okxWalletProvider = (globalThis as typeof globalThis & WithInjectedOkxWalletProvider)
|
||||
?.okxwallet;
|
||||
|
||||
return [...displacedProviders, ethereumProvider, web3Provider, okxWalletProvider].filter(
|
||||
isTruthy
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import './polyfills';
|
||||
import { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { BrowserRouter, HashRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { store } from '@/state/_store';
|
||||
@ -12,11 +12,13 @@ import './index.css';
|
||||
|
||||
import App from './App';
|
||||
|
||||
const Router = import.meta.env.VITE_ROUTER_TYPE === 'hash' ? HashRouter : BrowserRouter;
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<ErrorBoundary>
|
||||
<StrictMode>
|
||||
<Provider store={store}>
|
||||
<HashRouter children={<App />} />
|
||||
<Router children={<App />} />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
</ErrorBoundary>
|
||||
|
||||
140
src/views/dialogs/MobileDownloadDialog.tsx
Normal file
140
src/views/dialogs/MobileDownloadDialog.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Dialog } from '@/components/Dialog';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
type ElementProps = {
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
/*
|
||||
When/if deployer deploys the web app with smartbanner, "smartbanner:button-url-apple" and/or
|
||||
"smartbanner:button-url-google" <meta> are set.
|
||||
This implementation assumes "smartbanner:button-url-apple" and "smartbanner:button-url-google"
|
||||
are set to the same value with onelink or other redirect URL.
|
||||
Since there is no way for the desktop web app to know what mobile device the user is using,
|
||||
we should give a onelink URL which redirects to either iOS or Android app store depending on
|
||||
the mobile device used to scan the link.
|
||||
*/
|
||||
|
||||
// for testing only
|
||||
// export const mobileAppUrl = "http://example.com";
|
||||
|
||||
let mobileAppUrl: string | undefined | null = undefined;
|
||||
|
||||
export const getMobileAppUrl = () => {
|
||||
if (!mobileAppUrl) {
|
||||
mobileAppUrl =
|
||||
// for testing to verify <meta> is retrieved by name, QR code should show "@dYdX" as value
|
||||
// document.querySelector('meta[name="twitter:creator"]')?.getAttribute('content') ??
|
||||
document.querySelector('meta[name="smartbanner:button-url-apple"]')?.getAttribute('content') ??
|
||||
document.querySelector('meta[name="smartbanner:button-url-google"]')?.getAttribute('content');
|
||||
}
|
||||
return mobileAppUrl;
|
||||
}
|
||||
|
||||
const MobileQrCode = ({
|
||||
url,
|
||||
}: {
|
||||
url: string;
|
||||
}) => {
|
||||
return (
|
||||
<Styled.QrCodeContainer isShowing={true}>
|
||||
<QrCode hasLogo size={432} value={url} />
|
||||
</Styled.QrCodeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
MobileDownloadDialog should only been shown on desktop when mobileAppUrl has value. That's controlled by AccountMenu.tsx.
|
||||
*/
|
||||
|
||||
export const MobileDownloadDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const content = (
|
||||
<MobileQrCode url={mobileAppUrl!} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog isOpen setIsOpen={setIsOpen} title={
|
||||
stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP })
|
||||
}>
|
||||
<Styled.Content>{content}</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.column}
|
||||
gap: 1rem;
|
||||
|
||||
strong {
|
||||
font-weight: 900;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
footer {
|
||||
${layoutMixins.row}
|
||||
justify-content: space-between;
|
||||
|
||||
svg {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.WaitingSpan = styled.span`
|
||||
strong {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.QrCodeContainer = styled.figure<{ isShowing: boolean }>`
|
||||
${layoutMixins.stack}
|
||||
|
||||
overflow: hidden;
|
||||
border-radius: 0.75em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: 0.2s;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(var(--hover-filter-base));
|
||||
}
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
transition: 0.16s;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
pointer-events: none;
|
||||
|
||||
${({ isShowing }) =>
|
||||
!isShowing &&
|
||||
css`
|
||||
filter: blur(1rem) brightness(1.4);
|
||||
will-change: filter;
|
||||
`}
|
||||
}
|
||||
|
||||
> span {
|
||||
place-self: center;
|
||||
|
||||
font-size: 1.4em;
|
||||
color: var(--color-text-2);
|
||||
|
||||
${({ isShowing }) =>
|
||||
isShowing &&
|
||||
css`
|
||||
opacity: 0;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, BASE_ROUTE } from '@/constants/routes';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import breakpoints from '@/styles/breakpoints';
|
||||
@ -40,7 +40,7 @@ export const NewMarketAgreementDialog = ({ acceptTerms, setIsOpen }: ElementProp
|
||||
</Styled.Link>
|
||||
),
|
||||
TERMS_OF_USE: (
|
||||
<Styled.Link href={`/#${AppRoute.Terms}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
|
||||
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
|
||||
</Styled.Link>
|
||||
),
|
||||
|
||||
@ -2,7 +2,7 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, BASE_ROUTE } from '@/constants/routes';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
|
||||
@ -34,12 +34,12 @@ export const AcknowledgeTerms = ({ onClose, onContinue }: ElementProps) => {
|
||||
key: STRING_KEYS.TOS_TITLE,
|
||||
params: {
|
||||
TERMS_LINK: (
|
||||
<Styled.Link href={`/#${AppRoute.Terms}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
|
||||
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
|
||||
</Styled.Link>
|
||||
),
|
||||
PRIVACY_POLICY_LINK: (
|
||||
<Styled.Link href={`/#${AppRoute.Privacy}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Privacy}`}>
|
||||
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
|
||||
</Styled.Link>
|
||||
),
|
||||
|
||||
@ -63,12 +63,11 @@ export const SourceSelectMenu = ({
|
||||
return (
|
||||
<SearchSelectMenu
|
||||
items={[
|
||||
exchangeItems.length > 0 &&
|
||||
(testFlags.showCexWithdrawal || type === TransferType.deposit) && {
|
||||
group: 'exchanges',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }),
|
||||
items: exchangeItems,
|
||||
},
|
||||
exchangeItems.length > 0 && {
|
||||
group: 'exchanges',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }),
|
||||
items: exchangeItems,
|
||||
},
|
||||
chainItems.length > 0 && {
|
||||
group: 'chains',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
|
||||
|
||||
@ -40,6 +40,7 @@ import { getAppTheme } from '@/state/configsSelectors';
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
import { truncateAddress } from '@/lib/wallet';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { getMobileAppUrl } from '../dialogs/MobileDownloadDialog';
|
||||
|
||||
export const AccountMenu = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
@ -189,6 +190,18 @@ export const AccountMenu = () => {
|
||||
label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
|
||||
onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
|
||||
},
|
||||
...(getMobileAppUrl()
|
||||
? [
|
||||
{
|
||||
value: 'MobileDownload',
|
||||
icon: <Icon iconName={IconName.Qr} />,
|
||||
label: stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP }),
|
||||
onSelect: () => {
|
||||
dispatch(openDialog({ type: DialogTypes.MobileDownload }));
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(onboardingState === OnboardingState.AccountConnected && hdKey
|
||||
? [
|
||||
{
|
||||
|
||||
3
vercel.json
Normal file
3
vercel.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user