diff --git a/.env.example b/.env.example index 6a4d2ac..b48a7d5 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/README.md b/README.md index fd87758..39fb04c 100644 --- a/README.md +++ b/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 diff --git a/src/App.tsx b/src/App.tsx index d1093e5..fb52216 100644 --- a/src/App.tsx +++ b/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 = () => { } /> } /> - - } /> + } + /> diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 491cabd..16d7787 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -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`; diff --git a/src/lib/__test__/addressUtils.ts b/src/lib/__test__/addressUtils.spec.ts similarity index 100% rename from src/lib/__test__/addressUtils.ts rename to src/lib/__test__/addressUtils.spec.ts diff --git a/src/lib/__test__/urlUtils.spec.ts b/src/lib/__test__/urlUtils.spec.ts new file mode 100644 index 0000000..40ad600 --- /dev/null +++ b/src/lib/__test__/urlUtils.spec.ts @@ -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'); + }); +}); diff --git a/src/lib/urlUtils.ts b/src/lib/urlUtils.ts new file mode 100644 index 0000000..be5694e --- /dev/null +++ b/src/lib/urlUtils.ts @@ -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}` : ''}`; +}; diff --git a/src/main.tsx b/src/main.tsx index e8a6619..c73a8ac 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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( - } /> + } /> diff --git a/src/views/dialogs/NewMarketAgreementDialog.tsx b/src/views/dialogs/NewMarketAgreementDialog.tsx index 1346f02..80e26bc 100644 --- a/src/views/dialogs/NewMarketAgreementDialog.tsx +++ b/src/views/dialogs/NewMarketAgreementDialog.tsx @@ -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 ), TERMS_OF_USE: ( - + {stringGetter({ key: STRING_KEYS.TERMS_OF_USE })} ), diff --git a/src/views/dialogs/OnboardingDialog/AcknowledgeTerms.tsx b/src/views/dialogs/OnboardingDialog/AcknowledgeTerms.tsx index 3f29a43..c1210b7 100644 --- a/src/views/dialogs/OnboardingDialog/AcknowledgeTerms.tsx +++ b/src/views/dialogs/OnboardingDialog/AcknowledgeTerms.tsx @@ -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: ( - + {stringGetter({ key: STRING_KEYS.TERMS_OF_USE })} ), PRIVACY_POLICY_LINK: ( - + {stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })} ), diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..0f32683 --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +}