Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58439bed54 | ||
|
|
38dd5f6e88 | ||
|
|
8b183207f0 | ||
|
|
776b528bf6 | ||
|
|
74dcb56d67 | ||
|
|
02b9421bf7 | ||
|
|
5959a1cf02 | ||
|
|
c678d403ac | ||
|
|
7ced921f47 | ||
|
|
970b4fddc9 | ||
|
|
1a41ccaf2a | ||
|
|
26b426c9e9 | ||
|
|
994ba832f6 | ||
|
|
597456a0d1 | ||
|
|
3b75e60bd2 | ||
|
|
e453977299 | ||
|
|
dbc34599a6 | ||
|
|
f762e9bfac | ||
|
|
ed3c336cc0 | ||
|
|
b4f9f03bdc | ||
|
|
44ed52d5e2 | ||
|
|
42595a3667 | ||
|
|
2fc55bbe9f | ||
|
|
97452d7785 |
@@ -8,7 +8,14 @@ VITE_V3_TOKEN_ADDRESS=
|
||||
VITE_TOKEN_MIGRATION_URI=
|
||||
|
||||
AMPLITUDE_API_KEY=
|
||||
AMPLITUDE_SERVER_URL=
|
||||
BUGSNAG_API_KEY=
|
||||
IOS_APP_ID=
|
||||
INTERCOM_APP_ID=
|
||||
STATUS_PAGE_SCRIPT_URI=
|
||||
|
||||
SMARTBANNER_APP_NAME=
|
||||
SMARTBANNER_ORG_NAME=
|
||||
SMARTBANNER_ICON_URL=
|
||||
SMARTBANNER_APPSTORE_URL=
|
||||
SMARTBANNER_GOOGLEPLAY_URL=
|
||||
|
||||
@@ -39,6 +39,7 @@ jobs:
|
||||
pnpm run build:inject-amplitude
|
||||
pnpm run build:inject-bugsnag
|
||||
pnpm run build:inject-statuspage
|
||||
pnpm run build:inject-smartbanner
|
||||
sh scripts/inject-app-deeplinks.sh
|
||||
|
||||
- name: Upload to IPFS via web3.storage
|
||||
|
||||
+53
-8
@@ -2,15 +2,25 @@ import '@/polyfills';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { WagmiConfig } from 'wagmi';
|
||||
import { GrazProvider } from 'graz';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
import { store } from '@/state/_store';
|
||||
import { SupportedLocales } from '@/constants/localization';
|
||||
|
||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||
import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode';
|
||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||
import { DialogAreaProvider } from '@/hooks/useDialogArea';
|
||||
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
||||
import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets';
|
||||
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||
|
||||
import { GlobalStyle } from '@/styles/globalStyle';
|
||||
|
||||
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
||||
|
||||
import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode';
|
||||
|
||||
import {
|
||||
AppTheme,
|
||||
AppThemeSystemSetting,
|
||||
@@ -18,11 +28,38 @@ import {
|
||||
setAppThemeSetting,
|
||||
setAppColorMode,
|
||||
} from '@/state/configs';
|
||||
import { setLocaleLoaded } from '@/state/localization';
|
||||
|
||||
import { setLocaleLoaded, setSelectedLocale } from '@/state/localization';
|
||||
import { store } from '@/state/_store';
|
||||
|
||||
import { config } from '@/lib/wagmi';
|
||||
|
||||
import '@/index.css';
|
||||
import './ladle.css';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<Component {...props}>{children}</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const providers = [
|
||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(RestrictionProvider),
|
||||
wrapProvider(DydxProvider),
|
||||
wrapProvider(AccountsProvider),
|
||||
wrapProvider(SubaccountProvider),
|
||||
wrapProvider(DialogAreaProvider),
|
||||
wrapProvider(PotentialMarketsProvider),
|
||||
wrapProvider(AppThemeAndColorModeProvider),
|
||||
];
|
||||
|
||||
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [theme, setTheme] = useState(AppTheme.Classic);
|
||||
const [colorMode, setColorMode] = useState(AppColorMode.GreenUp);
|
||||
@@ -33,9 +70,20 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
}, [theme, colorMode]);
|
||||
|
||||
useEffect(() => {
|
||||
store.dispatch(setSelectedLocale({ locale: SupportedLocales.EN }));
|
||||
store.dispatch(setLocaleLoaded(true));
|
||||
}, []);
|
||||
|
||||
const content = [...providers].reverse().reduce(
|
||||
(children, Provider) => {
|
||||
return <Provider>{children}</Provider>;
|
||||
},
|
||||
<StoryContent>
|
||||
<GlobalStyle />
|
||||
{children}
|
||||
</StoryContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<StoryHeader>
|
||||
@@ -79,10 +127,7 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
</SelectMenu>
|
||||
</StoryHeader>
|
||||
<hr />
|
||||
<AppThemeAndColorModeProvider>
|
||||
<GlobalStyle />
|
||||
<StoryContent>{children}</StoryContent>
|
||||
</AppThemeAndColorModeProvider>
|
||||
{content}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -78,11 +78,12 @@ Set environment variables via `.env`.
|
||||
- `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`.
|
||||
- `AMPLITUDE_SERVER_URL` (optional): Custom Amplitude server URL
|
||||
- `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`.
|
||||
|
||||
- `SMARTBANNER_APP_NAME`, `SMARTBANNER_ORG_NAME`, `SMARTBANNER_ICON_URL`, `SMARTBANNER_APPSTORE_URL` (optional): Used for enabling the smart app banner; used with `pnpm run build:inject-smartbanner`.
|
||||
|
||||
# Deployments
|
||||
|
||||
@@ -106,6 +107,9 @@ 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.
|
||||
|
||||
If you wish to incorporate smart banner for iOS and/or Android apps, you can use our scripts:
|
||||
`pnpm run build:inject-smartbanner`. You will need to provide your own app configurations for these services. In the Environment Variables section, name the variables as `SMARTBANNER_APP_NAME`, `SMARTBANNER_ORG_NAME`, `SMARTBANNER_ICON_URL` and `SMARTBANNER_APPSTORE_URL` or `SMARTBANNER_GOOGLEPLAY_URL` and provide the respective values.
|
||||
|
||||
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
|
||||
|
||||
## Deploying to IPFS
|
||||
|
||||
+4
-3
@@ -15,6 +15,7 @@
|
||||
"build:inject-bugsnag": "node scripts/inject-bugsnag.js",
|
||||
"build:inject-intercom": "node scripts/inject-intercom.js",
|
||||
"build:inject-statuspage": "node scripts/inject-statuspage.js",
|
||||
"build:inject-smartbanner": "node scripts/inject-smartbanner.js",
|
||||
"deploy:ipfs": "node scripts/upload-ipfs.js --verbose",
|
||||
"deploy:update-ipns": "node scripts/update-ipns.js",
|
||||
"deploy:update-dnslink": "node scripts/update-dnslink.js",
|
||||
@@ -39,9 +40,9 @@
|
||||
"@cosmjs/proto-signing": "^0.32.1",
|
||||
"@cosmjs/stargate": "^0.32.1",
|
||||
"@cosmjs/tendermint-rpc": "^0.32.1",
|
||||
"@dydxprotocol/v4-abacus": "^1.3.2",
|
||||
"@dydxprotocol/v4-abacus": "^1.4.2",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.20",
|
||||
"@dydxprotocol/v4-localization": "^1.1.22",
|
||||
"@dydxprotocol/v4-localization": "^1.1.25",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
@@ -117,7 +118,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@ladle/react": "^2.15.0",
|
||||
"@ladle/react": "^4.0.2",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/luxon": "^3.3.0",
|
||||
|
||||
Generated
+1925
-805
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "exchange.dydx.trading.debug",
|
||||
"sha256_cert_fingerprints": [
|
||||
"8A:9C:CC:49:B0:35:9A:91:67:CB:98:B0:B5:87:92:5F:9E:B7:EF:CE:A0:47:57:85:A4:35:3E:0C:E1:56:9E:A2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "exchange.dydx.trading",
|
||||
"sha256_cert_fingerprints": [
|
||||
"B2:2D:CC:27:9D:52:05:98:63:C9:7B:34:36:70:A3:8E:00:31:28:08:2D:2E:70:76:C9:31:AE:F9:55:21:15:A5"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
+1302
-1246
File diff suppressed because it is too large
Load Diff
@@ -176,6 +176,13 @@
|
||||
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
|
||||
},
|
||||
"JUP-USD": {
|
||||
"name": "Jupiter",
|
||||
"tags": ["Defi"],
|
||||
"websiteLink": "https://station.jup.ag/",
|
||||
"whitepaperLink": "https://station.jup.ag/blog/green-paper",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/jupiter-ag/"
|
||||
},
|
||||
"LDO-USD": {
|
||||
"name": "Lido DAO",
|
||||
"tags": ["Defi"],
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,15 @@
|
||||
<!-- Smartbanner: Configure the mobile app -->
|
||||
<meta name="smartbanner:title" content="SMARTBANNER_APP_NAME">
|
||||
<meta name="smartbanner:author" content="SMARTBANNER_ORG_NAME">
|
||||
<meta name="smartbanner:icon-apple" content="SMARTBANNER_ICON_URL">
|
||||
<meta name="smartbanner:icon-google" content="SMARTBANNER_ICON_URL">
|
||||
<!-- Smartbanner: The rest of the configurations can be kept as is -->
|
||||
<meta name="smartbanner:price" content="FREE">
|
||||
<meta name="smartbanner:price-suffix-apple" content=" - On the App Store">
|
||||
<meta name="smartbanner:price-suffix-google" content=" - In Google Play">
|
||||
<meta name="smartbanner:button" content="VIEW">
|
||||
<meta name="smartbanner:close-label" content="Close">
|
||||
<meta name="smartbanner:exclude-user-agent-regex" content="^.*(Windows NT|Intel Mac OS X).*$">
|
||||
<script src="https://cdn.jsdelivr.net/npm/smartbanner.js@1.22.0/dist/smartbanner.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/smartbanner.js@1.22.0/dist/smartbanner.min.css" rel="stylesheet">
|
||||
<!-- Smartbanner: End configuration -->
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -36,3 +36,17 @@ Customize Intercom Messenger by adding logo and brand colors
|
||||
3. Add API key in Github > Secrets and Variables > Actions as `INTERCOM_APP_ID`
|
||||
4. In your deploy scripts add `pnpm run build:inject-intercom` after your pnpm build / vite build command.
|
||||
5. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-amplitude && pnpm run build:inject-intercom`
|
||||
|
||||
### Smartbanner
|
||||
Smartbanner to show download links to iOS and/or Android native apps on mobile devices.
|
||||
|
||||
<b>To use with dydxprotocol/v4-web:</b>
|
||||
1. iOS app App Store link or Android app Google Play link.
|
||||
2. Add configurations in Github > Secrets and Variables > Actions as
|
||||
`SMARTBANNER_APP_NAME` for app name
|
||||
`SMARTBANNER_ORG_NAME` for organization name
|
||||
`SMARTBANNER_ICON_URL` for icon image
|
||||
`SMARTBANNER_APPSTORE_URL` for iOS App Store link
|
||||
`SMARTBANNER_GOOGLEPLAY_URL` for Android Google Play link
|
||||
3. In your deploy scripts add `pnpm run build:inject-smartbanner` after your pnpm build / vite build command.
|
||||
4. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-smartbanner`
|
||||
|
||||
@@ -3,12 +3,13 @@ import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const AMPLITUDE_API_KEY = process.env.AMPLITUDE_API_KEY;
|
||||
const AMPLITUDE_SERVER_URL = process.env.AMPLITUDE_SERVER_URL;
|
||||
|
||||
const currentPath = fileURLToPath(import.meta.url);
|
||||
const projectRoot = path.dirname(currentPath);
|
||||
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');
|
||||
|
||||
if(AMPLITUDE_API_KEY){
|
||||
if (AMPLITUDE_API_KEY) {
|
||||
try {
|
||||
const html = await fs.readFile(htmlFilePath, 'utf-8');
|
||||
|
||||
@@ -18,8 +19,34 @@ if(AMPLITUDE_API_KEY){
|
||||
`;
|
||||
|
||||
const amplitudeListenerScript = `<script type="module">
|
||||
!function(){var e="${AMPLITUDE_API_KEY}";e&&(globalThis.amplitude.init(e),globalThis.amplitude.setOptOut(!1),globalThis.addEventListener("dydx:track",function(e){var t=e.detail.eventType,d=e.detail.eventData;globalThis.amplitude.track(t,d)}),globalThis.addEventListener("dydx:identify",function(e){var t=e.detail.property,d=e.detail.propertyValue;if("walletAddress"===t)globalThis.amplitude.setUserId(d);else{var i=new globalThis.amplitude.Identify;i.set(t,d),globalThis.amplitude.identify(i)}}),console.log("Amplitude enabled."))}();
|
||||
</script>`;
|
||||
!(function () {
|
||||
var e = "${AMPLITUDE_API_KEY}";
|
||||
e &&
|
||||
(globalThis.amplitude.init(e${
|
||||
AMPLITUDE_SERVER_URL
|
||||
? `, undefined, {
|
||||
serverUrl: "${AMPLITUDE_SERVER_URL}"
|
||||
}`
|
||||
: ''
|
||||
}),
|
||||
globalThis.amplitude.setOptOut(!1),
|
||||
globalThis.addEventListener("dydx:track", function (e) {
|
||||
var t = e.detail.eventType,
|
||||
d = e.detail.eventData;
|
||||
globalThis.amplitude.track(t, d);
|
||||
}),
|
||||
globalThis.addEventListener("dydx:identify", function (e) {
|
||||
var t = e.detail.property,
|
||||
d = e.detail.propertyValue;
|
||||
if ("walletAddress" === t) globalThis.amplitude.setUserId(d);
|
||||
else {
|
||||
var i = new globalThis.amplitude.Identify();
|
||||
i.set(t, d), globalThis.amplitude.identify(i);
|
||||
}
|
||||
}),
|
||||
console.log("Amplitude enabled."));
|
||||
})();
|
||||
</script>`;
|
||||
|
||||
const injectedHtml = html.replace(
|
||||
'<div id="root"></div>',
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const SMARTBANNER_APP_NAME = process.env.SMARTBANNER_APP_NAME;
|
||||
const SMARTBANNER_ORG_NAME = process.env.SMARTBANNER_ORG_NAME;
|
||||
const SMARTBANNER_ICON_URL = process.env.SMARTBANNER_ICON_URL;
|
||||
const SMARTBANNER_APPSTORE_URL = process.env.SMARTBANNER_APPSTORE_URL;
|
||||
const SMARTBANNER_GOOGLEPLAY_URL = process.env.SMARTBANNER_GOOGLEPLAY_URL;
|
||||
|
||||
const currentPath = fileURLToPath(import.meta.url);
|
||||
const projectRoot = path.dirname(currentPath);
|
||||
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');
|
||||
const smartbannerFilePath = path.resolve(projectRoot, '../dist/smartbanner.html');
|
||||
|
||||
if (
|
||||
SMARTBANNER_APP_NAME &&
|
||||
SMARTBANNER_ORG_NAME &&
|
||||
SMARTBANNER_ICON_URL &&
|
||||
(SMARTBANNER_APPSTORE_URL || SMARTBANNER_GOOGLEPLAY_URL)
|
||||
) {
|
||||
try {
|
||||
const html = await fs.readFile(htmlFilePath, 'utf-8');
|
||||
let smartbanner = await fs.readFile(smartbannerFilePath, 'utf-8');
|
||||
smartbanner = smartbanner
|
||||
.replace('SMARTBANNER_APP_NAME', SMARTBANNER_APP_NAME)
|
||||
.replace('SMARTBANNER_ORG_NAME', SMARTBANNER_ORG_NAME)
|
||||
.replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL)
|
||||
.replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL);
|
||||
|
||||
/* hardcoded injection depending on whether the app is available on App Store and/or Google Play */
|
||||
|
||||
if (SMARTBANNER_APPSTORE_URL) {
|
||||
smartbanner = `\t<meta name="smartbanner:button-url-apple" content="${SMARTBANNER_APPSTORE_URL}">\n` + smartbanner;
|
||||
}
|
||||
if (SMARTBANNER_GOOGLEPLAY_URL) {
|
||||
smartbanner = `\t<meta name="smartbanner:button-url-google" content="${SMARTBANNER_GOOGLEPLAY_URL}">\n` + smartbanner;
|
||||
}
|
||||
if (SMARTBANNER_APPSTORE_URL) {
|
||||
if (SMARTBANNER_GOOGLEPLAY_URL) {
|
||||
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="android,ios">\n` + smartbanner;
|
||||
} else {
|
||||
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="ios">\n` + smartbanner;
|
||||
}
|
||||
} else {
|
||||
if (SMARTBANNER_GOOGLEPLAY_URL) {
|
||||
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="android">\n` + smartbanner;
|
||||
}
|
||||
}
|
||||
|
||||
const injectedHtml = html.replace('</head>', `${smartbanner}\n</head>`);
|
||||
|
||||
await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');
|
||||
|
||||
console.log('Smartbanner scripts successfully injected.');
|
||||
} catch (err) {
|
||||
console.error('Error injecting Smartbanner scripts:', err);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -52,9 +52,9 @@ const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
|
||||
const ProfilePage = lazy(() => import('@/pages/Profile'));
|
||||
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
|
||||
const TradePage = lazy(() => import('@/pages/trade/Trade'));
|
||||
const RewardsPage = lazy(() => import('@/pages/rewards/RewardsPage'));
|
||||
const TermsOfUsePage = lazy(() => import('@/pages/TermsOfUsePage'));
|
||||
const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage'));
|
||||
const TokenPage = lazy(() => import('@/pages/token/Token'));
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -87,7 +87,7 @@ const Content = () => {
|
||||
<Route path={MarketsRoute.New} element={<NewMarket />} />
|
||||
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
||||
</Route>
|
||||
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
||||
<Route path={`/${chainTokenLabel}/*`} element={<TokenPage />} />
|
||||
{isTablet && (
|
||||
<>
|
||||
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
||||
@@ -207,6 +207,7 @@ Styled.Content = styled.div<{ isShowingHeader: boolean; isShowingFooter: boolean
|
||||
|
||||
Styled.Main = styled.main`
|
||||
${layoutMixins.contentSectionAttached}
|
||||
box-shadow: none;
|
||||
|
||||
grid-area: Main;
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@ const AlertContainer = styled.div<StyleProps>`
|
||||
case AlertType.Info: {
|
||||
return css`
|
||||
--alert-accent-color: var(--color-text-1);
|
||||
|
||||
--alert-default-background-opacity: 0.133; // Relative
|
||||
// --alert-background: var(--color-layer-6); // Absolute
|
||||
`;
|
||||
}
|
||||
case AlertType.Success: {
|
||||
|
||||
@@ -31,6 +31,7 @@ const assetIcons = {
|
||||
ETH: '/currencies/eth.png',
|
||||
FIL: '/currencies/fil.png',
|
||||
ICP: '/currencies/icp.png',
|
||||
JUP: '/currencies/jup.png',
|
||||
LDO: '/currencies/ldo.png',
|
||||
LINK: '/currencies/link.png',
|
||||
LTC: '/currencies/ltc.png',
|
||||
|
||||
@@ -103,14 +103,14 @@ const buttonActionVariants = {
|
||||
|
||||
[ButtonAction.Create]: css`
|
||||
--button-textColor: var(--color-text-button);
|
||||
--button-backgroundColor: var(--color-success);
|
||||
--button-backgroundColor: var(--color-green);
|
||||
--button-border: solid var(--border-width) var(--color-border-white);
|
||||
--button-hover-filter: brightness(var(--hover-filter-variant));
|
||||
`,
|
||||
|
||||
[ButtonAction.Destroy]: css`
|
||||
--button-textColor: var(--color-text-button);
|
||||
--button-backgroundColor: var(--color-error);
|
||||
--button-backgroundColor: var(--color-red);
|
||||
--button-border: solid var(--border-width) var(--color-border-white);
|
||||
--button-hover-filter: brightness(var(--hover-filter-variant));
|
||||
`,
|
||||
@@ -122,7 +122,7 @@ const buttonActionVariants = {
|
||||
`,
|
||||
|
||||
[ButtonAction.Reset]: css`
|
||||
--button-textColor: var(--color-error);
|
||||
--button-textColor: var(--color-red);
|
||||
--button-backgroundColor: var(--color-layer-3);
|
||||
--button-border: solid var(--border-width) var(--color-border-red);
|
||||
--button-hover-filter: brightness(var(--hover-filter-variant));
|
||||
|
||||
@@ -6,6 +6,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
||||
type ElementProps = {
|
||||
title: string;
|
||||
subtitle?: React.ReactNode;
|
||||
slotLeft?: React.ReactNode;
|
||||
slotRight?: React.ReactNode;
|
||||
};
|
||||
|
||||
@@ -16,14 +17,16 @@ type StyleProps = {
|
||||
export const ContentSectionHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
slotLeft,
|
||||
slotRight,
|
||||
className,
|
||||
}: ElementProps & StyleProps) => (
|
||||
<Styled.ContentSectionHeader className={className}>
|
||||
<div>
|
||||
{slotLeft}
|
||||
<Styled.Header>
|
||||
{title && <h3>{title}</h3>}
|
||||
{subtitle && <p>{subtitle}</p>}
|
||||
</div>
|
||||
</Styled.Header>
|
||||
{slotRight}
|
||||
</Styled.ContentSectionHeader>
|
||||
);
|
||||
@@ -40,9 +43,15 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
|
||||
|
||||
padding: 1rem var(--header-horizontal-padding);
|
||||
|
||||
> div {
|
||||
${layoutMixins.column}
|
||||
@media ${breakpoints.tablet} {
|
||||
flex-wrap: wrap;
|
||||
--header-horizontal-padding: 1.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Header = styled.div`
|
||||
${layoutMixins.column}
|
||||
flex: 1;
|
||||
|
||||
h3 {
|
||||
color: var(--color-text-2);
|
||||
@@ -54,9 +63,4 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
|
||||
font: var(--font-small-book);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
flex-wrap: wrap;
|
||||
--header-horizontal-padding: 1.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -7,35 +7,50 @@ import { StoryWrapper } from '.ladle/components';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
export const DetailsStory: Story<Parameters<typeof Details>> = () => (
|
||||
export const DetailsStory: Story<Parameters<typeof Details>[0]> = (args) => (
|
||||
<StoryWrapper>
|
||||
<Styled.Resizable>
|
||||
<Details
|
||||
items={[
|
||||
{
|
||||
key: 'item-1',
|
||||
label: 'Item 1',
|
||||
tooltip: 'leverage',
|
||||
value: 'Value 1',
|
||||
},
|
||||
{
|
||||
key: 'item-2',
|
||||
label: 'Really really really long item name 2',
|
||||
tooltip: 'liquidation-price',
|
||||
value: 'Value 2',
|
||||
},
|
||||
{
|
||||
key: 'item-3',
|
||||
label: 'Item 3',
|
||||
tooltip: 'realized-pnl',
|
||||
value: 'Value 3',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Details {...args} />
|
||||
</Styled.Resizable>
|
||||
</StoryWrapper>
|
||||
);
|
||||
|
||||
DetailsStory.args = {
|
||||
items: [
|
||||
{
|
||||
key: 'item-1',
|
||||
label: 'Item 1',
|
||||
tooltip: 'leverage',
|
||||
value: 'Value 1',
|
||||
},
|
||||
{
|
||||
key: 'item-2',
|
||||
label: 'Really really really long item name 2',
|
||||
tooltip: 'liquidation-price',
|
||||
value: 'Value 2',
|
||||
},
|
||||
{
|
||||
key: 'item-3',
|
||||
label: 'Item 3',
|
||||
tooltip: 'realized-pnl',
|
||||
value: 'Value 3',
|
||||
},
|
||||
],
|
||||
showSubitems: false,
|
||||
isLoading: false,
|
||||
withOverflow: false,
|
||||
withSeparators: false,
|
||||
};
|
||||
|
||||
DetailsStory.argTypes = {
|
||||
justifyItems: { options: ['start', 'end'], control: { type: 'select' }, defaultValue: 'start' },
|
||||
layout: {
|
||||
options: ['column', 'row', 'rowColumns', 'grid', 'stackColumn'],
|
||||
control: { type: 'select' },
|
||||
defaultValue: 'column',
|
||||
},
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Resizable = styled.section`
|
||||
|
||||
@@ -4,8 +4,18 @@ import { DropdownMenu } from '@/components/DropdownMenu';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const DropdownMenuStory: Story<Parameters<typeof DropdownMenu>> = (args) => {
|
||||
const exampleItems = [
|
||||
export const DropdownMenuStory: Story<Parameters<typeof DropdownMenu>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<DropdownMenu {...args}>
|
||||
<span>Menu</span>
|
||||
</DropdownMenu>
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
DropdownMenuStory.args = {
|
||||
items: [
|
||||
{
|
||||
value: '0',
|
||||
label: 'Item 0',
|
||||
@@ -29,19 +39,9 @@ export const DropdownMenuStory: Story<Parameters<typeof DropdownMenu>> = (args)
|
||||
onSelect: () => alert('Item 3 action'),
|
||||
highlightColor: 'destroy',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<DropdownMenu {...args} items={exampleItems}>
|
||||
<span>Menu</span>
|
||||
</DropdownMenu>
|
||||
</StoryWrapper>
|
||||
);
|
||||
],
|
||||
};
|
||||
|
||||
DropdownMenuStory.args = {};
|
||||
|
||||
DropdownMenuStory.argTypes = {
|
||||
align: {
|
||||
options: ['start', 'center', 'end'],
|
||||
|
||||
@@ -91,10 +91,10 @@ Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'create' | 'destroy' }>
|
||||
--item-highlighted-textColor: var(--color-accent);
|
||||
`,
|
||||
['create']: `
|
||||
--item-highlighted-textColor: var(--color-success);
|
||||
--item-highlighted-textColor: var(--color-green);
|
||||
`,
|
||||
['destroy']: `
|
||||
--item-highlighted-textColor: var(--color-error);
|
||||
--item-highlighted-textColor: var(--color-red);
|
||||
`,
|
||||
}[$highlightColor])}
|
||||
|
||||
|
||||
@@ -28,21 +28,22 @@ const exampleItems = [
|
||||
},
|
||||
];
|
||||
|
||||
export const DropdownSelectMenuStory: Story<Parameters<typeof DropdownSelectMenu>> = (args) => {
|
||||
export const DropdownSelectMenuStory: Story<
|
||||
Pick<Parameters<typeof DropdownSelectMenu>[0], 'items' | 'align' | 'sideOffset' | 'disabled'>
|
||||
> = (args) => {
|
||||
const [item, setItem] = useState(exampleItems[0].value);
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<DropdownSelectMenu
|
||||
items={exampleItems}
|
||||
value={item}
|
||||
onValueChange={(value) => setItem(value)}
|
||||
{...args}
|
||||
/>
|
||||
<DropdownSelectMenu value={item} onValueChange={(value) => setItem(value)} {...args} />
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
DropdownSelectMenuStory.args = {};
|
||||
DropdownSelectMenuStory.args = {
|
||||
items: exampleItems,
|
||||
sideOffset: 1,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
DropdownSelectMenuStory.argTypes = {
|
||||
align: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Icon, IconName } from '@/components/Icon';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const IconStory: Story<Parameters<typeof Icon>> = (args) => {
|
||||
export const IconStory: Story<Parameters<typeof Icon>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<Icon {...args} />
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
CoinsIcon,
|
||||
CommentIcon,
|
||||
CopyIcon,
|
||||
CurrencySignIcon,
|
||||
DepositIcon,
|
||||
DepthChartIcon,
|
||||
DiscordIcon,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
FundingChartIcon,
|
||||
GearIcon,
|
||||
GiftboxIcon,
|
||||
GovernanceIcon,
|
||||
HelpCircleIcon,
|
||||
HideIcon,
|
||||
HistoryIcon,
|
||||
@@ -70,6 +72,7 @@ import {
|
||||
StarIcon,
|
||||
SunIcon,
|
||||
TerminalIcon,
|
||||
TokenIcon,
|
||||
TradeIcon,
|
||||
TransferIcon,
|
||||
TriangleIcon,
|
||||
@@ -103,6 +106,7 @@ export enum IconName {
|
||||
Coins = 'Coins',
|
||||
Comment = 'Comment',
|
||||
Copy = 'Copy',
|
||||
CurrencySign = 'CurrencySign',
|
||||
Deposit = 'Deposit',
|
||||
DepthChart = 'DepthChart',
|
||||
Discord = 'Discord',
|
||||
@@ -113,6 +117,7 @@ export enum IconName {
|
||||
FundingChart = 'FundingChart',
|
||||
Gear = 'Gear',
|
||||
Giftbox = 'Giftbox',
|
||||
Governance = 'Governance',
|
||||
HelpCircle = 'HelpCircle',
|
||||
Hide = 'Hide',
|
||||
History = 'History',
|
||||
@@ -149,6 +154,7 @@ export enum IconName {
|
||||
Sun = 'Sun',
|
||||
Terminal = 'Terminal',
|
||||
TogglesMenu = 'TogglesMenu',
|
||||
Token = 'Token',
|
||||
Trade = 'Trade',
|
||||
Transfer = 'Transfer',
|
||||
Triangle = 'Triangle',
|
||||
@@ -182,6 +188,7 @@ const icons = {
|
||||
[IconName.Coins]: CoinsIcon,
|
||||
[IconName.Comment]: CommentIcon,
|
||||
[IconName.Copy]: CopyIcon,
|
||||
[IconName.CurrencySign]: CurrencySignIcon,
|
||||
[IconName.Deposit]: DepositIcon,
|
||||
[IconName.DepthChart]: DepthChartIcon,
|
||||
[IconName.Discord]: DiscordIcon,
|
||||
@@ -192,6 +199,7 @@ const icons = {
|
||||
[IconName.FundingChart]: FundingChartIcon,
|
||||
[IconName.Gear]: GearIcon,
|
||||
[IconName.Giftbox]: GiftboxIcon,
|
||||
[IconName.Governance]: GovernanceIcon,
|
||||
[IconName.HelpCircle]: HelpCircleIcon,
|
||||
[IconName.Hide]: HideIcon,
|
||||
[IconName.History]: HistoryIcon,
|
||||
@@ -227,6 +235,7 @@ const icons = {
|
||||
[IconName.Sun]: SunIcon,
|
||||
[IconName.Terminal]: TerminalIcon,
|
||||
[IconName.TogglesMenu]: TogglesMenuIcon,
|
||||
[IconName.Token]: TokenIcon,
|
||||
[IconName.Trade]: TradeIcon,
|
||||
[IconName.Transfer]: TransferIcon,
|
||||
[IconName.Triangle]: TriangleIcon,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Link } from '@/components/Link';
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
export const LinkStory: Story<Parameters<typeof Link>> = (args) => {
|
||||
export const LinkStory: Story<Parameters<typeof Link>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<Styled.Container>
|
||||
|
||||
@@ -5,8 +5,24 @@ import { HashRouter } from 'react-router-dom';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const NavigationMenuStory: Story<Parameters<typeof NavigationMenu>> = (args) => {
|
||||
const exampleItems = [
|
||||
export const NavigationMenuStory: Story<
|
||||
Pick<Parameters<typeof NavigationMenu>[0], 'items' | 'orientation' | 'submenuPlacement'>
|
||||
> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<HashRouter
|
||||
children={
|
||||
<NavigationMenu {...args}>
|
||||
<span>Menu</span>
|
||||
</NavigationMenu>
|
||||
}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
NavigationMenuStory.args = {
|
||||
items: [
|
||||
{
|
||||
group: 'navigation',
|
||||
groupLabel: 'Views',
|
||||
@@ -93,23 +109,9 @@ export const NavigationMenuStory: Story<Parameters<typeof NavigationMenu>> = (ar
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<HashRouter
|
||||
children={
|
||||
<NavigationMenu {...args} items={exampleItems}>
|
||||
<span>Menu</span>
|
||||
</NavigationMenu>
|
||||
}
|
||||
/>
|
||||
</StoryWrapper>
|
||||
);
|
||||
],
|
||||
};
|
||||
|
||||
NavigationMenuStory.args = {};
|
||||
|
||||
NavigationMenuStory.argTypes = {
|
||||
orientation: {
|
||||
options: ['vertical', 'horizontal'],
|
||||
|
||||
@@ -65,6 +65,9 @@ type ElementProps = {
|
||||
resolution?: number;
|
||||
stripRelativeWords?: boolean;
|
||||
};
|
||||
timeOptions?: {
|
||||
useUTC?: boolean;
|
||||
};
|
||||
tag?: React.ReactNode;
|
||||
withParentheses?: boolean;
|
||||
locale?: string;
|
||||
@@ -89,6 +92,7 @@ export const Output = ({
|
||||
relativeTimeFormatOptions = {
|
||||
format: 'singleCharacter',
|
||||
},
|
||||
timeOptions,
|
||||
tag,
|
||||
withParentheses,
|
||||
locale = navigator.language || 'en-US',
|
||||
@@ -166,16 +170,21 @@ export const Output = ({
|
||||
if ((typeof value !== 'string' && typeof value !== 'number') || !value) return null;
|
||||
const date = new Date(value);
|
||||
const dateString = {
|
||||
[OutputType.Date]: date.toLocaleString(selectedLocale, { dateStyle: 'medium' }),
|
||||
[OutputType.Date]: date.toLocaleString(selectedLocale, {
|
||||
dateStyle: 'medium',
|
||||
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
|
||||
}),
|
||||
[OutputType.DateTime]: date.toLocaleString(selectedLocale, {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'short',
|
||||
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
|
||||
}),
|
||||
[OutputType.Time]: date.toLocaleString(selectedLocale, {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
|
||||
}),
|
||||
}[type];
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StoryWrapper } from '.ladle/components';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { InputType } from './Input';
|
||||
|
||||
export const SearchInputStory: Story<Parameters<typeof SearchInput>> = (args) => (
|
||||
export const SearchInputStory: Story<Parameters<typeof SearchInput>[0]> = (args) => (
|
||||
<StoryWrapper>
|
||||
<Styled.Container>
|
||||
<SearchInput placeholder="Search something..." type={InputType.Search} />
|
||||
|
||||
@@ -6,7 +6,7 @@ import { SearchSelectMenu } from '@/components/SearchSelectMenu';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>> = (args) => {
|
||||
export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>[0]> = (args) => {
|
||||
const [selectedItem, setSelectedItem] = useState<string>();
|
||||
|
||||
const exampleItems = [
|
||||
@@ -24,10 +24,7 @@ export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>> =
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<Container>
|
||||
<SearchSelectMenu
|
||||
{...args}
|
||||
items={exampleItems}
|
||||
>
|
||||
<SearchSelectMenu {...args} items={exampleItems}>
|
||||
{!selectedItem ? <span>Search and Select</span> : <span>{selectedItem}</span>}
|
||||
</SearchSelectMenu>
|
||||
</Container>
|
||||
|
||||
@@ -26,7 +26,7 @@ const exampleItems: { value: string; label: string }[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const SelectMenuStory: Story<Parameters<typeof SelectMenu>> = (args) => {
|
||||
export const SelectMenuStory: Story<Parameters<typeof SelectMenu>[0]> = (args) => {
|
||||
const [value, setValue] = useState(exampleItems[0].value);
|
||||
const [value2, setValue2] = useState(exampleItems[2].value);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
|
||||
--switch-thumb-backgroundColor: var(--color-layer-6);
|
||||
|
||||
--switch-active-backgroundColor: var(--color-accent);
|
||||
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
|
||||
--switch-active-thumb-backgroundColor: var(--color-white);
|
||||
|
||||
position: relative;
|
||||
width: var(--switch-width);
|
||||
|
||||
+27
-10
@@ -68,7 +68,7 @@ export type TableItem<TableRowData> = {
|
||||
onSelect?: (key: TableRowData) => void;
|
||||
};
|
||||
|
||||
type ColumnDef<TableRowData extends object> = {
|
||||
export type ColumnDef<TableRowData extends object> = {
|
||||
columnKey: string;
|
||||
label: React.ReactNode;
|
||||
tag?: React.ReactNode;
|
||||
@@ -95,7 +95,10 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
|
||||
selectionBehavior?: 'replace' | 'toggle';
|
||||
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
|
||||
slotEmpty?: React.ReactNode;
|
||||
initialNumRowsToShow?: number;
|
||||
viewMoreConfig?: {
|
||||
initialNumRowsToShow: number;
|
||||
numRowsPerPage?: number;
|
||||
};
|
||||
// collection: TableCollection<string>;
|
||||
// children: React.ReactNode;
|
||||
};
|
||||
@@ -125,7 +128,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
||||
selectionMode = 'single',
|
||||
selectionBehavior = 'toggle',
|
||||
slotEmpty,
|
||||
initialNumRowsToShow,
|
||||
viewMoreConfig,
|
||||
// shouldRowRender,
|
||||
|
||||
// collection,
|
||||
@@ -141,8 +144,18 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
||||
style,
|
||||
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
|
||||
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
|
||||
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
|
||||
const enableViewMore = numRowsToShow !== undefined;
|
||||
const [numRowsToShow, setNumRowsToShow] = useState(viewMoreConfig?.initialNumRowsToShow);
|
||||
const enableViewMore = viewMoreConfig !== undefined;
|
||||
|
||||
const onViewMoreClick = () => {
|
||||
if (!viewMoreConfig) return;
|
||||
const { numRowsPerPage } = viewMoreConfig;
|
||||
if (numRowsPerPage) {
|
||||
setNumRowsToShow((prev) => (prev ?? 0) + numRowsPerPage);
|
||||
} else {
|
||||
setNumRowsToShow(data.length);
|
||||
}
|
||||
};
|
||||
|
||||
const currentBreakpoints = useBreakpoints();
|
||||
const shownColumns = columns.filter(
|
||||
@@ -218,9 +231,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
|
||||
}
|
||||
numColumns={shownColumns.length}
|
||||
onViewMoreClick={
|
||||
enableViewMore && numRowsToShow < data.length
|
||||
? () => setNumRowsToShow(data.length)
|
||||
: undefined
|
||||
enableViewMore && numRowsToShow! < data.length ? onViewMoreClick : undefined
|
||||
}
|
||||
// shouldRowRender={shouldRowRender}
|
||||
hideHeader={hideHeader}
|
||||
@@ -513,7 +524,7 @@ const TableColumnHeader = <TableRowData extends object>({
|
||||
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
return (
|
||||
<Styled.Tr key="viewmore">
|
||||
<Styled.ViewMoreTr key="viewmore">
|
||||
<Styled.Td
|
||||
colSpan={colSpan}
|
||||
onMouseDown={(e: MouseEvent) => e.preventDefault()}
|
||||
@@ -523,7 +534,7 @@ export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: ()
|
||||
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
|
||||
</Styled.ViewMoreButton>
|
||||
</Styled.Td>
|
||||
</Styled.Tr>
|
||||
</Styled.ViewMoreTr>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -673,6 +684,8 @@ Styled.TableWrapper = styled.div<{
|
||||
--table-lastColumn-cell-align: end; // start | center | end | var(--table-cell-align)
|
||||
--tableCell-padding: 0 1rem;
|
||||
|
||||
--tableViewMore-borderColor: inherit;
|
||||
|
||||
// Rules
|
||||
|
||||
flex: 1;
|
||||
@@ -984,3 +997,7 @@ Styled.ViewMoreButton = styled(Button)`
|
||||
margin-left: 0.5ch;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.ViewMoreTr = styled(Styled.Tr)`
|
||||
--border-color: var(--tableViewMore-borderColor);
|
||||
`;
|
||||
|
||||
@@ -13,29 +13,11 @@ enum TabItem {
|
||||
Item3 = 'Item3',
|
||||
}
|
||||
|
||||
const TabItems = [
|
||||
{
|
||||
value: TabItem.Item1,
|
||||
label: 'Item 1',
|
||||
content: <div>Item 1 Content</div>,
|
||||
},
|
||||
{
|
||||
value: TabItem.Item2,
|
||||
label: 'Item 2',
|
||||
content: <div>Item 2 Content</div>,
|
||||
},
|
||||
{
|
||||
value: TabItem.Item3,
|
||||
label: 'Item 3',
|
||||
content: <div>Item 3 Content</div>,
|
||||
},
|
||||
];
|
||||
|
||||
export const TabsStory: Story<Parameters<typeof Tabs>> = (args) => {
|
||||
export const TabsStory: Story<Parameters<typeof Tabs>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<Styled.Container>
|
||||
<Tabs items={TabItems} {...args} />
|
||||
<Tabs {...args} />
|
||||
</Styled.Container>
|
||||
</StoryWrapper>
|
||||
);
|
||||
@@ -43,6 +25,23 @@ export const TabsStory: Story<Parameters<typeof Tabs>> = (args) => {
|
||||
|
||||
TabsStory.args = {
|
||||
fullWidthTabs: false,
|
||||
items: [
|
||||
{
|
||||
value: TabItem.Item1,
|
||||
label: 'Item 1',
|
||||
content: <div>Item 1 Content</div>,
|
||||
},
|
||||
{
|
||||
value: TabItem.Item2,
|
||||
label: 'Item 2',
|
||||
content: <div>Item 2 Content</div>,
|
||||
},
|
||||
{
|
||||
value: TabItem.Item3,
|
||||
label: 'Item 3',
|
||||
content: <div>Item 3 Content</div>,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
TabsStory.argTypes = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Story } from '@ladle/react';
|
||||
|
||||
import { Tag } from '@/components/Tag';
|
||||
import { Tag, TagSign, TagSize, TagType } from '@/components/Tag';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const TagStory: Story<Parameters<typeof Tag>> = (args) => {
|
||||
export const TagStory: Story<Parameters<typeof Tag>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<Tag {...args} />
|
||||
@@ -14,4 +14,23 @@ export const TagStory: Story<Parameters<typeof Tag>> = (args) => {
|
||||
|
||||
TagStory.args = {
|
||||
children: 'USDC',
|
||||
isHighlighted: false,
|
||||
};
|
||||
|
||||
TagStory.argTypes = {
|
||||
size: {
|
||||
options: Object.values(TagSize),
|
||||
control: { type: 'select' },
|
||||
defaultValue: TagSize.Small,
|
||||
},
|
||||
type: {
|
||||
options: [...Object.values(TagType), undefined],
|
||||
control: { type: 'select' },
|
||||
defaultValue: undefined,
|
||||
},
|
||||
sign: {
|
||||
options: [...Object.values(TagSign), undefined],
|
||||
control: { type: 'select' },
|
||||
defaultValue: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export enum TagSize {
|
||||
Small,
|
||||
Medium,
|
||||
Small = 'Small',
|
||||
Medium = 'Medium',
|
||||
}
|
||||
|
||||
export enum TagType {
|
||||
Asset,
|
||||
Side,
|
||||
Number,
|
||||
Asset = 'Asset',
|
||||
Side = 'Side',
|
||||
Number = 'Number',
|
||||
}
|
||||
|
||||
export enum TagSign {
|
||||
Positive,
|
||||
Negative,
|
||||
Neutral,
|
||||
Positive = 'Positive',
|
||||
Negative = 'Negative',
|
||||
Neutral = 'Neutral',
|
||||
}
|
||||
|
||||
type StyleProps = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
import { ToggleButton } from './ToggleButton';
|
||||
|
||||
export const ToggleButtonStory: Story<Parameters<typeof ToggleButton>> = (args) => (
|
||||
export const ToggleButtonStory: Story<Parameters<typeof ToggleButton>[0]> = (args) => (
|
||||
<StoryWrapper>
|
||||
<ToggleButton {...args}>Toggle me</ToggleButton>
|
||||
</StoryWrapper>
|
||||
|
||||
@@ -21,16 +21,22 @@ const ToggleGroupItems = [
|
||||
},
|
||||
];
|
||||
|
||||
export const ToggleGroupStory: Story<Parameters<typeof ToggleGroup>> = (args) => {
|
||||
export const ToggleGroupStory: Story<
|
||||
Pick<Parameters<typeof ToggleGroup>[0], 'items' | 'size' | 'shape'>
|
||||
> = (args) => {
|
||||
const [value, setValue] = useState('0');
|
||||
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<ToggleGroup items={ToggleGroupItems} value={value} onValueChange={setValue} {...args} />
|
||||
<ToggleGroup value={value} onValueChange={setValue} {...args} />
|
||||
</StoryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
ToggleGroupStory.args = {
|
||||
items: ToggleGroupItems,
|
||||
};
|
||||
|
||||
ToggleGroupStory.argTypes = {
|
||||
size: {
|
||||
options: Object.values(ButtonSize),
|
||||
|
||||
@@ -117,7 +117,7 @@ Styled.ConfirmButton = styled(Styled.IconButton)`
|
||||
--button-backgroundColor: hsla(203, 25%, 19%, 1);
|
||||
|
||||
svg {
|
||||
color: var(--color-success);
|
||||
color: var(--color-green);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -125,7 +125,7 @@ Styled.CancelButton = styled(Styled.IconButton)`
|
||||
--button-backgroundColor: hsla(296, 16%, 18%, 1);
|
||||
|
||||
svg {
|
||||
color: var(--color-error);
|
||||
color: var(--color-red);
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StoryWrapper } from '.ladle/components';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
export const WithLabelStory: Story<Parameters<typeof WithLabel>> = (args) => {
|
||||
export const WithLabelStory: Story<Parameters<typeof WithLabel>[0]> = (args) => {
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
|
||||
|
||||
@@ -3,32 +3,29 @@ import type { Story } from '@ladle/react';
|
||||
import { Button } from '@/components/Button';
|
||||
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { type DetailsItem } from './Details';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
const items: DetailsItem[] = [
|
||||
{
|
||||
key: 'item-1',
|
||||
label: 'Item 1',
|
||||
value: 'Value 1',
|
||||
},
|
||||
{
|
||||
key: 'item-2',
|
||||
label: 'Item 2',
|
||||
value: 'Value 2',
|
||||
},
|
||||
{
|
||||
key: 'item-3',
|
||||
label: 'Item 3',
|
||||
value: 'Value 3',
|
||||
},
|
||||
];
|
||||
|
||||
export const WithReceiptStory: Story<Parameters<WithReceipt>> = (args) => (
|
||||
export const WithReceiptStory: Story<Omit<Parameters<typeof WithReceipt>[0], 'slotReceipt'>> = (
|
||||
args
|
||||
) => (
|
||||
<StoryWrapper>
|
||||
<div style={{ width: 200 }}>
|
||||
<WithReceipt {...args}>
|
||||
<WithReceipt
|
||||
slotReceipt={
|
||||
<div
|
||||
style={{
|
||||
padding: '1rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
Receipt Content
|
||||
</div>
|
||||
}
|
||||
{...args}
|
||||
>
|
||||
<Button>Hello there</Button>
|
||||
</WithReceipt>
|
||||
</div>
|
||||
@@ -36,7 +33,7 @@ export const WithReceiptStory: Story<Parameters<WithReceipt>> = (args) => (
|
||||
);
|
||||
|
||||
WithReceiptStory.args = {
|
||||
items,
|
||||
hideReceipt: false,
|
||||
};
|
||||
|
||||
WithReceiptStory.argTypes = {
|
||||
|
||||
@@ -106,8 +106,6 @@ Styled.Container = styled.div`
|
||||
--stickyArea1-leftGap: var(--border-width);
|
||||
min-height: var(--stickyArea-height);
|
||||
|
||||
${layoutMixins.withOuterAndInnerBorders}
|
||||
|
||||
display: grid;
|
||||
grid-template: var(--withSidebar-gridTemplate);
|
||||
`;
|
||||
@@ -120,6 +118,7 @@ Styled.Side = styled.aside`
|
||||
${layoutMixins.sticky}
|
||||
max-height: var(--stickyArea-height);
|
||||
backdrop-filter: none;
|
||||
background-color: var(--color-layer-2);
|
||||
|
||||
${layoutMixins.stack}
|
||||
`;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { tooltipStrings } from '@/constants/tooltips';
|
||||
|
||||
import { StoryWrapper } from '.ladle/components';
|
||||
|
||||
export const Tooltip: Story<Parameters<typeof WithTooltip>> = (args) => {
|
||||
export const Tooltip: Story<Parameters<typeof WithTooltip>[0]> = (args) => {
|
||||
return (
|
||||
<StoryWrapper>
|
||||
<WithTooltip {...args}>
|
||||
|
||||
@@ -119,6 +119,13 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
|
||||
// ------ Wallet ------ //
|
||||
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
|
||||
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
|
||||
export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards;
|
||||
export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward;
|
||||
export const HistoricalTradingRewardsPeriod =
|
||||
Abacus.exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod;
|
||||
const historicalTradingRewardsPeriod = [...HistoricalTradingRewardsPeriod.values()] as const;
|
||||
export type HistoricalTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number];
|
||||
|
||||
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
|
||||
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
|
||||
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
|
||||
@@ -202,6 +209,9 @@ export const RestrictionType = Abacus.exchange.dydx.abacus.output.Restriction;
|
||||
const restrictionTypes = [...RestrictionType.values()] as const;
|
||||
export type RestrictionTypes = (typeof restrictionTypes)[number];
|
||||
|
||||
// ------ Api data ------ //
|
||||
export const ApiData = Abacus.exchange.dydx.abacus.state.manager.ApiData;
|
||||
|
||||
// ------ Enum Conversions ------ //
|
||||
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
|
||||
? A
|
||||
@@ -236,6 +246,15 @@ export const HISTORICAL_PNL_PERIODS: Record<
|
||||
[HistoricalPnlPeriod.Period90d.name]: HistoricalPnlPeriod.Period90d,
|
||||
};
|
||||
|
||||
export const HISTORICAL_TRADING_REWARDS_PERIODS: Record<
|
||||
KotlinIrEnumValues<typeof HistoricalTradingRewardsPeriod>,
|
||||
HistoricalTradingRewardsPeriods
|
||||
> = {
|
||||
[HistoricalTradingRewardsPeriod.MONTHLY.name]: HistoricalTradingRewardsPeriod.MONTHLY,
|
||||
[HistoricalTradingRewardsPeriod.WEEKLY.name]: HistoricalTradingRewardsPeriod.WEEKLY,
|
||||
[HistoricalTradingRewardsPeriod.DAILY.name]: HistoricalTradingRewardsPeriod.DAILY,
|
||||
};
|
||||
|
||||
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
|
||||
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
|
||||
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
|
||||
|
||||
@@ -173,3 +173,5 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
validatorUrl: string;
|
||||
}
|
||||
: never;
|
||||
|
||||
export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';
|
||||
|
||||
@@ -5,6 +5,7 @@ export enum DialogTypes {
|
||||
DisplaySettings = 'DisplaySettings',
|
||||
ExchangeOffline = 'ExchangeOffline',
|
||||
ExternalLink = 'ExternalLink',
|
||||
ExternalNavStride = 'ExternalNavStride',
|
||||
FillDetails = 'FillDetails',
|
||||
Help = 'Help',
|
||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||
|
||||
@@ -95,7 +95,7 @@ export type TooltipStrings = {
|
||||
stringParams?: any;
|
||||
urlConfigs?: LinksConfigs;
|
||||
}) => {
|
||||
title: string;
|
||||
title?: string;
|
||||
body: string;
|
||||
learnMoreLink?: string;
|
||||
};
|
||||
|
||||
@@ -1,42 +1,9 @@
|
||||
export type ExchangeConfigParsedCsv = Array<{
|
||||
base_asset: string;
|
||||
exchange: string;
|
||||
pair: string;
|
||||
|
||||
adjust_by_market: string;
|
||||
min_2_depth: string;
|
||||
avg_30d_vol: string;
|
||||
reference_price: string;
|
||||
risk_assessment: string;
|
||||
num_oracles: string;
|
||||
liquidity_tier: string;
|
||||
asset_name: string;
|
||||
}>;
|
||||
|
||||
export type ExchangeConfigItem = {
|
||||
exchangeName: string;
|
||||
ticker: string;
|
||||
adjustByMarket?: string;
|
||||
};
|
||||
|
||||
export type PotentialMarketParsedCsv = Array<{
|
||||
base_asset: string;
|
||||
reference_price: string;
|
||||
num_oracles: string;
|
||||
liquidity_tier: string;
|
||||
asset_name: string;
|
||||
p: string;
|
||||
atomic_resolution: string;
|
||||
min_exchanges: string;
|
||||
min_price_change_ppm: string;
|
||||
price_exponent: string;
|
||||
step_base_quantum: string;
|
||||
ticksize_exponent: string;
|
||||
subticks_per_tick: string;
|
||||
min_order_size: string;
|
||||
quantum_conversion_exponent: string;
|
||||
}>;
|
||||
|
||||
export type PotentialMarketItem = {
|
||||
baseAsset: string;
|
||||
referencePrice: string;
|
||||
@@ -56,30 +23,3 @@ export type PotentialMarketItem = {
|
||||
};
|
||||
|
||||
export const NUM_ORACLES_TO_QUALIFY_AS_SAFE = 6;
|
||||
|
||||
export const LIQUIDITY_TIERS = {
|
||||
0: {
|
||||
label: 'Large-cap',
|
||||
initialMarginFraction: 0.05,
|
||||
maintenanceMarginFraction: 0.03,
|
||||
impactNotional: 10_000,
|
||||
},
|
||||
1: {
|
||||
label: 'Mid-cap',
|
||||
initialMarginFraction: 0.1,
|
||||
maintenanceMarginFraction: 0.05,
|
||||
impactNotional: 5_000,
|
||||
},
|
||||
2: {
|
||||
label: 'Long-tail',
|
||||
initialMarginFraction: 0.2,
|
||||
maintenanceMarginFraction: 0.1,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
3: {
|
||||
label: 'Safety',
|
||||
initialMarginFraction: 1,
|
||||
maintenanceMarginFraction: 0.2,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,6 +29,12 @@ export enum HistoryRoute {
|
||||
Payments = 'payments',
|
||||
}
|
||||
|
||||
export enum TokenRoute {
|
||||
TradingRewards = 'trading-rewards',
|
||||
StakingRewards = 'staking-rewards',
|
||||
Governance = 'governance',
|
||||
}
|
||||
|
||||
export enum MobileSettingsRoute {
|
||||
Language = 'language',
|
||||
Notifications = 'notifications',
|
||||
|
||||
@@ -5,7 +5,8 @@ export type Theme = {
|
||||
[AppColorMode.RedUp]: ThemeColorBase;
|
||||
};
|
||||
|
||||
export type ThemeColorBase = LayerColors &
|
||||
export type ThemeColorBase = BaseColors &
|
||||
LayerColors &
|
||||
BorderColors &
|
||||
TextColors &
|
||||
GradientColors &
|
||||
@@ -17,6 +18,12 @@ export type ThemeColorBase = LayerColors &
|
||||
ComponentColors &
|
||||
Filters;
|
||||
|
||||
type BaseColors = {
|
||||
white: string;
|
||||
green: string;
|
||||
red: string;
|
||||
};
|
||||
|
||||
type LayerColors = {
|
||||
layer0: string;
|
||||
layer1: string;
|
||||
@@ -86,7 +93,6 @@ type IconColors = {
|
||||
type ComponentColors = {
|
||||
inputBackground: string;
|
||||
popoverBackground: string;
|
||||
switchThumbActiveBackground: string;
|
||||
toggleBackground: string;
|
||||
tooltipBackground: string;
|
||||
};
|
||||
|
||||
@@ -198,4 +198,7 @@ export const tradeTooltips: TooltipStrings = {
|
||||
title: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_TITLE }),
|
||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_BODY }),
|
||||
}),
|
||||
'reward-history': ({ stringGetter }) => ({
|
||||
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REWARD_HISTORY_BODY }),
|
||||
}),
|
||||
} as const;
|
||||
|
||||
+54
-20
@@ -4,7 +4,6 @@ import { AlertType } from '@/constants/alerts';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { TimeUnitShort } from '@/constants/time';
|
||||
|
||||
// TODO: rename to OrderType
|
||||
export enum TradeTypes {
|
||||
MARKET = 'MARKET',
|
||||
LIMIT = 'LIMIT',
|
||||
@@ -15,6 +14,16 @@ export enum TradeTypes {
|
||||
TRAILING_STOP = 'TRAILING_STOP',
|
||||
}
|
||||
|
||||
enum ClosingTradeTypes {
|
||||
LIQUIDATED = 'LIQUIDATED',
|
||||
LIQUIDATION = 'LIQUIDATION',
|
||||
OFFSETTING = 'OFFSETTING',
|
||||
DELEVERAGED = 'DELEVERAGED',
|
||||
FINAL_SETTLEMENT = 'FINAL_SETTLEMENT',
|
||||
}
|
||||
|
||||
export type OrderType = TradeTypes | ClosingTradeTypes;
|
||||
|
||||
export enum TimeInForceOptions {
|
||||
GTT = 'GTT',
|
||||
FOK = 'FOK',
|
||||
@@ -40,49 +49,74 @@ export const POSITION_SIDE_STRINGS: Record<PositionSide, string> = {
|
||||
[PositionSide.Short]: STRING_KEYS.SHORT_POSITION_SHORT,
|
||||
};
|
||||
|
||||
export const TRADE_TYPE_STRINGS: Record<
|
||||
TradeTypes,
|
||||
export const ORDER_TYPE_STRINGS: Record<
|
||||
OrderType,
|
||||
{
|
||||
tradeTypeKeyShort: string;
|
||||
tradeTypeKey: string;
|
||||
descriptionKey: string;
|
||||
orderTypeKeyShort: string;
|
||||
orderTypeKey: string;
|
||||
descriptionKey: string | null;
|
||||
}
|
||||
> = {
|
||||
[TradeTypes.LIMIT]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.LIMIT_ORDER_SHORT,
|
||||
tradeTypeKey: STRING_KEYS.LIMIT_ORDER,
|
||||
orderTypeKeyShort: STRING_KEYS.LIMIT_ORDER_SHORT,
|
||||
orderTypeKey: STRING_KEYS.LIMIT_ORDER,
|
||||
descriptionKey: STRING_KEYS.LIMIT_ORDER_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.MARKET]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.MARKET_ORDER_SHORT,
|
||||
tradeTypeKey: STRING_KEYS.MARKET_ORDER,
|
||||
orderTypeKeyShort: STRING_KEYS.MARKET_ORDER_SHORT,
|
||||
orderTypeKey: STRING_KEYS.MARKET_ORDER,
|
||||
descriptionKey: STRING_KEYS.MARKET_ORDER_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.STOP_LIMIT]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.STOP_LIMIT,
|
||||
tradeTypeKey: STRING_KEYS.STOP_LIMIT,
|
||||
orderTypeKeyShort: STRING_KEYS.STOP_LIMIT,
|
||||
orderTypeKey: STRING_KEYS.STOP_LIMIT,
|
||||
descriptionKey: STRING_KEYS.STOP_LIMIT_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.STOP_MARKET]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.STOP_MARKET,
|
||||
tradeTypeKey: STRING_KEYS.STOP_MARKET,
|
||||
orderTypeKeyShort: STRING_KEYS.STOP_MARKET,
|
||||
orderTypeKey: STRING_KEYS.STOP_MARKET,
|
||||
descriptionKey: STRING_KEYS.STOP_MARKET_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.TAKE_PROFIT]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||
tradeTypeKey: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||
orderTypeKeyShort: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||
orderTypeKey: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||
descriptionKey: STRING_KEYS.TAKE_PROFIT_LIMIT_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.TAKE_PROFIT_MARKET]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.TAKE_PROFIT_MARKET,
|
||||
tradeTypeKey: STRING_KEYS.TAKE_PROFIT_MARKET,
|
||||
orderTypeKeyShort: STRING_KEYS.TAKE_PROFIT_MARKET,
|
||||
orderTypeKey: STRING_KEYS.TAKE_PROFIT_MARKET,
|
||||
descriptionKey: STRING_KEYS.TAKE_PROFIT_MARKET_DESCRIPTION,
|
||||
},
|
||||
[TradeTypes.TRAILING_STOP]: {
|
||||
tradeTypeKeyShort: STRING_KEYS.TRAILING_STOP,
|
||||
tradeTypeKey: STRING_KEYS.TRAILING_STOP,
|
||||
orderTypeKeyShort: STRING_KEYS.TRAILING_STOP,
|
||||
orderTypeKey: STRING_KEYS.TRAILING_STOP,
|
||||
descriptionKey: STRING_KEYS.TRAILING_STOP_DESCRIPTION,
|
||||
},
|
||||
[ClosingTradeTypes.LIQUIDATED]: {
|
||||
orderTypeKeyShort: STRING_KEYS.LIQUIDATED,
|
||||
orderTypeKey: STRING_KEYS.LIQUIDATED,
|
||||
descriptionKey: null,
|
||||
},
|
||||
[ClosingTradeTypes.LIQUIDATION]: {
|
||||
orderTypeKeyShort: STRING_KEYS.LIQUIDATION,
|
||||
orderTypeKey: STRING_KEYS.LIQUIDATION,
|
||||
descriptionKey: null,
|
||||
},
|
||||
[ClosingTradeTypes.OFFSETTING]: {
|
||||
orderTypeKeyShort: STRING_KEYS.OFFSETTING,
|
||||
orderTypeKey: STRING_KEYS.OFFSETTING,
|
||||
descriptionKey: null,
|
||||
},
|
||||
[ClosingTradeTypes.DELEVERAGED]: {
|
||||
orderTypeKeyShort: STRING_KEYS.DELEVERAGED,
|
||||
orderTypeKey: STRING_KEYS.DELEVERAGED,
|
||||
descriptionKey: null,
|
||||
},
|
||||
[ClosingTradeTypes.FINAL_SETTLEMENT]: {
|
||||
orderTypeKeyShort: STRING_KEYS.FINAL_SETTLEMENT,
|
||||
orderTypeKey: STRING_KEYS.FINAL_SETTLEMENT,
|
||||
descriptionKey: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const GOOD_TIL_TIME_TIMESCALE_STRINGS: Record<TimeUnitShort, string> = {
|
||||
|
||||
@@ -5,9 +5,11 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import { LanguageCode, ResolutionString, widget } from 'public/tradingview/charting_library';
|
||||
|
||||
import { DEFAULT_RESOLUTION } from '@/constants/candles';
|
||||
import { SUPPORTED_LOCALE_BASE_TAGS } from '@/constants/localization';
|
||||
import { SUPPORTED_LOCALE_BASE_TAGS, STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { useDydxClient, useLocalStorage } from '@/hooks';
|
||||
|
||||
import { useDydxClient, useLocalStorage, useStringGetter } from '@/hooks';
|
||||
import { store } from '@/state/_store';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
@@ -23,14 +25,19 @@ import { getSavedResolution, getWidgetOptions, getWidgetOverrides } from '@/lib/
|
||||
*/
|
||||
export const useTradingView = ({
|
||||
tvWidgetRef,
|
||||
displayButtonRef,
|
||||
setIsChartReady,
|
||||
}: {
|
||||
tvWidgetRef: React.MutableRefObject<any>;
|
||||
displayButtonRef: React.MutableRefObject<any>;
|
||||
setIsChartReady: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const marketId = useSelector(getCurrentMarketId);
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const appTheme = useSelector(getAppTheme);
|
||||
const appColorMode = useSelector(getAppColorMode);
|
||||
|
||||
const marketId = useSelector(getCurrentMarketId);
|
||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||
const selectedLocale = useSelector(getSelectedLocale);
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
@@ -49,7 +56,6 @@ export const useTradingView = ({
|
||||
const widgetOptions = getWidgetOptions();
|
||||
const widgetOverrides = getWidgetOverrides({ appTheme, appColorMode });
|
||||
const options = {
|
||||
// debug: true,
|
||||
...widgetOptions,
|
||||
...widgetOverrides,
|
||||
datafeed: getDydxDatafeed(store, getCandlesForDatafeed),
|
||||
@@ -63,6 +69,17 @@ export const useTradingView = ({
|
||||
tvWidgetRef.current = tvChartWidget;
|
||||
|
||||
tvWidgetRef.current.onChartReady(() => {
|
||||
tvWidgetRef?.current?.headerReady().then(() => {
|
||||
displayButtonRef.current = tvWidgetRef?.current?.createButton();
|
||||
displayButtonRef.current.innerHTML = `<span>${stringGetter({
|
||||
key: STRING_KEYS.ORDER_LINES,
|
||||
})}</span> <div class="displayOrdersButton-toggle"></div>`;
|
||||
displayButtonRef.current.setAttribute(
|
||||
'title',
|
||||
stringGetter({ key: STRING_KEYS.ORDER_LINES_TOOLTIP })
|
||||
);
|
||||
});
|
||||
|
||||
tvWidgetRef?.current?.subscribe('onAutoSaveNeeded', () =>
|
||||
tvWidgetRef?.current?.save((chartConfig: object) => setTvChartConfig(chartConfig))
|
||||
);
|
||||
@@ -72,6 +89,8 @@ export const useTradingView = ({
|
||||
}
|
||||
|
||||
return () => {
|
||||
displayButtonRef.current?.remove();
|
||||
displayButtonRef.current = null;
|
||||
tvWidgetRef.current?.remove();
|
||||
tvWidgetRef.current = null;
|
||||
setIsChartReady(false);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
||||
import type {
|
||||
IChartingLibraryWidget,
|
||||
IOrderLineAdapter,
|
||||
ThemeName,
|
||||
} from 'public/tradingview/charting_library';
|
||||
|
||||
import { AppColorMode, AppTheme } from '@/state/configs';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
|
||||
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
||||
import { getWidgetOverrides, getOrderLineColors } from '@/lib/tradingView/utils';
|
||||
|
||||
/**
|
||||
* @description Method to define a type guard and check that an element is an IFRAME
|
||||
@@ -22,9 +26,11 @@ const isIFrame = (element: HTMLElement | null): element is HTMLIFrameElement =>
|
||||
* In order to support our Classic along with Dark/Light, we are directly accessing the <html> within the iFrame.
|
||||
*/
|
||||
export const useTradingViewTheme = ({
|
||||
orderLines,
|
||||
tvWidget,
|
||||
isWidgetReady,
|
||||
}: {
|
||||
orderLines: Record<string, IOrderLineAdapter>;
|
||||
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
||||
isWidgetReady?: boolean;
|
||||
}) => {
|
||||
@@ -37,8 +43,8 @@ export const useTradingViewTheme = ({
|
||||
.changeTheme?.(
|
||||
{
|
||||
[AppTheme.Classic]: '',
|
||||
[AppTheme.Dark]: 'Dark',
|
||||
[AppTheme.Light]: 'Light',
|
||||
[AppTheme.Dark]: 'dark',
|
||||
[AppTheme.Light]: 'light',
|
||||
}[appTheme] as ThemeName
|
||||
)
|
||||
.then(() => {
|
||||
@@ -49,9 +55,17 @@ export const useTradingViewTheme = ({
|
||||
|
||||
if (isIFrame(frame) && frame.contentWindow) {
|
||||
const innerHtml = frame.contentWindow.document.documentElement;
|
||||
|
||||
if (appTheme === AppTheme.Classic) {
|
||||
innerHtml?.classList.remove('theme-dark', 'theme-light');
|
||||
switch (appTheme) {
|
||||
case AppTheme.Classic:
|
||||
innerHtml?.classList.remove('theme-dark', 'theme-light');
|
||||
break;
|
||||
case AppTheme.Dark:
|
||||
innerHtml?.classList.remove('theme-light');
|
||||
innerHtml?.classList.add('theme-dark');
|
||||
break;
|
||||
case AppTheme.Light:
|
||||
innerHtml?.classList.remove('theme-dark');
|
||||
innerHtml?.classList.add('theme-light');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +87,22 @@ export const useTradingViewTheme = ({
|
||||
'volume.color.1': studies_overrides['volume.volume.color.1'],
|
||||
});
|
||||
}
|
||||
|
||||
// Necessary to update existing chart lines
|
||||
Object.entries(orderLines).forEach(([key, line]) => {
|
||||
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getOrderLineColors({ side: key.split('-')[0], appTheme, appColorMode });
|
||||
|
||||
line
|
||||
.setLineColor(orderColor)
|
||||
.setQuantityBackgroundColor(orderColor)
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [appTheme, appColorMode]);
|
||||
}, [appTheme, appColorMode, isWidgetReady]);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||
|
||||
import type { ConnectNetworkEvent, NetworkConfig } from '@/constants/abacus';
|
||||
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
|
||||
import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
@@ -83,7 +84,8 @@ const useDydxClientContext = () => {
|
||||
{
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
broadcastTimeoutMs: 60_000,
|
||||
}
|
||||
},
|
||||
DEFAULT_TRANSACTION_MEMO
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useMarketsData = (
|
||||
return Object.values(allPerpetualMarkets)
|
||||
.filter(isTruthy)
|
||||
.map((marketData) => ({
|
||||
asset: allAssets[marketData.assetId],
|
||||
asset: allAssets[marketData.assetId] ?? {},
|
||||
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
||||
...marketData,
|
||||
...marketData.perpetual,
|
||||
@@ -46,9 +46,10 @@ export const useMarketsData = (
|
||||
|
||||
if (searchFilter) {
|
||||
return filtered.filter(
|
||||
({ asset }) =>
|
||||
({ asset, id }) =>
|
||||
asset?.name?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
|
||||
asset?.id?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
|
||||
asset?.id?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
|
||||
id?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
|
||||
);
|
||||
}
|
||||
return filtered;
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type {
|
||||
ExchangeConfigItem,
|
||||
ExchangeConfigParsedCsv,
|
||||
PotentialMarketItem,
|
||||
PotentialMarketParsedCsv,
|
||||
} from '@/constants/potentialMarkets';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import type { ExchangeConfigItem, PotentialMarketItem } from '@/constants/potentialMarkets';
|
||||
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
|
||||
const PotentialMarketsContext = createContext<ReturnType<typeof usePotentialMarketsContext>>({
|
||||
potentialMarkets: undefined,
|
||||
exchangeConfigs: undefined,
|
||||
hasPotentialMarketsData: false,
|
||||
liquidityTiers: {
|
||||
0: {
|
||||
label: 'Large-cap',
|
||||
initialMarginFraction: 0.05,
|
||||
maintenanceMarginFraction: 0.03,
|
||||
impactNotional: 10_000,
|
||||
},
|
||||
1: {
|
||||
label: 'Mid-cap',
|
||||
initialMarginFraction: 0.1,
|
||||
maintenanceMarginFraction: 0.05,
|
||||
impactNotional: 5_000,
|
||||
},
|
||||
2: {
|
||||
label: 'Long-tail',
|
||||
initialMarginFraction: 0.2,
|
||||
maintenanceMarginFraction: 0.1,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
3: {
|
||||
label: 'Safety',
|
||||
initialMarginFraction: 1,
|
||||
maintenanceMarginFraction: 0.2,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
PotentialMarketsContext.displayName = 'PotentialMarkets';
|
||||
@@ -27,6 +51,7 @@ const EXCHANGE_CONFIG_FILE_PATH = '/configs/potentialMarketExchangeConfig.json';
|
||||
const POTENTIAL_MARKETS_FILE_PATH = '/configs/potentialMarketParameters.json';
|
||||
|
||||
export const usePotentialMarketsContext = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [potentialMarkets, setPotentialMarkets] = useState<PotentialMarketItem[]>();
|
||||
const [exchangeConfigs, setExchangeConfigs] = useState<Record<string, ExchangeConfigItem[]>>();
|
||||
|
||||
@@ -54,9 +79,40 @@ export const usePotentialMarketsContext = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const liquidityTiers = useMemo(
|
||||
() => ({
|
||||
0: {
|
||||
label: stringGetter({ key: STRING_KEYS.LARGE_CAP }),
|
||||
initialMarginFraction: 0.05,
|
||||
maintenanceMarginFraction: 0.03,
|
||||
impactNotional: 10_000,
|
||||
},
|
||||
1: {
|
||||
label: 'Mid-cap',
|
||||
initialMarginFraction: 0.1,
|
||||
maintenanceMarginFraction: 0.05,
|
||||
impactNotional: 5_000,
|
||||
},
|
||||
2: {
|
||||
label: stringGetter({ key: STRING_KEYS.LONG_TAIL }),
|
||||
initialMarginFraction: 0.2,
|
||||
maintenanceMarginFraction: 0.1,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
3: {
|
||||
label: stringGetter({ key: STRING_KEYS.SAFETY }),
|
||||
initialMarginFraction: 1,
|
||||
maintenanceMarginFraction: 0.2,
|
||||
impactNotional: 2_500,
|
||||
},
|
||||
}),
|
||||
[stringGetter]
|
||||
);
|
||||
|
||||
return {
|
||||
potentialMarkets,
|
||||
exchangeConfigs,
|
||||
hasPotentialMarketsData: Boolean(potentialMarkets && exchangeConfigs),
|
||||
liquidityTiers,
|
||||
};
|
||||
};
|
||||
|
||||
+21
-17
@@ -5,23 +5,25 @@ import { useSelectedNetwork } from '@/hooks';
|
||||
const FALLBACK_URL = 'https://help.dydx.exchange/';
|
||||
|
||||
export interface LinksConfigs {
|
||||
tos: string,
|
||||
privacy: string,
|
||||
mintscan: string,
|
||||
mintscanBase: string,
|
||||
feedback?: string,
|
||||
help?: string,
|
||||
blogs?: string,
|
||||
foundation?: string,
|
||||
initialMarginFractionLearnMore?: string,
|
||||
reduceOnlyLearnMore?: string,
|
||||
documentation?: string,
|
||||
community?: string,
|
||||
governanceLearnMore?: string,
|
||||
stakingLearnMore?: string,
|
||||
keplrDashboard?: string,
|
||||
accountExportLearnMore?: string,
|
||||
walletLearnMore?: string
|
||||
tos: string;
|
||||
privacy: string;
|
||||
mintscan: string;
|
||||
mintscanBase: string;
|
||||
feedback?: string;
|
||||
help?: string;
|
||||
blogs?: string;
|
||||
foundation?: string;
|
||||
initialMarginFractionLearnMore?: string;
|
||||
reduceOnlyLearnMore?: string;
|
||||
documentation?: string;
|
||||
community?: string;
|
||||
governanceLearnMore?: string;
|
||||
newMarketProposalLearnMore: string;
|
||||
stakingLearnMore?: string;
|
||||
keplrDashboard?: string;
|
||||
strideZoneApp?: string;
|
||||
accountExportLearnMore?: string;
|
||||
walletLearnMore?: string;
|
||||
}
|
||||
|
||||
export const useURLConfigs = (): LinksConfigs => {
|
||||
@@ -42,8 +44,10 @@ export const useURLConfigs = (): LinksConfigs => {
|
||||
documentation: linksConfigs.documentation || FALLBACK_URL,
|
||||
community: linksConfigs.community || FALLBACK_URL,
|
||||
governanceLearnMore: linksConfigs.governanceLearnMore || FALLBACK_URL,
|
||||
newMarketProposalLearnMore: linksConfigs.newMarketProposalLearnMore || FALLBACK_URL,
|
||||
stakingLearnMore: linksConfigs.stakingLearnMore || FALLBACK_URL,
|
||||
keplrDashboard: linksConfigs.keplrDashboard || FALLBACK_URL,
|
||||
strideZoneApp: linksConfigs.strideZoneApp || FALLBACK_URL,
|
||||
accountExportLearnMore: linksConfigs.accountExportLearnMore || FALLBACK_URL,
|
||||
walletLearnMore: linksConfigs.walletLearnMore || FALLBACK_URL,
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ const ChaosLabsIcon: React.FC = () => {
|
||||
fill={fills[1]}
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M47.6624 4.38721C46.5167 4.38721 45.5396 4.77222 44.7521 5.54468C43.9625 6.31199 43.5687 7.27749 43.5687 8.41904C43.5687 9.55454 43.9632 10.5194 44.7514 11.2929L44.7527 11.2939C45.5403 12.0594 46.5173 12.4406 47.6624 12.4406C48.8074 12.4406 49.7845 12.0594 50.572 11.2939L50.5734 11.2929C51.3616 10.5194 51.756 9.55454 51.756 8.41904C51.756 7.27749 51.3623 6.312 50.5727 5.54469C49.7851 4.77223 48.8081 4.38721 47.6624 4.38721ZM45.4207 8.41904C45.4207 7.76676 45.6364 7.22501 46.0671 6.77549C46.5039 6.32689 47.0294 6.10561 47.6624 6.10561C48.2954 6.10561 48.8168 6.32697 49.2462 6.77466L49.2476 6.77617C49.6857 7.22615 49.9034 7.7677 49.9034 8.41904C49.9034 9.06265 49.6864 9.60104 49.2476 10.0516L49.2462 10.0531C48.8168 10.5008 48.2954 10.7222 47.6624 10.7222C47.0294 10.7222 46.5039 10.5009 46.0671 10.0523C45.6357 9.60218 45.4207 9.06359 45.4207 8.41904Z"
|
||||
fill={fills[1]}
|
||||
/>
|
||||
@@ -50,8 +50,8 @@ const ChaosLabsIcon: React.FC = () => {
|
||||
fill={fills[1]}
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M40.3586 4.56055H38.6659L35.2261 12.3667H37.1862L37.9072 10.7289H41.1173L41.8383 12.3667H43.7984L40.3586 4.56055ZM38.6686 9.00014L39.5126 7.08737L40.3559 9.00014H38.6686Z"
|
||||
fill={fills[1]}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.9255 9.70119C10.9255 8.14969 9.99456 7.61773 8.13272 7.39612C6.80283 7.21876 6.53685 6.86416 6.53685 6.24348C6.53685 5.62279 6.98018 5.22393 7.86674 5.22393C8.66467 5.22393 9.10801 5.48991 9.32962 6.15485C9.37398 6.28784 9.50697 6.37647 9.63996 6.37647H10.3492C10.5265 6.37647 10.6595 6.24348 10.6595 6.06623V6.02186C10.4822 5.04658 9.68422 4.29301 8.66467 4.20439V3.14047C8.66467 2.96312 8.53169 2.83013 8.31007 2.78577H7.64513C7.46777 2.78577 7.33479 2.91876 7.29042 3.14047V4.16002C5.96053 4.33737 5.11834 5.22393 5.11834 6.33221C5.11834 7.79508 6.0049 8.3713 7.86674 8.59302C9.10801 8.81463 9.50697 9.08061 9.50697 9.78992C9.50697 10.4992 8.88629 10.9868 8.0441 10.9868C6.89145 10.9868 6.49249 10.4991 6.3595 9.83418C6.31524 9.65693 6.18225 9.5682 6.04926 9.5682H5.29559C5.11834 9.5682 4.98535 9.70119 4.98535 9.87854V9.92291C5.1626 11.0311 5.87191 11.829 7.33479 12.0507V13.1146C7.33479 13.2919 7.46778 13.4249 7.68939 13.4692H8.35433C8.53169 13.4692 8.66468 13.3363 8.70904 13.1146V12.0507C10.0389 11.829 10.9255 10.8981 10.9255 9.70119Z" fill="currentColor"/>
|
||||
<path d="M5.73821 14.3561C2.2805 13.1149 0.507281 9.25822 1.79291 5.84477C2.45786 3.98293 3.92073 2.56441 5.73821 1.89947C5.91557 1.81085 6.00419 1.67786 6.00419 1.45614V0.835558C6.00419 0.658204 5.91557 0.525216 5.73821 0.480957C5.69385 0.480957 5.60522 0.480957 5.56086 0.525215C1.34958 1.8551 -0.955596 6.33247 0.374292 10.5437C1.17223 13.0262 3.07843 14.9324 5.56086 15.7303C5.73821 15.8189 5.91557 15.7303 5.95982 15.553C6.00419 15.5087 6.00419 15.4643 6.00419 15.3757V14.755C6.00419 14.622 5.8712 14.4448 5.73821 14.3561ZM10.4372 0.525215C10.2598 0.436592 10.0825 0.525216 10.0382 0.70257C9.99385 0.746935 9.99385 0.791193 9.99385 0.879924V1.5005C9.99385 1.67786 10.1268 1.8551 10.2598 1.94383C13.7175 3.18499 15.4908 7.04167 14.2051 10.4551C13.5402 12.317 12.0773 13.7355 10.2598 14.4004C10.0825 14.489 9.99385 14.622 9.99385 14.8438V15.4643C9.99385 15.6417 10.0825 15.7747 10.2598 15.8189C10.3042 15.8189 10.3928 15.8189 10.4372 15.7747C14.6485 14.4448 16.9536 9.96742 15.6238 5.75615C14.8258 3.22936 12.8752 1.32315 10.4372 0.525215Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13.5V8M10.5 13.5V8M5.5 13.5V8M2 5.5L8 1.5L14 5.5M13 13.5V6.388C11.3459 6.12904 9.67421 5.99931 8 6C6.29933 6 4.62933 6.13333 3 6.388V13.5M2 13.5H14M8 4H8.00533V4.00533H8V4Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 360 B |
+3
-1
@@ -1,4 +1,3 @@
|
||||
import ChaosLabsIcon from './chaos-labs';
|
||||
export { default as AddressConnectorIcon } from './address-connector.svg';
|
||||
export { default as ArrowIcon } from './arrow.svg';
|
||||
export { default as Bar3Icon } from './bar3.svg';
|
||||
@@ -20,6 +19,7 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
|
||||
export { default as CoinsIcon } from './coins.svg';
|
||||
export { default as CommentIcon } from './comment.svg';
|
||||
export { default as CopyIcon } from './copy.svg';
|
||||
export { default as CurrencySignIcon } from './currency-sign.svg';
|
||||
export { default as DepositIcon } from './deposit.svg';
|
||||
export { default as DepthChartIcon } from './depth-chart.svg';
|
||||
export { default as DiscordIcon } from './discord.svg';
|
||||
@@ -29,6 +29,7 @@ export { default as FileIcon } from './file.svg';
|
||||
export { default as FundingChartIcon } from './funding-chart.svg';
|
||||
export { default as GearIcon } from './gear.svg';
|
||||
export { default as GiftboxIcon } from './giftbox.svg';
|
||||
export { default as GovernanceIcon } from './governance.svg';
|
||||
export { default as HelpCircleIcon } from './help-circle.svg';
|
||||
export { default as HideIcon } from './hide.svg';
|
||||
export { default as HistoryIcon } from './history.svg';
|
||||
@@ -60,6 +61,7 @@ export { default as StarIcon } from './star.svg';
|
||||
export { default as SunIcon } from './sun.svg';
|
||||
export { default as TerminalIcon } from './terminal.svg';
|
||||
export { default as TogglesMenuIcon } from './toggles-menu.svg';
|
||||
export { default as TokenIcon } from './token.svg';
|
||||
export { default as TradeIcon } from './trade.svg';
|
||||
export { default as TransferIcon } from './transfer.svg';
|
||||
export { default as TriangleIcon } from './triangle.svg';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.78448 0L0.498535 11.999H3.0425L11.3714 0H8.78448Z" fill="currentColor"/>
|
||||
<path d="M3.30742 0L5.74539 3.53569L4.47339 5.46426L0.710449 0H3.30742Z" fill="currentColor"/>
|
||||
<path d="M9.03109 12L6.32812 8.08934L7.73738 6.21436L11.575 12H9.03109Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 378 B |
@@ -30,6 +30,7 @@ import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDi
|
||||
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
||||
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
|
||||
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
|
||||
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
|
||||
|
||||
export const DialogManager = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -59,6 +60,7 @@ export const DialogManager = () => {
|
||||
[DialogTypes.Help]: <HelpDialog {...modalProps} />,
|
||||
[DialogTypes.ExternalNavKeplr]: <ExternalNavKeplrDialog {...modalProps} />,
|
||||
[DialogTypes.ExternalLink]: <ExternalLinkDialog {...modalProps} />,
|
||||
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
|
||||
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
type HumanReadableTransferPayload,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
|
||||
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
|
||||
@@ -115,7 +116,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
{
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
broadcastTimeoutMs: 60_000,
|
||||
}
|
||||
},
|
||||
DEFAULT_TRANSACTION_MEMO
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -370,8 +372,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
value: {
|
||||
...params.msg,
|
||||
timeoutTimestamp: params.msg.timeoutTimestamp
|
||||
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||
? BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
|
||||
? // Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||
BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
@@ -387,7 +389,11 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
}
|
||||
|
||||
ibcMsg.value.token.amount = amount.toString();
|
||||
const tx = await this.nobleClient.send([ibcMsg]);
|
||||
const tx = await this.nobleClient.send(
|
||||
[ibcMsg],
|
||||
undefined,
|
||||
`${DEFAULT_TRANSACTION_MEMO} | ${this.nobleWallet?.address}`
|
||||
);
|
||||
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
|
||||
@@ -426,8 +432,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
value: {
|
||||
...parsedIbcPayload.msg,
|
||||
timeoutTimestamp: parsedIbcPayload.msg.timeoutTimestamp
|
||||
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||
? BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
|
||||
? // Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||
BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
+15
-2
@@ -3,6 +3,8 @@ import type { LocalWallet } from '@dydxprotocol/v4-client-js';
|
||||
import type {
|
||||
ClosePositionInputFields,
|
||||
Nullable,
|
||||
HistoricalTradingRewardsPeriod,
|
||||
HistoricalTradingRewardsPeriods,
|
||||
HumanReadablePlaceOrderPayload,
|
||||
HumanReadableCancelOrderPayload,
|
||||
TradeInputFields,
|
||||
@@ -23,6 +25,7 @@ import {
|
||||
CoroutineTimer,
|
||||
TransferType,
|
||||
AbacusAppConfig,
|
||||
ApiData,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
@@ -33,8 +36,6 @@ import type { RootStore } from '@/state/_store';
|
||||
import { setTradeFormInputs } from '@/state/inputs';
|
||||
import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors';
|
||||
|
||||
import { testFlags } from '@/lib/testFlags';
|
||||
|
||||
import AbacusRest from './rest';
|
||||
import AbacusAnalytics from './analytics';
|
||||
import AbacusWebsocket from './websocket';
|
||||
@@ -227,6 +228,15 @@ class AbacusStateManager {
|
||||
this.stateManager.historicalPnlPeriod = period;
|
||||
};
|
||||
|
||||
setHistoricalTradingRewardPeriod = (
|
||||
period: (typeof HistoricalTradingRewardsPeriod)[keyof typeof HistoricalTradingRewardsPeriod]
|
||||
) => {
|
||||
this.stateManager.historicalTradingRewardPeriod = period;
|
||||
};
|
||||
|
||||
refreshHistoricalTradingRewards = () =>
|
||||
this.stateManager.refresh(ApiData.HISTORICAL_TRADING_REWARDS);
|
||||
|
||||
switchNetwork = (network: DydxNetwork) => {
|
||||
this.stateManager.environmentId = network;
|
||||
|
||||
@@ -278,6 +288,9 @@ class AbacusStateManager {
|
||||
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
|
||||
this.stateManager.historicalPnlPeriod;
|
||||
|
||||
getHistoricalTradingRewardPeriod = (): HistoricalTradingRewardsPeriods =>
|
||||
this.stateManager.historicalTradingRewardPeriod;
|
||||
|
||||
handleCandlesSubscription = ({
|
||||
channelId,
|
||||
subscribe,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
setSubaccount,
|
||||
setTransfers,
|
||||
setWallet,
|
||||
setTradingRewards,
|
||||
} from '@/state/account';
|
||||
|
||||
import { setApiState } from '@/state/app';
|
||||
@@ -96,6 +97,12 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.has(Changes.tradingRewards)) {
|
||||
if (updatedState.account?.tradingRewards) {
|
||||
dispatch(setTradingRewards(updatedState.account?.tradingRewards));
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.has(Changes.configs)) {
|
||||
dispatch(setConfigs(updatedState.configs));
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ class TestFlags {
|
||||
return !!this.queryParams.displayinitializingmarkets;
|
||||
}
|
||||
|
||||
get showMobileSignInOption() {
|
||||
return !!this.queryParams.mobilesignin;
|
||||
}
|
||||
|
||||
get addressOverride():string {
|
||||
return this.queryParams.address;
|
||||
}
|
||||
|
||||
get showTradingRewards() {
|
||||
return !!this.queryParams.tradingrewards;
|
||||
}
|
||||
}
|
||||
|
||||
export const testFlags = new TestFlags();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
||||
|
||||
import type { AppTheme, AppColorMode } from '@/state/configs';
|
||||
import { AppTheme, type AppColorMode } from '@/state/configs';
|
||||
|
||||
import { Themes } from '@/styles/themes';
|
||||
|
||||
@@ -47,6 +49,30 @@ export const getHistorySlice = ({
|
||||
return bars.filter(({ time }) => time >= fromMs);
|
||||
};
|
||||
|
||||
export const getOrderLineColors = ({
|
||||
appTheme,
|
||||
appColorMode,
|
||||
side,
|
||||
}: {
|
||||
appTheme: AppTheme;
|
||||
appColorMode: AppColorMode;
|
||||
side: OrderSide;
|
||||
}) => {
|
||||
const theme = Themes[appTheme][appColorMode];
|
||||
const orderColor = {
|
||||
[OrderSide.BUY]: theme.positive,
|
||||
[OrderSide.SELL]: theme.negative,
|
||||
}[side];
|
||||
|
||||
return {
|
||||
orderColor,
|
||||
borderColor: theme.borderDefault,
|
||||
backgroundColor: theme.layer1,
|
||||
textColor: theme.textTertiary,
|
||||
textButtonColor: theme.textButton,
|
||||
};
|
||||
};
|
||||
|
||||
export const getWidgetOverrides = ({
|
||||
appTheme,
|
||||
appColorMode,
|
||||
@@ -57,6 +83,7 @@ export const getWidgetOverrides = ({
|
||||
const theme = Themes[appTheme][appColorMode];
|
||||
|
||||
return {
|
||||
theme: appTheme === AppTheme.Dark ? 'dark' : AppTheme.Light ? 'light' : '',
|
||||
overrides: {
|
||||
'paneProperties.background': theme.layer2,
|
||||
'paneProperties.horzGridProperties.color': theme.layer3,
|
||||
|
||||
+151
-84
@@ -4,9 +4,15 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useEnsName } from 'wagmi';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { TransferType } from '@/constants/abacus';
|
||||
|
||||
import { OnboardingState } from '@/constants/account';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
|
||||
import { wallets } from '@/constants/wallets';
|
||||
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Details } from '@/components/Details';
|
||||
@@ -16,23 +22,21 @@ import { IconButton, type IconButtonProps } from '@/components/IconButton';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { Toolbar } from '@/components/Toolbar';
|
||||
|
||||
import { OnboardingState } from '@/constants/account';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
|
||||
import { wallets } from '@/constants/wallets';
|
||||
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
|
||||
|
||||
import { getOnboardingState } from '@/state/accountSelectors';
|
||||
import {
|
||||
getHistoricalTradingRewardsForCurrentWeek,
|
||||
getOnboardingState,
|
||||
} from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
import { truncateAddress } from '@/lib/wallet';
|
||||
|
||||
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
||||
import { MigratePanel } from './rewards/MigratePanel';
|
||||
import { GovernancePanel } from './rewards/GovernancePanel';
|
||||
import { StakingPanel } from './rewards/StakingPanel';
|
||||
import { DYDXBalancePanel } from './token/rewards/DYDXBalancePanel';
|
||||
import { MigratePanel } from './token/rewards/MigratePanel';
|
||||
import { GovernancePanel } from './token/rewards/GovernancePanel';
|
||||
import { StakingPanel } from './token/staking/StakingPanel';
|
||||
import { StrideStakingPanel } from './token/staking/StrideStakingPanel';
|
||||
import { NewMarketsPanel } from './token/rewards/NewMarketsPanel';
|
||||
|
||||
const ENS_CHAIN_ID = 1; // Ethereum
|
||||
|
||||
@@ -52,6 +56,8 @@ const Profile = () => {
|
||||
chainId: ENS_CHAIN_ID,
|
||||
});
|
||||
|
||||
const currentWeekTradingReward = useSelector(getHistoricalTradingRewardsForCurrentWeek);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
key: 'deposit',
|
||||
@@ -159,64 +165,62 @@ const Profile = () => {
|
||||
);
|
||||
})}
|
||||
</Styled.Actions>
|
||||
<Styled.EqualGrid>
|
||||
<Styled.PanelButton
|
||||
slotHeader={
|
||||
<Styled.InlineRow>
|
||||
<Icon iconName={IconName.Gear} />
|
||||
{stringGetter({ key: STRING_KEYS.SETTINGS })}
|
||||
</Styled.InlineRow>
|
||||
}
|
||||
onClick={() => navigate(AppRoute.Settings)}
|
||||
|
||||
<Styled.SettingsButton
|
||||
slotHeader={
|
||||
<Styled.InlineRow>
|
||||
<Icon iconName={IconName.Gear} />
|
||||
{stringGetter({ key: STRING_KEYS.SETTINGS })}
|
||||
</Styled.InlineRow>
|
||||
}
|
||||
onClick={() => navigate(AppRoute.Settings)}
|
||||
/>
|
||||
<Styled.HelpButton
|
||||
slotHeader={
|
||||
<Styled.InlineRow>
|
||||
<Icon iconName={IconName.HelpCircle} />
|
||||
{stringGetter({ key: STRING_KEYS.HELP })}
|
||||
</Styled.InlineRow>
|
||||
}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||
/>
|
||||
|
||||
<Styled.MigratePanel />
|
||||
|
||||
<Styled.DYDXBalancePanel />
|
||||
|
||||
<Styled.RewardsPanel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
|
||||
href={`/${chainTokenLabel}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{
|
||||
key: 'week-rewards',
|
||||
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
|
||||
value: currentWeekTradingReward?.amount ?? '-',
|
||||
},
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
<Styled.PanelButton
|
||||
slotHeader={
|
||||
<Styled.InlineRow>
|
||||
<Icon iconName={IconName.HelpCircle} />
|
||||
{stringGetter({ key: STRING_KEYS.HELP })}
|
||||
</Styled.InlineRow>
|
||||
}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||
</Styled.RewardsPanel>
|
||||
<Styled.FeesPanel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
|
||||
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.MAKER }), value: '-' },
|
||||
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.TAKER }), value: '-' },
|
||||
{ key: 'volume', label: stringGetter({ key: STRING_KEYS.VOLUME_30D }), value: '-' },
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
</Styled.EqualGrid>
|
||||
</Styled.FeesPanel>
|
||||
|
||||
<MigratePanel />
|
||||
<DYDXBalancePanel />
|
||||
|
||||
<Styled.EqualGrid>
|
||||
<Styled.RewardsPanel
|
||||
slotHeaderContent="Trading Rewards"
|
||||
href={`/${chainTokenLabel}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{
|
||||
key: 'week-rewards',
|
||||
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
|
||||
value: '-',
|
||||
},
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
</Styled.RewardsPanel>
|
||||
<Panel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
|
||||
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
|
||||
hasSeparator
|
||||
>
|
||||
<Styled.Details
|
||||
items={[
|
||||
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.MAKER }), value: '-' },
|
||||
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.TAKER }), value: '-' },
|
||||
{ key: 'volume', label: stringGetter({ key: STRING_KEYS.VOLUME_30D }), value: '-' },
|
||||
]}
|
||||
layout="grid"
|
||||
/>
|
||||
</Panel>
|
||||
</Styled.EqualGrid>
|
||||
|
||||
<Styled.TablePanel
|
||||
<Styled.HistoryPanel
|
||||
slotHeaderContent={stringGetter({ key: STRING_KEYS.HISTORY })}
|
||||
href={`${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Trades}`}
|
||||
hasSeparator
|
||||
@@ -230,10 +234,12 @@ const Profile = () => {
|
||||
]}
|
||||
withInnerBorders={false}
|
||||
/>
|
||||
</Styled.TablePanel>
|
||||
</Styled.HistoryPanel>
|
||||
|
||||
<GovernancePanel />
|
||||
<StakingPanel />
|
||||
<Styled.GovernancePanel />
|
||||
<Styled.NewMarketsPanel />
|
||||
<Styled.StakingPanel />
|
||||
<Styled.StrideStakingPanel />
|
||||
</Styled.MobileProfileLayout>
|
||||
);
|
||||
};
|
||||
@@ -245,14 +251,43 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.MobileProfileLayout = styled.div`
|
||||
${layoutMixins.contentContainerPage}
|
||||
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 0.9rem;
|
||||
max-width: 100vw;
|
||||
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
grid-template-areas:
|
||||
'header header'
|
||||
'actions actions'
|
||||
'settings help'
|
||||
'migrate migrate'
|
||||
'balance balance'
|
||||
'rewards fees'
|
||||
'history history'
|
||||
'governance newMarkets'
|
||||
'keplr stride';
|
||||
|
||||
@media ${breakpoints.mobile} {
|
||||
grid-template-areas:
|
||||
'header header'
|
||||
'actions actions'
|
||||
'settings help'
|
||||
'migrate migrate'
|
||||
'balance balance'
|
||||
'rewards fees'
|
||||
'history history'
|
||||
'governance governance'
|
||||
'newMarkets newMarkets'
|
||||
'keplr keplr'
|
||||
'stride stride';
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Header = styled.header`
|
||||
grid-area: header;
|
||||
${layoutMixins.row}
|
||||
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
@@ -299,6 +334,7 @@ Styled.Address = styled.h1`
|
||||
Styled.Actions = styled(Toolbar)`
|
||||
${layoutMixins.spacedRow}
|
||||
--stickyArea-topHeight: 5rem;
|
||||
grid-area: actions;
|
||||
|
||||
> a,
|
||||
> label {
|
||||
@@ -318,7 +354,7 @@ Styled.ActionButton = styled(IconButton)<{ iconName?: IconName }>`
|
||||
${({ iconName }) =>
|
||||
iconName === IconName.Close
|
||||
? css`
|
||||
--button-textColor: var(--color-error);
|
||||
--button-textColor: var(--color-red);
|
||||
--button-icon-size: 0.75em;
|
||||
`
|
||||
: iconName === IconName.Transfer &&
|
||||
@@ -329,22 +365,16 @@ Styled.ActionButton = styled(IconButton)<{ iconName?: IconName }>`
|
||||
`}
|
||||
`;
|
||||
|
||||
Styled.EqualGrid = styled.div`
|
||||
${layoutMixins.gridEqualColumns}
|
||||
|
||||
gap: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Details = styled(Details)`
|
||||
font: var(--font-small-book);
|
||||
--details-value-font: var(--font-medium-book);
|
||||
`;
|
||||
|
||||
Styled.RewardsPanel = styled(Panel)`
|
||||
grid-area: rewards;
|
||||
align-self: flex-start;
|
||||
|
||||
&,
|
||||
> * {
|
||||
height: 100%;
|
||||
> div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -353,7 +383,12 @@ Styled.RewardsPanel = styled(Panel)`
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TablePanel = styled(Panel)`
|
||||
Styled.FeesPanel = styled(Panel)`
|
||||
grid-area: fees;
|
||||
`;
|
||||
|
||||
Styled.HistoryPanel = styled(Panel)`
|
||||
grid-area: history;
|
||||
--panel-content-paddingY: 0;
|
||||
--panel-content-paddingX: 0;
|
||||
|
||||
@@ -393,3 +428,35 @@ Styled.PanelButton = styled(Panel)`
|
||||
--panel-paddingY: 0
|
||||
--panel-paddingX:0;
|
||||
`;
|
||||
|
||||
Styled.SettingsButton = styled(Styled.PanelButton)`
|
||||
grid-area: settings;
|
||||
`;
|
||||
|
||||
Styled.HelpButton = styled(Styled.PanelButton)`
|
||||
grid-area: help;
|
||||
`;
|
||||
|
||||
Styled.MigratePanel = styled(MigratePanel)`
|
||||
grid-area: migrate;
|
||||
`;
|
||||
|
||||
Styled.DYDXBalancePanel = styled(DYDXBalancePanel)`
|
||||
grid-area: balance;
|
||||
`;
|
||||
|
||||
Styled.GovernancePanel = styled(GovernancePanel)`
|
||||
grid-area: governance;
|
||||
`;
|
||||
|
||||
Styled.StakingPanel = styled(StakingPanel)`
|
||||
grid-area: keplr;
|
||||
`;
|
||||
|
||||
Styled.NewMarketsPanel = styled(NewMarketsPanel)`
|
||||
grid-area: newMarkets;
|
||||
`;
|
||||
|
||||
Styled.StrideStakingPanel = styled(StrideStakingPanel)`
|
||||
grid-area: stride;
|
||||
`;
|
||||
|
||||
@@ -45,7 +45,8 @@ export default () => {
|
||||
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
|
||||
const { numTotalPositions, numTotalOpenOrders } = useSelector(getTradeInfoNumbers, shallowEqual) || {};
|
||||
const { numTotalPositions, numTotalOpenOrders } =
|
||||
useSelector(getTradeInfoNumbers, shallowEqual) || {};
|
||||
const numPositions = shortenNumberForDisplay(numTotalPositions);
|
||||
const numOrders = shortenNumberForDisplay(numTotalOpenOrders);
|
||||
|
||||
@@ -119,30 +120,43 @@ export default () => {
|
||||
items: [
|
||||
{
|
||||
value: PortfolioRoute.Overview,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.Overview} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
|
||||
href: PortfolioRoute.Overview,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Positions,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.Positions} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: (
|
||||
<>
|
||||
{stringGetter({ key: STRING_KEYS.POSITIONS })}
|
||||
{numPositions > 0 && (
|
||||
<Tag type={TagType.Number}> {numPositions} </Tag>
|
||||
)}
|
||||
{numPositions &&
|
||||
(typeof numPositions === 'string' || numPositions > 0) && (
|
||||
<Tag type={TagType.Number}> {numPositions} </Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
href: PortfolioRoute.Positions,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Orders,
|
||||
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.OrderPending} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: (
|
||||
<>
|
||||
{stringGetter({ key: STRING_KEYS.ORDERS })}
|
||||
{numOrders > 0 && (
|
||||
{numOrders && (typeof numOrders === 'string' || numOrders > 0) && (
|
||||
<Tag type={TagType.Number}> {numOrders} </Tag>
|
||||
)}
|
||||
</>
|
||||
@@ -151,13 +165,21 @@ export default () => {
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.Fees,
|
||||
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.Calculator} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.FEES }),
|
||||
href: PortfolioRoute.Fees,
|
||||
},
|
||||
{
|
||||
value: PortfolioRoute.History,
|
||||
slotBefore: <Styled.Icon iconName={IconName.History} />,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.History} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.HISTORY }),
|
||||
href: PortfolioRoute.History,
|
||||
},
|
||||
@@ -232,16 +254,14 @@ Styled.NavigationMenu = styled(NavigationMenu)`
|
||||
padding-top: 0;
|
||||
`;
|
||||
|
||||
Styled.Icon = styled(Icon)`
|
||||
--icon-backgroundColor: var(--color-layer-4);
|
||||
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
margin-left: -0.25em;
|
||||
|
||||
box-sizing: content-box;
|
||||
background-color: var(--icon-backgroundColor);
|
||||
Styled.IconContainer = styled.div`
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--color-layer-4);
|
||||
border-radius: 50%;
|
||||
padding: 0.25em;
|
||||
margin-left: -0.25rem;
|
||||
`;
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { BackButton } from '@/components/BackButton';
|
||||
import { Panel } from '@/components/Panel';
|
||||
|
||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||
import { MigratePanel } from './MigratePanel';
|
||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||
import { GovernancePanel } from './GovernancePanel';
|
||||
import { StakingPanel } from './StakingPanel';
|
||||
import { NewMarketsPanel } from './NewMarketsPanel';
|
||||
|
||||
const RewardsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Styled.Page>
|
||||
{isTablet && (
|
||||
<Styled.MobileHeader>
|
||||
<BackButton onClick={() => navigate(AppRoute.Profile)} />
|
||||
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
|
||||
</Styled.MobileHeader>
|
||||
)}
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
|
||||
|
||||
{isTablet ? (
|
||||
<LaunchIncentivesPanel />
|
||||
) : (
|
||||
<Styled.PanelRowIncentivesAndBalance>
|
||||
<LaunchIncentivesPanel />
|
||||
<DYDXBalancePanel />
|
||||
</Styled.PanelRowIncentivesAndBalance>
|
||||
)}
|
||||
{isNotTablet && (
|
||||
<Styled.PanelRow>
|
||||
<NewMarketsPanel />
|
||||
<GovernancePanel />
|
||||
<StakingPanel />
|
||||
</Styled.PanelRow>
|
||||
)}
|
||||
|
||||
<RewardsHelpPanel />
|
||||
</Styled.Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default RewardsPage;
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Page = styled.div`
|
||||
${layoutMixins.contentContainerPage}
|
||||
gap: 1.5rem;
|
||||
padding: 2rem;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
--content-max-width: 80rem;
|
||||
max-width: min(calc(100vw - 4rem), var(--content-max-width));
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--stickyArea-topHeight: var(--page-header-height-mobile);
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
> * {
|
||||
max-width: calc(100vw - 2rem);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.MobileHeader = styled.header`
|
||||
${layoutMixins.contentSectionDetachedScrollable}
|
||||
${layoutMixins.stickyHeader}
|
||||
z-index: 2;
|
||||
padding: 1.25rem 0;
|
||||
margin-bottom: -1.5rem;
|
||||
|
||||
font: var(--font-large-medium);
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-2);
|
||||
`;
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
height: fit-content;
|
||||
`;
|
||||
|
||||
Styled.PanelRow = styled.div`
|
||||
${layoutMixins.gridEqualColumns}
|
||||
gap: 1.5rem;
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
grid-auto-flow: row;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
|
||||
grid-template-columns: 2fr 1fr;
|
||||
`;
|
||||
@@ -0,0 +1,60 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { DetachedSection } from '@/components/ContentSection';
|
||||
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
|
||||
|
||||
import { GovernancePanel } from './rewards/GovernancePanel';
|
||||
import { NewMarketsPanel } from './rewards/NewMarketsPanel';
|
||||
|
||||
export default () => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
return (
|
||||
<DetachedSection>
|
||||
<Styled.HeaderSection>
|
||||
<ContentSectionHeader
|
||||
title={stringGetter({ key: STRING_KEYS.GOVERNANCE })}
|
||||
subtitle="Participate in the ecosystem by voting on Governance proposals or submitting your own."
|
||||
/>
|
||||
</Styled.HeaderSection>
|
||||
|
||||
<Styled.ContentWrapper>
|
||||
<Styled.Row>
|
||||
<GovernancePanel />
|
||||
<NewMarketsPanel />
|
||||
</Styled.Row>
|
||||
</Styled.ContentWrapper>
|
||||
</DetachedSection>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.HeaderSection = styled.section`
|
||||
${layoutMixins.contentSectionDetached}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.ContentWrapper = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1.5rem;
|
||||
max-width: 80rem;
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
Styled.Row = styled.div`
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
`;
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { TokenRoute } from '@/constants/routes';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
import { NavigationMenu } from '@/components/NavigationMenu';
|
||||
import { WithSidebar } from '@/components/WithSidebar';
|
||||
|
||||
const RewardsPage = lazy(() => import('./rewards/RewardsPage'));
|
||||
const StakingPage = lazy(() => import('./staking/StakingPage'));
|
||||
const GovernancePage = lazy(() => import('./Governance'));
|
||||
|
||||
export default () => {
|
||||
const { isTablet } = useBreakpoints();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const routesComponent = (
|
||||
<Suspense fallback={<LoadingSpace id="token-page" />}>
|
||||
<Routes>
|
||||
<Route path={TokenRoute.TradingRewards} element={<RewardsPage />} />
|
||||
<Route path={TokenRoute.StakingRewards} element={<StakingPage />} />
|
||||
<Route path={TokenRoute.Governance} element={<GovernancePage />} />
|
||||
<Route path="*" element={<Navigate to={TokenRoute.TradingRewards} replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
return (
|
||||
<WithSidebar
|
||||
sidebar={
|
||||
isTablet ? null : (
|
||||
<Styled.SideBar>
|
||||
<Styled.NavigationMenu
|
||||
items={[
|
||||
{
|
||||
group: 'views',
|
||||
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
|
||||
items: [
|
||||
{
|
||||
value: TokenRoute.TradingRewards,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.Token} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.TRADING_REWARDS }),
|
||||
href: TokenRoute.TradingRewards,
|
||||
},
|
||||
{
|
||||
value: TokenRoute.StakingRewards,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.CurrencySign} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: 'Staking Rewards', // stringGetter({ key: STRING_KEYS.STAKING_REWARDS }),
|
||||
href: TokenRoute.StakingRewards,
|
||||
tag: stringGetter({ key: STRING_KEYS.NEW }),
|
||||
},
|
||||
{
|
||||
value: TokenRoute.Governance,
|
||||
slotBefore: (
|
||||
<Styled.IconContainer>
|
||||
<Icon iconName={IconName.Governance} />
|
||||
</Styled.IconContainer>
|
||||
),
|
||||
label: stringGetter({ key: STRING_KEYS.GOVERNANCE }),
|
||||
href: TokenRoute.Governance,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Styled.SideBar>
|
||||
)
|
||||
}
|
||||
>
|
||||
{routesComponent}
|
||||
</WithSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
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;
|
||||
`;
|
||||
|
||||
Styled.IconContainer = styled.div`
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--color-layer-4);
|
||||
border-radius: 50%;
|
||||
margin-left: -0.25rem;
|
||||
`;
|
||||
+2
-1
@@ -22,7 +22,7 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
||||
|
||||
export const DYDXBalancePanel = () => {
|
||||
export const DYDXBalancePanel = ({ className }: { className?: string }) => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
@@ -33,6 +33,7 @@ export const DYDXBalancePanel = () => {
|
||||
|
||||
return (
|
||||
<Panel
|
||||
className={className}
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
@@ -14,13 +14,14 @@ import { Link } from '@/components/Link';
|
||||
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
export const GovernancePanel = () => {
|
||||
export const GovernancePanel = ({ className }: { className?: string }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
const { governanceLearnMore } = useURLConfigs();
|
||||
|
||||
return (
|
||||
<Panel
|
||||
className={className}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
||||
}
|
||||
@@ -72,4 +73,4 @@ Styled.Title = styled.h3`
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
margin-bottom: -1rem;
|
||||
`;
|
||||
`;
|
||||
+7
-3
@@ -28,7 +28,7 @@ import { log } from '@/lib/telemetry';
|
||||
|
||||
const SEASON_NUMBER = 2;
|
||||
|
||||
export const LaunchIncentivesPanel = () => {
|
||||
export const LaunchIncentivesPanel = ({ className }: { className?: string }) => {
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -37,11 +37,15 @@ export const LaunchIncentivesPanel = () => {
|
||||
}, []);
|
||||
|
||||
return isNotTablet ? (
|
||||
<Styled.Panel slotHeader={<LaunchIncentivesTitle />} slotRight={<EstimatedRewards />}>
|
||||
<Styled.Panel
|
||||
className={className}
|
||||
slotHeader={<LaunchIncentivesTitle />}
|
||||
slotRight={<EstimatedRewards />}
|
||||
>
|
||||
<LaunchIncentivesContent />
|
||||
</Styled.Panel>
|
||||
) : (
|
||||
<Styled.Panel>
|
||||
<Styled.Panel className={className}>
|
||||
<Styled.Column>
|
||||
<EstimatedRewards />
|
||||
<LaunchIncentivesTitle />
|
||||
@@ -19,21 +19,22 @@ import { Tag } from '@/components/Tag';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
export const NewMarketsPanel = () => {
|
||||
export const NewMarketsPanel = ({ className }: { className?: string }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const navigate = useNavigate();
|
||||
const { hasPotentialMarketsData } = usePotentialMarkets();
|
||||
const { chainTokenDecimals, chainTokenLabel } = useTokenConfigs();
|
||||
const { newMarketProposal } = useGovernanceVariables();
|
||||
const initialDepositAmountDecimals = isMainnet ? 0 : 11;
|
||||
const initialDepositAmountBN = MustBigNumber(newMarketProposal.initialDepositAmount).div(
|
||||
Number(`1e${chainTokenDecimals}`)
|
||||
);
|
||||
const initialDepositAmountDecimals = isMainnet ? 0 : chainTokenDecimals;
|
||||
|
||||
if (!hasPotentialMarketsData) return null;
|
||||
|
||||
return (
|
||||
<Panel
|
||||
className={className}
|
||||
slotHeaderContent={
|
||||
<Styled.Title>
|
||||
{stringGetter({ key: STRING_KEYS.ADD_A_MARKET })}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import {
|
||||
HISTORICAL_TRADING_REWARDS_PERIODS,
|
||||
HistoricalTradingRewardsPeriod,
|
||||
HistoricalTradingRewardsPeriods,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import breakpoints from '@/styles/breakpoints';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { ToggleGroup } from '@/components/ToggleGroup';
|
||||
import { WithTooltip } from '@/components/WithTooltip';
|
||||
import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTable';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
export const RewardHistoryPanel = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const [selectedPeriod, setSelectedPeriod] = useState<HistoricalTradingRewardsPeriods>(
|
||||
abacusStateManager.getHistoricalTradingRewardPeriod() || HistoricalTradingRewardsPeriod.WEEKLY
|
||||
);
|
||||
|
||||
const onSelectPeriod = useCallback(
|
||||
(periodName: string) => {
|
||||
const selectedPeriod =
|
||||
HISTORICAL_TRADING_REWARDS_PERIODS[
|
||||
periodName as keyof typeof HISTORICAL_TRADING_REWARDS_PERIODS
|
||||
];
|
||||
setSelectedPeriod(selectedPeriod);
|
||||
abacusStateManager.setHistoricalTradingRewardPeriod(selectedPeriod);
|
||||
},
|
||||
[setSelectedPeriod, selectedPeriod]
|
||||
);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
<WithTooltip tooltip="reward-history">
|
||||
<h3>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}</h3>
|
||||
</WithTooltip>
|
||||
<span>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION })}</span>
|
||||
</Styled.Title>
|
||||
<ToggleGroup
|
||||
items={[
|
||||
{
|
||||
value: HistoricalTradingRewardsPeriod.MONTHLY.name,
|
||||
label: stringGetter({ key: STRING_KEYS.MONTHLY }),
|
||||
},
|
||||
{
|
||||
value: HistoricalTradingRewardsPeriod.WEEKLY.name,
|
||||
label: stringGetter({ key: STRING_KEYS.WEEKLY }),
|
||||
},
|
||||
{
|
||||
value: HistoricalTradingRewardsPeriod.DAILY.name,
|
||||
label: stringGetter({ key: STRING_KEYS.DAILY }),
|
||||
},
|
||||
]}
|
||||
value={selectedPeriod.name}
|
||||
onValueChange={onSelectPeriod}
|
||||
/>
|
||||
</Styled.Header>
|
||||
}
|
||||
>
|
||||
<TradingRewardHistoryTable period={selectedPeriod} />
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Header = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
|
||||
padding: 1rem 1rem 0;
|
||||
margin-bottom: -0.5rem;
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
padding: 1.25rem 1.5rem 0;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Title = styled.div`
|
||||
${layoutMixins.column}
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
|
||||
h3 {
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 0.75rem;
|
||||
`;
|
||||
+13
-15
@@ -29,20 +29,18 @@ export const RewardsHelpPanel = () => {
|
||||
>
|
||||
<Accordion
|
||||
items={[
|
||||
{
|
||||
header: 'Who is eligible for trading rewards?',
|
||||
content: 'All traders are eligible for trading rewards.',
|
||||
},
|
||||
{
|
||||
header: 'How do trading rewards work?',
|
||||
content:
|
||||
'Immediately after each fill, trading rewards are sent directly to the trader’s dYdX Chain address, based on the amount of fees paid by the trader.',
|
||||
},
|
||||
{
|
||||
header: 'How do I claim my rewards?',
|
||||
content:
|
||||
'Each block, trading rewards are automatically sent directly to the trader’s dYdX Chain address.',
|
||||
},
|
||||
{
|
||||
header: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_QUESTION }),
|
||||
content: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_ANSWER }),
|
||||
},
|
||||
{
|
||||
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION }),
|
||||
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER }),
|
||||
},
|
||||
{
|
||||
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION }),
|
||||
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Styled.HelpCard>
|
||||
@@ -72,7 +70,7 @@ Styled.Header = styled.div`
|
||||
font: var(--font-small-book);
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
padding: 1.5rem 1.25rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@@ -0,0 +1,182 @@
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { BackButton } from '@/components/BackButton';
|
||||
import { DetachedSection } from '@/components/ContentSection';
|
||||
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
|
||||
|
||||
import { testFlags } from '@/lib/testFlags';
|
||||
|
||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||
import { MigratePanel } from './MigratePanel';
|
||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||
import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel';
|
||||
import { RewardHistoryPanel } from './RewardHistoryPanel';
|
||||
|
||||
const RewardsPage = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isTablet && (
|
||||
<ContentSectionHeader
|
||||
title={stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
|
||||
slotLeft={<BackButton onClick={() => navigate(AppRoute.Profile)} />}
|
||||
/>
|
||||
)}
|
||||
<DetachedSection>
|
||||
<Styled.GridLayout
|
||||
showTradingRewards={testFlags.showTradingRewards}
|
||||
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
|
||||
>
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
|
||||
|
||||
{isTablet ? (
|
||||
<Styled.LaunchIncentivesPanel />
|
||||
) : (
|
||||
<>
|
||||
<Styled.LaunchIncentivesPanel />
|
||||
<Styled.DYDXBalancePanel />
|
||||
</>
|
||||
)}
|
||||
|
||||
{testFlags.showTradingRewards && (
|
||||
<Styled.TradingRewardsColumn>
|
||||
<TradingRewardsSummaryPanel />
|
||||
{isTablet && <RewardsHelpPanel />}
|
||||
<RewardHistoryPanel />
|
||||
</Styled.TradingRewardsColumn>
|
||||
)}
|
||||
|
||||
{isNotTablet && (
|
||||
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
|
||||
<RewardsHelpPanel />
|
||||
</Styled.OtherColumn>
|
||||
)}
|
||||
</Styled.GridLayout>
|
||||
</DetachedSection>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RewardsPage;
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.MobileHeader = styled.header`
|
||||
${layoutMixins.contentSectionDetachedScrollable}
|
||||
${layoutMixins.stickyHeader}
|
||||
z-index: 2;
|
||||
padding: 1.25rem 0;
|
||||
|
||||
font: var(--font-large-medium);
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-2);
|
||||
`;
|
||||
|
||||
Styled.GridLayout = styled.div<{ showTradingRewards?: boolean; showMigratePanel?: boolean }>`
|
||||
--gap: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: var(--gap);
|
||||
max-width: 80rem;
|
||||
|
||||
> * {
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
${({ showTradingRewards, showMigratePanel }) =>
|
||||
showTradingRewards && showMigratePanel
|
||||
? css`
|
||||
grid-template-areas:
|
||||
'migrate migrate'
|
||||
'incentives incentives'
|
||||
'balance balance'
|
||||
'rewards other';
|
||||
`
|
||||
: showTradingRewards
|
||||
? css`
|
||||
grid-template-areas:
|
||||
'incentives balance'
|
||||
'rewards other';
|
||||
`
|
||||
: showMigratePanel
|
||||
? css`
|
||||
grid-template-areas:
|
||||
'migrate migrate'
|
||||
'incentives incentives'
|
||||
'balance balance'
|
||||
'other other';
|
||||
`
|
||||
: css`
|
||||
grid-template-areas:
|
||||
'incentives balance'
|
||||
'other other';
|
||||
`};
|
||||
|
||||
@media ${breakpoints.notTablet} {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
--gap: 1rem;
|
||||
grid-template-columns: 1fr;
|
||||
width: calc(100vw - 2rem);
|
||||
margin: 0 auto;
|
||||
|
||||
${({ showTradingRewards }) =>
|
||||
showTradingRewards
|
||||
? css`
|
||||
grid-template-areas:
|
||||
'incentives'
|
||||
'rewards';
|
||||
`
|
||||
: css`
|
||||
grid-template-areas: 'incentives';
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.MigratePanel = styled(MigratePanel)`
|
||||
grid-area: migrate;
|
||||
`;
|
||||
|
||||
Styled.LaunchIncentivesPanel = styled(LaunchIncentivesPanel)`
|
||||
grid-area: incentives;
|
||||
`;
|
||||
|
||||
Styled.DYDXBalancePanel = styled(DYDXBalancePanel)`
|
||||
grid-area: balance;
|
||||
`;
|
||||
|
||||
Styled.TradingRewardsColumn = styled.div`
|
||||
grid-area: rewards;
|
||||
${layoutMixins.flexColumn}
|
||||
`;
|
||||
|
||||
Styled.OtherColumn = styled.div<{ showTradingRewards?: boolean }>`
|
||||
grid-area: other;
|
||||
${layoutMixins.flexColumn}
|
||||
|
||||
${({ showTradingRewards }) =>
|
||||
!showTradingRewards &&
|
||||
css`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
> section:last-of-type {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
@@ -0,0 +1,137 @@
|
||||
import { useEffect } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useStringGetter, useTokenConfigs } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
import { Details } from '@/components/Details';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { Panel } from '@/components/Panel';
|
||||
|
||||
import { getHistoricalTradingRewardsForCurrentWeek } from '@/state/accountSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
export const TradingRewardsSummaryPanel = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
const currentWeekTradingReward = useSelector(
|
||||
getHistoricalTradingRewardsForCurrentWeek,
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
abacusStateManager.refreshHistoricalTradingRewards();
|
||||
}, []);
|
||||
|
||||
return !currentWeekTradingReward ? null : (
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>{stringGetter({ key: STRING_KEYS.TRADING_REWARDS_SUMMARY })}</Styled.Header>
|
||||
}
|
||||
>
|
||||
<Styled.Content>
|
||||
<Styled.TradingRewardsDetails
|
||||
layout="grid"
|
||||
items={[
|
||||
{
|
||||
key: 'week',
|
||||
label: (
|
||||
<Styled.Label>
|
||||
<h4>{stringGetter({ key: STRING_KEYS.THIS_WEEK })}</h4>
|
||||
</Styled.Label>
|
||||
),
|
||||
value: (
|
||||
<Styled.Column>
|
||||
<Output
|
||||
slotRight={<Styled.AssetIcon symbol={chainTokenLabel} />}
|
||||
type={OutputType.Asset}
|
||||
value={currentWeekTradingReward.amount}
|
||||
/>
|
||||
<Styled.TimePeriod>
|
||||
<Output
|
||||
type={OutputType.Date}
|
||||
value={currentWeekTradingReward.startedAtInMilliseconds}
|
||||
timeOptions={{ useUTC: true }}
|
||||
/>
|
||||
→
|
||||
<Output
|
||||
type={OutputType.Date}
|
||||
value={currentWeekTradingReward.endedAtInMilliseconds}
|
||||
timeOptions={{ useUTC: true }}
|
||||
/>
|
||||
</Styled.TimePeriod>
|
||||
</Styled.Column>
|
||||
),
|
||||
},
|
||||
// TODO(@aforaleka): add all-time when supported
|
||||
]}
|
||||
/>
|
||||
</Styled.Content>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Header = styled.div`
|
||||
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
`;
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 0.75rem;
|
||||
`;
|
||||
|
||||
Styled.TradingRewardsDetails = styled(Details)`
|
||||
--details-item-backgroundColor: var(--color-layer-6);
|
||||
|
||||
grid-template-columns: 1fr; // TODO(@aforaleka): change to 1fr 1fr when all-time is supported
|
||||
gap: 1rem;
|
||||
|
||||
> div {
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.75em;
|
||||
background-color: var(--color-layer-5);
|
||||
}
|
||||
|
||||
dt {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
output {
|
||||
color: var(--color-text-2);
|
||||
font: var(--font-large-book);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Label = styled.div`
|
||||
${layoutMixins.spacedRow}
|
||||
|
||||
font: var(--font-base-book);
|
||||
color: var(--color-text-1);
|
||||
`;
|
||||
|
||||
Styled.TimePeriod = styled.div`
|
||||
${layoutMixins.inlineRow}
|
||||
|
||||
&, output {
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Column = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 0.33rem;
|
||||
`;
|
||||
|
||||
Styled.AssetIcon = styled(AssetIcon)`
|
||||
margin-left: 0.5ch;
|
||||
`;
|
||||
@@ -0,0 +1,67 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { DetachedSection } from '@/components/ContentSection';
|
||||
import { ContentSectionHeader } from '@/components/ContentSectionHeader';
|
||||
|
||||
import { StakingPanel } from './StakingPanel';
|
||||
import { StrideStakingPanel } from './StrideStakingPanel';
|
||||
import { DYDXBalancePanel } from '../rewards/DYDXBalancePanel';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<DetachedSection>
|
||||
<Styled.HeaderSection>
|
||||
<ContentSectionHeader
|
||||
title="Staking Rewards"
|
||||
subtitle="Stake to earn APR. Unstaking can take up to 30 days."
|
||||
/>
|
||||
</Styled.HeaderSection>
|
||||
|
||||
<Styled.ContentWrapper>
|
||||
<Styled.Row>
|
||||
<Styled.InnerRow>
|
||||
<StrideStakingPanel />
|
||||
<StakingPanel />
|
||||
</Styled.InnerRow>
|
||||
<DYDXBalancePanel />
|
||||
</Styled.Row>
|
||||
</Styled.ContentWrapper>
|
||||
</DetachedSection>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.HeaderSection = styled.section`
|
||||
${layoutMixins.contentSectionDetached}
|
||||
|
||||
@media ${breakpoints.tablet} {
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1rem;
|
||||
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.ContentWrapper = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 1.5rem;
|
||||
max-width: 80rem;
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
Styled.Row = styled.div`
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
`;
|
||||
|
||||
Styled.InnerRow = styled.div`
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
height: fit-content;
|
||||
`;
|
||||
@@ -1,7 +1,6 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
|
||||
@@ -14,22 +13,19 @@ import { Link } from '@/components/Link';
|
||||
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
export const StakingPanel = () => {
|
||||
export const StakingPanel = ({ className }: { className?: string }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
const { stakingLearnMore } = useURLConfigs();
|
||||
|
||||
return (
|
||||
<Panel
|
||||
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
|
||||
slotRight={
|
||||
<Styled.Arrow>
|
||||
<Styled.IconButton
|
||||
action={ButtonAction.Base}
|
||||
iconName={IconName.Arrow}
|
||||
size={ButtonSize.Small}
|
||||
/>
|
||||
</Styled.Arrow>
|
||||
<Styled.Panel
|
||||
className={className}
|
||||
slotHeaderContent={
|
||||
<Styled.Header>
|
||||
<Styled.Title>Stake with Keplr</Styled.Title>
|
||||
<Styled.Img src="/third-party/keplr.png" alt={stringGetter({ key: STRING_KEYS.KEPLR })} />
|
||||
</Styled.Header>
|
||||
}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||
>
|
||||
@@ -39,12 +35,40 @@ export const StakingPanel = () => {
|
||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||
</Link>
|
||||
</Styled.Description>
|
||||
</Panel>
|
||||
</Styled.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
align-items: start;
|
||||
|
||||
header {
|
||||
justify-content: unset;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Header = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
`;
|
||||
|
||||
Styled.Img = styled.img`
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-left: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Description = styled.div`
|
||||
color: var(--color-text-0);
|
||||
--link-color: var(--color-text-1);
|
||||
@@ -61,13 +85,3 @@ Styled.IconButton = styled(IconButton)`
|
||||
color: var(--color-text-0);
|
||||
--color-border: var(--color-layer-6);
|
||||
`;
|
||||
|
||||
Styled.Arrow = styled.div`
|
||||
padding-right: 1.5rem;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
margin-bottom: -1rem;
|
||||
`;
|
||||
@@ -0,0 +1,94 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { useStringGetter, useTokenConfigs, useURLConfigs } from '@/hooks';
|
||||
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { Link } from '@/components/Link';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { Tag } from '@/components/Tag';
|
||||
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
export const StrideStakingPanel = ({ className }: { className?: string }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
const { stakingLearnMore } = useURLConfigs();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.Panel
|
||||
className={className}
|
||||
slotHeaderContent={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
Liquid Stake with Stride
|
||||
<Tag isHighlighted>{stringGetter({ key: STRING_KEYS.NEW })}</Tag>
|
||||
</Styled.Title>
|
||||
<Styled.Img src="/third-party/stride.png" alt="Stride" />
|
||||
</Styled.Header>
|
||||
}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavStride }))}
|
||||
>
|
||||
<Styled.Description>
|
||||
{`Stake your ${chainTokenLabel} tokens for st${chainTokenLabel} which you can deploy around the ecosystem.`}
|
||||
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
|
||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||
</Link>
|
||||
</Styled.Description>
|
||||
</Styled.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Panel = styled(Panel)`
|
||||
align-items: start;
|
||||
|
||||
header {
|
||||
justify-content: unset;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Header = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
Styled.Title = styled.h3`
|
||||
font: var(--font-medium-book);
|
||||
color: var(--color-text-2);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5ch;
|
||||
`;
|
||||
|
||||
Styled.Img = styled.img`
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-left: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Description = styled.div`
|
||||
color: var(--color-text-0);
|
||||
--link-color: var(--color-text-1);
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
::before {
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.IconButton = styled(IconButton)`
|
||||
color: var(--color-text-0);
|
||||
--color-border: var(--color-layer-6);
|
||||
`;
|
||||
@@ -3,7 +3,7 @@ import { useSelector, shallowEqual } from 'react-redux';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { TRADE_TYPE_STRINGS } from '@/constants/trade';
|
||||
import { ORDER_TYPE_STRINGS } from '@/constants/trade';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
@@ -43,7 +43,7 @@ export const TradeDialogTrigger = () => {
|
||||
<Styled.TradeSummary>
|
||||
<Styled.TradeType>
|
||||
<span>
|
||||
{stringGetter({ key: TRADE_TYPE_STRINGS[selectedTradeType].tradeTypeKey })}
|
||||
{stringGetter({ key: ORDER_TYPE_STRINGS[selectedTradeType].orderTypeKey })}
|
||||
</span>
|
||||
<OrderSideTag size={TagSize.Medium} orderSide={selectedOrderSide} />
|
||||
</Styled.TradeType>
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
HistoricalPnlPeriods,
|
||||
SubAccountHistoricalPNLs,
|
||||
UsageRestriction,
|
||||
TradingRewards,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { OnboardingGuard, OnboardingState } from '@/constants/account';
|
||||
@@ -24,6 +25,7 @@ import { getLocalStorage } from '@/lib/localStorage';
|
||||
export type AccountState = {
|
||||
balances?: Record<string, AccountBalance>;
|
||||
stakingBalances?: Record<string, AccountBalance>;
|
||||
tradingRewards?: TradingRewards;
|
||||
wallet?: Nullable<Wallet>;
|
||||
walletType?: WalletType;
|
||||
|
||||
@@ -179,6 +181,9 @@ export const accountSlice = createSlice({
|
||||
setStakingBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
|
||||
state.stakingBalances = action.payload;
|
||||
},
|
||||
setTradingRewards: (state, action: PayloadAction<TradingRewards>) => {
|
||||
state.tradingRewards = action.payload;
|
||||
},
|
||||
addUncommittedOrderClientId: (state, action: PayloadAction<number>) => {
|
||||
state.uncommittedOrderClientIds.push(action.payload);
|
||||
},
|
||||
@@ -206,6 +211,7 @@ export const {
|
||||
viewedOrders,
|
||||
setBalances,
|
||||
setStakingBalances,
|
||||
setTradingRewards,
|
||||
addUncommittedOrderClientId,
|
||||
removeUncommittedOrderClientId,
|
||||
} = accountSlice.actions;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Nullable, kollections } from '@dydxprotocol/v4-abacus';
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
AbacusOrderStatus,
|
||||
AbacusPositionSide,
|
||||
ORDER_SIDES,
|
||||
HistoricalTradingReward,
|
||||
HistoricalTradingRewardsPeriod,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { OnboardingState } from '@/constants/account';
|
||||
@@ -349,6 +352,38 @@ export const getBalances = (state: RootState) => state.account?.balances;
|
||||
* */
|
||||
export const getStakingBalances = (state: RootState) => state.account?.stakingBalances;
|
||||
|
||||
/**
|
||||
* @returns account all time trading rewards
|
||||
*/
|
||||
export const getTotalTradingRewards = (state: RootState) => state.account?.tradingRewards?.total;
|
||||
|
||||
/**
|
||||
* @returns account trading rewards aggregated by period
|
||||
*/
|
||||
export const getHistoricalTradingRewards = (state: RootState) =>
|
||||
state.account?.tradingRewards?.historical;
|
||||
|
||||
/**
|
||||
* @returns account historical trading rewards for the specified perid
|
||||
*/
|
||||
export const getHistoricalTradingRewardsForPeriod = (period: string) =>
|
||||
createSelector(
|
||||
[getHistoricalTradingRewards],
|
||||
(
|
||||
historicalTradingRewards: Nullable<
|
||||
kollections.Map<string, kollections.List<HistoricalTradingReward>>
|
||||
>
|
||||
) => historicalTradingRewards?.get(period)
|
||||
);
|
||||
|
||||
/**
|
||||
* @returns account historical trading rewards for the current week
|
||||
*/
|
||||
export const getHistoricalTradingRewardsForCurrentWeek = createSelector(
|
||||
[getHistoricalTradingRewardsForPeriod(HistoricalTradingRewardsPeriod.WEEKLY.name)],
|
||||
(historicalTradingRewards) => historicalTradingRewards?.firstOrNull()
|
||||
);
|
||||
|
||||
/**
|
||||
* @returns UsageRestriction of the current session
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,10 @@ import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
export const GlobalStyle = createGlobalStyle`
|
||||
:root {
|
||||
--color-white: ${({ theme }) => theme.white};
|
||||
--color-green: ${({ theme }) => theme.green};
|
||||
--color-red: ${({ theme }) => theme.red};
|
||||
|
||||
--color-layer-0: ${({ theme }) => theme.layer0};
|
||||
--color-layer-1: ${({ theme }) => theme.layer1};
|
||||
--color-layer-2: ${({ theme }) => theme.layer2};
|
||||
|
||||
+12
-3
@@ -4,6 +4,10 @@ import { BrightnessFilterToken, ColorToken, OpacityToken } from '@/constants/sty
|
||||
import { generateFadedColorVariant } from '@/lib/styles';
|
||||
|
||||
const ClassicThemeBase: ThemeColorBase = {
|
||||
white: ColorToken.White,
|
||||
green: ColorToken.Green1,
|
||||
red: ColorToken.Red2,
|
||||
|
||||
layer0: ColorToken.GrayBlue7,
|
||||
layer1: ColorToken.GrayBlue6,
|
||||
layer2: ColorToken.GrayBlue5,
|
||||
@@ -51,7 +55,6 @@ const ClassicThemeBase: ThemeColorBase = {
|
||||
|
||||
inputBackground: ColorToken.GrayBlue3,
|
||||
popoverBackground: generateFadedColorVariant(ColorToken.GrayBlue4, OpacityToken.Opacity90),
|
||||
switchThumbActiveBackground: ColorToken.White,
|
||||
toggleBackground: ColorToken.GrayBlue3,
|
||||
tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66),
|
||||
|
||||
@@ -61,6 +64,10 @@ const ClassicThemeBase: ThemeColorBase = {
|
||||
};
|
||||
|
||||
const DarkThemeBase: ThemeColorBase = {
|
||||
white: ColorToken.White,
|
||||
green: ColorToken.Green0,
|
||||
red: ColorToken.Red0,
|
||||
|
||||
layer0: ColorToken.Black,
|
||||
layer1: ColorToken.DarkGray11,
|
||||
layer2: ColorToken.DarkGray13,
|
||||
@@ -108,7 +115,6 @@ const DarkThemeBase: ThemeColorBase = {
|
||||
|
||||
inputBackground: ColorToken.DarkGray6,
|
||||
popoverBackground: generateFadedColorVariant(ColorToken.DarkGray8, OpacityToken.Opacity90),
|
||||
switchThumbActiveBackground: ColorToken.White,
|
||||
toggleBackground: ColorToken.DarkGray6,
|
||||
tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66),
|
||||
|
||||
@@ -118,6 +124,10 @@ const DarkThemeBase: ThemeColorBase = {
|
||||
};
|
||||
|
||||
const LightThemeBase: ThemeColorBase = {
|
||||
white: ColorToken.White,
|
||||
green: ColorToken.Green2,
|
||||
red: ColorToken.Red1,
|
||||
|
||||
layer0: ColorToken.White,
|
||||
layer1: ColorToken.LightGray6,
|
||||
layer2: ColorToken.White,
|
||||
@@ -165,7 +175,6 @@ const LightThemeBase: ThemeColorBase = {
|
||||
|
||||
inputBackground: ColorToken.White,
|
||||
popoverBackground: generateFadedColorVariant(ColorToken.LightGray8, OpacityToken.Opacity90),
|
||||
switchThumbActiveBackground: ColorToken.White,
|
||||
toggleBackground: ColorToken.LightGray4,
|
||||
tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66),
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ export const AccountInfoConnectedState = () => {
|
||||
<WithTooltip tooltip={tooltip} stringParams={stringParams}>
|
||||
<Styled.WithUsage>
|
||||
{label}
|
||||
{hasError ? <Styled.Icon iconName={IconName.CautionCircle} /> : slotRight}
|
||||
{hasError ? <Styled.CautionIcon iconName={IconName.CautionCircle} /> : slotRight}
|
||||
</Styled.WithUsage>
|
||||
</WithTooltip>
|
||||
),
|
||||
@@ -230,7 +230,7 @@ Styled.CircleContainer = styled.div`
|
||||
border-radius: 50%;
|
||||
`;
|
||||
|
||||
Styled.Icon = styled(Icon)`
|
||||
Styled.CautionIcon = styled(Icon)`
|
||||
color: var(--color-error);
|
||||
`;
|
||||
|
||||
|
||||
@@ -557,11 +557,11 @@ Styled.PositionTile = styled(PositionTile)``;
|
||||
|
||||
Styled.ClosePositionButton = styled(Button)`
|
||||
--button-border: solid var(--border-width) var(--color-border-red);
|
||||
--button-textColor: var(--color-error);
|
||||
--button-textColor: var(--color-red);
|
||||
`;
|
||||
|
||||
Styled.ClosePositionToggleButton = styled(ToggleButton)`
|
||||
--button-border: solid var(--border-width) var(--color-border-red);
|
||||
--button-toggle-off-textColor: var(--color-error);
|
||||
--button-toggle-on-textColor: var(--color-error);
|
||||
--button-toggle-off-textColor: var(--color-red);
|
||||
--button-toggle-on-textColor: var(--color-red);
|
||||
`;
|
||||
|
||||
@@ -33,9 +33,11 @@ Styled.PositionInfoContainer = styled.div`
|
||||
|
||||
PositionTileStory.args = {
|
||||
currentSize: 0.2,
|
||||
oraclePrice: 1300,
|
||||
notionalTotal: 1300,
|
||||
postOrderSize: 0.2,
|
||||
stepSizeDecimals: 3,
|
||||
symbol: 'ETH',
|
||||
tickSizeDecimals: 1,
|
||||
isLoading: false,
|
||||
showNarrowVariation: false,
|
||||
};
|
||||
|
||||
@@ -1,28 +1,52 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import type { IChartingLibraryWidget, ResolutionString } from 'public/tradingview/charting_library';
|
||||
import type {
|
||||
IChartingLibraryWidget,
|
||||
IOrderLineAdapter,
|
||||
ResolutionString,
|
||||
} from 'public/tradingview/charting_library';
|
||||
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
import { AbacusOrderStatus } from '@/constants/abacus';
|
||||
import { DEFAULT_RESOLUTION, RESOLUTION_CHART_CONFIGS } from '@/constants/candles';
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
import { type OrderType, ORDER_TYPE_STRINGS } from '@/constants/trade';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { useTradingView, useTradingViewTheme } from '@/hooks/tradingView';
|
||||
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { getCurrentMarketOrders } from '@/state/accountSelectors';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
import { setTvChartResolution } from '@/state/perpetuals';
|
||||
|
||||
import { getCurrentMarketId, getSelectedResolutionForMarket } from '@/state/perpetualsSelectors';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { getOrderLineColors } from '@/lib/tradingView/utils';
|
||||
|
||||
type TvWidget = IChartingLibraryWidget & { _id?: string; _ready?: boolean };
|
||||
|
||||
export const TvChart = () => {
|
||||
const [isChartReady, setIsChartReady] = useState(false);
|
||||
let orderLines: Record<string, IOrderLineAdapter> = {};
|
||||
|
||||
export const TvChart = () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const [isChartReady, setIsChartReady] = useState(false);
|
||||
const [showOrderLines, setShowOrderLines] = useState(false);
|
||||
|
||||
const displayButtonRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
const appTheme = useSelector(getAppTheme);
|
||||
const appColorMode = useSelector(getAppColorMode);
|
||||
|
||||
const currentMarketId: string = useSelector(getCurrentMarketId) || DEFAULT_MARKETID;
|
||||
const currentMarketOrders = useSelector(getCurrentMarketOrders, shallowEqual);
|
||||
|
||||
const selectedResolution: string =
|
||||
useSelector(getSelectedResolutionForMarket(currentMarketId)) || DEFAULT_RESOLUTION;
|
||||
@@ -33,8 +57,8 @@ export const TvChart = () => {
|
||||
const chart = isWidgetReady ? tvWidget?.chart() : undefined;
|
||||
const chartResolution = chart?.resolution?.();
|
||||
|
||||
const { savedResolution } = useTradingView({ tvWidgetRef, setIsChartReady });
|
||||
useTradingViewTheme({ tvWidget, isWidgetReady });
|
||||
const { savedResolution } = useTradingView({ tvWidgetRef, displayButtonRef, setIsChartReady });
|
||||
useTradingViewTheme({ tvWidget, isWidgetReady, orderLines });
|
||||
|
||||
const setVisibleRangeForResolution = ({ resolution }: { resolution: ResolutionString }) => {
|
||||
// Different resolutions have different timeframes to display data efficiently.
|
||||
@@ -49,6 +73,9 @@ export const TvChart = () => {
|
||||
tvWidget?.activeChart().setVisibleRange(newRange, { percentRightMargin: 10 });
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing chart resolution
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (chartResolution) {
|
||||
if (chartResolution !== selectedResolution) {
|
||||
@@ -69,6 +96,111 @@ export const TvChart = () => {
|
||||
}
|
||||
}, [currentMarketId, isWidgetReady]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle order line toggle state
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (isChartReady && displayButtonRef && displayButtonRef.current) {
|
||||
displayButtonRef.current.onclick = () => {
|
||||
const newShowOrderLinesState = !showOrderLines;
|
||||
if (newShowOrderLinesState) {
|
||||
displayButtonRef.current?.classList?.add('order-lines-active');
|
||||
} else {
|
||||
displayButtonRef.current?.classList?.remove('order-lines-active');
|
||||
}
|
||||
setShowOrderLines(newShowOrderLinesState);
|
||||
};
|
||||
}
|
||||
}, [isChartReady, showOrderLines]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle drawing order lines
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (tvWidget && isChartReady) {
|
||||
tvWidget.onChartReady(() => {
|
||||
tvWidget.chart().dataReady(() => {
|
||||
if (showOrderLines) {
|
||||
drawOrderLines();
|
||||
} else {
|
||||
deleteOrderLines();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [isChartReady, showOrderLines, currentMarketOrders, currentMarketId]);
|
||||
|
||||
const drawOrderLines = () => {
|
||||
if (!currentMarketOrders) return;
|
||||
currentMarketOrders.forEach(
|
||||
({
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
side,
|
||||
cancelReason,
|
||||
remainingSize,
|
||||
size,
|
||||
triggerPrice,
|
||||
price,
|
||||
trailingPercent,
|
||||
}) => {
|
||||
const key = `${side.rawValue}-${id}`;
|
||||
const quantity = (remainingSize ?? size).toString();
|
||||
|
||||
const orderType = type.rawValue as OrderType;
|
||||
const orderLabel = stringGetter({
|
||||
key: ORDER_TYPE_STRINGS[orderType].orderTypeKey,
|
||||
});
|
||||
const orderString = trailingPercent ? `${orderLabel} ${trailingPercent}%` : orderLabel;
|
||||
|
||||
const shouldShow =
|
||||
!cancelReason &&
|
||||
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
|
||||
|
||||
const maybeOrderLine = key in orderLines ? orderLines[key] : null;
|
||||
if (maybeOrderLine) {
|
||||
if (!shouldShow) {
|
||||
maybeOrderLine.remove();
|
||||
delete orderLines[key];
|
||||
return;
|
||||
} else if (maybeOrderLine.getQuantity() !== quantity) {
|
||||
maybeOrderLine.setQuantity(quantity);
|
||||
return;
|
||||
}
|
||||
} else if (shouldShow) {
|
||||
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getOrderLineColors({ side: side.rawValue, appTheme, appColorMode });
|
||||
|
||||
const orderLine = tvWidget
|
||||
?.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.setPrice(MustBigNumber(triggerPrice ?? price).toNumber())
|
||||
.setQuantity(quantity)
|
||||
.setText(orderString)
|
||||
.setLineColor(orderColor)
|
||||
.setQuantityBackgroundColor(orderColor)
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
|
||||
if (orderLine) {
|
||||
orderLines[key] = orderLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteOrderLines = () => {
|
||||
Object.values(orderLines).forEach((line) => {
|
||||
line.remove();
|
||||
});
|
||||
orderLines = {};
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled.PriceChart isChartReady={isChartReady}>
|
||||
{!isChartReady && <LoadingSpace id="tv-chart-loading" />}
|
||||
|
||||
@@ -39,7 +39,6 @@ export const DisplaySettingsDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const sectionHeader = (heading: string) => {
|
||||
return (
|
||||
<Styled.Header>
|
||||
{heading}
|
||||
{heading}
|
||||
<HorizontalSeparatorFiller />
|
||||
</Styled.Header>
|
||||
@@ -287,10 +286,10 @@ Styled.ArrowIcon = styled(Icon)<{ direction: 'up' | 'down'; color: 'green' | 're
|
||||
${({ color }) =>
|
||||
({
|
||||
['green']: css`
|
||||
color: var(--color-success);
|
||||
color: var(--color-green);
|
||||
`,
|
||||
['red']: css`
|
||||
color: var(--color-error);
|
||||
color: var(--color-red);
|
||||
`,
|
||||
}[color])}
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { ButtonAction, ButtonType } from '@/constants/buttons';
|
||||
@@ -10,18 +11,26 @@ import { Dialog } from '@/components/Dialog';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
buttonText?: ReactNode;
|
||||
link: string;
|
||||
linkDescription?: string;
|
||||
title?: ReactNode;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const ExternalLinkDialog = ({ setIsOpen, link, linkDescription }: ElementProps) => {
|
||||
export const ExternalLinkDialog = ({
|
||||
setIsOpen,
|
||||
buttonText,
|
||||
link,
|
||||
linkDescription,
|
||||
title,
|
||||
}: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
setIsOpen={setIsOpen}
|
||||
title={stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE })}
|
||||
title={title ?? stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE })}
|
||||
description={
|
||||
linkDescription ?? stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_DESCRIPTION })
|
||||
}
|
||||
@@ -29,7 +38,7 @@ export const ExternalLinkDialog = ({ setIsOpen, link, linkDescription }: Element
|
||||
<Styled.Content>
|
||||
<p>{stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_DISCLAIMER })}.</p>
|
||||
<Button type={ButtonType.Link} action={ButtonAction.Primary} href={link}>
|
||||
{stringGetter({ key: STRING_KEYS.CONTINUE })}
|
||||
{buttonText ?? stringGetter({ key: STRING_KEYS.CONTINUE })}
|
||||
</Button>
|
||||
</Styled.Content>
|
||||
</Dialog>
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { useCallback } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Button } from '@/components/Button';
|
||||
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
|
||||
import { closeDialog, openDialog } from '@/state/dialogs';
|
||||
|
||||
type ElementProps = {
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const ExternalNavStrideDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { strideZoneApp, accountExportLearnMore } = useURLConfigs();
|
||||
const dispatch = useDispatch();
|
||||
const { isTablet } = useBreakpoints();
|
||||
|
||||
const openExternalNavDialog = useCallback(() => {
|
||||
dispatch(closeDialog());
|
||||
dispatch(
|
||||
openDialog({
|
||||
type: DialogTypes.ExternalLink,
|
||||
dialogProps: {
|
||||
buttonText: (
|
||||
<Styled.Span>
|
||||
Liquid Stake on Stride <Icon iconName={IconName.LinkOut} />
|
||||
</Styled.Span>
|
||||
),
|
||||
link: strideZoneApp,
|
||||
title: 'Liquid staking and leaving website',
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
setIsOpen={setIsOpen}
|
||||
title={stringGetter({ key: STRING_KEYS.HAVE_YOU_EXPORTED })}
|
||||
placement={isTablet ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
||||
>
|
||||
<Styled.Content>
|
||||
<Styled.Button
|
||||
type={ButtonType.Button}
|
||||
size={ButtonSize.XLarge}
|
||||
onClick={openExternalNavDialog}
|
||||
>
|
||||
<span>
|
||||
<strong>{stringGetter({ key: STRING_KEYS.YES })}</strong>, Navigate to Stride Zone.
|
||||
</span>
|
||||
|
||||
<Styled.IconButton
|
||||
action={ButtonAction.Base}
|
||||
iconName={IconName.Arrow}
|
||||
size={ButtonSize.XSmall}
|
||||
/>
|
||||
</Styled.Button>
|
||||
|
||||
<Styled.Button
|
||||
type={ButtonType.Link}
|
||||
size={ButtonSize.XLarge}
|
||||
href={accountExportLearnMore}
|
||||
>
|
||||
<span>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.LEARN_TO_EXPORT,
|
||||
params: {
|
||||
STRONG_NO: <strong>{stringGetter({ key: STRING_KEYS.NO })}</strong>,
|
||||
},
|
||||
})}
|
||||
</span>
|
||||
|
||||
<Styled.IconButton
|
||||
action={ButtonAction.Base}
|
||||
iconName={IconName.Arrow}
|
||||
size={ButtonSize.XSmall}
|
||||
/>
|
||||
</Styled.Button>
|
||||
</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;
|
||||
`;
|
||||
|
||||
Styled.Button = styled(Button)`
|
||||
--button-font: var(--font-base-book);
|
||||
--button-padding: 0 1.5rem;
|
||||
|
||||
gap: 0;
|
||||
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
Styled.IconButton = styled(IconButton)`
|
||||
color: var(--color-text-0);
|
||||
--color-border: var(--color-layer-6);
|
||||
`;
|
||||
|
||||
Styled.Span = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5ch;
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user