Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2a295e8
feat - simpleui wip
pp346 Dec 10, 2025
9f9ab57
Merge branch 'main' into feat/simpleui
pp346 Dec 11, 2025
56d2b5d
Merge branch 'main' into feat/simpleui
pp346 Dec 15, 2025
68a1686
Merge branch 'main' into feat/simpleui
pp346 Dec 16, 2025
f5fae33
Merge branch 'main' into feat/simpleui
pp346 Dec 23, 2025
2189d3a
updates
pp346 Jan 12, 2026
6ab2106
Merge branch 'main' into feat/simpleui
pp346 Jan 12, 2026
a348603
Trade header fixes + remove web available to trade
pp346 Jan 13, 2026
b157a74
fix footer
pp346 Jan 13, 2026
f77aa12
fix trade forms
pp346 Jan 13, 2026
cd20550
fixes
pp346 Jan 13, 2026
5fc105e
fix sticky top
pp346 Jan 14, 2026
dc60a84
Merge branch 'main' into feat/simpleui
pp346 Jan 20, 2026
be6866b
updates based on feedback
pp346 Jan 20, 2026
c92e017
updates
pp346 Jan 21, 2026
dcb61cf
fix zindex mobile dropdown
pp346 Jan 23, 2026
d7b1995
fix border color market order button
pp346 Jan 23, 2026
ef5288b
fix tab colors
pp346 Jan 23, 2026
2bd0c74
updates
pp346 Jan 23, 2026
d61f23b
Merge branch 'main' into feat/simpleui
pp346 Jan 27, 2026
3a7003d
fixes
pp346 Jan 27, 2026
64ccd91
revert tradingview comment
pp346 Jan 27, 2026
a66d875
updates
pp346 Jan 27, 2026
31eb08f
revert simpleui change
pp346 Jan 27, 2026
0efe77d
review updates
pp346 Jan 27, 2026
78d2813
remove filter on nav menu item
pp346 Jan 27, 2026
42e2155
fix long short text color when not selected
pp346 Jan 27, 2026
8757f9d
fix portfolio
pp346 Jan 27, 2026
6823512
comment
pp346 Jan 28, 2026
558d0ba
remove unused props
pp346 Jan 28, 2026
ec4cda2
lint fies
pp346 Jan 28, 2026
e3f3c38
fix bbuild
pp346 Jan 28, 2026
5c32311
Merge branch 'main' into feat/simpleui
pp346 Feb 9, 2026
5f51df4
updates
pp346 Feb 10, 2026
2bb8b34
fix
pp346 Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { useCommandMenu } from './hooks/useCommandMenu';
import { useComplianceState } from './hooks/useComplianceState';
import { useInitializePage } from './hooks/useInitializePage';
import { useLocalStorage } from './hooks/useLocalStorage';
import { useMobileWebEnabled } from './hooks/useMobileWebEnabled';
import { useReferralCode } from './hooks/useReferralCode';
import { useShouldShowFooter } from './hooks/useShouldShowFooter';
import { useSimpleUiEnabled } from './hooks/useSimpleUiEnabled';
Expand All @@ -79,7 +80,6 @@ import breakpoints from './styles/breakpoints';
const MarketsPage = lazy(() => import('@/pages/markets/Markets'));
const PortfolioPage = lazy(() => import('@/pages/portfolio/Portfolio'));
const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
const ProfilePage = lazy(() => import('@/pages/Profile'));
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
const TradePage = lazy(() => import('@/pages/trade/Trade'));
const SpotPage = lazy(() => import('@/pages/spot/Spot'));
Expand All @@ -92,6 +92,10 @@ const VaultPage = lazy(() => import('@/pages/vaults/VaultPage'));
const SimpleMarketsPage = lazy(() => import('@/pages/markets/simple-ui/MarketsMobile'));
const SimpleAssetPage = lazy(() => import('@/pages/trade/simple-ui/AssetPage'));

// Mobile Web
const MobileWebTradePage = lazy(() => import('@/pages/trade/mobile-web/Trade'));
const MobileWebTradeFormPage = lazy(() => import('@/pages/trade/mobile-web/TradeForm'));

