Compare commits

..
Author SHA1 Message Date
Bill He 849bda8e8e privy theme 2024-01-18 21:22:44 -08:00
Bill He 57edb711d5 PWA 2024-01-18 19:55:43 -08:00
Bill He 8235c64c93 Noble deposit 2024-01-18 19:10:16 -08:00
Bill He 750fb077f5 remove debugs 2024-01-18 12:36:10 -08:00
Bill He d781cb3199 conditions 2024-01-18 12:28:57 -08:00
Bill He f6bf635b0d debug 2024-01-18 12:21:47 -08:00
Bill He 56187f7a84 ignore ready 2024-01-18 12:18:44 -08:00
Bill He 93c76f1c3f Privy integration [WIP] 2024-01-18 11:58:21 -08:00
77 changed files with 3659 additions and 2282 deletions
+9 -18
View File
@@ -5,35 +5,29 @@ import styled from 'styled-components';
import { store } from '@/state/_store'; import { store } from '@/state/_store';
import { GlobalStyle } from '@/styles/globalStyle';
import { SelectMenu, SelectItem } from '@/components/SelectMenu'; import { SelectMenu, SelectItem } from '@/components/SelectMenu';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { AppTheme, setAppTheme } from '@/state/configs';
import { setLocaleLoaded } from '@/state/localization'; import { setLocaleLoaded } from '@/state/localization';
import '@/index.css'; import '@/index.css';
import './ladle.css'; import './ladle.css';
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState(AppTheme.Classic); const [theme, setTheme] = useState('Default theme');
useEffect(() => { useEffect(() => {
store.dispatch(setAppTheme(theme));
switch (theme) { switch (theme) {
case AppTheme.Dark: { case 'Dark theme': {
document?.documentElement?.classList.remove('theme-light'); document?.documentElement?.classList.remove('theme-light');
document?.documentElement?.classList.add('theme-dark'); document?.documentElement?.classList.add('theme-dark');
break; break;
} }
case AppTheme.Light: { case 'Light theme': {
document?.documentElement?.classList.remove('theme-dark'); document?.documentElement?.classList.remove('theme-dark');
document?.documentElement?.classList.add('theme-light'); document?.documentElement?.classList.add('theme-light');
break; break;
} }
case AppTheme.Classic: { default: {
document?.documentElement?.classList.remove('theme-dark', 'theme-light'); document?.documentElement?.classList.remove('theme-dark', 'theme-light');
break; break;
} }
@@ -54,15 +48,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
> >
{[ {[
{ {
value: AppTheme.Classic, value: 'Default theme',
label: 'Default theme', label: 'Default theme',
}, },
{ {
value: AppTheme.Dark, value: 'Dark theme',
label: 'Dark theme', label: 'Dark theme',
}, },
{ {
value: AppTheme.Light, value: 'Light theme',
label: 'Light theme', label: 'Light theme',
}, },
].map(({ value, label }) => ( ].map(({ value, label }) => (
@@ -75,11 +69,8 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
</SelectMenu> </SelectMenu>
</StoryHeader> </StoryHeader>
<hr /> <hr />
<AppThemeProvider> <StoryContent>{children}</StoryContent>
<GlobalStyle /> </Provider>
<StoryContent>{children}</StoryContent>
</AppThemeProvider>
</Provider>
); );
}; };
+1
View File
@@ -0,0 +1 @@
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
+10 -7
View File
@@ -33,17 +33,19 @@
"packageManager": "pnpm@8.6.6", "packageManager": "pnpm@8.6.6",
"dependencies": { "dependencies": {
"@0xsquid/sdk": "^1.10.0", "@0xsquid/sdk": "^1.10.0",
"@cosmjs/amino": "^0.32.1", "@cosmjs/amino": "^0.31.0",
"@cosmjs/crypto": "^0.32.1", "@cosmjs/crypto": "^0.31.0",
"@cosmjs/encoding": "^0.32.1", "@cosmjs/encoding": "^0.31.0",
"@cosmjs/proto-signing": "^0.32.1", "@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.32.1", "@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.32.1", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.2.5", "@dydxprotocol/v4-abacus": "^1.2.5",
"@dydxprotocol/v4-client-js": "^1.0.14", "@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.11", "@dydxprotocol/v4-localization": "^1.1.11",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@privy-io/react-auth": "^1.53.1",
"@privy-io/wagmi-connector": "^0.1.12",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
@@ -151,6 +153,7 @@
"url-polyfill": "^1.1.12", "url-polyfill": "^1.1.12",
"util": "^0.12.5", "util": "^0.12.5",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-pwa": "^0.17.4",
"vite-plugin-svgr": "^3.2.0", "vite-plugin-svgr": "^3.2.0",
"vitest": "^0.32.2", "vitest": "^0.32.2",
"w3name": "^1.0.8", "w3name": "^1.0.8",
+2753 -198
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -1,7 +1,7 @@
{ {
"apps": { "apps": {
"ios": { "ios": {
"scheme": "dydx-t-v4" "scheme": "dydxv4"
} }
}, },
"deployments": { "deployments": {
@@ -215,11 +215,10 @@
} }
], ],
"validators": [ "validators": [
"https://validator.v4dev4.dydx.exchange" "http://validator.v4dev4.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.0xsquid.com", "0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/", "nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"faucet": "https://faucet.v4dev4.dydx.exchange"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
@@ -474,7 +473,7 @@
"apps": { "apps": {
"ios": { "ios": {
"minimalVersion": "1.0", "minimalVersion": "1.0",
"build": 40000, "build":40000,
"url": "https://apps.apple.com/app/dydx/id1564787350" "url": "https://apps.apple.com/app/dydx/id1564787350"
} }
} }
+42 -41
View File
@@ -2,6 +2,8 @@ import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components'; import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi'; import { WagmiConfig } from 'wagmi';
import { PrivyProvider } from '@privy-io/react-auth';
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
import { GrazProvider } from 'graz'; import { GrazProvider } from 'graz';
@@ -16,7 +18,6 @@ import {
} from '@/hooks'; } from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient'; import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts'; import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea'; import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications'; import { NotificationsProvider } from '@/hooks/useNotifications';
@@ -33,10 +34,9 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
import { DialogManager } from '@/layout/DialogManager'; import { DialogManager } from '@/layout/DialogManager';
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog'; import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { config } from '@/lib/wagmi'; import { config, privyConfig } from '@/lib/wagmi';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { GlobalStyle } from '@/styles/globalStyle';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner'; import { LoadingSpace } from './components/Loading/LoadingSpinner';
@@ -68,52 +68,49 @@ const Content = () => {
const { chainTokenLabel } = useTokenConfigs(); const { chainTokenLabel } = useTokenConfigs();
return ( return (
<> <Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
<GlobalStyle /> {isNotTablet && <HeaderDesktop />}
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Main> <Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}> <Suspense fallback={<LoadingSpace id="main" />}>
<Routes> <Routes>
<Route path={AppRoute.Trade}> <Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} /> <Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} /> <Route path={AppRoute.Trade} element={<TradePage />} />
</Route> </Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} /> <Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} /> <Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && ( {isTablet && (
<> <>
<Route path={AppRoute.Alerts} element={<AlertsPage />} /> <Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} /> <Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} /> <Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</> </>
)} )}
<Route element={<GuardedMobileRoute />}> <Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} /> <Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route> </Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} /> <Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} /> <Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} /> <Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes> </Routes>
</Suspense> </Suspense>
</Styled.Main> </Styled.Main>
{isTablet ? <FooterMobile /> : <FooterDesktop />} {isTablet ? <FooterMobile /> : <FooterDesktop />}
<Styled.NotificationsToastArea /> <Styled.NotificationsToastArea />
<Styled.DialogArea ref={setDialogArea}> <Styled.DialogArea ref={setDialogArea}>
<DialogManager /> <DialogManager />
</Styled.DialogArea> </Styled.DialogArea>
<GlobalCommandDialog /> <GlobalCommandDialog />
</Styled.Content> </Styled.Content>
</>
); );
}; };
@@ -125,8 +122,13 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
}; };
const providers = [ const providers = [
wrapProvider(PrivyProvider, {
appId: import.meta.env.VITE_PRIVY_APP_ID,
config: privyConfig,
}),
wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider), wrapProvider(GrazProvider),
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: config }),
wrapProvider(WagmiConfig, { config }), wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider), wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider), wrapProvider(RestrictionProvider),
@@ -136,7 +138,6 @@ const providers = [
wrapProvider(LocalNotificationsProvider), wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider), wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider), wrapProvider(DialogAreaProvider),
wrapProvider(AppThemeProvider),
]; ];
const App = () => { const App = () => {
-13
View File
@@ -1,13 +0,0 @@
import { forwardRef } from 'react';
type CanvasProps = {
className?: string;
width: number | string;
height: number | string;
};
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
({ className, width, height }, canvasRef) => (
<canvas ref={canvasRef} className={className} width={width} height={height} />
)
);
+1
View File
@@ -11,5 +11,6 @@ export const DetachedScrollableSection = styled.section`
export const AttachedExpandingSection = styled.section` export const AttachedExpandingSection = styled.section`
${layoutMixins.contentSectionAttached} ${layoutMixins.contentSectionAttached}
${layoutMixins.expandingColumnWithHeader}
gap: var(--border-width); gap: var(--border-width);
`; `;
+1 -1
View File
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
DiffArrowStory.argTypes = { DiffArrowStory.argTypes = {
direction: { direction: {
options: ['left', 'right', 'up', 'down'], options: ['left', 'right'],
control: { type: 'select' }, control: { type: 'select' },
defaultValue: 'right', defaultValue: 'right',
}, },
+1 -7
View File
@@ -10,7 +10,7 @@ type ElementProps = {
type StyleProps = { type StyleProps = {
className?: string; className?: string;
direction?: 'right' | 'left' | 'up' | 'down'; direction?: 'right' | 'left';
}; };
export type DiffArrowProps = ElementProps & StyleProps; export type DiffArrowProps = ElementProps & StyleProps;
@@ -57,11 +57,5 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
left: css` left: css`
transform: scaleX(-1); transform: scaleX(-1);
`, `,
up: css`
transform: rotate(-90deg);
`,
down: css`
transform: rotate(90deg);
`
}[direction || 'right'])} }[direction || 'right'])}
`; `;
-6
View File
@@ -44,7 +44,6 @@ import {
MenuIcon, MenuIcon,
MigrateIcon, MigrateIcon,
MintscanIcon, MintscanIcon,
MoonIcon,
OrderbookIcon, OrderbookIcon,
OrderCanceledIcon, OrderCanceledIcon,
OrderFilledIcon, OrderFilledIcon,
@@ -67,7 +66,6 @@ import {
ShowIcon, ShowIcon,
TogglesMenuIcon, TogglesMenuIcon,
StarIcon, StarIcon,
SunIcon,
TerminalIcon, TerminalIcon,
TradeIcon, TradeIcon,
TransferIcon, TransferIcon,
@@ -121,7 +119,6 @@ export enum IconName {
Menu = 'Menu', Menu = 'Menu',
Migrate = 'Migrate', Migrate = 'Migrate',
Mintscan = 'Mintscan', Mintscan = 'Mintscan',
Moon = 'Moon',
Onboarding = 'Onboarding', Onboarding = 'Onboarding',
Orderbook = 'OrderbookIcon', Orderbook = 'OrderbookIcon',
OrderCanceled = 'OrderCanceled', OrderCanceled = 'OrderCanceled',
@@ -144,7 +141,6 @@ export enum IconName {
Share = 'Share', Share = 'Share',
Show = 'Show', Show = 'Show',
Star = 'Star', Star = 'Star',
Sun = 'Sun',
Terminal = 'Terminal', Terminal = 'Terminal',
TogglesMenu = 'TogglesMenu', TogglesMenu = 'TogglesMenu',
Trade = 'Trade', Trade = 'Trade',
@@ -199,7 +195,6 @@ const icons = {
[IconName.Menu]: MenuIcon, [IconName.Menu]: MenuIcon,
[IconName.Migrate]: MigrateIcon, [IconName.Migrate]: MigrateIcon,
[IconName.Mintscan]: MintscanIcon, [IconName.Mintscan]: MintscanIcon,
[IconName.Moon]: MoonIcon,
[IconName.Orderbook]: OrderbookIcon, [IconName.Orderbook]: OrderbookIcon,
[IconName.OrderCanceled]: OrderCanceledIcon, [IconName.OrderCanceled]: OrderCanceledIcon,
[IconName.OrderFilled]: OrderFilledIcon, [IconName.OrderFilled]: OrderFilledIcon,
@@ -221,7 +216,6 @@ const icons = {
[IconName.Share]: ShareIcon, [IconName.Share]: ShareIcon,
[IconName.Show]: ShowIcon, [IconName.Show]: ShowIcon,
[IconName.Star]: StarIcon, [IconName.Star]: StarIcon,
[IconName.Sun]: SunIcon,
[IconName.Terminal]: TerminalIcon, [IconName.Terminal]: TerminalIcon,
[IconName.TogglesMenu]: TogglesMenuIcon, [IconName.TogglesMenu]: TogglesMenuIcon,
[IconName.Trade]: TradeIcon, [IconName.Trade]: TradeIcon,
+1 -1
View File
@@ -1,11 +1,11 @@
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { RiskLevels } from '@/constants/abacus'; import { RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { Ring } from '@/components/Ring'; import { Ring } from '@/components/Ring';
import { abacusHelper } from '@/lib/abacus'; import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = { type ElementProps = {
value: number; value: number;
+1 -1
View File
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
type PanelProps = { type PanelProps = {
slotHeaderContent?: React.ReactNode; slotHeaderContent?: string;
slotHeader?: React.ReactNode; slotHeader?: React.ReactNode;
slotRight?: React.ReactNode; slotRight?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
+1 -1
View File
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
--switch-thumb-backgroundColor: var(--color-layer-6); --switch-thumb-backgroundColor: var(--color-layer-6);
--switch-active-backgroundColor: var(--color-accent); --switch-active-backgroundColor: var(--color-accent);
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground}; --switch-active-thumb-backgroundColor: var(--color-white);
position: relative; position: relative;
width: var(--switch-width); width: var(--switch-width);
+9 -70
View File
@@ -33,20 +33,17 @@ import {
import { useAsyncList } from 'react-stately'; import { useAsyncList } from 'react-stately';
import { useBreakpoints, useStringGetter } from '@/hooks'; import { useBreakpoints } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints'; import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { Checkbox } from '@/components/Checkbox';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { CaretIcon } from '@/icons';
import { STRING_KEYS } from '@/constants/localization';
import { MustBigNumber } from '@/lib/numbers';
import { Icon, IconName } from './Icon'; import { Icon, IconName } from './Icon';
import { Tag } from './Tag'; import { Tag } from './Tag';
import { Button } from './Button'; import { MustBigNumber } from '@/lib/numbers';
export { TableCell } from './Table/TableCell'; export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader'; export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -68,7 +65,7 @@ export type TableItem<TableRowData> = {
onSelect?: (key: TableRowData) => void; onSelect?: (key: TableRowData) => void;
}; };
type ColumnDef<TableRowData extends object> = { export type ColumnDef<TableRowData extends object> = {
columnKey: string; columnKey: string;
label: React.ReactNode; label: React.ReactNode;
tag?: React.ReactNode; tag?: React.ReactNode;
@@ -83,7 +80,7 @@ type ColumnDef<TableRowData extends object> = {
width?: ColumnSize; width?: ColumnSize;
}; };
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = { type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
label?: string; label?: string;
columns: ColumnDef<TableRowData>[]; columns: ColumnDef<TableRowData>[];
data: TableRowData[]; data: TableRowData[];
@@ -95,7 +92,6 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
selectionBehavior?: 'replace' | 'toggle'; selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void; onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode; slotEmpty?: React.ReactNode;
initialNumRowsToShow?: number;
// collection: TableCollection<string>; // collection: TableCollection<string>;
// children: React.ReactNode; // children: React.ReactNode;
}; };
@@ -125,7 +121,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
selectionMode = 'single', selectionMode = 'single',
selectionBehavior = 'toggle', selectionBehavior = 'toggle',
slotEmpty, slotEmpty,
initialNumRowsToShow = data.length,
// shouldRowRender, // shouldRowRender,
// collection, // collection,
@@ -141,7 +136,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
style, style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => { }: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>()); const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
const currentBreakpoints = useBreakpoints(); const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter( const shownColumns = columns.filter(
@@ -215,12 +209,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
onRowAction && onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!)) ((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
} }
numColumns={shownColumns.length}
onViewMoreClick={
numRowsToShow !== undefined && numRowsToShow < data.length
? () => setNumRowsToShow(data.length)
: undefined
}
// shouldRowRender={shouldRowRender} // shouldRowRender={shouldRowRender}
hideHeader={hideHeader} hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows} withGradientCardRows={withGradientCardRows}
@@ -245,7 +233,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
)} )}
</TableHeader> </TableHeader>
<TableBody items={list.items.slice(0, numRowsToShow)}> <TableBody items={list.items}>
{(item) => ( {(item) => (
<Row key={getRowKey(item)}> <Row key={getRowKey(item)}>
{(columnKey) => ( {(columnKey) => (
@@ -279,8 +267,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
onRowAction?: (key: TableRowKey) => void; onRowAction?: (key: TableRowKey) => void;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean; // shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
children: CollectionChildren<TableRowData>; children: CollectionChildren<TableRowData>;
numColumns: number;
onViewMoreClick?: () => void;
hideHeader?: boolean; hideHeader?: boolean;
withGradientCardRows?: boolean; withGradientCardRows?: boolean;
@@ -290,7 +276,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
withScrollSnapColumns?: boolean; withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean; withScrollSnapRows?: boolean;
}) => { }) => {
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props; const { selectionMode, selectionBehavior } = props;
const state = useTableState<TableRowData>({ const state = useTableState<TableRowData>({
...props, ...props,
@@ -351,7 +337,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
<TableBodyRowGroup <TableBodyRowGroup
withGradientCardRows={props.withGradientCardRows} withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders} withInnerBorders={props.withInnerBorders}
withOuterBorder={props.withOuterBorder}
> >
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */} {/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
{[...collection.body.childNodes].map((row) => {[...collection.body.childNodes].map((row) =>
@@ -397,9 +382,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
</TableRow> </TableRow>
) )
)} )}
{onViewMoreClick ? (
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
) : undefined}
</TableBodyRowGroup> </TableBodyRowGroup>
</Styled.Table> </Styled.Table>
); );
@@ -433,7 +415,6 @@ const TableBodyRowGroup = ({
children, children,
withGradientCardRows, withGradientCardRows,
withInnerBorders, withInnerBorders,
withOuterBorder,
}: { children: React.ReactNode } & StyleProps) => { }: { children: React.ReactNode } & StyleProps) => {
const { rowGroupProps } = useTableRowGroup(); const { rowGroupProps } = useTableRowGroup();
@@ -442,7 +423,6 @@ const TableBodyRowGroup = ({
{...rowGroupProps} {...rowGroupProps}
withGradientCardRows={withGradientCardRows} withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders} withInnerBorders={withInnerBorders}
withOuterBorder={withOuterBorder}
> >
{children} {children}
</Styled.Tbody> </Styled.Tbody>
@@ -509,23 +489,6 @@ const TableColumnHeader = <TableRowData extends object>({
); );
}; };
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.Tr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
onPointerDown={(e: MouseEvent) => e.preventDefault()}
>
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.Tr>
);
};
export const TableRow = <TableRowData extends object>({ export const TableRow = <TableRowData extends object>({
item, item,
children, children,
@@ -697,7 +660,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
justify-items: center; justify-items: center;
align-content: center; align-content: center;
padding: 4rem; padding: 2rem;
gap: 0.75em; gap: 0.75em;
color: var(--color-text-0); color: var(--color-text-0);
@@ -905,18 +868,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingLeft: var(--border-width); --stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width); --stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
}
`}
${({ withOuterBorder }) =>
withOuterBorder &&
css`
tr:last-of-type:not(:only-of-type) {
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
}
tr:first-of-type { tr:first-of-type {
box-shadow: none; box-shadow: none;
} }
@@ -971,15 +922,3 @@ Styled.Row = styled.div`
${layoutMixins.inlineRow} ${layoutMixins.inlineRow}
padding: var(--tableCell-padding); padding: var(--tableCell-padding);
`; `;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
--button-textColor: var(--color-text-1);
width: 100%;
svg {
width: 0.675rem;
margin-left: 0.5ch;
}
`;
+2 -2
View File
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus'; import { type RiskLevels } from '@/constants/abacus';
import { abacusHelper } from '@/lib/abacus'; import { UsageColorFromRiskLevel } from '@/styles/colors';
import { UsageColorFromRiskLevel } from '@/lib/styles';
import { abacusHelper } from '@/lib/abacus';
type ElementProps = { type ElementProps = {
value: number; value: number;
}; };
+6 -1
View File
@@ -110,7 +110,12 @@ Styled.Abbr = styled.abbr`
Styled.Content = styled(Content)` Styled.Content = styled(Content)`
--tooltip-backgroundColor: var(--color-layer-4); --tooltip-backgroundColor: var(--color-layer-4);
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground}; --tooltip-backgroundColor: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%),
0.66
);
${popoverMixins.popover} ${popoverMixins.popover}
--popover-backgroundColor: var(--tooltip-backgroundColor); --popover-backgroundColor: var(--tooltip-backgroundColor);
+6 -1
View File
@@ -27,7 +27,12 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
${popoverMixins.popover} ${popoverMixins.popover}
--popover-radius: 0.5rem; --popover-radius: 0.5rem;
--popover-background-color: ${({ theme }) => theme.popoverBackground}; --popover-background-color: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness)),
0.9
);
--popover-backdrop-filter: saturate(120%) blur(12px); --popover-backdrop-filter: saturate(120%) blur(12px);
display: grid; display: grid;
+1 -16
View File
@@ -282,22 +282,13 @@ export const TRADE_TYPES: Record<
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET, [AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
[AbacusOrderType.liquidated.name]: null, [AbacusOrderType.liquidated.name]: null,
[AbacusOrderType.liquidated.rawValue]: null, [AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.name]: null, [AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.rawValue]: null, [AbacusOrderType.liquidation.rawValue]: null,
[AbacusOrderType.trailingStop.name]: null, [AbacusOrderType.trailingStop.name]: null,
[AbacusOrderType.trailingStop.rawValue]: null, [AbacusOrderType.trailingStop.rawValue]: null,
[AbacusOrderType.offsetting.name]: null,
[AbacusOrderType.offsetting.rawValue]: null,
[AbacusOrderType.deleveraged.name]: null,
[AbacusOrderType.deleveraged.rawValue]: null,
[AbacusOrderType.finalSettlement.name]: null,
[AbacusOrderType.finalSettlement.rawValue]: null,
}; };
// Custom types involving Abacus // Custom types involving Abacus
@@ -316,9 +307,3 @@ export type NetworkConfig = Partial<{
}>; }>;
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>; export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
side?: 'ask' | 'bid';
mine: number;
key: string;
};
-15
View File
@@ -1,15 +0,0 @@
/**
* @description Orderbook display constants
*/
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
export const ORDERBOOK_ANIMATION_DURATION = 400;
/**
* @description Orderbook pixel constants
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
*/
export const ORDERBOOK_HEIGHT = 800;
export const ORDERBOOK_WIDTH = 300;
export const ORDERBOOK_ROW_HEIGHT = 24;
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
-70
View File
@@ -1,70 +0,0 @@
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
@@ -1,77 +0,0 @@
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;
};
+11
View File
@@ -31,6 +31,7 @@ import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
export enum WalletConnectionType { export enum WalletConnectionType {
CoinbaseWalletSdk = 'coinbaseWalletSdk', CoinbaseWalletSdk = 'coinbaseWalletSdk',
CosmosSigner = 'CosmosSigner', CosmosSigner = 'CosmosSigner',
Privy = 'Privy',
InjectedEip1193 = 'injectedEip1193', InjectedEip1193 = 'injectedEip1193',
WalletConnect2 = 'walletConnect2', WalletConnect2 = 'walletConnect2',
} }
@@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
[WalletConnectionType.CosmosSigner]: { [WalletConnectionType.CosmosSigner]: {
name: 'CosmosSigner', name: 'CosmosSigner',
}, },
[WalletConnectionType.Privy]: {
name: 'Privy',
},
}; };
// Wallets // Wallets
@@ -90,6 +94,7 @@ export enum WalletType {
WalletConnect2 = 'WALLETCONNECT_2', WalletConnect2 = 'WALLETCONNECT_2',
// TestWallet = 'TEST_WALLET', // TestWallet = 'TEST_WALLET',
OtherWallet = 'OTHER_WALLET', OtherWallet = 'OTHER_WALLET',
Privy = 'PRIVY',
} }
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = { const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
@@ -243,6 +248,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
icon: KeplrIcon, icon: KeplrIcon,
connectionTypes: [WalletConnectionType.CosmosSigner], connectionTypes: [WalletConnectionType.CosmosSigner],
}, },
[WalletType.Privy]: {
type: WalletType.Privy,
stringKey: STRING_KEYS.KEPLR,
icon: GenericWalletIcon,
connectionTypes: [WalletConnectionType.Privy],
},
}; };
// Injected EIP-1193 Providers // Injected EIP-1193 Providers
-4
View File
@@ -1,4 +0,0 @@
export * from './useCenterOrderbook';
export * from './useSpreadRowScrollListener';
export * from './useDrawOrderbook';
export * from './useOrderbookValues';
-22
View File
@@ -1,22 +0,0 @@
import { useEffect, type RefObject } from 'react';
type Props = {
marketId: string;
orderbookRef: RefObject<HTMLDivElement>;
};
/**
* @description Center Orderbook on load and market change
* Assumed that the two sides are the same height
*/
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
const orderbookEl = orderbookRef.current;
const { clientHeight, scrollHeight } = orderbookEl ?? {};
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
useEffect(() => {
if (orderbookEl && shouldScroll) {
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
}
}, [shouldScroll, marketId]);
};
-373
View File
@@ -1,373 +0,0 @@
import { useEffect, useMemo, useRef } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
import {
ORDERBOOK_ANIMATION_DURATION,
ORDERBOOK_HEIGHT,
ORDERBOOK_ROW_HEIGHT,
ORDERBOOK_ROW_PADDING_RIGHT,
ORDERBOOK_WIDTH,
} from '@/constants/orderbook';
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
import { getAppTheme } from '@/state/configsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import {
getHistogramXValues,
getRektFromIdx,
getXByColumn,
getYForElements,
} from '@/lib/orderbookHelpers';
import { useAppThemeContext } from '../useAppTheme';
type ElementProps = {
data: Array<PerpetualMarketOrderbookLevel | undefined>;
histogramRange: number;
side: PerpetualMarketOrderbookLevel['side'];
};
type StyleProps = {
histogramSide: 'left' | 'right';
};
enum OrderbookRowAnimationType {
REMOVE,
NEW,
NONE,
}
export const useDrawOrderbook = ({
data,
histogramRange,
histogramSide,
side,
}: ElementProps & StyleProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
useSelector(getCurrentMarketConfig, shallowEqual) || {};
const prevData = useRef<typeof data>(data);
const theme = useAppThemeContext();
/**
* Scale canvas using device pixel ratio to unblur drawn text
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
**/
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
const ratio = window.devicePixelRatio || 1;
if (!canvas) {
return {
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
};
}
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
if (ctx) {
ctx.scale(ratio, ratio);
ctx.font = '12px Satoshi';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.imageSmoothingQuality = 'high';
}
return {
canvasWidth: canvas.width / ratio,
canvasHeight: canvas.height / ratio,
rowHeight: ORDERBOOK_ROW_HEIGHT,
};
}, [canvas]);
const drawBars = ({
barType,
ctx,
depthOrSizeValue,
gradientMultiplier,
histogramAccentColor,
histogramSide,
idx,
}: {
barType: 'depth' | 'size';
ctx: CanvasRenderingContext2D;
depthOrSizeValue: number;
gradientMultiplier: number;
histogramAccentColor: string;
histogramSide: 'left' | 'right';
idx: number;
}) => {
const { x1, x2, y1, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
// X values
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
const barWidth = depthOrSizeValue
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
: 0;
const { gradient, bar } = getHistogramXValues({
barWidth,
canvasWidth,
gradientMultiplier,
histogramSide,
});
// Gradient
let linearGradient;
try {
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
linearGradient.addColorStop(0, histogramAccentColor);
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = linearGradient;
} catch (err) {
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
}
ctx.beginPath();
// Bar
const { bar: y } = getYForElements({ y: y1, rowHeight });
if (ctx.roundRect) {
ctx.roundRect(
bar.x1,
y,
bar.x2,
rowHeight - 2,
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
);
} else {
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
}
ctx.fill();
};
const drawText = ({
animationType = OrderbookRowAnimationType.NONE,
ctx,
idx,
mine,
price,
size,
}: {
animationType?: OrderbookRowAnimationType;
ctx: CanvasRenderingContext2D;
idx: number;
mine?: number;
price?: number;
size?: number;
}) => {
const { y1 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
const { text: y } = getYForElements({ y: y1, rowHeight });
let textColor: string;
let updatedTextColor: string | undefined;
switch (animationType) {
case OrderbookRowAnimationType.REMOVE: {
textColor = theme.textSecondary;
break;
}
case OrderbookRowAnimationType.NEW: {
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
textColor = theme.textPrimary;
break;
}
default: {
textColor = theme.textPrimary;
break;
}
}
// Size text
if (size) {
ctx.fillStyle = updatedTextColor ?? textColor;
ctx.fillText(
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Price text
if (price) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Mine text
if (mine) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
};
const drawOrderbookRow = ({
ctx,
idx,
rowToRender,
animationType = OrderbookRowAnimationType.NONE,
}: {
ctx: CanvasRenderingContext2D;
idx: number;
rowToRender?: PerpetualMarketOrderbookLevel;
animationType?: OrderbookRowAnimationType;
}) => {
if (!rowToRender) return;
const { depth, mine, price, size } = rowToRender;
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
// Depth Bar
if (depth) {
drawBars({
barType: 'depth',
ctx,
depthOrSizeValue: depth,
gradientMultiplier: 1.3,
histogramAccentColor,
histogramSide,
idx,
});
}
// Size Bar
drawBars({
barType: 'size',
ctx,
depthOrSizeValue: size,
gradientMultiplier: 5,
histogramAccentColor,
histogramSide,
idx,
});
// Size, Price, Mine
drawText({
animationType,
ctx,
idx,
mine,
price,
size,
});
};
// Update histograms and row contents on data change
useEffect(() => {
const ctx = canvas?.getContext('2d');
if (!canvas || !ctx) return;
// Clear canvas before redraw
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Animate row removal and update
const mapOfOrderbookPriceLevels =
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
const empty: number[] = [];
const removed: number[] = [];
const updated: number[] = [];
prevData.current.forEach((row, idx) => {
if (!row) {
empty.push(idx);
return;
}
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
removed.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.REMOVE,
});
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
animationType: OrderbookRowAnimationType.NONE,
});
} else {
updated.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.NEW,
});
}
});
setTimeout(() => {
[...empty, ...removed, ...updated].forEach((idx) => {
const { x1, y1, x2, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
});
});
}, ORDERBOOK_ANIMATION_DURATION);
prevData.current = data;
}, [
canvasHeight,
canvasWidth,
rowHeight,
data,
histogramRange,
stepSizeDecimals,
tickSizeDecimals,
histogramSide,
side,
theme,
currentOrderbookMap,
]);
return { canvasRef };
};
-126
View File
@@ -1,126 +0,0 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
import { MustBigNumber } from '@/lib/numbers';
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const openOrdersBySideAndPrice =
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
return useMemo(() => {
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.asks?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `ask-${idx}`,
side: 'ask',
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.bids?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `bid-${idx}`,
side: 'bid',
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
// Prevent the bid/ask sides from crossing by using the offsets.
// While the books are crossing...
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
// Drop the order on the side with the lower offset.
// The offset of the other side is higher and so supercedes.
if (bids[0]!.offset === asks[0].offset) {
// If offsets are the same, give precedence to the larger size. In this case,
// one of the sizes *should* be zero, but we simply check for the larger size.
if (bids[0]!.size > asks[0].size) {
asks.shift();
} else {
bids.pop();
}
} else {
// Offsets are not equal. Give precedence to the larger offset.
if (bids[0]!.offset > asks[0].offset) {
asks.shift();
} else {
bids.pop();
}
}
}
const spread =
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
const spreadPercent = orderbook?.spreadPercent;
const histogramRange = Math.max(
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
);
return {
asks,
bids,
spread,
spreadPercent,
histogramRange,
hasOrderbook: !!orderbook,
};
}, [orderbook, openOrdersBySideAndPrice]);
};
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]);
};
@@ -1,39 +0,0 @@
import { type RefObject, useEffect, useState } from 'react';
export const useSpreadRowScrollListener = ({
orderbookRef,
spreadRowRef,
}: {
orderbookRef: RefObject<HTMLDivElement>;
spreadRowRef: RefObject<HTMLDivElement>;
}) => {
const [displaySide, setDisplaySide] = useState<string>();
useEffect(() => {
const onScroll = () => {
if (spreadRowRef.current && orderbookRef.current) {
const { clientHeight } = orderbookRef.current;
const parent = orderbookRef.current.getBoundingClientRect();
const spread = spreadRowRef.current.getBoundingClientRect();
const spreadTop = spread.top - parent.top;
const spreadBottom = spread.bottom - parent.top;
if (spreadBottom > clientHeight) {
setDisplaySide('bottom');
} else if (spreadTop < 0) {
setDisplaySide('top');
} else {
setDisplaySide(undefined);
}
}
};
orderbookRef.current?.addEventListener('scroll', onScroll, false);
return () => {
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
};
}, [orderbookRef.current, spreadRowRef.current]);
return displaySide;
};
+2 -1
View File
@@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library'; import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
import { AppTheme } from '@/state/configs'; import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors'; import { getAppTheme } from '@/state/configsSelectors';
import { getWidgetOverrides } from '@/lib/tradingView/utils'; import { getWidgetOverrides } from '@/lib/tradingView/utils';
@@ -28,7 +29,7 @@ export const useTradingViewTheme = ({
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null; tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
isWidgetReady?: boolean; isWidgetReady?: boolean;
}) => { }) => {
const appTheme: AppTheme = useSelector(getAppTheme); const appTheme = useSelector(getAppTheme);
useEffect(() => { useEffect(() => {
if (tvWidget && isWidgetReady) { if (tvWidget && isWidgetReady) {
+55 -4
View File
@@ -1,13 +1,19 @@
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react'; import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AES, enc } from 'crypto-js'; import { AES, enc } from 'crypto-js';
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js'; import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account'; import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage'; import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets'; import {
DydxAddress,
EvmAddress,
PrivateInformation,
WalletConnectionType,
getSignTypedData,
} from '@/constants/wallets';
import { setOnboardingState, setOnboardingGuard } from '@/state/account'; import { setOnboardingState, setOnboardingGuard } from '@/state/account';
import { forceOpenDialog } from '@/state/dialogs'; import { forceOpenDialog } from '@/state/dialogs';
@@ -20,6 +26,11 @@ import { useLocalStorage } from './useLocalStorage';
import { useRestrictions } from './useRestrictions'; import { useRestrictions } from './useRestrictions';
import { useWalletConnection } from './useWalletConnection'; import { useWalletConnection } from './useWalletConnection';
import { useSignTypedData } from 'wagmi';
import { getSelectedNetwork } from '@/state/appSelectors';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { usePrivy } from '@privy-io/react-auth';
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined); const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
AccountsContext.displayName = 'Accounts'; AccountsContext.displayName = 'Accounts';
@@ -153,6 +164,7 @@ const useAccountsContext = () => {
// dYdX wallet / onboarding state // dYdX wallet / onboarding state
const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>(); const [localDydxWallet, setLocalDydxWallet] = useState<LocalWallet>();
const [localNobleWallet, setLocalNobleWallet] = useState<LocalWallet>();
const [hdKey, setHdKey] = useState<PrivateInformation>(); const [hdKey, setHdKey] = useState<PrivateInformation>();
const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]); const dydxAccounts = useMemo(() => localDydxWallet?.accounts, [localDydxWallet]);
@@ -162,6 +174,11 @@ const useAccountsContext = () => {
[localDydxWallet] [localDydxWallet]
); );
const nobleAddress = useMemo(
() => localNobleWallet?.address,
[localNobleWallet]
);
const setWalletFromEvmSignature = async (signature: string) => { const setWalletFromEvmSignature = async (signature: string) => {
const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({ const { wallet, mnemonic, privateKey, publicKey } = await getWalletFromEvmSignature({
signature, signature,
@@ -176,6 +193,20 @@ const useAccountsContext = () => {
} }
}, [evmAddress, dydxAddress]); }, [evmAddress, dydxAddress]);
const selectedNetwork = useSelector(getSelectedNetwork);
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { ready, authenticated } = usePrivy();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (connectedDydxAddress && signerGraz) { if (connectedDydxAddress && signerGraz) {
@@ -192,7 +223,17 @@ const useAccountsContext = () => {
const evmDerivedAccount = evmDerivedAddresses[evmAddress]; const evmDerivedAccount = evmDerivedAddresses[evmAddress];
if (evmDerivedAccount?.encryptedSignature) { if (walletConnectionType === WalletConnectionType.Privy && authenticated && ready) {
try {
const signature = await signTypedDataAsync();
await setWalletFromEvmSignature(signature);
dispatch(setOnboardingState(OnboardingState.AccountConnected));
} catch (error) {
log('useAccounts/decryptSignature', error);
forgetEvmSignature();
}
} else if (evmDerivedAccount?.encryptedSignature) {
try { try {
const signature = decryptSignature(evmDerivedAccount.encryptedSignature); const signature = decryptSignature(evmDerivedAccount.encryptedSignature);
@@ -211,7 +252,15 @@ const useAccountsContext = () => {
dispatch(setOnboardingState(OnboardingState.Disconnected)); dispatch(setOnboardingState(OnboardingState.Disconnected));
} }
})(); })();
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]); }, [
evmAddress,
evmDerivedAddresses,
signerWagmi,
connectedDydxAddress,
signerGraz,
authenticated,
ready,
]);
// abacus // abacus
useEffect(() => { useEffect(() => {
@@ -224,6 +273,7 @@ const useAccountsContext = () => {
if (hdKey?.mnemonic) { if (hdKey?.mnemonic) {
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX); const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
abacusStateManager.setNobleWallet(nobleWallet); abacusStateManager.setNobleWallet(nobleWallet);
setLocalNobleWallet(nobleWallet);
} }
}; };
setNobleWallet(); setNobleWallet();
@@ -322,6 +372,7 @@ const useAccountsContext = () => {
localDydxWallet, localDydxWallet,
dydxAccounts, dydxAccounts,
dydxAddress, dydxAddress,
nobleAddress,
// Onboarding state // Onboarding state
saveHasAcknowledgedTerms, saveHasAcknowledgedTerms,
-16
View File
@@ -1,16 +0,0 @@
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];
}
+1
View File
@@ -21,6 +21,7 @@ export const useDisplayedWallets = () => {
// WalletType.BitKeep, // WalletType.BitKeep,
// WalletType.Coin98, // WalletType.Coin98,
WalletType.Privy,
WalletType.OtherWallet, WalletType.OtherWallet,
].filter(isTruthy); ].filter(isTruthy);
}; };
+13 -2
View File
@@ -1,5 +1,9 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useNetwork, useSwitchNetwork } from 'wagmi'; import { useNetwork, useSwitchNetwork } from 'wagmi';
import { useSwitchNetwork as useSwitchNetworkPrivy } from '@privy-io/wagmi-connector';
import { WalletConnectionType } from '@/constants/wallets';
import { useWalletConnection } from './useWalletConnection';
export const useMatchingEvmNetwork = ({ export const useMatchingEvmNetwork = ({
chainId, chainId,
@@ -11,7 +15,10 @@ export const useMatchingEvmNetwork = ({
onError?: (error: Error) => void; onError?: (error: Error) => void;
}) => { }) => {
const { chain } = useNetwork(); const { chain } = useNetwork();
const { walletConnectionType } = useWalletConnection();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError }); const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
const { isLoading: isLoadingPrivy, switchNetworkAsync: switchNetworkAsyncPrivy } =
useSwitchNetworkPrivy({ onError });
// If chainId is not a number, we can assume it is a non EVM compatible chain // If chainId is not a number, we can assume it is a non EVM compatible chain
const isMatchingNetwork = useMemo( const isMatchingNetwork = useMemo(
@@ -21,7 +28,11 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => { const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) { if (!isMatchingNetwork) {
await switchNetworkAsync?.(Number(chainId)); if (walletConnectionType === WalletConnectionType.Privy) {
await switchNetworkAsyncPrivy?.(Number(chainId));
} else {
await switchNetworkAsync?.(Number(chainId));
}
} }
}, [chainId, chain]); }, [chainId, chain]);
@@ -34,6 +45,6 @@ export const useMatchingEvmNetwork = ({
return { return {
isMatchingNetwork, isMatchingNetwork,
matchNetwork, matchNetwork,
isSwitchingNetwork: isLoading, isSwitchingNetwork: isLoading || isLoadingPrivy,
}; };
}; };
+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]);
};
+1 -7
View File
@@ -198,13 +198,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
); );
const ibcMsg: EncodeObject = { const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl, typeUrl: transaction.msgTypeUrl,
value: { value: transaction.msg,
...transaction.msg,
timeoutTimestamp: transaction.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(transaction.msg.timeoutTimestamp).toString())
: undefined,
},
}; };
return await compositeClient.send( return await compositeClient.send(
+12 -20
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState, useMemo } from 'react'; import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { usePrivy, useLogout } from '@privy-io/react-auth';
import { EvmDerivedAddresses } from '@/constants/account';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
@@ -107,20 +107,21 @@ export const useWalletConnection = () => {
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector }); const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
const { suggestAndConnect: connectGraz } = useConnectGraz(); const { suggestAndConnect: connectGraz } = useConnectGraz();
const [evmDerivedAddresses] = useLocalStorage({ const { login, ready, authenticated } = usePrivy();
key: LocalStorageKey.EvmDerivedAddresses, const { logout } = useLogout();
defaultValue: {} as EvmDerivedAddresses,
});
const connectWallet = useCallback( const connectWallet = useCallback(
async ({ walletType, forceConnect }: { walletType?: WalletType; forceConnect?: boolean }) => { async ({ walletType }: { walletType: WalletType }) => {
if (!walletType) return { walletType, walletConnectionType };
const walletConnection = getWalletConnection({ walletType }); const walletConnection = getWalletConnection({ walletType });
try { try {
if (!walletConnection) { if (!walletConnection) {
throw new Error('Onboarding: No wallet connection found.'); throw new Error('Onboarding: No wallet connection found.');
} else if (walletConnection.type === WalletConnectionType.Privy) {
if (!isConnectedWagmi && !authenticated && ready) {
login();
}
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) { } else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
const cosmosWalletType = { const cosmosWalletType = {
[WalletType.Keplr as string]: CosmosWalletType.KEPLR, [WalletType.Keplr as string]: CosmosWalletType.KEPLR,
@@ -139,11 +140,7 @@ export const useWalletConnection = () => {
}); });
} }
} else { } else {
const isAccountConnected = Boolean( if (!isConnectedWagmi) {
evmAddress && evmDerivedAddresses[evmAddress]?.encryptedSignature
);
// if account connected (via remember me), do not show wagmi popup until forceConnect
if (!isConnectedWagmi && (forceConnect || !isAccountConnected)) {
await connectWagmi({ await connectWagmi({
connector: resolveWagmiConnector({ connector: resolveWagmiConnector({
walletType, walletType,
@@ -167,7 +164,7 @@ export const useWalletConnection = () => {
walletConnectionType: walletConnection.type, walletConnectionType: walletConnection.type,
}; };
}, },
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi] [isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, login, ready, authenticated]
); );
const disconnectWallet = useCallback(async () => { const disconnectWallet = useCallback(async () => {
@@ -176,6 +173,7 @@ export const useWalletConnection = () => {
if (isConnectedWagmi) await disconnectWagmi(); if (isConnectedWagmi) await disconnectWagmi();
if (isConnectedGraz) await disconnectGraz(); if (isConnectedGraz) await disconnectGraz();
if (authenticated) await logout();
}, [isConnectedGraz, isConnectedWagmi]); }, [isConnectedGraz, isConnectedWagmi]);
// Wallet selection // Wallet selection
@@ -239,12 +237,6 @@ export const useWalletConnection = () => {
evmAddressWagmi, evmAddressWagmi,
signerWagmi, signerWagmi,
publicClientWagmi, publicClientWagmi,
isConnectedWagmi,
connectWallet: () =>
connectWallet({
walletType: selectedWalletType,
forceConnect: true,
}),
// Wallet connection (Cosmos) // Wallet connection (Cosmos)
dydxAddress, dydxAddress,
-2
View File
@@ -39,7 +39,6 @@ export { default as MarketsIcon } from './markets.svg';
export { default as MenuIcon } from './menu.svg'; export { default as MenuIcon } from './menu.svg';
export { default as MigrateIcon } from './migrate.svg'; export { default as MigrateIcon } from './migrate.svg';
export { default as MintscanIcon } from './logos/mintscan.svg'; export { default as MintscanIcon } from './logos/mintscan.svg';
export { default as MoonIcon } from './moon.svg';
export { default as OrderbookIcon } from './orderbook.svg'; export { default as OrderbookIcon } from './orderbook.svg';
export { default as OverviewIcon } from './overview.svg'; export { default as OverviewIcon } from './overview.svg';
export { default as PencilIcon } from './pencil.svg'; export { default as PencilIcon } from './pencil.svg';
@@ -57,7 +56,6 @@ export { default as SendIcon } from './send.svg';
export { default as ShareIcon } from './share.svg'; export { default as ShareIcon } from './share.svg';
export { default as ShowIcon } from './show.svg'; export { default as ShowIcon } from './show.svg';
export { default as StarIcon } from './star.svg'; export { default as StarIcon } from './star.svg';
export { default as SunIcon } from './sun.svg';
export { default as TerminalIcon } from './terminal.svg'; export { default as TerminalIcon } from './terminal.svg';
export { default as TogglesMenuIcon } from './toggles-menu.svg'; export { default as TogglesMenuIcon } from './toggles-menu.svg';
export { default as TradeIcon } from './trade.svg'; export { default as TradeIcon } from './trade.svg';
+43 -50
View File
@@ -1,52 +1,45 @@
import { useAppThemeContext } from '@/hooks/useAppTheme'; const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => (
<svg
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => { width="135"
const theme = useAppThemeContext(); height="145"
const fill = theme.logoFill; viewBox="0 0 135 145"
fill="none"
return ( xmlns="http://www.w3.org/2000/svg"
<svg >
width="135" <path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill="white" />
height="145" <path
viewBox="0 0 135 145" d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
fill="none" fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
xmlns="http://www.w3.org/2000/svg" />
> <path
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill={fill} /> d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
<path fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z" />
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`} <defs>
/> <linearGradient
<path id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z" x1="27.1293"
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`} y1="9.0625"
/> x2="69.773"
<defs> y2="60.4324"
<linearGradient gradientUnits="userSpaceOnUse"
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'} >
x1="27.1293" <stop stopColor="white" />
y1="9.0625" <stop offset="1" stopColor="white" stopOpacity="0.55" />
x2="69.773" </linearGradient>
y2="60.4324" <linearGradient
gradientUnits="userSpaceOnUse" id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
> x1="111.1"
<stop stopColor={fill} /> y1="133.996"
<stop offset="1" stopColor={fill} stopOpacity="0.55" /> x2="58.6959"
</linearGradient> y2="63.4999"
<linearGradient gradientUnits="userSpaceOnUse"
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'} >
x1="111.1" <stop stopColor="#6966FF" />
y1="133.996" <stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
x2="58.6959" </linearGradient>
y2="63.4999" </defs>
gradientUnits="userSpaceOnUse" </svg>
> );
<stop stopColor="#6966FF" />
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
</linearGradient>
</defs>
</svg>
);
};
export default LogoShortIcon; export default LogoShortIcon;
-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>

Before

Width:  |  Height:  |  Size: 380 B

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>

Before

Width:  |  Height:  |  Size: 393 B

+2 -2
View File
@@ -1,5 +1,6 @@
@import 'styles/fonts.css'; @import 'styles/fonts.css';
@import 'styles/text.css'; @import 'styles/text.css';
@import 'styles/colors.css';
@import 'styles/animations.css'; @import 'styles/animations.css';
:root { :root {
@@ -8,7 +9,6 @@
background-color: var(--color-layer-2); background-color: var(--color-layer-2);
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
--border: var(--default-border-width) solid var(--color-border);
} }
body, body,
@@ -54,7 +54,7 @@ button:disabled {
cursor: pointer; cursor: pointer;
} }
:focus-visible { :focus-visible{
outline: var(--color-accent) 1px solid; outline: var(--color-accent) 1px solid;
outline-offset: -1px; outline-offset: -1px;
/* outline-offset: -2px; */ /* outline-offset: -2px; */
+2 -22
View File
@@ -56,10 +56,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.store = undefined; this.store = undefined;
} }
get isNobleClientConnected(): boolean {
return this.nobleClient?.isConnected ?? false;
}
setStore(store: RootStore): void { setStore(store: RootStore): void {
this.store = store; this.store = store;
} }
@@ -173,10 +169,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
return parsedObj as T; return parsedObj as T;
} }
if (typeof x === 'bigint') {
return x.toString() as T;
}
throw new Error(`Unsupported data type: ${typeof x}`); throw new Error(`Unsupported data type: ${typeof x}`);
} }
@@ -367,13 +359,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
try { try {
const ibcMsg = { const ibcMsg = {
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer', typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
value: { value: params.msg,
...params.msg,
timeoutTimestamp: params.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
: undefined,
},
}; };
const fee = await this.nobleClient.simulateTransaction([ibcMsg]); const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
@@ -423,13 +409,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
); );
const ibcMsg: EncodeObject = { const ibcMsg: EncodeObject = {
typeUrl: parsedIbcPayload.msgTypeUrl, typeUrl: parsedIbcPayload.msgTypeUrl,
value: { value: parsedIbcPayload.msg,
...parsedIbcPayload.msg,
timeoutTimestamp: parsedIbcPayload.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
: undefined,
},
}; };
const tx = await this.compositeClient.send( const tx = await this.compositeClient.send(
+5 -10
View File
@@ -171,15 +171,6 @@ class AbacusStateManager {
this.setTransferValue({ value: null, field: TransferInputField.usdcSize }); this.setTransferValue({ value: null, field: TransferInputField.usdcSize });
}; };
resetInputState = () => {
this.clearTransferInputValues();
this.setTransferValue({
field: TransferInputField.type,
value: null,
});
this.clearTradeInputValues();
};
// ------ Set Data ------ // // ------ Set Data ------ //
setStore = (store: RootStore) => { setStore = (store: RootStore) => {
this.store = store; this.store = store;
@@ -271,7 +262,11 @@ class AbacusStateManager {
) => this.stateManager.cancelOrder(orderId, callback); ) => this.stateManager.cancelOrder(orderId, callback);
cctpWithdraw = ( cctpWithdraw = (
callback: (success: boolean, parsingError: Nullable<ParsingError>, data: string) => void callback: (
success: boolean,
parsingError: Nullable<ParsingError>,
data: string,
) => void
): void => this.stateManager.commitCCTPWithdraw(callback); ): void => this.stateManager.commitCCTPWithdraw(callback);
// ------ Utils ------ // // ------ Utils ------ //
-133
View File
@@ -1,133 +0,0 @@
// ------ Canvas helper methods ------ //
import type { MarketOrderbook, Nullable, PerpetualMarketOrderbookLevel } from '@/constants/abacus';
/**
* @returns top left x,y and bottom x,y from array idx
*/
export const getRektFromIdx = ({
idx,
canvasWidth,
canvasHeight,
rowHeight,
side = 'bid',
}: {
idx: number;
canvasWidth: number;
canvasHeight: number;
rowHeight: number;
side: PerpetualMarketOrderbookLevel['side'];
}) => {
/**
* Does not change
*/
const x1 = 0;
const x2 = Math.floor(canvasWidth);
/**
* Changes based on side
* Asks: The drawing should starts from the bottom of the orderbook
*/
if (side === 'ask') {
const y1 = Math.floor(canvasHeight - (idx + 1) * rowHeight);
const y2 = Math.floor(y1 + rowHeight);
return { x1, x2, y1, y2 };
}
const y1 = Math.floor(idx * rowHeight);
const y2 = Math.floor(y1 + rowHeight);
return { x1, x2, y1, y2 };
};
/**
* @returns y value for text and bar
*/
export const getYForElements = ({ y, rowHeight }: { y: number; rowHeight: number }) => ({
text: Math.floor(y + rowHeight / 2),
bar: Math.floor(y + 1),
});
/**
* @description X coordinate for text
* @returns Get X value by column, colIdx starts at 0
*/
export const getXByColumn = ({ canvasWidth, colIdx }: { canvasWidth: number; colIdx: number }) => {
return Math.floor(((colIdx + 1) * canvasWidth) / 3);
};
/**
* to === 'right' to === 'left'
* |===================| |===================|
* bar: a b c d
* a = 0
* b = depthBarWidth
* c = canvasWidth - depthBarWidth
* d = canvasWidth
*
*/
export const getHistogramXValues = ({
barWidth,
canvasWidth,
gradientMultiplier,
histogramSide,
}: {
barWidth: number;
canvasWidth: number;
gradientMultiplier: number;
histogramSide: 'left' | 'right';
}) => {
const gradient = {
x1: Math.floor(histogramSide === 'left' ? barWidth : canvasWidth - barWidth),
x2: Math.floor(
histogramSide === 'left'
? 0 - (canvasWidth * gradientMultiplier - canvasWidth)
: canvasWidth * gradientMultiplier
),
};
const bar = {
x1: Math.floor(histogramSide === 'left' ? 0 : canvasWidth - barWidth),
x2: Math.floor(histogramSide === 'left' ? Math.min(barWidth, canvasWidth) : canvasWidth),
};
return {
bar,
gradient,
};
};
// ------ Orderbook helper methods ------ //
export const processOrderbookToCreateMap = ({
orderbookMap,
newOrderbook,
}: {
orderbookMap?: {
asks: Record<string, number>;
bids: Record<string, number>;
};
newOrderbook: Nullable<MarketOrderbook>;
}) => {
// Create Orderbooks Map indexed by Price
const asks = newOrderbook?.asks?.toArray() ?? [];
const bids = newOrderbook?.bids?.toArray() ?? [];
const prevAsks = orderbookMap?.asks ?? {};
const prevBids = orderbookMap?.bids ?? {};
const newAsks = Object.fromEntries(asks.map((ask) => [ask.price, ask.size]) ?? {});
const newBids = Object.fromEntries(bids.map((bid) => [bid.price, bid.size]) ?? {});
Object.keys(prevAsks).forEach((price) => {
if (!newAsks[price] || newAsks[price] === 0) {
newAsks[price] = 0;
}
});
Object.keys(prevBids).forEach((price) => {
if (!newBids[price] || newBids[price] === 0) {
newBids[price] = 0;
}
});
return {
newAsks,
newBids,
};
};
-20
View File
@@ -1,20 +0,0 @@
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}`;
};
+23 -21
View File
@@ -1,9 +1,11 @@
import Color from 'color';
import isEmpty from 'lodash/isEmpty';
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles'; import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
import { Colors } from '@/styles/colors';
import { AppTheme } from '@/state/configs'; import { AppTheme } from '@/state/configs';
import { Themes } from '@/styles/themes';
export const mapCandle = ({ export const mapCandle = ({
startedAt, startedAt,
open, open,
@@ -47,42 +49,42 @@ export const getHistorySlice = ({
return bars.filter(({ time }) => time >= fromMs); return bars.filter(({ time }) => time >= fromMs);
}; };
export const getWidgetOverrides = (appTheme: AppTheme) => { export const getWidgetOverrides = (theme: AppTheme) => {
const theme = Themes[appTheme]; const colors = Colors[theme];
return { return {
overrides: { overrides: {
'paneProperties.background': theme.layer2, 'paneProperties.background': Color(colors.layer2).hex(),
'paneProperties.horzGridProperties.color': theme.layer3, 'paneProperties.horzGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.vertGridProperties.color': theme.layer3, 'paneProperties.vertGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.crossHairProperties.style': 1, 'paneProperties.crossHairProperties.style': 1,
'paneProperties.legendProperties.showBarChange': false, 'paneProperties.legendProperties.showBarChange': false,
'paneProperties.backgroundType': 'solid', 'paneProperties.backgroundType': 'solid',
'mainSeriesProperties.style': 1, 'mainSeriesProperties.style': 1,
'mainSeriesProperties.candleStyle.upColor': theme.positive, 'mainSeriesProperties.candleStyle.upColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.borderUpColor': theme.positive, 'mainSeriesProperties.candleStyle.borderUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.wickUpColor': theme.positive, 'mainSeriesProperties.candleStyle.wickUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.downColor': theme.negative, 'mainSeriesProperties.candleStyle.downColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.borderDownColor': theme.negative, 'mainSeriesProperties.candleStyle.borderDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.wickDownColor': theme.negative, 'mainSeriesProperties.candleStyle.wickDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker', 'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
'scalesProperties.textColor': theme.textPrimary, 'scalesProperties.textColor': Color(colors.text1).hex(),
'scalesProperties.backgroundColor': theme.layer2, 'scalesProperties.backgroundColor': Color(colors.layer2).hex(),
'scalesProperties.lineColor': theme.layer3, 'scalesProperties.lineColor': Color(colors.layer3).hex(),
}, },
studies_overrides: { studies_overrides: {
'volume.volume.color.0': theme.negative, 'volume.volume.color.0': Color(colors.negative).hex(),
'volume.volume.color.1': theme.positive, 'volume.volume.color.1': Color(colors.positive).hex(),
'volume.volume ma.visible': false, 'volume.volume ma.visible': false,
'relative strength index.plot.color': theme.accent, 'relative strength index.plot.color': Color(colors.accent).hex(),
'relative strength index.plot.linewidth': 1.5, 'relative strength index.plot.linewidth': 1.5,
'relative strength index.hlines background.color': '#134A9F', 'relative strength index.hlines background.color': '#134A9F',
}, },
loading_screen: { loading_screen: {
backgroundColor: theme.layer2, backgroundColor: Color(colors.layer2).hex(),
foregroundColor: theme.layer2, foregroundColor: Color(colors.layer2).hex(),
}, },
}; };
}; };
+14
View File
@@ -1,5 +1,6 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi'; import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
import { goerli } from 'wagmi/chains'; import { goerli } from 'wagmi/chains';
import type { PrivyClientConfig } from '@privy-io/react-auth';
import { import {
arbitrum, arbitrum,
@@ -85,6 +86,19 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
kava, kava,
]; ];
export const privyConfig: PrivyClientConfig = {
embeddedWallets: {
createOnLogin: 'users-without-wallets',
requireUserPasswordOnCreate: true,
noPromptOnSignature: true,
},
loginMethods: ['email', 'sms', 'twitter', 'google', 'apple'],
appearance: {
theme: '#28283c',
},
defaultChain: sepolia
};
const { chains, publicClient, webSocketPublicClient } = configureChains( const { chains, publicClient, webSocketPublicClient } = configureChains(
WAGMI_SUPPORTED_CHAINS, WAGMI_SUPPORTED_CHAINS,
[ [
+6
View File
@@ -62,6 +62,12 @@ export const getWalletConnection = ({
type: WalletConnectionType.CosmosSigner, type: WalletConnectionType.CosmosSigner,
}; };
} }
case WalletConnectionType.Privy: {
return {
type: WalletConnectionType.Privy,
};
}
} }
} }
}; };
+6 -11
View File
@@ -5,7 +5,7 @@ import { useEnsName } from 'wagmi';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ButtonSize } from '@/constants/buttons'; import { ButtonSize } from '@/constants/buttons';
import { TransferType } from '@/constants/abacus'; import { TransferInputField, TransferType } from '@/constants/abacus';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -26,13 +26,12 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors'; import { getOnboardingState } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs'; import { openDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy'; import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet'; import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel'; import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel'; import { MigratePanel } from './rewards/MigratePanel';
import { GovernancePanel } from './rewards/GovernancePanel';
import { StakingPanel } from './rewards/StakingPanel';
const ENS_CHAIN_ID = 1; // Ethereum const ENS_CHAIN_ID = 1; // Ethereum
@@ -179,7 +178,7 @@ const Profile = () => {
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))} onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
/> />
</Styled.EqualGrid> </Styled.EqualGrid>
<MigratePanel /> <MigratePanel />
<DYDXBalancePanel /> <DYDXBalancePanel />
@@ -231,9 +230,6 @@ const Profile = () => {
withInnerBorders={false} withInnerBorders={false}
/> />
</Styled.TablePanel> </Styled.TablePanel>
<GovernancePanel />
<StakingPanel />
</Styled.MobileProfileLayout> </Styled.MobileProfileLayout>
); );
}; };
@@ -247,7 +243,6 @@ Styled.MobileProfileLayout = styled.div`
gap: 1rem; gap: 1rem;
padding: 1.25rem 0.9rem; padding: 1.25rem 0.9rem;
max-width: 100vw;
`; `;
Styled.Header = styled.header` Styled.Header = styled.header`
@@ -264,8 +259,8 @@ Styled.ProfileIcon = styled.div`
border-radius: 50%; border-radius: 50%;
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
${({ theme }) => theme.profileYellow} 0%, var(--theme-classic-color-yellow) 0%,
${({ theme }) => theme.profileRed} 100% var(--theme-classic-color-red) 100%
); );
`; `;
@@ -342,7 +337,7 @@ Styled.Details = styled(Details)`
Styled.RewardsPanel = styled(Panel)` Styled.RewardsPanel = styled(Panel)`
align-self: flex-start; align-self: flex-start;
&, &,
> * { > * {
height: 100%; height: 100%;
-75
View File
@@ -1,75 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const GovernancePanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { governanceLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+2 -3
View File
@@ -146,15 +146,14 @@ Styled.Title = styled.h3`
color: var(--color-text-2); color: var(--color-text-2);
padding: var(--panel-paddingY) var(--panel-paddingX) 0; padding: var(--panel-paddingY) var(--panel-paddingX) 0;
margin-bottom: -0.5rem;
`; `;
Styled.MigrateAction = styled.div` Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns} ${layoutMixins.flexEqualColumns}
align-items: center; align-items: center;
margin: 1rem;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
margin: 1rem;
width: 100%; width: 100%;
background-color: var(--color-layer-2); background-color: var(--color-layer-2);
@@ -234,4 +233,4 @@ Styled.InlineRow = styled.div`
${layoutMixins.inlineRow} ${layoutMixins.inlineRow}
color: var(--color-text-0); color: var(--color-text-0);
--link-color: var(--color-text-1); --link-color: var(--color-text-1);
`; `;
+77 -9
View File
@@ -3,29 +3,45 @@ import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { AppRoute } from '@/constants/routes'; import { AppRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter } from '@/hooks'; import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton'; import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel'; import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
import { DYDXBalancePanel } from './DYDXBalancePanel'; import { DYDXBalancePanel } from './DYDXBalancePanel';
import { MigratePanel } from './MigratePanel'; import { MigratePanel } from './MigratePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel'; import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { RewardsHelpPanel } from './RewardsHelpPanel'; import { RewardsHelpPanel } from './RewardsHelpPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
const RewardsPage = () => { const RewardsPage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints(); const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate(); const navigate = useNavigate();
const panelArrow = (
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
);
return ( return (
<Styled.Page> <Styled.Page>
{isTablet && ( {isTablet && (
@@ -45,12 +61,37 @@ const RewardsPage = () => {
</Styled.PanelRowIncentivesAndBalance> </Styled.PanelRowIncentivesAndBalance>
)} )}
{isNotTablet && ( <Styled.PanelRow>
<Styled.PanelRow> <Styled.Panel
<GovernancePanel /> slotHeaderContent={
<StakingPanel /> <Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
</Styled.PanelRow> }
)} slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
<Styled.Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
</Styled.PanelRow>
<RewardsHelpPanel /> <RewardsHelpPanel />
</Styled.Page> </Styled.Page>
@@ -99,6 +140,24 @@ Styled.Panel = styled(Panel)`
height: fit-content; height: fit-content;
`; `;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.PanelRow = styled.div` Styled.PanelRow = styled.div`
${layoutMixins.gridEqualColumns} ${layoutMixins.gridEqualColumns}
gap: 1.5rem; gap: 1.5rem;
@@ -112,3 +171,12 @@ Styled.PanelRow = styled.div`
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)` Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
`; `;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding: 1rem;
`;
-73
View File
@@ -1,73 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const StakingPanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+36 -6
View File
@@ -1,4 +1,6 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import styled, { AnyStyledComponent } from 'styled-components';
import { TradeLayouts } from '@/constants/layout'; import { TradeLayouts } from '@/constants/layout';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -7,9 +9,11 @@ import { useStringGetter } from '@/hooks';
import { Tabs } from '@/components/Tabs'; import { Tabs } from '@/components/Tabs';
import { CanvasOrderbook } from '@/views/CanvasOrderbook'; import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
import { LiveTrades } from '@/views/tables/LiveTrades'; import { LiveTrades } from '@/views/tables/LiveTrades';
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
enum Tab { enum Tab {
Orderbook = 'Orderbook', Orderbook = 'Orderbook',
Trades = 'Trades', Trades = 'Trades',
@@ -23,22 +27,29 @@ const HISTOGRAM_SIDES_BY_LAYOUT = {
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => { export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const [value, setValue] = useState(Tab.Orderbook); const [value, setValue] = useState(Tab.Orderbook);
const [scrollBehavior, setScrollBehavior] = useState<OrderbookScrollBehavior>('snapToCenter');
const marketId = useSelector(getCurrentMarketId);
useEffect(() => {
setScrollBehavior('snapToCenter');
}, [marketId]);
return ( return (
<Tabs <Styled.Tabs
fullWidthTabs fullWidthTabs
value={value} value={value}
onValueChange={(value: Tab) => { onValueChange={(value: Tab) => {
setScrollBehavior('snapToCenter');
setValue(value); setValue(value);
}} }}
items={[ items={[
{ {
asChild: true, content: <Orderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
content: <CanvasOrderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }), label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
value: Tab.Orderbook, value: Tab.Orderbook,
forceMount: true,
}, },
{ {
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />, content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
@@ -46,7 +57,26 @@ export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) =>
value: Tab.Trades, value: Tab.Trades,
}, },
]} ]}
onWheel={() => setScrollBehavior('free')}
withTransitions={false} withTransitions={false}
isShowingOrderbook={value === Tab.Orderbook}
scrollBehavior={scrollBehavior}
// style={{
// scrollSnapType: {
// 'snapToCenter': 'y mandatory',
// 'free': 'none',
// 'snapToCenterUnlessHovered': 'none',
// }[scrollBehavior],
// }}
/> />
); );
}; };
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Tabs = styled(Tabs)<{
isShowingOrderbook: boolean;
scrollBehavior: OrderbookScrollBehavior;
}>`
${orderbookMixins.scrollArea}
`;
+5 -26
View File
@@ -14,7 +14,6 @@ import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_MARKETID } from '@/constants/markets'; import { DEFAULT_MARKETID } from '@/constants/markets';
import { getLocalStorage } from '@/lib/localStorage'; import { getLocalStorage } from '@/lib/localStorage';
import { processOrderbookToCreateMap } from '@/lib/orderbookHelpers';
interface CandleDataByMarket { interface CandleDataByMarket {
data: Record<string, Candle[]>; data: Record<string, Candle[]>;
@@ -27,13 +26,6 @@ export interface PerpetualsState {
liveTrades?: Record<string, MarketTrade[]>; liveTrades?: Record<string, MarketTrade[]>;
markets?: Record<string, PerpetualMarket>; markets?: Record<string, PerpetualMarket>;
orderbooks?: Record<string, MarketOrderbook>; orderbooks?: Record<string, MarketOrderbook>;
orderbooksMap?: Record<
string,
{
asks: Record<string, number>;
bids: Record<string, number>;
}
>;
historicalFundings: Record<string, MarketHistoricalFunding[]>; historicalFundings: Record<string, MarketHistoricalFunding[]>;
} }
@@ -43,7 +35,6 @@ const initialState: PerpetualsState = {
liveTrades: {}, liveTrades: {},
markets: undefined, markets: undefined,
orderbooks: undefined, orderbooks: undefined,
orderbooksMap: undefined,
historicalFundings: {}, historicalFundings: {},
}; };
@@ -105,24 +96,12 @@ export const perpetualsSlice = createSlice({
setOrderbook: ( setOrderbook: (
state: PerpetualsState, state: PerpetualsState,
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }> action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
) => { ) => ({
state.orderbooks = merge({}, state.orderbooks, { ...state,
orderbooks: merge({}, state.orderbooks, {
[action.payload.marketId]: action.payload.orderbook, [action.payload.marketId]: action.payload.orderbook,
}); }),
}),
const { newAsks, newBids } = processOrderbookToCreateMap({
orderbookMap: state.orderbooksMap?.[action.payload.marketId],
newOrderbook: action.payload.orderbook,
});
state.orderbooksMap = {
...(state.orderbooksMap ?? {}),
[action.payload.marketId]: {
asks: newAsks,
bids: newBids,
},
};
},
setTvChartResolution: ( setTvChartResolution: (
state: PerpetualsState, state: PerpetualsState,
action: PayloadAction<{ marketId: string; resolution: string }> action: PayloadAction<{ marketId: string; resolution: string }>
+13 -9
View File
@@ -7,6 +7,7 @@ import { mapCandle } from '@/lib/tradingView/utils';
import type { RootState } from './_store'; import type { RootState } from './_store';
/** /**
* @param state
* @returns marketId of the market the user is currently viewing * @returns marketId of the market the user is currently viewing
*/ */
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId; export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
@@ -20,6 +21,7 @@ export const getCurrentMarketAssetId = (state: RootState) => {
}; };
/** /**
* @param state
* @returns Record of PerpetualMarket indexed by MarketId * @returns Record of PerpetualMarket indexed by MarketId
*/ */
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets; export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
@@ -32,11 +34,13 @@ export const getMarketData = (marketId: string) => (state: RootState) =>
getPerpetualMarkets(state)?.[marketId]; getPerpetualMarkets(state)?.[marketId];
/** /**
* @param state
* @returns marketIds of all markets * @returns marketIds of all markets
*/ */
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []); export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
/** /**
* @param state
* @returns PerpetualMarket data of the market the user is currently viewing * @returns PerpetualMarket data of the market the user is currently viewing
*/ */
export const getCurrentMarketData = (state: RootState) => { export const getCurrentMarketData = (state: RootState) => {
@@ -45,6 +49,7 @@ export const getCurrentMarketData = (state: RootState) => {
}; };
/** /**
* @param state
* @returns MarketConfig data of the market the user is currently viewing * @returns MarketConfig data of the market the user is currently viewing
*/ */
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs; export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
@@ -57,11 +62,13 @@ export const getMarketConfig = (marketId: string) => (state: RootState) =>
getMarketData(marketId)(state)?.configs; getMarketData(marketId)(state)?.configs;
/** /**
* @param state
* @returns Record of list of MarketTrades indexed by marketId * @returns Record of list of MarketTrades indexed by marketId
*/ */
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades; export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
/** /**
* @param state
* @returns List of MarketTrades of the market the user is currently viewing * @returns List of MarketTrades of the market the user is currently viewing
*/ */
export const getCurrentMarketLiveTrades = (state: RootState) => { export const getCurrentMarketLiveTrades = (state: RootState) => {
@@ -71,11 +78,13 @@ export const getCurrentMarketLiveTrades = (state: RootState) => {
}; };
/** /**
* @param state
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId. * @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
*/ */
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks; export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
/** /**
* @param state
* @returns Orderbook data for the market the user is currently viewing * @returns Orderbook data for the market the user is currently viewing
*/ */
export const getCurrentMarketOrderbook = (state: RootState) => { export const getCurrentMarketOrderbook = (state: RootState) => {
@@ -85,20 +94,13 @@ export const getCurrentMarketOrderbook = (state: RootState) => {
}; };
/** /**
* @returns Orderbook data as a Map of price and size for the current market * @param state
*/
export const getCurrentMarketOrderbookMap = (state: RootState) => {
const orderbookMap = state.perpetuals.orderbooksMap;
const currentMarketId = getCurrentMarketId(state);
return orderbookMap?.[currentMarketId || ''];
};
/**
* @returns Record of historical funding indexed by marketId. * @returns Record of historical funding indexed by marketId.
*/ */
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings; export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
/** /**
* @param state
* @returns Historical funding data for the market the user is currently viewing * @returns Historical funding data for the market the user is currently viewing
*/ */
export const getCurrentMarketHistoricalFundings = createSelector( export const getCurrentMarketHistoricalFundings = createSelector(
@@ -108,6 +110,7 @@ export const getCurrentMarketHistoricalFundings = createSelector(
); );
/** /**
* @param state
* @returns Mid market price for the market the user is currently viewing * @returns Mid market price for the market the user is currently viewing
*/ */
export const getCurrentMarketMidMarketPrice = (state: RootState) => { export const getCurrentMarketMidMarketPrice = (state: RootState) => {
@@ -147,6 +150,7 @@ export const getSelectedResolutionForMarket = (marketId: string) => (state: Root
state.perpetuals.candles?.[marketId]?.selectedResolution; state.perpetuals.candles?.[marketId]?.selectedResolution;
/** /**
* @param state
* @returns Current market's next funding rate * @returns Current market's next funding rate
*/ */
export const getCurrentMarketNextFundingRate = createSelector( export const getCurrentMarketNextFundingRate = createSelector(
+180
View File
@@ -0,0 +1,180 @@
: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
@@ -0,0 +1,125 @@
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-radius: 0.5em;
--input-height: var(--form-input-height); --input-height: var(--form-input-height);
--input-width: 100%; --input-width: 100%;
--input-backgroundColor: ${({ theme }) => theme.inputBackground}; --input-backgroundColor: var(--color-layer-4);
--input-borderColor: var(--color-layer-6); --input-borderColor: var(--color-layer-6);
${layoutMixins.row} ${layoutMixins.row}
-42
View File
@@ -1,42 +0,0 @@
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
@@ -1,157 +0,0 @@
import { AppTheme } from '@/state/configs';
import type { 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,81 +0,0 @@
import { forwardRef } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import type { Nullable } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { TOKEN_DECIMALS } from '@/constants/numbers';
import { ORDERBOOK_ROW_HEIGHT } from '@/constants/orderbook';
import { useStringGetter } from '@/hooks';
import { Output, OutputType } from '@/components/Output';
import { WithTooltip } from '@/components/WithTooltip';
type StyleProps = {
side?: 'top' | 'bottom';
};
type ElementProps = {
spread?: Nullable<number>;
spreadPercent?: Nullable<number>;
tickSizeDecimals?: Nullable<number>;
};
export const OrderbookRow = styled.div`
display: flex;
flex-shrink: 0;
align-items: center;
height: ${ORDERBOOK_ROW_HEIGHT}px;
min-height: ${ORDERBOOK_ROW_HEIGHT}px;
position: relative;
padding-right: 0.5rem;
font: var(--font-mini-book);
> span {
flex: 1 1 0%;
text-align: right;
}
`;
export const SpreadRow = forwardRef<HTMLDivElement, StyleProps & ElementProps>(
({ side, spread, spreadPercent, tickSizeDecimals = TOKEN_DECIMALS }, ref) => {
const stringGetter = useStringGetter();
return (
<Styled.SpreadRow ref={ref} side={side}>
<span>
<WithTooltip tooltip="spread">
{stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD })}
</WithTooltip>
</span>
<span>
<Output type={OutputType.Number} value={spread} fractionDigits={tickSizeDecimals} />
</span>
<span>
<Output type={OutputType.Percent} value={spreadPercent} />
</span>
</Styled.SpreadRow>
);
}
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.SpreadRow = styled(OrderbookRow)<{ side?: 'top' | 'bottom' }>`
height: 2rem;
border-top: var(--border);
border-bottom: var(--border);
span {
margin-bottom: 2px;
}
${({ side }) =>
side &&
{
top: css`
border-top: none;
`,
bottom: css`
border-bottom: none;
`,
}[side]}
`;
-284
View File
@@ -1,284 +0,0 @@
import { forwardRef, useCallback, useMemo, useRef } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { Nullable, type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { ORDERBOOK_HEIGHT, ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
import { useStringGetter } from '@/hooks';
import {
useCalculateOrderbookData,
useCenterOrderbook,
useDrawOrderbook,
useSpreadRowScrollListener,
} from '@/hooks/Orderbook';
import { Canvas } from '@/components/Canvas';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { Tag } from '@/components/Tag';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getCurrentInput } from '@/state/inputsSelectors';
import { OrderbookRow, SpreadRow } from './OrderbookRow';
type ElementProps = {
maxRowsPerSide?: number;
layout?: 'vertical' | 'horizontal';
};
type StyleProps = {
hideHeader?: boolean;
histogramSide?: 'left' | 'right';
className?: string;
};
export const CanvasOrderbook = forwardRef(
(
{
histogramSide = 'right',
maxRowsPerSide = ORDERBOOK_MAX_ROWS_PER_SIDE,
}: ElementProps & StyleProps,
ref
) => {
const { asks, bids, hasOrderbook, histogramRange, spread, spreadPercent } =
useCalculateOrderbookData({
maxRowsPerSide,
});
const stringGetter = useStringGetter();
const currentMarket = useSelector(getCurrentMarketId) ?? '';
const currentMarketConfig = useSelector(getCurrentMarketConfig, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { tickSizeDecimals = 2 } = currentMarketConfig ?? {};
/**
* Slice asks and bids to maxRowsPerSide using empty rows
*/
const { asksSlice, bidsSlice } = useMemo(() => {
let asksSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
const emptyAskRows =
asks.length < maxRowsPerSide
? new Array<undefined>(maxRowsPerSide - asks.length).fill(undefined)
: [];
asksSlice = [...emptyAskRows, ...asks.reverse()];
let bidsSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
const emptyBidRows =
bids.length < maxRowsPerSide
? new Array<undefined>(maxRowsPerSide - bids.length).fill(undefined)
: [];
bidsSlice = [...bids, ...emptyBidRows];
return {
asksSlice,
bidsSlice,
};
}, [asks, bids]);
const orderbookRef = useRef<HTMLDivElement>(null);
useCenterOrderbook({ orderbookRef, marketId: currentMarket });
/**
* Display top or bottom spreadRow when center spreadRow is off screen
*/
const spreadRowRef = useRef<HTMLDivElement>(null);
const displaySide = useSpreadRowScrollListener({
orderbookRef,
spreadRowRef,
});
/**
* Row action
*/
const currentInput = useSelector(getCurrentInput);
const dispatch = useDispatch();
const onRowAction = useCallback(
(price: Nullable<number>) => {
if (currentInput === 'trade' && price) {
dispatch(setTradeFormInputs({ limitPriceInput: price?.toString() }));
}
},
[currentInput]
);
const { canvasRef: asksCanvasRef } = useDrawOrderbook({
data: [...asksSlice].reverse(),
histogramRange,
histogramSide,
side: 'ask',
});
const { canvasRef: bidsCanvasRef } = useDrawOrderbook({
data: bidsSlice,
histogramRange,
histogramSide,
side: 'bid',
});
return (
<Styled.OrderbookContainer ref={ref}>
<Styled.OrderbookContent $isLoading={!hasOrderbook}>
<Styled.Header>
<span>
{stringGetter({ key: STRING_KEYS.SIZE })} {id && <Tag>{id}</Tag>}
</span>
<span>
{stringGetter({ key: STRING_KEYS.PRICE })} <Tag>USD</Tag>
</span>
<span>{stringGetter({ key: STRING_KEYS.MINE })}</span>
</Styled.Header>
{displaySide === 'top' && (
<Styled.SpreadRow
side="top"
spread={spread}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
)}
<Styled.OrderbookWrapper ref={orderbookRef}>
<Styled.OrderbookSideContainer $side="asks">
<Styled.HoverRows $bottom>
{asksSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
row ? (
<Styled.Row
key={idx}
title={`${row.price}`}
onClick={() => {
onRowAction(row.price);
}}
/>
) : (
<Styled.Row key={idx} />
)
)}
</Styled.HoverRows>
<Styled.OrderbookCanvas ref={asksCanvasRef} width="100%" height="100%" />
</Styled.OrderbookSideContainer>
<SpreadRow
ref={spreadRowRef}
spread={spread?.toNumber()}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
<Styled.OrderbookSideContainer $side="bids">
<Styled.HoverRows>
{bidsSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
row ? (
<Styled.Row
key={idx}
title={`${row.price}`}
onClick={
row?.price
? () => {
onRowAction(row.price);
}
: undefined
}
/>
) : (
<Styled.Row key={idx} />
)
)}
</Styled.HoverRows>
<Styled.OrderbookCanvas ref={bidsCanvasRef} width="100%" height="100%" />
</Styled.OrderbookSideContainer>
</Styled.OrderbookWrapper>
{displaySide === 'bottom' && (
<Styled.SpreadRow
side="bottom"
spread={spread}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
)}
</Styled.OrderbookContent>
{!hasOrderbook && <LoadingSpace id="canvas-orderbook" />}
</Styled.OrderbookContainer>
);
}
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.OrderbookContainer = styled.div`
display: flex;
flex: 1 1 0%;
flex-direction: column;
overflow: hidden;
`;
Styled.OrderbookContent = styled.div<{ $isLoading?: boolean }>`
max-height: 100%;
display: flex;
flex-direction: column;
position: relative;
${({ $isLoading }) => $isLoading && 'flex: 1;'}
`;
Styled.Header = styled(OrderbookRow)`
height: 2rem;
border-bottom: var(--border);
color: var(--color-text-0);
`;
Styled.OrderbookWrapper = styled.div`
overflow-y: auto;
display: flex;
flex-direction: column;
flex: 1 1 0%;
`;
Styled.OrderbookSideContainer = styled.div<{ $side: 'bids' | 'asks' }>`
min-height: ${ORDERBOOK_HEIGHT}px;
${({ $side }) => css`
--accent-color: ${$side === 'bids' ? 'var(--color-positive)' : 'var(--color-negative)'};
`}
position: relative;
`;
Styled.OrderbookCanvas = styled(Canvas)`
width: 100%;
height: 100%;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
font-feature-settings: var(--fontFeature-monoNumbers);
`;
Styled.HoverRows = styled.div<{ $bottom?: boolean }>`
position: absolute;
width: 100%;
${({ $bottom }) => $bottom && 'bottom: 0;'}
`;
Styled.Row = styled(OrderbookRow)<{ onClick?: () => void }>`
${({ onClick }) =>
onClick
? css`
cursor: pointer;
&:hover {
background-color: var(--color-layer-4);
filter: darkness(0.1);
}
`
: css`
cursor: default;
`}
`;
Styled.SpreadRow = styled(SpreadRow)`
position: absolute;
`;
+1 -1
View File
@@ -13,7 +13,7 @@ import {
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks'; import { useStringGetter } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues'; import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { TooltipContent } from '@/components/visx/TooltipContent'; import { TooltipContent } from '@/components/visx/TooltipContent';
import { Details } from '@/components/Details'; import { Details } from '@/components/Details';
+13 -3
View File
@@ -12,7 +12,7 @@ import {
import { StringGetterFunction } from '@/constants/localization'; import { StringGetterFunction } from '@/constants/localization';
import { useBreakpoints } from '@/hooks'; import { useBreakpoints } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues'; import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors'; import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
@@ -83,8 +83,18 @@ export const DepthChart = ({
const { stepSizeDecimals, tickSizeDecimals } = const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {}; useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
const { bids, asks, lowestBid, highestBid, lowestAsk, highestAsk, midMarketPrice, orderbook } = const {
useOrderbookValuesForDepthChart(); bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
orderbook,
} = useOrderbookValuesForDepthChart();
// Chart state // Chart state
@@ -1,6 +1,4 @@
import { useState } from 'react';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -13,7 +11,7 @@ import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon'; import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link'; import { Link } from '@/components/Link';
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks'; import { useAccounts, useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets'; import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
@@ -27,6 +25,8 @@ export const ChooseWallet = () => {
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts(); const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
const { isMobile } = useBreakpoints();
return ( return (
<> <>
{selectedWalletType && selectedWalletError && ( {selectedWalletType && selectedWalletError && (
@@ -56,7 +56,11 @@ export const ChooseWallet = () => {
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />} slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
size={ButtonSize.Small} size={ButtonSize.Small}
> >
<div>{stringGetter({ key: wallets[walletType].stringKey })}</div> <div>
{walletType !== WalletType.Privy && import.meta.env.VITE_PRIVY_APP_ID
? stringGetter({ key: wallets[walletType].stringKey })
: `Socials ${isMobile ? '' : '(Email, SMS, etc.)'}`}
</div>
</Styled.WalletButton> </Styled.WalletButton>
))} ))}
</Styled.Wallets> </Styled.Wallets>
@@ -72,7 +72,6 @@ export const GenerateKeys = ({
if (message) { if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType }); log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message); setError(message);
throw error;
} }
} }
}; };
@@ -34,11 +34,30 @@ export const ChainSelectMenu = ({ label, selectedChain, onSelectChain }: Element
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />, slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
})); }));
const selectedOption = chains.find((item) => item.type === selectedChain); const selectedOption = selectedChain === 'coinbase' ? {
stringKey: 'Coinbase',
iconUrl: '/wallets/coinbase-wallet.png',
} : chains.find((item) => item.type === selectedChain);
const exchanges = [
{
value: 'coinbase',
label: 'Coinbase',
onSelect: () => {
onSelectChain('coinbase');
},
slotBefore: <Styled.Img src="/wallets/coinbase-wallet.png" alt="" />,
}
];
return ( return (
<SearchSelectMenu <SearchSelectMenu
items={[ items={[
{
group: 'exchanges',
groupLabel: 'Exchanges',
items: exchanges,
},
{ {
group: 'chains', group: 'chains',
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }), groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
@@ -17,7 +17,6 @@ import type { EvmAddress } from '@/constants/wallets';
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks'; import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance'; import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
import { useLocalNotifications } from '@/hooks/useLocalNotifications'; import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins'; import { formMixins } from '@/styles/formMixins';
@@ -39,12 +38,14 @@ import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers'; import { MustBigNumber } from '@/lib/numbers';
import { getNobleChainId, NATIVE_TOKEN_ADDRESS } from '@/lib/squid'; import { getNobleChainId, NATIVE_TOKEN_ADDRESS } from '@/lib/squid';
import { log } from '@/lib/telemetry'; import { log } from '@/lib/telemetry';
import { parseWalletError } from '@/lib/wallet'; import { parseWalletError, truncateAddress } from '@/lib/wallet';
import { ChainSelectMenu } from './ChainSelectMenu'; import { ChainSelectMenu } from './ChainSelectMenu';
import { TokenSelectMenu } from './TokenSelectMenu'; import { TokenSelectMenu } from './TokenSelectMenu';
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt'; import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
import { QrCode } from '@/components/QrCode';
import { CopyButton } from '@/components/CopyButton';
type DepositFormProps = { type DepositFormProps = {
onDeposit?: () => void; onDeposit?: () => void;
@@ -56,8 +57,9 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { selectedNetwork } = useSelectedNetwork(); const { selectedNetwork } = useSelectedNetwork();
const [isCoinbase, setIsCoinbase] = useState(false);
const { evmAddress, signerWagmi, publicClientWagmi } = useAccounts(); const { evmAddress, signerWagmi, publicClientWagmi, nobleAddress } = useAccounts();
const { addTransferNotification } = useLocalNotifications(); const { addTransferNotification } = useLocalNotifications();
@@ -121,7 +123,11 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}); });
return () => { return () => {
abacusStateManager.resetInputState(); abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
}; };
}, []); }, []);
@@ -130,7 +136,10 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}, [error]); }, [error]);
const onSelectChain = useCallback((chain: string) => { const onSelectChain = useCallback((chain: string) => {
if (chain) { if (chain === 'coinbase') {
setIsCoinbase(true);
} else if (chain) {
setIsCoinbase(false);
abacusStateManager.clearTransferInputValues(); abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({ abacusStateManager.setTransferValue({
field: TransferInputField.chain, field: TransferInputField.chain,
@@ -232,10 +241,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
throw new Error('Missing request payload'); throw new Error('Missing request payload');
} }
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true); setIsLoading(true);
await validateTokenApproval(); await validateTokenApproval();
@@ -365,11 +370,43 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
return <LoadingSpace id="DepositForm" />; return <LoadingSpace id="DepositForm" />;
} }
const [requireUserActionInWallet, setRequireUserActionInWallet] = useState(false); if (isCoinbase) {
return (
<Styled.Form onSubmit={onSubmit}>
<ChainSelectMenu
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
onSelectChain={onSelectChain}
/>
{nobleAddress && (
<>
<Styled.ReceiveQr
side="bottom"
detailItems={[
{
key: 'nobleAddress',
label: "Noble Address",
value: nobleAddress,
},
]}
>
<QrCode size={432} value={nobleAddress} />
</Styled.ReceiveQr>
<p>
* This address only supports transfers of USDC to the Noble chain.
</p>
<CopyButton value={nobleAddress} />
</>
)}
</Styled.Form>
);
}
return ( return (
<Styled.Form onSubmit={onSubmit}> <Styled.Form onSubmit={onSubmit}>
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} /> <ChainSelectMenu
selectedChain={isCoinbase ? 'coinbase' : chainIdStr || undefined}
onSelectChain={onSelectChain}
/>
<TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} /> <TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} />
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}> <Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
<FormInput <FormInput
@@ -385,11 +422,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
/> />
</Styled.WithDetailsReceipt> </Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>} {errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{requireUserActionInWallet && (
<AlertMessage type={AlertType.Warning}>
{stringGetter({ key: STRING_KEYS.CHECK_WALLET_FOR_REQUEST })}
</AlertMessage>
)}
<Styled.Footer> <Styled.Footer>
<DepositButtonAndReceipt <DepositButtonAndReceipt
isDisabled={isDisabled} isDisabled={isDisabled}
@@ -398,8 +431,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setSlippage={onSetSlippage} setSlippage={onSetSlippage}
slippage={slippage} slippage={slippage}
sourceToken={sourceToken || undefined} sourceToken={sourceToken || undefined}
setRequireUserActionInWallet={setRequireUserActionInWallet}
setError={setError}
/> />
</Styled.Footer> </Styled.Footer>
</Styled.Form> </Styled.Form>
@@ -436,3 +467,13 @@ Styled.TransactionInfo = styled.span`
Styled.FormInputButton = styled(Button)` Styled.FormInputButton = styled(Button)`
${formMixins.inputInnerButton} ${formMixins.inputInnerButton}
`; `;
Styled.Exchange = styled.div`
${layoutMixins.flexColumn}
gap: 1rem;
align-items: center;
`;
Styled.ReceiveQr = styled(WithDetailsReceipt)`
`;
@@ -1,7 +1,8 @@
import { type Dispatch, type SetStateAction, useState, type ReactNode, useEffect } from 'react'; import { type Dispatch, type ReactNode, type SetStateAction, useState, useMemo } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import type { RouteData } from '@0xsquid/sdk'; import type { RouteData } from '@0xsquid/sdk';
import { formatUnits } from 'viem';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons'; import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
@@ -11,7 +12,6 @@ import { NumberSign, TOKEN_DECIMALS } from '@/constants/numbers';
import { useStringGetter, useTokenConfigs } from '@/hooks'; import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork'; import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -39,8 +39,7 @@ type ElementProps = {
isLoading?: boolean; isLoading?: boolean;
chainId?: string | number; chainId?: string | number;
setError?: Dispatch<SetStateAction<Error | null>>; setError?: Dispatch<SetStateAction<Error | undefined>>;
setRequireUserActionInWallet: (val: boolean) => void;
slippage: number; slippage: number;
slotError?: ReactNode; slotError?: ReactNode;
setSlippage: (slippage: number) => void; setSlippage: (slippage: number) => void;
@@ -58,7 +57,6 @@ export const DepositButtonAndReceipt = ({
isDisabled, isDisabled,
isLoading, isLoading,
slotError, slotError,
setRequireUserActionInWallet,
}: ElementProps) => { }: ElementProps) => {
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false); const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
const [isEditingSlippage, setIsEditingSlipapge] = useState(false); const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
@@ -66,24 +64,6 @@ export const DepositButtonAndReceipt = ({
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual); const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { connectWallet, isConnectedWagmi } = useWalletConnection();
const connectWagmi = async () => {
try {
setRequireUserActionInWallet(false);
await connectWallet();
setRequireUserActionInWallet(false);
} catch (e) {
setRequireUserActionInWallet(true);
}
};
useEffect(() => {
if (!isConnectedWagmi && canAccountTrade) {
connectWagmi();
}
}, [isConnectedWagmi, canAccountTrade]);
const { const {
matchNetwork: switchNetwork, matchNetwork: switchNetwork,
isSwitchingNetwork, isSwitchingNetwork,
@@ -100,7 +80,7 @@ export const DepositButtonAndReceipt = ({
useSelector(getSubaccountBuyingPower, shallowEqual) || {}; useSelector(getSubaccountBuyingPower, shallowEqual) || {};
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {}; const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { usdcLabel } = useTokenConfigs(); const { usdcDecimals, usdcLabel } = useTokenConfigs();
const feeSubitems: DetailsItem[] = []; const feeSubitems: DetailsItem[] = [];
@@ -136,9 +116,7 @@ export const DepositButtonAndReceipt = ({
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag> {stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span> </span>
), ),
value: ( value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />
),
subitems: [ subitems: [
{ {
key: 'minimum-deposit-amount', key: 'minimum-deposit-amount',
@@ -148,11 +126,7 @@ export const DepositButtonAndReceipt = ({
</span> </span>
), ),
value: ( value: (
<Output <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
type={OutputType.Fiat}
fractionDigits={TOKEN_DECIMALS}
value={summary?.toAmountMin}
/>
), ),
tooltip: 'minimum-deposit-amount', tooltip: 'minimum-deposit-amount',
}, },
@@ -279,8 +253,6 @@ export const DepositButtonAndReceipt = ({
> >
{!canAccountTrade ? ( {!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} /> <OnboardingTriggerButton size={ButtonSize.Base} />
) : !isConnectedWagmi ? (
<Button action={ButtonAction.Primary} onClick={connectWallet} state={{ isLoading: true }} />
) : !isMatchingNetwork ? ( ) : !isMatchingNetwork ? (
<Button <Button
action={ButtonAction.Primary} action={ButtonAction.Primary}
@@ -101,7 +101,11 @@ export const WithdrawForm = () => {
}); });
return () => { return () => {
abacusStateManager.resetInputState(); abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
}; };
}, []); }, []);
@@ -144,10 +148,6 @@ export const WithdrawForm = () => {
throw new Error('Invalid request payload'); throw new Error('Invalid request payload');
} }
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true); setIsLoading(true);
setError(undefined); setError(undefined);
@@ -44,7 +44,6 @@ export const TradeSideToggle = memo(() => {
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>` const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
--toggle-radius: 0.5em; --toggle-radius: 0.5em;
--toggle-color: var(--color-negative); --toggle-color: var(--color-negative);
--toggle-background: ${({ theme }) => theme.toggleBackground};
${({ value }) => ${({ value }) =>
value === OrderSide.BUY && value === OrderSide.BUY &&
@@ -53,7 +52,7 @@ const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
`} `}
border-radius: var(--toggle-radius); border-radius: var(--toggle-radius);
background-color: var(--toggle-background); background-color: var(--color-layer-4);
position: relative; position: relative;
> button { > button {
+5 -1
View File
@@ -118,7 +118,11 @@ export const TransferForm = ({
onChangeAsset(selectedAsset); onChangeAsset(selectedAsset);
return () => { return () => {
abacusStateManager.resetInputState(); abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
}; };
}, []); }, []);
+2
View File
@@ -36,6 +36,8 @@ import { openDialog } from '@/state/dialogs';
import { MustBigNumber } from '@/lib/numbers'; import { MustBigNumber } from '@/lib/numbers';
import { getHydratedTradingData } from '@/lib/orders'; import { getHydratedTradingData } from '@/lib/orders';
import { tableMixins } from '@/styles/tableMixins';
import { breakpoints } from '@/styles';
const MOBILE_FILLS_PER_PAGE = 50; const MOBILE_FILLS_PER_PAGE = 50;
-1
View File
@@ -163,7 +163,6 @@ Styled.SizeOutput = styled(Output)<StyleProps>`
Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>` Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>`
tr { tr {
--histogram-bucket-size: 1; --histogram-bucket-size: 1;
background-color: var(--color-layer-2);
&[data-side=${OrderSide.BUY}] { &[data-side=${OrderSide.BUY}] {
--accent-color: var(--color-positive); --accent-color: var(--color-positive);
+4 -3
View File
@@ -1,11 +1,10 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components'; import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
import { OrderSide } from '@dydxprotocol/v4-client-js'; import { OrderSide } from '@dydxprotocol/v4-client-js';
import { type OrderbookLine } from '@/constants/abacus'; import { type OrderbookLine, TradeInputField } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
import { useBreakpoints, useStringGetter } from '@/hooks'; import { useBreakpoints, useStringGetter } from '@/hooks';
@@ -30,6 +29,8 @@ import { OrderbookTradesOutput, OrderbookTradesTable } from './OrderbookTradesTa
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
const ORDERBOOK_MAX_ROWS_PER_SIDE = 35;
type ElementProps = { type ElementProps = {
maxRowsPerSide?: number; maxRowsPerSide?: number;
layout?: 'vertical' | 'horizontal'; layout?: 'vertical' | 'horizontal';
+7
View File
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import path from 'path'; import path from 'path';
import svgr from 'vite-plugin-svgr'; import svgr from 'vite-plugin-svgr';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({ export default defineConfig(({ mode }) => ({
@@ -50,6 +51,12 @@ export default defineConfig(({ mode }) => ({
svgr({ svgr({
exportAsDefault: true, exportAsDefault: true,
}), }),
VitePWA({
registerType: 'autoUpdate',
devOptions: {
enabled: true,
},
}),
], ],
publicDir: 'public', publicDir: 'public',
})); }));