Compare commits
2 Commits
mike/updat
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d24a02f7b9 | ||
|
|
caaea7e475 |
@ -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
|
||||
|
||||
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>
|
||||
|
||||
@ -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`;
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
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}` : ''}`;
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
),
|
||||
|
||||
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