const Content = () => {
useInitializePage();
useAnalytics();
Expand All @@ -111,6 +115,7 @@ const Content = () => {
const isShowingFooter = useShouldShowFooter();
const abDefaultToMarkets = useCustomFlagValue(CustomFlags.abDefaultToMarkets);
const isSimpleUi = useSimpleUiEnabled();
const isMobileWebEnabled = useMobileWebEnabled();
const { showComplianceBanner } = useComplianceState();
const isSimpleUiUserMenuOpen = useAppSelector(getIsUserMenuOpen);

Expand All @@ -128,7 +133,9 @@ const Content = () => {

const { dialogAreaRef } = useDialogArea() ?? {};

if (isSimpleUi) {
const showMobileWeb = isMobileWebEnabled && isTablet;

if (isSimpleUi && !showMobileWeb) {
const matchMarkets = matchPath(AppRoute.Markets, location.pathname);
const backgroundColor =
matchMarkets && isSimpleUiUserMenuOpen ? 'var(--color-layer-1)' : 'transparent';
Expand Down Expand Up @@ -203,33 +210,37 @@ const Content = () => {
<Route path={`${AppRoute.Referrals}/*`} element={<AffiliatesPage />} />

<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
<Route
path=":market"
element={showMobileWeb ? <MobileWebTradePage /> : <TradePage />}
/>
<Route
path={AppRoute.Trade}
element={showMobileWeb ? <MobileWebTradePage /> : <TradePage />}
/>
</Route>

<Route path={AppRoute.Spot}>
<Route path=":tokenMint" element={<SpotPage />} />
<Route index element={<SpotPage />} />
</Route>

<Route path={AppRoute.TradeForm}>
<Route path=":market" element={<MobileWebTradeFormPage />} />
<Route path={AppRoute.TradeForm} element={<MobileWebTradeFormPage />} />
</Route>

<Route path={AppRoute.Markets}>
<Route path={AppRoute.Markets} element={<MarketsPage />} />
</Route>

<Route path={`/${chainTokenLabel}/*`} element={<RewardsPage />} />

{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}

<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>

<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Vault}>
<Route path={AppRoute.Vault} element={<VaultPage />} />
</Route>
Expand All @@ -250,9 +261,9 @@ const Content = () => {
</Suspense>
</$Main>

{isTablet ? <FooterMobile /> : <FooterDesktop />}
{showMobileWeb ? <FooterMobile /> : <FooterDesktop />}

<NotificationsToastArea tw="z-[2] [grid-area:Main]" />
<NotificationsToastArea tw="z-[2000] [grid-area:Main]" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be fine. but I think we need to have some reasoning or consistency behind assigning z-index to items.


<$DialogArea ref={dialogAreaRef}>
<DialogManager />
Expand Down
13 changes: 11 additions & 2 deletions src/bonsai/forms/trade/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function getTradeFormFieldStates(
goodTil: DEFAULT_GOOD_TIL_TIME,
stopLossOrder: undefined,
takeProfitOrder: undefined,
isClosingPosition: false,
};

// Initialize all fields as not visible
Expand Down Expand Up @@ -103,7 +104,7 @@ export function getTradeFormFieldStates(
states[key] = {
...(states[key] as any),
state: 'enabled',
effectiveValue: states[key].effectiveValue ?? states[key].rawValue ?? defaults[key],
effectiveValue: states[key]?.effectiveValue ?? states[key]?.rawValue ?? defaults[key],
};
});
}
Expand Down Expand Up @@ -151,7 +152,14 @@ export function getTradeFormFieldStates(
makeTriggersVisibleIfPossible(result);
switch (type) {
case TradeFormType.MARKET:
makeVisible(result, ['marketId', 'side', 'size', 'marginMode', 'reduceOnly']);
makeVisible(result, [
'marketId',
'side',
'size',
'marginMode',
'reduceOnly',
'isClosingPosition',
]);
setMarginMode(result);
disableReduceOnlyIfIncreasingMarketOrder(result);

Expand All @@ -166,6 +174,7 @@ export function getTradeFormFieldStates(
'marginMode',
'reduceOnly',
'postOnly',
'isClosingPosition',
]);
defaultSizeIfSizeInputIsInvalid(result);
setMarginMode(result);
Expand Down
6 changes: 6 additions & 0 deletions src/bonsai/forms/trade/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const getMinimumRequiredFields = (
triggerPrice: undefined,
stopLossOrder: undefined,
takeProfitOrder: undefined,
isClosingPosition: undefined,
};

// Add marketId if provided
Expand Down Expand Up @@ -86,6 +87,11 @@ export const tradeFormReducer = createVanillaReducer({
marginMode,
}),

setIsClosingPosition: (state, isClosingPosition: boolean) => ({
...state,
isClosingPosition,
}),

// Size related actions
setSizeToken: (state, value: string) => ({
...state,
Expand Down
2 changes: 1 addition & 1 deletion src/bonsai/forms/trade/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function calculateTradeSummary(

const fieldStates = getTradeFormFieldStates(state, accountData, baseAccount);

const effectiveTrade = mapValues(fieldStates, (s) => s.effectiveValue) as TradeForm;
const effectiveTrade = mapValues(fieldStates, (s) => s?.effectiveValue) as TradeForm;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need the ?. This is because you are using ?: boolean instead of : boolean | undefined


const options = calculateTradeFormOptions(state.type, fieldStates, baseAccount);

Expand Down
2 changes: 2 additions & 0 deletions src/bonsai/forms/trade/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export type TradeForm = {
// additional triggers
stopLossOrder: TriggerOrderState | undefined;
takeProfitOrder: TriggerOrderState | undefined;

isClosingPosition?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the rationale behind adding this as a type of field, but semantically it doesn't really fit here. It isn't an actual field the user can interact/change. Adding this here leads you to make some weird changes above like having to add it to the list of items that you makeVisible despite there being no UI component to control it within the form.

Can we think of a different way i.e. maybe add to TradeFormInputData instead and pass the isClosingPosition into calculateSummary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can keep it in field but we definitely don't need it in makeVisible right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you are using a completely new route i.e. trade-form, we could technically use a new route like close-form and not bother with state either

};

// Define the FieldState type with conditional properties
Expand Down
3 changes: 3 additions & 0 deletions src/components/Collapsible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const $Root = styled(Root)`
&[data-state='open'] {
gap: 0.5rem;
}

padding-bottom: 1rem;
border-bottom: solid 1px var(--color-border);
`;

const $Trigger = styled(Trigger)`
Expand Down
24 changes: 13 additions & 11 deletions src/components/CollapsibleTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,19 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
))}
</$TabsList>

<Toolbar>
{currentTab?.slotToolbar ?? slotToolbar}
<CollapsibleTrigger asChild>
<$IconButton
iconName={IconName.Caret}
isToggle
buttonStyle={ButtonStyle.WithoutBackground}
shape={ButtonShape.Square}
/>
</CollapsibleTrigger>
</Toolbar>
{slotToolbar && (
<Toolbar>
{currentTab?.slotToolbar ?? slotToolbar}
<CollapsibleTrigger asChild>
<$IconButton
iconName={IconName.Caret}
isToggle
buttonStyle={ButtonStyle.WithoutBackground}
shape={ButtonShape.Square}
/>
</CollapsibleTrigger>
</Toolbar>
)}
</$Header>

<CollapsibleContent tw="stack shadow-none">
Expand Down
114 changes: 114 additions & 0 deletions src/components/MobileDropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { forwardRef } from 'react';

import { Content, Item, Portal, Root, Separator, Trigger } from '@radix-ui/react-dropdown-menu';
import { CaretDownIcon } from '@radix-ui/react-icons';
import { Fragment } from 'react/jsx-runtime';
import styled from 'styled-components';

import { forwardRefFn } from '@/lib/genericFunctionalComponentUtils';

import { Button, ButtonProps } from './Button';
import { DropdownMenuItem } from './DropdownMenu';

type StyleProps = {
align?: 'center' | 'start' | 'end';
side?: 'top' | 'bottom';
sideOffset?: number;
className?: string;
withPortal?: boolean;
};

export const MobileDropdownMenu = forwardRefFn(
<T extends string>({
className,
children,
items,
slotTop,
align,
side,
sideOffset,
withPortal = true,
}: StyleProps & {
children: React.ReactNode;
items: DropdownMenuItem<T>[];
slotTop?: React.ReactNode;
}) => {
const content = (
<Content
align={align}
side={side}
sideOffset={sideOffset}
tw="z-20 w-fit overflow-hidden rounded-[0.5rem] border border-solid border-color-border bg-color-layer-4"
>
{slotTop && (
<>
<div tw="px-1 py-0.5">{slotTop}</div>
<Separator tw="border-b-[length:--border-width] border-b-color-border [border-bottom-style:solid]" />
</>
)}
{items.map((item, idx) => (
<Fragment key={item.value}>
<Item
tw="row cursor-pointer select-none justify-between px-1 py-0.75 font-medium-book first:rounded-tl-[0.5rem] first:rounded-tr-[0.5rem] last:rounded-bl-[0.5rem] last:rounded-br-[0.5rem] disabled:cursor-default"
onSelect={item.onSelect}
disabled={!item.onSelect}
css={{
...(item.active ? { backgroundColor: 'var(--color-layer-3)' } : {}),
cursor: item.onSelect ? 'pointer' : 'not-allowed',
color: {
active: 'var(--color-text-0)',
accent: 'var(--color-accent)',
create: 'var(--color-green)',
destroy: 'var(--color-red)',
none: item.onSelect ? undefined : 'var(--color-text-1)',
}[item.highlightColor ?? (item.active ? 'active' : 'none')],
}}
>
<span tw="whitespace-nowrap">{item.label}</span>
{item.icon && <span tw="row ml-1">{item.icon}</span>}
</Item>
{idx !== items.length - 1 && (
<Separator tw="border-b-[length:--border-width] border-b-color-border [border-bottom-style:solid]" />
)}
</Fragment>
))}
</Content>
);

const dropdownContent = withPortal ? <Portal>{content}</Portal> : content;

return (
<Root>
<Trigger className={className} asChild>
{children}
</Trigger>

{dropdownContent}
</Root>
);
}
);

export const DropdownMenuTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
({ children, ...props }, ref) => (
<DropdownMenuButton {...props} ref={ref}>
{children} <CaretDownIcon />
</DropdownMenuButton>
)
);

const DropdownMenuButton = styled(Button)`
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 8px;
border-width: 1.5px;
border-color: var(--color-layer-4);

&[data-state='open'] {
svg {
transition: rotate 0.3s var(--ease-out-expo);
rotate: -0.5turn;
}
}
`;
7 changes: 5 additions & 2 deletions src/components/NavigationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import styled, { css, keyframes } from 'styled-components';

import { MenuConfig, MenuItem } from '@/constants/menus';

import { useBreakpoints } from '@/hooks/useBreakpoints';

import { layoutMixins } from '@/styles/layoutMixins';
import { popoverMixins } from '@/styles/popoverMixins';

Expand Down Expand Up @@ -63,11 +65,12 @@ const NavItemWithRef = <MenuItemValue extends string>(
ref: Ref<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement>
) => {
const location = useLocation();
const { isTablet } = useBreakpoints();

const children = (
<>
{slotBefore}
<span>
<span css={isTablet ? { fontSize: 16 } : undefined}>
{label}
{tag && (
<>
Expand Down Expand Up @@ -95,7 +98,7 @@ const NavItemWithRef = <MenuItemValue extends string>(
tw="whitespace-nowrap"
{...props}
>
{children}
{isTablet ? <div tw="mx-auto flex items-center gap-0.25">{children}</div> : children}
</NavLink>
</Link>
) : props.onClick ? (
Expand Down
Loading
Loading