Compare commits

...
Author SHA1 Message Date
moo-onthelawn 87731097b8 TRCL-3497 Use color tokens in app + tradingView widget (#240)
* update to use tokens, small fixes

* fix eol lint

* move usage style helper to lib/styles

* move files, fix text colors

* fix logo in light mode

* remove colors.css file

* small lint things

* add transparency to button + destructive borders
2024-01-24 16:40:25 -05:00
mulan xia 1e9209028e fix lint error 2024-01-22 16:42:19 -05:00
mulan xia 2861286bdb introduce tokens file + theme type 2024-01-22 16:28:37 -05:00
Bill e93850484b CCTP deposit/withdraw to throw error if nobleClient is not initialized (#215)
* CCTP deposit/withdraw to throw error if nobleClient is not initialized

* update error str
2024-01-22 09:55:26 -08:00
aleka 521b6d6135 test flag for sign in option (#236) 2024-01-17 16:22:17 -08:00
aleka 94772bf4dc bump abacus to remove base position notional (#235) 2024-01-17 15:52:02 -08:00
Adam Fraser 149f21e7e0 Add trailing blocks to NetworkStatus analytics event (#230) 2024-01-17 10:34:04 -05:00
aleka 0a3ef3a7ab bump abacus (#233) 2024-01-17 10:33:29 -05:00
Bill fef9935883 Support notification for trading rewards (#224)
* Support notification for trading rewards

* bump abacus

* Stack notifications based on notification type

* address comments
2024-01-16 10:19:48 -08:00
Bill c7a5b706e1 Fix toAmount/toAmountMin on withdraw (#232) 2024-01-15 10:31:23 -08:00
moo-onthelawn f304bb069a TRCL-1979 Show open positions / orders in portfolio sidebar (#228)
* add tags for open orders + positions

* clean up, use new num selector function

* update logic for open orders
2024-01-12 14:44:09 -05:00
Bill ad6aa1af49 Bump abacus, fix onboarding toAmount (#229) 2024-01-11 11:52:02 -08:00
Jared Vu 55b2c3fb4a Update Funding Chart Tooltip (#225)
* 🚧 depth chart

*  Fixed types and shortened FundingRateTooltip

* 💄 update depth chart color scale to use css var

* 💄 use layer-6 instead of text-1

* 🌐 Add localization, fix nits
2024-01-11 10:31:53 -08:00
moo-onthelawn 83bcde00eb TRCL-1623 Update Positions icon in nav sidebar (#226)
* update icon

* remove unused cube icon
2024-01-11 11:02:23 -05:00
59 changed files with 1573 additions and 975 deletions
+18 -9
View File
@@ -5,29 +5,35 @@ import styled from 'styled-components';
import { store } from '@/state/_store';
import { GlobalStyle } from '@/styles/globalStyle';
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { AppTheme, setAppTheme } from '@/state/configs';
import { setLocaleLoaded } from '@/state/localization';
import '@/index.css';
import './ladle.css';
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState('Default theme');
const [theme, setTheme] = useState(AppTheme.Classic);
useEffect(() => {
store.dispatch(setAppTheme(theme));
switch (theme) {
case 'Dark theme': {
case AppTheme.Dark: {
document?.documentElement?.classList.remove('theme-light');
document?.documentElement?.classList.add('theme-dark');
break;
}
case 'Light theme': {
case AppTheme.Light: {
document?.documentElement?.classList.remove('theme-dark');
document?.documentElement?.classList.add('theme-light');
break;
}
default: {
case AppTheme.Classic: {
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
break;
}
@@ -48,15 +54,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
>
{[
{
value: 'Default theme',
value: AppTheme.Classic,
label: 'Default theme',
},
{
value: 'Dark theme',
value: AppTheme.Dark,
label: 'Dark theme',
},
{
value: 'Light theme',
value: AppTheme.Light,
label: 'Light theme',
},
].map(({ value, label }) => (
@@ -69,8 +75,11 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
</SelectMenu>
</StoryHeader>
<hr />
<StoryContent>{children}</StoryContent>
</Provider>
<AppThemeProvider>
<GlobalStyle />
<StoryContent>{children}</StoryContent>
</AppThemeProvider>
</Provider>
);
};
+2 -2
View File
@@ -39,9 +39,9 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.1.33",
"@dydxprotocol/v4-abacus": "^1.2.5",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.6",
"@dydxprotocol/v4-localization": "^1.1.11",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
+12 -12
View File
@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@0xsquid/sdk':
specifier: ^1.10.0
@@ -27,14 +23,14 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
specifier: ^1.1.33
version: 1.1.33
specifier: ^1.2.5
version: 1.2.5
'@dydxprotocol/v4-client-js':
specifier: ^1.0.11
version: 1.0.11
'@dydxprotocol/v4-localization':
specifier: ^1.1.6
version: 1.1.6
specifier: ^1.1.11
version: 1.1.11
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -991,8 +987,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@1.1.33:
resolution: {integrity: sha512-VlIOaOPR4ImSWGmKK3+M4VHdMDdTk7qI77Iopppq11qU7f8wdwk4OeedTSuAMxezKez2nCP3J/x3Da+FtrKRqg==}
/@dydxprotocol/v4-abacus@1.2.5:
resolution: {integrity: sha512-pB3Fv1bzJRUDp29kAN61daZ34gTSGrSTNh+/AP3TUB63/Q/gaXQVBf91xVcso4Hw23xM3FwIcrRK0cvOSWgXKg==}
dev: false
/@dydxprotocol/v4-client-js@1.0.11:
@@ -1023,8 +1019,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.1.6:
resolution: {integrity: sha512-Bon6NSRU4/FqneAbnP2G28EAPr0hp4LhvAayX61o0O1PGkxnLzAHkXeFppdM0Zn0fOcp1S1MJ+gvz138ZDephQ==}
/@dydxprotocol/v4-localization@1.1.11:
resolution: {integrity: sha512-ZHnyrWD1bEpvY1tctkcmSV6A5+hQSYBFwyjbiyOqepqDqrv24J5a3hlZU94lDyYf+7Sdk3alOvy9Z7Lpk59nvw==}
dev: false
/@dydxprotocol/v4-proto@0.4.1:
@@ -14813,3 +14809,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+230
View File
@@ -0,0 +1,230 @@
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1258">
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
</g>
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
</g>
</g>
<defs>
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

+40 -34
View File
@@ -16,6 +16,7 @@ import {
} from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications';
@@ -35,6 +36,7 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { config } from '@/lib/wagmi';
import { breakpoints } from '@/styles';
import { GlobalStyle } from '@/styles/globalStyle';
import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner';
@@ -66,49 +68,52 @@ const Content = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<>
<GlobalStyle />
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
{isTablet ? <FooterMobile /> : <FooterDesktop />}
{isTablet ? <FooterMobile /> : <FooterDesktop />}
<Styled.NotificationsToastArea />
<Styled.NotificationsToastArea />
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<GlobalCommandDialog />
</Styled.Content>
<GlobalCommandDialog />
</Styled.Content>
</>
);
};
@@ -131,6 +136,7 @@ const providers = [
wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider),
wrapProvider(AppThemeProvider),
];
const App = () => {
+3 -3
View File
@@ -24,7 +24,6 @@ import {
CoinsIcon,
CommentIcon,
CopyIcon,
CubeIcon,
DepositIcon,
DepthChartIcon,
DiscordIcon,
@@ -60,6 +59,7 @@ import {
PriceChartIcon,
PrivacyIcon,
QrIcon,
RewardStarIcon,
SearchIcon,
SendIcon,
ShareIcon,
@@ -99,7 +99,6 @@ export enum IconName {
Coins = 'Coins',
Comment = 'Comment',
Copy = 'Copy',
Cube = 'Cube',
Deposit = 'Deposit',
DepthChart = 'DepthChart',
Discord = 'Discord',
@@ -136,6 +135,7 @@ export enum IconName {
PriceChart = 'PriceChart',
Privacy = 'Privacy',
Qr = 'Qr',
RewardStar = 'RewardStar',
Search = 'Search',
Send = 'Send',
Share = 'Share',
@@ -175,7 +175,6 @@ const icons = {
[IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon,
[IconName.Cube]: CubeIcon,
[IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon,
@@ -211,6 +210,7 @@ const icons = {
[IconName.PriceChart]: PriceChartIcon,
[IconName.Privacy]: PrivacyIcon,
[IconName.Qr]: QrIcon,
[IconName.RewardStar]: RewardStarIcon,
[IconName.Search]: SearchIcon,
[IconName.Send]: SendIcon,
[IconName.Share]: ShareIcon,
+1 -1
View File
@@ -1,11 +1,11 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { Ring } from '@/components/Ring';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = {
value: number;
+1 -1
View File
@@ -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: var(--color-white);
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
position: relative;
width: var(--switch-width);
+1 -2
View File
@@ -27,9 +27,8 @@ export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProp
const $ToastArea = styled.aside`
// Params
--toasts-gap: 0.5rem;
// Rules
${layoutMixins.scrollArea}
z-index: 1;
pointer-events: none;
+2 -2
View File
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = {
value: number;
};
+1 -6
View File
@@ -110,12 +110,7 @@ Styled.Abbr = styled.abbr`
Styled.Content = styled(Content)`
--tooltip-backgroundColor: var(--color-layer-4);
--tooltip-backgroundColor: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%),
0.66
);
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
${popoverMixins.popover}
--popover-backgroundColor: var(--tooltip-backgroundColor);
+1 -6
View File
@@ -27,12 +27,7 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
${popoverMixins.popover}
--popover-radius: 0.5rem;
--popover-background-color: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness)),
0.9
);
--popover-background-color: ${({ theme }) => theme.popoverBackground};
--popover-backdrop-filter: saturate(120%) blur(12px);
display: grid;
@@ -1,26 +1,33 @@
import React, { useContext, useState } from 'react';
import React, { PropsWithChildren, useContext, useState } from 'react';
import { Point } from '@visx/point';
import { localPoint } from '@visx/event';
import { XYChart, DataContext } from '@visx/xychart';
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
export const XYChartWithPointerEvents = ({
onPointerMove, onPointerUp, onPointerPressedChange, ...props
onPointerMove,
onPointerUp,
onPointerPressedChange,
...props
}: {
onPointerMove?: (point: Point) => void;
onPointerUp?: (point: Point) => void;
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
onPointerPressedChange?: (isPointerPressed: boolean) => void;
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
const { xScale, yScale } = useContext(DataContext);
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
const pointerChartPosition =
xScale &&
yScale &&
pointerContainerPosition &&
new Point({
// @ts-expect-error invert supposedly doesn't exist on AxisScale
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
// @ts-expect-error invert supposedly doesn't exist on AxisScale
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
});
@@ -29,15 +36,13 @@ export const XYChartWithPointerEvents = ({
{...props}
onPointerMove={({ event }) => {
setLastPointerMoveEvent(event as React.PointerEvent);
if (pointerChartPosition)
onPointerMove?.(pointerChartPosition);
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
}}
onPointerOut={() => setLastPointerMoveEvent(undefined)}
onPointerDown={() => onPointerPressedChange?.(true)}
onPointerUp={() => {
onPointerPressedChange?.(false);
if (pointerChartPosition)
onPointerUp?.(pointerChartPosition);
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
}}
>
{props.children}
+3 -3
View File
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number];
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -162,7 +162,7 @@ export type TradeState<T> = {
};
export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
+1
View File
@@ -96,6 +96,7 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
elapsedTime?: number;
blockHeight?: number;
indexerBlockHeight?: number;
trailingBlocks?: number;
}
: // Navigation
T extends AnalyticsEvent.NavigatePage
+40
View File
@@ -0,0 +1,40 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { FundingDirection } from './markets';
// ------ Depth Chart ------ //
export enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
export type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
export type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const SERIES_KEY_FOR_ORDER_SIDE = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
// ------ Funding Chart ------ //
export enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
export type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
+1
View File
@@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD';
export enum FundingDirection {
ToShort = 'ToShort',
ToLong = 'ToLong',
None = 'None',
}
+2
View File
@@ -87,6 +87,8 @@ export type NotificationDisplayData = {
slotTitleLeft?: React.ReactNode;
slotTitleRight?: React.ReactNode;
groupKey: string; // Grouping key toast notification stacking
// Overrides title/body for Notification in NotificationMenu
renderCustomBody?: ({
isToast,
+70
View File
@@ -0,0 +1,70 @@
export enum ColorToken {
Black = '#000000',
White = '#FFFFFF',
LightGray0 = '#FAFAFA',
LightGray1 = '#F7F8FA',
LightGray2 = '#FAFAFD',
LightGray3 = '#F5F5F5',
LightGray4 = '#EDF0F2',
LightGray5 = '#EDEDED',
LightGray6 = '#E9ECEE',
LightGray7 = '#E6E6E6',
LightGray8 = '#DBDBDB',
LightGray9 = '#D5DADF',
LightGray10 = '#D1D9E0',
MediumGray0 = '#CACACE',
MediumGray1 = '#C2CBD3',
DarkGray0 = '#888891',
DarkGray1 = '#737373',
DarkGray2 = '#56565C',
DarkGray3 = '#4B4B4B',
DarkGray4 = '#494950',
DarkGray5 = '#3B3B3F',
DarkGray6 = '#303033',
DarkGray7 = '#313135',
DarkGray8 = '#292929',
DarkGray9 = '#23232F',
DarkGray10 = '#212124',
DarkGray11 = '#18181B',
DarkGray12 = '#181818',
DarkGray13 = '#0C0C0D',
GrayBlue0 = '#464659',
GrayBlue1 = '#38384D',
GrayBlue2 = '#303045',
GrayBlue3 = '#29293D',
GrayBlue4 = '#212131',
GrayBlue5 = '#181825',
GrayBlue6 = '#101018',
GrayBlue7 = '#08080C',
GrayPurple0 = '#DFDADF',
GrayPurple1 = '#C8C7D8',
GrayPurple2 = '#807E98',
Purple0 = '#7774FF',
Purple1 = '#6966FF',
Green0 = '#1AFFB9',
Green1 = '#3ED9A4',
Green2 = '#2CCC98',
Green3 = '#3EB68A',
Yellow0 = '#FFCC48',
Yellow1 = '#FFB647',
Red0 = '#FF5C5C',
Red1 = '#E76565',
Red2 = '#E45555',
}
/** Maps opacity to corresponding hex value */
export enum OpacityToken {
Opacity16 = '29',
Opacity20 = '33',
Opacity66 = 'A8',
Opacity90 = 'E6',
}
+77
View File
@@ -0,0 +1,77 @@
export type ThemeColors = LayerColors &
BorderColors &
TextColors &
GradientColors &
AccentColors &
StatusColors &
DirectionalColors &
RiskColors &
IconColors &
ComponentColors;
type LayerColors = {
layer0: string;
layer1: string;
layer2: string;
layer3: string;
layer4: string;
layer5: string;
layer6: string;
layer7: string;
};
type BorderColors = {
borderDefault: string;
borderDestructive: string;
borderButton: string;
};
type TextColors = {
textPrimary: string;
textSecondary: string;
textTertiary: string;
};
type GradientColors = {
gradientBase0: string;
gradientBase1: string;
};
type AccentColors = {
accent: string;
accentFaded: string;
favorite: string;
};
type StatusColors = {
success: string;
warning: string;
error: string;
};
type DirectionalColors = {
positive: string;
negative: string;
positiveFaded: string;
negativeFaded: string;
};
type RiskColors = {
riskLow: string;
riskMedium: string;
riskHigh: string;
};
type IconColors = {
logoFill: string;
profileYellow: string;
profileRed: string;
};
type ComponentColors = {
inputBackground: string;
popoverBackground: string;
switchThumbActiveBackground: string;
toggleBackground: string;
tooltipBackground: string;
};
+1 -2
View File
@@ -4,7 +4,6 @@ import { useSelector } from 'react-redux';
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors';
import { getWidgetOverrides } from '@/lib/tradingView/utils';
@@ -29,7 +28,7 @@ export const useTradingViewTheme = ({
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
isWidgetReady?: boolean;
}) => {
const appTheme = useSelector(getAppTheme);
const appTheme: AppTheme = useSelector(getAppTheme);
useEffect(() => {
if (tvWidget && isWidgetReady) {
+2 -1
View File
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
}, []);
// AnalyticsEvent.NetworkStatus
const { height, indexerHeight, status } = useApiState();
const { height, indexerHeight, status, trailingBlocks} = useApiState();
useEffect(() => {
if (status) {
@@ -114,6 +114,7 @@ export const useAnalytics = () => {
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
blockHeight: height ?? undefined,
indexerBlockHeight: indexerHeight ?? undefined,
trailingBlocks: trailingBlocks ?? undefined
});
}
}, [status]);
+1 -1
View File
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
export const useApiState = () => {
const stringGetter = useStringGetter();
const apiState = useSelector(getApiState, shallowEqual);
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
const indexerHeight = getIndexerHeight(apiState);
+16
View File
@@ -0,0 +1,16 @@
import { useSelector } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors';
import { Themes } from '@/styles/themes';
export const AppThemeProvider = ({ ...props }) => {
return <ThemeProvider theme={useAppThemeContext()} {...props} />
};
export const useAppThemeContext = () => {
const theme: AppTheme = useSelector(getAppTheme);
return Themes[theme];
}
+28
View File
@@ -28,6 +28,7 @@ import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon';
import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
@@ -80,6 +81,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<TradeNotification
isToast={isToast}
@@ -93,6 +95,29 @@ export const notificationTypes: NotificationTypeConfig[] = [
);
break;
}
case 'blockReward': {
trigger(
abacusNotif.id,
{
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: stringGetter({ key: abacusNotif.title }),
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<BlockRewardNotification
isToast={isToast}
data={parsedData}
notification={notification}
/>
),
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
true
);
break;
}
default:
trigger(
abacusNotif.id,
@@ -102,6 +127,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
);
@@ -197,6 +223,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
/>
),
toastSensitivity: 'foreground',
groupKey: NotificationType.SquidTransfer,
},
[isFinished]
);
@@ -242,6 +269,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
},
}),
toastSensitivity: 'foreground',
groupKey: NotificationType.ReleaseUpdates,
},
[]
);
+44
View File
@@ -0,0 +1,44 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { MustBigNumber } from '@/lib/numbers';
export const useOrderbookValuesForDepthChart = () => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
return useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
orderbook,
};
}, [orderbook]);
};
-3
View File
@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.95513 1.00169C7.89506 1.00645 7.83628 1.02054 7.78124 1.04337L1.31971 3.71042C1.2243 3.74983 1.14329 3.81414 1.08653 3.89553C1.02977 3.97691 0.999705 4.07186 1 4.16883V11.8366C1.00078 11.9327 1.03135 12.0265 1.08805 12.1069C1.14474 12.1873 1.22517 12.2508 1.31971 12.2898L7.78124 14.9568C7.85011 14.9853 7.92463 15 8 15C8.07536 15 8.14989 14.9853 8.21876 14.9568L14.6803 12.2898C14.7748 12.2508 14.8552 12.1873 14.9119 12.1069C14.9686 12.0265 14.9992 11.9327 15 11.8366V4.16883C15.0003 4.07186 14.9702 3.97691 14.9134 3.89553C14.8567 3.81414 14.7756 3.74983 14.6803 3.71042L8.21876 1.04337C8.13605 1.00911 8.04538 0.994778 7.95513 1.00169ZM8 2.04351L13.1378 4.16883L8 6.28894L2.86219 4.16883L8 2.04351ZM2.07693 4.93457L7.46153 7.15886V13.7328L2.07693 11.5084V4.93457ZM13.923 4.93457V11.5084L8.53846 13.7328V7.15886L13.923 4.93457Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 969 B

+1 -1
View File
@@ -19,7 +19,6 @@ 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 CubeIcon } from './cube.svg';
export { default as DepositIcon } from './deposit.svg';
export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg';
@@ -51,6 +50,7 @@ export { default as PriceChartIcon } from './price-chart.svg';
export { default as PrivacyIcon } from './privacy.svg';
export { default as ProfileIcon } from './profile.svg';
export { default as QrIcon } from './qr.svg';
export { default as RewardStarIcon } from './reward-star.svg';
export { default as SearchIcon } from './search.svg';
export { default as SendIcon } from './send.svg';
export { default as ShareIcon } from './share.svg';
+50 -43
View File
@@ -1,45 +1,52 @@
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => (
<svg
width="135"
height="145"
viewBox="0 0 135 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill="white" />
<path
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
/>
<path
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
/>
<defs>
<linearGradient
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
x1="27.1293"
y1="9.0625"
x2="69.773"
y2="60.4324"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.55" />
</linearGradient>
<linearGradient
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
x1="111.1"
y1="133.996"
x2="58.6959"
y2="63.4999"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6966FF" />
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
</linearGradient>
</defs>
</svg>
);
import { useAppThemeContext } from '@/hooks/useAppTheme';
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => {
const theme = useAppThemeContext();
const fill = theme.logoFill;
return (
<svg
width="135"
height="145"
viewBox="0 0 135 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill={fill} />
<path
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
/>
<path
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
/>
<defs>
<linearGradient
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
x1="27.1293"
y1="9.0625"
x2="69.773"
y2="60.4324"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill} />
<stop offset="1" stopColor={fill} stopOpacity="0.55" />
</linearGradient>
<linearGradient
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
x1="111.1"
y1="133.996"
x2="58.6959"
y2="63.4999"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6966FF" />
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
</linearGradient>
</defs>
</svg>
);
};
export default LogoShortIcon;
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 481 KiB

-1
View File
@@ -1,6 +1,5 @@
@import 'styles/fonts.css';
@import 'styles/text.css';
@import 'styles/colors.css';
@import 'styles/animations.css';
:root {
@@ -1,7 +1,11 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import styled, { css } from 'styled-components';
import { NotificationStatus } from '@/constants/notifications';
import {
type Notification,
type NotificationDisplayData,
NotificationStatus,
} from '@/constants/notifications';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { useNotifications } from '@/hooks/useNotifications';
import { useBreakpoints } from '@/hooks/useBreakpoints';
@@ -13,54 +17,31 @@ import { Toast } from '@/components/Toast';
import { ToastArea } from '@/components/ToastArea';
import { ToggleButton } from '@/components/ToggleButton';
type ElementProps = {
notifications: {
notification: Notification<any, any>;
key: string;
displayData: NotificationDisplayData;
}[];
};
type StyleProps = {
className?: string;
};
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
export const NotificationStack = ({ notifications, className }: ElementProps & StyleProps) => {
const [shouldStackNotifications, setshouldStackNotifications] = useState(true);
const {
notifications,
getKey,
getDisplayData,
markUnseen,
markSeen,
isMenuOpen,
onNotificationAction,
} = useNotifications();
const { markUnseen, markSeen, onNotificationAction } = useNotifications();
const { isMobile } = useBreakpoints();
const notificationMap = useMemo(() => {
return (
Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS)
);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
const hasMultipleToasts = notificationMap.length > 1;
const hasMultipleToasts = notifications.length > 1;
return (
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
<StyledToastArea
swipeDirection={isMobile ? 'up' : 'right'}
className={className}
size={notifications.length}
>
{hasMultipleToasts && (
<StyledToggleButton
shape={ButtonShape.Pill}
@@ -72,12 +53,12 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
</StyledToggleButton>
)}
{notificationMap.map(({ notification, key, displayData }, idx) => (
{notifications.map(({ notification, key, displayData }, idx) => (
<StyledToast
key={key}
isStacked={shouldStackNotifications}
isStacked={idx > 0 && shouldStackNotifications}
isOpen={notification.status < NotificationStatus.Unseen}
layer={notificationMap.length - 1 - idx}
layer={idx}
notification={notification}
slotIcon={displayData.icon}
slotTitle={displayData.title}
@@ -110,19 +91,13 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
);
};
const StyledToastArea = styled(ToastArea)`
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
const StyledToastArea = styled(ToastArea)<{ size: number }>`
position: relative;
${({ size }) =>
size &&
css`
padding-bottom: calc(${size - 1} * 2px);
`}
`;
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
@@ -133,7 +108,7 @@ const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
position: absolute;
left: 0;
top: calc(${layer} * 2px);
right: calc(${layer} * -2px);
right: 0;
`
: css`
margin-bottom: 0.75rem;
@@ -145,7 +120,7 @@ const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
pointer-events: auto;
display: none;
position: absolute;
top: 4px;
top: -0.5rem;
left: calc(50% - 0.75rem);
--button-width: 2rem;
--button-height: 1rem;
@@ -0,0 +1,71 @@
import { useMemo } from 'react';
import { groupBy } from 'lodash';
import styled from 'styled-components';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { NotificationStatus } from '@/constants/notifications';
import { useNotifications } from '@/hooks/useNotifications';
import { NotificationStack } from './NotifcationStack';
type StyleProps = {
className?: string;
};
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const { notifications, getKey, getDisplayData, isMenuOpen } = useNotifications();
const notificationMapByType = useMemo(() => {
const notificationMap = Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS);
return groupBy(notificationMap, (notification) => notification.displayData?.groupKey);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
return (
<StyledToastArea className={className}>
{Object.keys(notificationMapByType).map((groupKey) => (
<NotificationStack notifications={notificationMapByType[groupKey]} key={groupKey} />
))}
</StyledToastArea>
);
};
const StyledToastArea = styled.div`
${layoutMixins.scrollArea}
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
pointer-events: none;
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
+4
View File
@@ -56,6 +56,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.store = undefined;
}
get isNobleClientConnected(): boolean {
return this.nobleClient?.isConnected ?? false;
}
setStore(store: RootStore): void {
this.store = store;
}
+20
View File
@@ -0,0 +1,20 @@
import { css } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
({
low: css`
color: var(--color-risk-low);
`,
medium: css`
color: var(--color-risk-medium);
`,
high: css`
color: var(--color-risk-high);
`,
}[riskLevel.name]);
export const generateFadedColorVariant = (colorHex: string, opacityHex: string) => {
return `${colorHex}${opacityHex}`;
};
+4 -4
View File
@@ -11,16 +11,16 @@ class TestFlags {
const params = new URLSearchParams(queryParamsString);
for (const [key, value] of params) {
this.queryParams[key] = value;
this.queryParams[key.toLowerCase()] = value;
}
}
get displayInitializingMarkets() {
return !!this.queryParams.displayInitializingMarkets;
return !!this.queryParams.displayinitializingmarkets;
}
get withCCTP() {
return !!this.queryParams.withCCTP;
get showMobileSignInOption() {
return !!this.queryParams.mobilesignin;
}
}
+21 -23
View File
@@ -1,11 +1,9 @@
import Color from 'color';
import isEmpty from 'lodash/isEmpty';
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
import { Colors } from '@/styles/colors';
import { AppTheme } from '@/state/configs';
import { Themes } from '@/styles/themes';
export const mapCandle = ({
startedAt,
open,
@@ -49,42 +47,42 @@ export const getHistorySlice = ({
return bars.filter(({ time }) => time >= fromMs);
};
export const getWidgetOverrides = (theme: AppTheme) => {
const colors = Colors[theme];
export const getWidgetOverrides = (appTheme: AppTheme) => {
const theme = Themes[appTheme];
return {
overrides: {
'paneProperties.background': Color(colors.layer2).hex(),
'paneProperties.horzGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.vertGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.background': theme.layer2,
'paneProperties.horzGridProperties.color': theme.layer3,
'paneProperties.vertGridProperties.color': theme.layer3,
'paneProperties.crossHairProperties.style': 1,
'paneProperties.legendProperties.showBarChange': false,
'paneProperties.backgroundType': 'solid',
'mainSeriesProperties.style': 1,
'mainSeriesProperties.candleStyle.upColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.borderUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.wickUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.downColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.borderDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.wickDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.upColor': theme.positive,
'mainSeriesProperties.candleStyle.borderUpColor': theme.positive,
'mainSeriesProperties.candleStyle.wickUpColor': theme.positive,
'mainSeriesProperties.candleStyle.downColor': theme.negative,
'mainSeriesProperties.candleStyle.borderDownColor': theme.negative,
'mainSeriesProperties.candleStyle.wickDownColor': theme.negative,
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
'scalesProperties.textColor': Color(colors.text1).hex(),
'scalesProperties.backgroundColor': Color(colors.layer2).hex(),
'scalesProperties.lineColor': Color(colors.layer3).hex(),
'scalesProperties.textColor': theme.textPrimary,
'scalesProperties.backgroundColor': theme.layer2,
'scalesProperties.lineColor': theme.layer3,
},
studies_overrides: {
'volume.volume.color.0': Color(colors.negative).hex(),
'volume.volume.color.1': Color(colors.positive).hex(),
'volume.volume.color.0': theme.negative,
'volume.volume.color.1': theme.positive,
'volume.volume ma.visible': false,
'relative strength index.plot.color': Color(colors.accent).hex(),
'relative strength index.plot.color': theme.accent,
'relative strength index.plot.linewidth': 1.5,
'relative strength index.hlines background.color': '#134A9F',
},
loading_screen: {
backgroundColor: Color(colors.layer2).hex(),
foregroundColor: Color(colors.layer2).hex(),
backgroundColor: theme.layer2,
foregroundColor: theme.layer2,
},
};
};
+2 -2
View File
@@ -259,8 +259,8 @@ Styled.ProfileIcon = styled.div`
border-radius: 50%;
background: linear-gradient(
135deg,
var(--theme-classic-color-yellow) 0%,
var(--theme-classic-color-red) 100%
${({ theme }) => theme.profileYellow} 0%,
${({ theme }) => theme.profileRed} 100%
);
`;
+25 -4
View File
@@ -17,11 +17,14 @@ import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
import { Button } from '@/components/Button';
import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { Tag, TagType } from '@/components/Tag';
import { WithSidebar } from '@/components/WithSidebar';
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { getOnboardingState, getSubaccount, getTradeInfoNumbers } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { shortenNumberForDisplay } from '@/lib/numbers';
import { PortfolioNavMobile } from './PortfolioNavMobile';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
@@ -42,6 +45,10 @@ export default () => {
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const { numTotalPositions, numTotalOpenOrders } = useSelector(getTradeInfoNumbers, shallowEqual) || {};
const numPositions = shortenNumberForDisplay(numTotalPositions);
const numOrders = shortenNumberForDisplay(numTotalOpenOrders);
const usdcBalance = freeCollateral?.current || 0;
useDocumentTitle(stringGetter({ key: STRING_KEYS.PORTFOLIO }));
@@ -118,14 +125,28 @@ export default () => {
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
label: (
<>
{stringGetter({ key: STRING_KEYS.POSITIONS })}
{numPositions > 0 && (
<Tag type={TagType.Number}> {numPositions} </Tag>
)}
</>
),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
label: stringGetter({ key: STRING_KEYS.ORDERS }),
label: (
<>
{stringGetter({ key: STRING_KEYS.ORDERS })}
{numOrders > 0 && (
<Tag type={TagType.Number}> {numOrders} </Tag>
)}
</>
),
href: PortfolioRoute.Orders,
},
{
+1 -8
View File
@@ -1,10 +1,8 @@
import { createSelector } from 'reselect';
import { SubaccountPosition } from '@/constants/abacus';
import { OnboardingState, OnboardingSteps } from '@/constants/account';
import {
getExistingOpenPositions,
getOnboardingGuards,
getOnboardingState,
getSubaccountId,
@@ -60,13 +58,8 @@ export const calculateIsAccountViewOnly = createSelector(
);
/**
* @description calculate whether the subaccount has open positions
* @description calculate whether the subaccount has uncommitted positions
*/
export const calculateHasOpenPositions = createSelector(
[getExistingOpenPositions],
(openPositions?: SubaccountPosition[]) => (openPositions?.length || 0) > 0
);
export const calculateHasUncommittedOrders = createSelector(
[getUncommittedOrderClientIds],
(uncommittedOrderClientIds: number[]) => uncommittedOrderClientIds.length > 0
+11 -5
View File
@@ -2,6 +2,7 @@ import { OrderSide } from '@dydxprotocol/v4-client-js';
import { createSelector } from 'reselect';
import {
type AbacusOrderStatuses,
type SubaccountOrder,
type SubaccountFill,
type SubaccountFundingPayment,
@@ -43,7 +44,6 @@ export const getSubaccountBuyingPower = (state: RootState) => state.account.suba
export const getSubaccountEquity = (state: RootState) => state.account.subaccount?.equity;
export const getSubaccountHistoricalPnl = (state: RootState) => state.account?.historicalPnl;
/**
* @param state
* @returns list of a subaccount's open positions. Each item in the list is an open position in a different market.
@@ -258,6 +258,14 @@ export const getCurrentMarketFundingPayments = createSelector(
!currentMarketId ? [] : marketFundingPayments[currentMarketId]
);
/**
* @param state
* @returns boolean on whether an order status is considered open
*/
const isOpenOrderStatus = (status: AbacusOrderStatuses) => {
return status !== AbacusOrderStatus.filled && status !== AbacusOrderStatus.cancelled;
};
/**
* @param state
* @returns Total numbers of the subaccount's open positions, open orders and fills
@@ -266,8 +274,7 @@ export const getTradeInfoNumbers = createSelector(
[getExistingOpenPositions, getSubaccountOrders, getSubaccountFills, getSubaccountFundingPayments],
(positions, orders, fills, fundingPayments) => ({
numTotalPositions: positions?.length,
numTotalOpenOrders: orders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numTotalOpenOrders: orders?.filter((order) => isOpenOrderStatus(order.status)).length,
numTotalFills: fills?.length,
numTotalFundingPayments: fundingPayments?.length,
})
@@ -281,8 +288,7 @@ export const getCurrentMarketTradeInfoNumbers = createSelector(
[getCurrentMarketOrders, getCurrentMarketFills, getCurrentMarketFundingPayments],
(marketOrders, marketFills, marketFundingPayments) => {
return {
numOpenOrders: marketOrders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numOpenOrders: marketOrders?.filter((order) => isOpenOrderStatus(order.status)).length,
numFills: marketFills?.length,
numFundingPayments: marketFundingPayments?.length,
};
+6 -1
View File
@@ -26,7 +26,12 @@ export const calculateFundingRateHistory = createSelector(
return data.map(({ effectiveAtMilliseconds, rate }) => ({
time: effectiveAtMilliseconds,
fundingRate: rate,
direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
direction:
rate === 0
? FundingDirection.None
: rate < 0
? FundingDirection.ToLong
: FundingDirection.ToShort,
}));
}
);
-180
View File
@@ -1,180 +0,0 @@
:root {
/* Parameters */
/*
--layer-base-hue
--layer-base-saturation
--layer-base-lightness
--color-text-0
--color-text-1
--color-text-2
--color-accent
--color-positive
--color-negative
--color-success
--color-warning
--color-error
--color-favorite
*/
/* Computed: Layers */
--color-layer-0: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 12%)
);
--color-layer-1: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 8%)
);
--color-layer-2: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 4%)
);
--color-layer-3: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
var(--layer-base-lightness)
);
--color-layer-4: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%)
);
--color-layer-5: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 2%),
calc(var(--layer-base-lightness) + 7%)
);
--color-layer-6: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 4%),
calc(var(--layer-base-lightness) + 10%)
);
--color-layer-7: hsla(var(--layer-base-hue), 0%, 100%, 0.07);
--color-gradient-base-0: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 5%),
var(--layer-base-lightness)
);
--color-gradient-base-1: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 1%),
calc(var(--layer-base-lightness) + 7%)
);
/* Computed: Borders */
--color-border: var(--color-layer-5);
--color-border-white: hsla(0, 0%, 100%, 0.2);
--color-border-red: hsla(360, 73%, 61%, 0.2);
}
/* Theme: Classic (default) */
:root {
/* Constants */
--theme-classic-hue-purple: 240;
--theme-classic-color-green: hsl(159, 67%, 39%);
--theme-classic-color-yellow: hsl(36, 100%, 64%);
--theme-classic-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: var(--theme-classic-hue-purple);
--layer-base-saturation: 20%;
--layer-base-lightness: 16%;
--color-text-0: hsl(245, 11%, 55%);
--color-text-1: hsl(244, 18%, 81%);
--color-text-2: hsl(240, 43%, 99%);
--color-accent: hsl(var(--theme-classic-hue-purple), 100%, 70%);
--color-positive: var(--theme-classic-color-green);
--color-negative: var(--theme-classic-color-red);
--color-success: var(--theme-classic-color-green);
--color-warning: var(--theme-classic-color-yellow);
--color-error: var(--theme-classic-color-red);
--color-favorite: var(--theme-classic-color-yellow);
--color-white: #fff;
--color-black: #000;
--color-gradient-positive: hsla(158, 49%, 48%, 0.16);
--color-gradient-negative: hsla(0, 100%, 66%, 0.16);
--color-accent-faded: hsla(var(--theme-classic-hue-purple), 100%, 70%, 0.16);
--color-risk-low: var(--theme-classic-color-green);
--color-risk-medium: var(--theme-classic-color-yellow);
--color-risk-high: var(--theme-classic-color-red);
}
/* Theme: Light */
:root.theme-light {
/* Constants */
--theme-light-hue-purple: 234;
--theme-light-color-green: hsl(159, 67%, 39%);
--theme-light-color-yellow: hsl(36, 100%, 64%);
--theme-light-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: var(--theme-light-hue-purple);
--layer-base-saturation: 0%;
--layer-base-lightness: 86%;
--color-text-0: hsl(0, 0%, 55%);
--color-text-1: hsl(0, 0%, 40%);
--color-text-2: hsl(0, 0%, 5%);
--color-accent: hsl(var(--theme-light-hue-purple), 100%, 70%);
--color-positive: var(--theme-light-color-green);
--color-negative: var(--theme-light-color-red);
--color-success: var(--theme-light-color-green);
--color-warning: var(--theme-light-color-yellow);
--color-error: var(--theme-light-color-red);
--color-favorite: var(--theme-light-color-yellow);
}
/* Theme: Dark */
:root.theme-dark {
/* Constants */
--theme-dark-hue-purple: 240;
--theme-dark-color-green: hsl(159, 67%, 39%);
--theme-dark-color-yellow: hsl(36, 100%, 64%);
--theme-dark-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: 0;
--layer-base-saturation: 0%;
--layer-base-lightness: 16%;
--color-text-0: hsl(0, 0%, 47%);
--color-text-1: hsl(0, 0%, 67%);
--color-text-2: hsl(0, 0%, 100%);
--color-accent: hsl(var(--theme-dark-hue-purple), 100%, 70%);
--color-positive: var(--theme-dark-color-green);
--color-negative: var(--theme-dark-color-red);
--color-success: var(--theme-dark-color-green);
--color-warning: var(--theme-dark-color-yellow);
--color-error: var(--theme-dark-color-red);
--color-favorite: var(--theme-dark-color-yellow);
}
-125
View File
@@ -1,125 +0,0 @@
import { css } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
import { AppTheme } from '@/state/configs';
const BaseElements = {
[AppTheme.Classic]: {
hue: 240,
saturation: 20,
lightness: 16,
},
[AppTheme.Dark]: {
hue: 0,
saturation: 0,
lightness: 16,
},
[AppTheme.Light]: {
hue: 234,
saturation: 0,
lightness: 86,
},
};
const LayerColors = ({ theme }: { theme: AppTheme }) => {
const { hue, saturation, lightness } = BaseElements[theme];
return {
layer0: `hsl(${hue}, ${saturation}%, ${lightness - 12}%)`,
layer1: `hsl(${hue}, ${saturation}%, ${lightness - 8}%)`,
layer2: `hsl(${hue}, ${saturation}%, ${lightness - 4}%)`,
layer3: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
layer4: `hsl(${hue}, ${saturation}%, ${lightness + 4}%)`,
layer5: `hsl(${hue}, ${saturation - 2}%, ${lightness + 7}%)`,
layer6: `hsl(${hue}, ${saturation - 4}%, ${lightness + 10}%)`,
};
};
const AccentColor = ({ theme }: { theme: AppTheme }) => {
const purpleHue = {
Classic: 240,
Dark: 240,
Light: 234,
}[theme];
return {
accent: `hsl(${purpleHue}, 100%, 70%)`,
};
};
export const Colors = {
[AppTheme.Classic]: {
...LayerColors({ theme: AppTheme.Classic }),
text0: 'hsl(245, 11%, 55%)',
text1: 'hsl(244, 18%, 81%)',
text2: 'hsl(240, 43%, 99%)',
...AccentColor({ theme: AppTheme.Classic }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
[AppTheme.Dark]: {
...LayerColors({ theme: AppTheme.Dark }),
text0: 'hsl(0, 0%, 47%)',
text1: 'hsl(0, 0%, 67%)',
text2: 'hsl(0, 0%, 100%)',
...AccentColor({ theme: AppTheme.Dark }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
[AppTheme.Light]: {
...LayerColors({ theme: AppTheme.Light }),
text0: 'hsl(0, 0%, 55%)',
text1: 'hsl(0, 0%, 40%)',
text2: 'hsl(0, 0%, 5%)',
...AccentColor({ theme: AppTheme.Light }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
};
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
({
low: css`
color: var(--color-risk-low);
`,
medium: css`
color: var(--color-risk-medium);
`,
high: css`
color: var(--color-risk-high);
`,
}[riskLevel.name]);
+1 -1
View File
@@ -21,7 +21,7 @@ export const formMixins: Record<
--input-radius: 0.5em;
--input-height: var(--form-input-height);
--input-width: 100%;
--input-backgroundColor: var(--color-layer-4);
--input-backgroundColor: ${({ theme }) => theme.inputBackground};
--input-borderColor: var(--color-layer-6);
${layoutMixins.row}
+42
View File
@@ -0,0 +1,42 @@
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
:root {
--color-layer-0: ${({ theme }) => theme.layer0};
--color-layer-1: ${({ theme }) => theme.layer1};
--color-layer-2: ${({ theme }) => theme.layer2};
--color-layer-3: ${({ theme }) => theme.layer3};
--color-layer-4: ${({ theme }) => theme.layer4};
--color-layer-5: ${({ theme }) => theme.layer5};
--color-layer-6: ${({ theme }) => theme.layer6};
--color-layer-7: ${({ theme }) => theme.layer7};
--color-border: ${({ theme }) => theme.borderDefault};
--color-border-white: ${({ theme }) => theme.borderButton};
--color-border-red: ${({ theme }) => theme.borderDestructive};
--color-text-0: ${({ theme }) => theme.textTertiary};
--color-text-1: ${({ theme }) => theme.textSecondary};
--color-text-2: ${({ theme }) => theme.textPrimary};
--color-gradient-base-0: ${({ theme }) => theme.gradientBase0};
--color-gradient-base-1: ${({ theme }) => theme.gradientBase1};
--color-accent: ${({ theme }) => theme.accent};
--color-accent-faded: ${({ theme }) => theme.accentFaded};
--color-favorite: ${({ theme }) => theme.favorite};
--color-success: ${({ theme }) => theme.success};
--color-warning: ${({ theme }) => theme.warning};
--color-error: ${({ theme }) => theme.error};
--color-positive: ${({ theme }) => theme.positive};
--color-negative: ${({ theme }) => theme.negative};
--color-gradient-positive: ${({ theme }) => theme.positiveFaded};
--color-gradient-negative: ${({ theme }) => theme.negativeFaded};
--color-risk-low: ${({ theme }) => theme.riskLow};
--color-risk-medium: ${({ theme }) => theme.riskMedium};
--color-risk-high: ${({ theme }) => theme.riskHigh};
}
`;
+157
View File
@@ -0,0 +1,157 @@
import { AppTheme } from '@/state/configs';
import { ThemeColors } from '@/constants/styles/colors';
import { ColorToken, OpacityToken } from '@/constants/styles/base';
import { generateFadedColorVariant } from '@/lib/styles';
const ClassicTheme: ThemeColors = {
layer0: ColorToken.GrayBlue7,
layer1: ColorToken.GrayBlue6,
layer2: ColorToken.GrayBlue5,
layer3: ColorToken.GrayBlue4,
layer4: ColorToken.GrayBlue3,
layer5: ColorToken.GrayBlue2,
layer6: ColorToken.GrayBlue1,
layer7: ColorToken.GrayBlue0,
borderDefault: ColorToken.GrayBlue2,
borderDestructive: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
textPrimary: ColorToken.LightGray2,
textSecondary: ColorToken.GrayPurple1,
textTertiary: ColorToken.GrayPurple2,
gradientBase0: ColorToken.DarkGray9,
gradientBase1: ColorToken.GrayBlue2,
accent: ColorToken.Purple1,
accentFaded: generateFadedColorVariant(ColorToken.Purple1, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green1,
warning: ColorToken.Yellow0,
error: ColorToken.Red2,
positive: ColorToken.Green1,
negative: ColorToken.Red2,
positiveFaded: generateFadedColorVariant(ColorToken.Green1, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity16),
riskLow: ColorToken.Green1,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red2,
logoFill: ColorToken.White,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.GrayBlue3,
popoverBackground: generateFadedColorVariant(ColorToken.GrayBlue4, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.GrayBlue3,
tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66),
};
const DarkTheme: ThemeColors = {
layer0: ColorToken.Black,
layer1: ColorToken.DarkGray11,
layer2: ColorToken.DarkGray13,
layer3: ColorToken.DarkGray10,
layer4: ColorToken.DarkGray6,
layer5: ColorToken.DarkGray5,
layer6: ColorToken.DarkGray4,
layer7: ColorToken.DarkGray2,
borderDefault: ColorToken.DarkGray4,
borderDestructive: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
textPrimary: ColorToken.LightGray0,
textSecondary: ColorToken.MediumGray0,
textTertiary: ColorToken.DarkGray0,
gradientBase0: ColorToken.DarkGray8,
gradientBase1: ColorToken.DarkGray5,
accent: ColorToken.Purple0,
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green0,
warning: ColorToken.Yellow0,
error: ColorToken.Red0,
positive: ColorToken.Green0,
negative: ColorToken.Red0,
positiveFaded: generateFadedColorVariant(ColorToken.Green0, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity16),
riskLow: ColorToken.Green0,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red0,
logoFill: ColorToken.White,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.DarkGray6,
popoverBackground: generateFadedColorVariant(ColorToken.DarkGray8, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.DarkGray6,
tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66),
};
const LightTheme: ThemeColors = {
layer0: ColorToken.White,
layer1: ColorToken.LightGray6,
layer2: ColorToken.White,
layer3: ColorToken.LightGray1,
layer4: ColorToken.White,
layer5: ColorToken.LightGray4,
layer6: ColorToken.LightGray9,
layer7: ColorToken.MediumGray1,
borderDefault: ColorToken.LightGray10,
borderDestructive: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.Black, OpacityToken.Opacity20),
textPrimary: ColorToken.DarkGray12,
textSecondary: ColorToken.DarkGray3,
textTertiary: ColorToken.DarkGray1,
gradientBase0: ColorToken.LightGray8,
gradientBase1: ColorToken.LightGray5,
accent: ColorToken.Purple0,
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green2,
warning: ColorToken.Yellow0,
error: ColorToken.Red1,
positive: ColorToken.Green2,
negative: ColorToken.Red1,
positiveFaded: generateFadedColorVariant(ColorToken.Green2, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity16),
riskLow: ColorToken.Green2,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red1,
logoFill: ColorToken.Black,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.White,
popoverBackground: generateFadedColorVariant(ColorToken.LightGray8, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.LightGray4,
tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66),
};
export const Themes = {
[AppTheme.Classic]: ClassicTheme,
[AppTheme.Dark]: DarkTheme,
[AppTheme.Light]: LightTheme,
};
-1
View File
@@ -29,7 +29,6 @@ export const MarketDetails: React.FC = () => {
if (!configs) return null;
const {
basePositionNotional,
tickSize,
stepSize,
initialMarginFraction,
+220
View File
@@ -0,0 +1,220 @@
import { useMemo } from 'react';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { shallowEqual, useSelector } from 'react-redux';
import type { Nullable } from '@/constants/abacus';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { MustBigNumber } from '@/lib/numbers';
type DepthChartTooltipProps = {
chartPointAtPointer: DepthChartPoint;
isEditingOrder?: boolean;
stepSizeDecimals: Nullable<number>;
tickSizeDecimals: Nullable<number>;
} & Pick<RenderTooltipParams<DepthChartDatum>, 'colorScale' | 'tooltipData'>;
export const DepthChartTooltipContent = ({
chartPointAtPointer,
colorScale,
isEditingOrder,
stepSizeDecimals,
tickSizeDecimals,
tooltipData,
}: DepthChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const { spread, spreadPercent, midMarketPrice } = useOrderbookValuesForDepthChart();
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const priceImpact = useMemo(() => {
if (nearestDatum) {
const depthChartSeries = nearestDatum.key as DepthChartSeries;
return {
[DepthChartSeries.Bids]: MustBigNumber(nearestDatum?.datum.price)
.minus(chartPointAtPointer.price)
.div(nearestDatum?.datum.price),
[DepthChartSeries.Asks]: MustBigNumber(chartPointAtPointer.price)
.minus(nearestDatum?.datum.price)
.div(chartPointAtPointer.price),
[DepthChartSeries.MidMarket]: undefined,
}[depthChartSeries];
}
return undefined;
}, [nearestDatum, chartPointAtPointer.price]);
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={
nearestDatum?.key &&
colorScale?.(
isEditingOrder ? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] : nearestDatum.key
)
}
>
<h4>
{isEditingOrder
? stringGetter({ key: STRING_KEYS.RELEASE_TO_EDIT })
: nearestDatum &&
{
[DepthChartSeries.Bids]: stringGetter({ key: STRING_KEYS.BIDS }),
[DepthChartSeries.Asks]: stringGetter({ key: STRING_KEYS.ASKS }),
[DepthChartSeries.MidMarket]: stringGetter({ key: STRING_KEYS.MID_MARKET }),
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output type={OutputType.Fiat} value={midMarketPrice} useGrouping={false} />
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={
nearestDatum
? nearestDatum?.datum.price * nearestDatum?.datum.depth
: undefined
}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: <Output useGrouping type={OutputType.Percent} value={priceImpact} />,
},
]
}
/>
</TooltipContent>
);
};
@@ -1,14 +1,20 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css, keyframes } from 'styled-components';
import { useSelector, shallowEqual } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { StringGetterFunction } from '@/constants/localization';
import { useBreakpoints } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { MustBigNumber } from '@/lib/numbers';
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
@@ -21,24 +27,25 @@ import {
darkTheme,
DataProvider,
EventEmitterProvider,
type EventHandlerParams,
} from '@visx/xychart';
import { LinearGradient } from '@visx/gradient';
import { curveStepAfter } from '@visx/curve';
import type { Point } from '@visx/point';
import { Point } from '@visx/point';
import Tooltip from '@/components/visx/XYChartTooltipWithBounds';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { Details } from '@/components/Details';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { Output, OutputType } from '@/components/Output';
import { OutputType } from '@/components/Output';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { MustBigNumber } from '@/lib/numbers';
import { DepthChartTooltipContent } from './Tooltip';
// @ts-ignore
const theme = buildChartTheme({
...darkTheme,
colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s
});
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
@@ -58,30 +65,6 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n
: formattedNumber;
};
enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
const seriesKeyForOrderSide = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const DepthChart = ({
onChartClick,
stringGetter,
@@ -96,8 +79,6 @@ export const DepthChart = ({
const { isMobile } = useBreakpoints();
// Chart data
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
@@ -112,43 +93,15 @@ export const DepthChart = ({
midMarketPrice,
spread,
spreadPercent,
} = useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
};
}, [orderbook]);
orderbook,
} = useOrderbookValuesForDepthChart();
// Chart state
const [isPointerPressed, setIsPointerPressed] = useState(false);
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
const isEditingOrder = isPointerPressed && chartPointAtPointer;
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer);
const [zoomDomain, setZoomDomain] = useState<undefined | number>();
@@ -193,12 +146,24 @@ export const DepthChart = ({
}, [orderbook, zoomDomain]);
const getChartPoint = useCallback(
({ x: price, y: size }: Point) =>
({
side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
(point: Point | EventHandlerParams<object>) => {
let price, size;
if (point instanceof Point) {
const { x, y } = point as Point;
price = x;
size = y;
} else {
const { svgPoint: { x, y } = {} } = point as EventHandlerParams<object>;
price = x;
size = y;
}
return {
side: MustBigNumber(price).lt(midMarketPrice!) ? OrderSide.BUY : OrderSide.SELL,
price,
size,
} as DepthChartPoint),
} as DepthChartPoint;
},
[midMarketPrice]
);
@@ -256,7 +221,7 @@ export const DepthChart = ({
}}
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
onPointerPressedChange={setIsPointerPressed}
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)}
>
<Axis
orientation="bottom"
@@ -363,7 +328,7 @@ export const DepthChart = ({
<Styled.XAxisLabelOutput
type={OutputType.Fiat}
value={
isEditingOrder
isEditingOrder && chartPointAtPointer
? chartPointAtPointer.price
: tooltipData!.nearestDatum?.datum.price
}
@@ -374,8 +339,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -389,7 +354,7 @@ export const DepthChart = ({
<Styled.YAxisLabelOutput
type={OutputType.Asset}
value={
isEditingOrder
isEditingOrder && chartPointAtPointer
? chartPointAtPointer.size
: tooltipData!.nearestDatum?.datum.depth
}
@@ -400,8 +365,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -410,181 +375,22 @@ export const DepthChart = ({
}
snapTooltipToDatumX={!isEditingOrder}
snapTooltipToDatumY={isEditingOrder ? false : isMobile}
renderTooltip={({ tooltipData, colorScale }) => {
const { nearestDatum } = tooltipData || {};
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={colorScale?.(
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: nearestDatum.key
)}
>
<h4>
{isEditingOrder
? 'Release mouse to edit order'
: {
[DepthChartSeries.Bids]: 'Bids',
[DepthChartSeries.Asks]: 'Asks',
[DepthChartSeries.MidMarket]: 'Mid-Market',
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
// label: stringGetter({ key: STRING_KEYS.ORDERBOOK_MID_MARKET_PRICE }),
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={midMarketPrice}
useGrouping={false}
/>
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={nearestDatum?.datum.price * nearestDatum?.datum.depth}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: (
<Output
useGrouping
type={OutputType.Percent}
value={{
[DepthChartSeries.Asks]: () =>
MustBigNumber(nearestDatum.datum.price)
.minus(lowestAsk.price)
.div(nearestDatum.datum.price),
[DepthChartSeries.Bids]: () =>
MustBigNumber(highestBid.price)
.minus(nearestDatum.datum.price)
.div(highestBid.price),
}[nearestDatum.key]()}
/>
),
},
]
}
/>
</TooltipContent>
);
}}
renderTooltip={({ tooltipData, colorScale }) =>
chartPointAtPointer && (
<DepthChartTooltipContent
{...{
tooltipData,
colorScale,
isEditingOrder,
chartPointAtPointer,
stringGetter,
selectedLocale,
stepSizeDecimals,
tickSizeDecimals,
}}
/>
)
}
/>
</XYChartWithPointerEvents>
</EventEmitterProvider>
+107
View File
@@ -0,0 +1,107 @@
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { useStringGetter } from '@/hooks';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
type FundingChartTooltipProps = {
fundingRateView: FundingRateResolution;
latestDatum: FundingChartDatum;
} & Pick<RenderTooltipParams<FundingChartDatum>, 'tooltipData'>;
export const FundingChartTooltipContent = ({
fundingRateView,
latestDatum,
tooltipData,
}: FundingChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
[FundingDirection.None]: undefined,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate',
label: stringGetter({
key: {
[FundingRateResolution.OneHour]: STRING_KEYS.RATE_1H,
[FundingRateResolution.EightHour]: STRING_KEYS.RATE_8H,
[FundingRateResolution.Annualized]: STRING_KEYS.ANNUALIZED,
}[fundingRateView],
}),
value: (
<Output
type={OutputType.SmallPercent}
value={
{
[FundingRateResolution.OneHour]: tooltipDatum.fundingRate,
[FundingRateResolution.EightHour]: tooltipDatum.fundingRate * 8,
[FundingRateResolution.Annualized]: tooltipDatum.fundingRate * (24 * 365),
}[fundingRateView]
}
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
};
@@ -3,39 +3,27 @@ import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { curveMonotoneX, curveStepAfter } from '@visx/curve';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { ButtonSize } from '@/constants/buttons';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { FundingDirection } from '@/constants/markets';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { Output, OutputType } from '@/components/Output';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { ToggleGroup } from '@/components/ToggleGroup';
import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { TooltipContent } from '@/components/visx/TooltipContent';
import type { TooltipContextType } from '@visx/xychart';
import { calculateFundingRateHistory } from '@/state/perpetualsCalculators';
import { MustBigNumber } from '@/lib/numbers';
enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
import { FundingChartTooltipContent } from './Tooltip';
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
@@ -116,114 +104,20 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
/>
);
}}
renderTooltip={({ tooltipData }) => {
const { nearestDatum } = tooltipData || {};
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate1h',
label: stringGetter({ key: STRING_KEYS.RATE_1H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate}
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRate8h',
label: stringGetter({ key: STRING_KEYS.RATE_8H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * 8}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** 8 - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRateAnnualized',
label: stringGetter({ key: STRING_KEYS.ANNUALIZED }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * (24 * 365)}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** (24 * 365) - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
}}
onTooltipContext={setTooltipContext}
renderTooltip={({ tooltipData }) => (
<FundingChartTooltipContent
fundingRateView={fundingRateView}
tooltipData={tooltipData}
latestDatum={latestDatum}
/>
)}
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)}
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
numGridLines={1}
slotEmpty={<LoadingSpace id="funding-chart-loading" />}
@@ -290,6 +184,7 @@ Styled.FundingRateToggle = styled.div`
Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>`
place-self: start center;
padding: clamp(1.5rem, 9rem - 15%, 4rem);
pointer-events: none;
font: var(--font-large-book);
@@ -235,6 +235,10 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
throw new Error('Missing request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
await validateTokenApproval();
@@ -108,20 +108,6 @@ export const DepositButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const { toAmount, toAmountMin } = useMemo(() => {
if (isCctp) {
return {
toAmount: summary?.toAmount,
toAmountMin: summary?.toAmountMin,
};
} else {
return {
toAmount: summary?.toAmount && formatUnits(BigInt(summary.toAmount), usdcDecimals),
toAmountMin: summary?.toAmountMin && formatUnits(BigInt(summary.toAmountMin), usdcDecimals),
};
}
}, [isCctp, summary]);
const submitButtonReceipt = [
{
key: 'expected-deposit-amount',
@@ -130,7 +116,7 @@ export const DepositButtonAndReceipt = ({
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span>
),
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
subitems: [
{
key: 'minimum-deposit-amount',
@@ -140,7 +126,7 @@ export const DepositButtonAndReceipt = ({
</span>
),
value: (
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
),
tooltip: 'minimum-deposit-amount',
},
@@ -148,6 +148,10 @@ export const WithdrawForm = () => {
throw new Error('Invalid request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
setError(undefined);
@@ -55,7 +55,7 @@ export const WithdrawButtonAndReceipt = ({
const stringGetter = useStringGetter();
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { usdcLabel } = useTokenConfigs();
@@ -85,26 +85,6 @@ export const WithdrawButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const { toAmount, toAmountMin } = useMemo(() => {
if (isCctp) {
return {
toAmount: summary?.toAmount,
toAmountMin: summary?.toAmountMin,
};
} else {
return {
toAmount:
summary?.toAmount &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmount), withdrawToken.decimals),
toAmountMin:
summary?.toAmountMin &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmountMin), withdrawToken.decimals),
};
}
}, [isCctp, summary, withdrawToken]);
const submitButtonReceipt = [
{
key: 'total-fees',
@@ -153,7 +133,7 @@ export const WithdrawButtonAndReceipt = ({
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: <Output type={OutputType.Asset} value={toAmount} fractionDigits={TOKEN_DECIMALS} />,
value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
subitems: [
{
key: 'minimum-amount-received',
@@ -164,7 +144,7 @@ export const WithdrawButtonAndReceipt = ({
</span>
),
value: (
<Output type={OutputType.Asset} value={toAmountMin} fractionDigits={TOKEN_DECIMALS} />
<Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
),
tooltip: 'minimum-amount-received',
},
@@ -44,6 +44,7 @@ export const TradeSideToggle = memo(() => {
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
--toggle-radius: 0.5em;
--toggle-color: var(--color-negative);
--toggle-background: ${({ theme }) => theme.toggleBackground};
${({ value }) =>
value === OrderSide.BUY &&
@@ -52,7 +53,7 @@ const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
`}
border-radius: var(--toggle-radius);
background-color: var(--color-layer-4);
background-color: var(--toggle-background);
position: relative;
> button {
+5 -4
View File
@@ -10,6 +10,9 @@ import { STRING_KEYS, StringGetterFunction, TOOLTIP_STRING_KEYS } from '@/consta
import { isMainnet } from '@/constants/networks';
import { DydxChainAsset, wallets } from '@/constants/wallets';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import {
useAccounts,
useBreakpoints,
@@ -35,10 +38,8 @@ import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import { MustBigNumber } from '@/lib/numbers';
import { testFlags } from '@/lib/testFlags';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@@ -178,7 +179,7 @@ export const AccountMenu = () => {
},
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
!isMainnet && {
(!isMainnet || testFlags.showMobileSignInOption) && {
value: 'MobileQrSignIn',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.TITLE_SIGN_INTO_MOBILE }),
@@ -0,0 +1,78 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { Details } from '@/components/Details';
import { Notification, NotificationProps } from '@/components/Notification';
import { Output, OutputType } from '@/components/Output';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
data: {
BLOCK_REWARD_AMOUNT: string;
BLOCK_REWARD_HEIGHT: string;
BLOCK_REWARD_TIME_MILLISECONDS: string;
TOKEN_NAME: string;
};
};
export type BlockRewardNotificationProps = NotificationProps & ElementProps;
export const BlockRewardNotification = ({
isToast,
data,
notification,
}: BlockRewardNotificationProps) => {
const stringGetter = useStringGetter();
const { BLOCK_REWARD_AMOUNT, TOKEN_NAME } = data;
return (
<Styled.Notification
isToast={isToast}
notification={notification}
slotIcon={<Icon iconName={IconName.RewardStar} />}
slotTitle={stringGetter({ key: STRING_KEYS.TRADING_REWARD_RECEIVED })}
slotCustomContent={
<Styled.Details
items={[
{
key: 'block_reward',
label: stringGetter({ key: STRING_KEYS.BLOCK_REWARD }),
value: (
<Styled.Output
type={OutputType.Asset}
value={BLOCK_REWARD_AMOUNT}
tag={TOKEN_NAME}
/>
),
},
]}
/>
}
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Details = styled(Details)`
--details-item-height: 1.5rem;
dd {
color: var(--color-text-2);
}
`;
Styled.Notification = styled(Notification)`
background-image: url('/dots-background-2.svg');
background-size: cover;
`;
Styled.Output = styled(Output)`
&:before {
content: '+';
color: var(--color-positive);
margin-right: 0.5ch;
}
`;