refactor(ui): redesign trade plans workflow shell

This commit is contained in:
Saravana Achu Mac 2026-05-07 10:13:37 -07:00
parent 0743c16b71
commit 0144124d0d

View File

@ -971,12 +971,14 @@ export function SimpleView() {
description="Create and manage short-term trade plans, then convert filled positions into long-term holds when you want to stop automated exits." description="Create and manage short-term trade plans, then convert filled positions into long-term holds when you want to stop automated exits."
/> />
<div className="grid gap-8 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]"> <div className="grid gap-8 xl:grid-cols-[minmax(0,1.05fr)_minmax(380px,0.95fr)]">
<Card> <Card className="overflow-hidden">
<CardHeader> <CardHeader className="border-b border-[var(--border)] bg-[var(--card-elevated)]/80 p-6">
<div> <div>
<CardTitle className="uppercase">{editingSetupId ? 'Edit setup' : 'New setup'}</CardTitle> <CardTitle>{editingSetupId ? 'Edit trade plan' : 'Create trade plan'}</CardTitle>
<CardDescription>Build a short-term buy plan or attach a managed profit exit to an existing holding.</CardDescription> <CardDescription className="mt-2 max-w-2xl leading-6">
Build a guided plan with clear setup, instrument, price, sizing, trigger, and review steps before automation is armed.
</CardDescription>
</div> </div>
<Button <Button
type="button" type="button"
@ -989,15 +991,21 @@ export function SimpleView() {
}} }}
variant="outline" variant="outline"
size="sm" size="sm"
className="uppercase tracking-[0.2em]"
> >
Reset Reset
</Button> </Button>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-6">
<form className="space-y-6" onSubmit={handleSubmit}> <form className="space-y-5" onSubmit={handleSubmit}>
<div className="grid gap-3 md:grid-cols-2"> <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<div className="mb-4">
<div className="text-sm font-bold text-[var(--foreground)]">1. Setup type</div>
<p className="mt-1 text-sm leading-6 text-[var(--muted-foreground)]">
Choose whether this plan opens a new position or manages profit-taking for an existing holding.
</p>
</div>
<div className="grid gap-3 md:grid-cols-2">
<Button <Button
type="button" type="button"
onClick={() => { onClick={() => {
@ -1006,17 +1014,18 @@ export function SimpleView() {
updateDraft('side', 'buy'); updateDraft('side', 'buy');
}} }}
variant="ghost" variant="ghost"
className={`rounded-[1.25rem] border px-4 py-4 text-left transition ${ className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
draft.side === 'buy' draft.side === 'buy'
? 'border-[var(--primary)] bg-[var(--accent-soft)]' ? 'border-[var(--primary)] bg-[var(--accent-soft)]'
: 'border-[var(--border)] bg-[var(--card-elevated)]' : 'border-[var(--border)] bg-[var(--card-elevated)]'
}`} }`}
> >
<div className="text-[11px] font-black uppercase tracking-[0.24em] text-[var(--muted-foreground)]">Create plan</div> <div>
<div className="mt-1 text-sm font-semibold text-[var(--foreground)]">New short-term buy plan</div> <div className="text-sm font-bold text-[var(--foreground)]">New short-term buy plan</div>
<div className="mt-1 text-sm text-[var(--muted-foreground)]">Arm a dip-buy trigger and let the app manage the profit exit after fill.</div> <div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Arm a dip-buy trigger and let the app manage the profit exit after fill.</div>
</Button> </div>
<Button </Button>
<Button
type="button" type="button"
onClick={() => { onClick={() => {
dispatch({ type: 'clear-feedback' }); dispatch({ type: 'clear-feedback' });
@ -1027,21 +1036,23 @@ export function SimpleView() {
} }
}} }}
variant="ghost" variant="ghost"
className={`rounded-[1.25rem] border px-4 py-4 text-left transition ${ className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
draft.side === 'sell' draft.side === 'sell'
? 'border-[var(--primary)] bg-[var(--accent-soft)]' ? 'border-[var(--primary)] bg-[var(--accent-soft)]'
: 'border-[var(--border)] bg-[var(--card-elevated)]' : 'border-[var(--border)] bg-[var(--card-elevated)]'
}`} }`}
> >
<div className="text-[11px] font-black uppercase tracking-[0.24em] text-[var(--muted-foreground)]">Manage holding</div> <div>
<div className="mt-1 text-sm font-semibold text-[var(--foreground)]">Attach an exit plan</div> <div className="text-sm font-bold text-[var(--foreground)]">Manage an existing holding</div>
<div className="mt-1 text-sm text-[var(--muted-foreground)]">Choose an existing holding and place it back under managed profit-taking.</div> <div className="mt-2 text-sm font-normal leading-6 text-[var(--muted-foreground)]">Choose a filled holding and place it back under managed profit-taking.</div>
</Button> </div>
</div> </Button>
</div>
</section>
{draft.side === 'sell' && ( {draft.side === 'sell' && (
<label className="space-y-2"> <label className="ux-field-stack rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Existing holding</span> <span className="ux-field-label">Existing holding</span>
<Select <Select
value={selectedHoldingTradeId || ''} value={selectedHoldingTradeId || ''}
onChange={(e: ChangeEvent<HTMLSelectElement>) => { onChange={(e: ChangeEvent<HTMLSelectElement>) => {
@ -1056,15 +1067,22 @@ export function SimpleView() {
label: `${holding.symbol} · ${holding.size} @ ${holding.entryPrice.toFixed(4)}`, label: `${holding.symbol} · ${holding.size} @ ${holding.entryPrice.toFixed(4)}`,
}))} }))}
/> />
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="ux-helper-text">
Trade Plans can manage an existing filled holding by attaching a profit exit target to it. Trade Plans can manage an existing filled holding by attaching a profit exit target to it.
</span> </span>
</label> </label>
)} )}
<div className="grid gap-4 md:grid-cols-2"> <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<label className="space-y-2"> <div className="mb-4">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Symbol</span> <div className="text-sm font-bold text-[var(--foreground)]">2. Instrument and market price</div>
<p className="mt-1 text-sm leading-6 text-[var(--muted-foreground)]">
Select the symbol and confirm the reference price that automation will use for trigger calculations.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2">
<label className="ux-field-stack">
<span className="ux-field-label">Symbol</span>
<Input <Input
value={draft.symbol} value={draft.symbol}
onChange={(e: ChangeEvent<HTMLInputElement>) => { onChange={(e: ChangeEvent<HTMLInputElement>) => {
@ -1089,11 +1107,11 @@ export function SimpleView() {
<option key={symbol} value={symbol} /> <option key={symbol} value={symbol} />
))} ))}
</datalist> </datalist>
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="ux-helper-text">
Start typing to pick a supported symbol. Suggestions come from live market symbols plus common supported assets. Start typing to pick a supported symbol. Suggestions come from live market symbols plus common supported assets.
</span> </span>
{normalizedSymbol && !isSuggestedSymbol ? ( {normalizedSymbol && !isSuggestedSymbol ? (
<span className="block text-[11px] text-amber-700 dark:text-amber-300"> <span className="block text-sm text-[var(--bl-warning)]">
This symbol is not in the current supported suggestions. Double-check it before saving. This symbol is not in the current supported suggestions. Double-check it before saving.
</span> </span>
) : null} ) : null}
@ -1132,32 +1150,19 @@ export function SimpleView() {
) : null} ) : null}
</label> </label>
<label className="space-y-2"> <div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Setup type</span> <label className="ux-field-stack">
<Select <span className="ux-field-label">Market price</span>
value={draft.side}
onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('side', e.target.value as SimpleSide)}
options={[
{ value: 'buy', label: 'Buy the dip + profit exit' },
{ value: 'sell', label: 'Manage existing holding at profit' },
]}
/>
</label>
</div>
<div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]">
<label className="space-y-2">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Market price (auto-fetched)</span>
<Input <Input
value={draft.currentMarketPrice} value={draft.currentMarketPrice}
readOnly readOnly
className="bg-[var(--muted)] text-[var(--foreground)]" className="bg-[var(--muted)] text-[var(--foreground)]"
/> />
<div className="space-y-1"> <div className="space-y-1">
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="ux-helper-text">
Uses live market price when available. Outside market hours, it falls back to the latest close. Uses live market price when available. Outside market hours, it falls back to the latest close.
</span> </span>
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="block text-sm text-[var(--muted-foreground)]">
Price source:{' '} Price source:{' '}
<span className="font-semibold text-[var(--foreground)]"> <span className="font-semibold text-[var(--foreground)]">
{marketPriceSource === 'live' {marketPriceSource === 'live'
@ -1174,7 +1179,7 @@ export function SimpleView() {
<Button <Button
type="button" type="button"
onClick={() => void handleLoadMarketPrice()} onClick={() => void handleLoadMarketPrice()}
className="self-end uppercase tracking-[0.2em]" className="self-start md:self-end"
variant="outline" variant="outline"
disabled={loadingPrice || !normalizedSymbol} disabled={loadingPrice || !normalizedSymbol}
> >
@ -1183,13 +1188,22 @@ export function SimpleView() {
Refresh Refresh
</span> </span>
</Button> </Button>
</div>
</div> </div>
</section>
<div className="grid gap-4 md:grid-cols-2"> <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<div className="mb-4">
<div className="text-sm font-bold text-[var(--foreground)]">3. Sizing and notes</div>
<p className="mt-1 text-sm leading-6 text-[var(--muted-foreground)]">
Define the planned exposure and add context for why the plan should run.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2">
{draft.side === 'buy' ? ( {draft.side === 'buy' ? (
<> <>
<label className="space-y-2"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Sizing method</span> <span className="ux-field-label">Sizing method</span>
<Select <Select
value={draft.sizingMode} value={draft.sizingMode}
onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('sizingMode', e.target.value as 'quantity' | 'amount')} onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('sizingMode', e.target.value as 'quantity' | 'amount')}
@ -1200,8 +1214,8 @@ export function SimpleView() {
/> />
</label> </label>
<label className="space-y-2"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-500"> <span className="ux-field-label">
{draft.sizingMode === 'amount' ? 'Spend amount (USD)' : 'Planned quantity'} {draft.sizingMode === 'amount' ? 'Spend amount (USD)' : 'Planned quantity'}
</span> </span>
<Input <Input
@ -1215,17 +1229,17 @@ export function SimpleView() {
}} }}
placeholder={draft.sizingMode === 'amount' ? '250' : '10'} placeholder={draft.sizingMode === 'amount' ? '250' : '10'}
/> />
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="ux-helper-text">
Quantity supports fractional shares/coins. Amount spends an approximate USD budget at trigger time. Quantity supports fractional shares/coins. Amount spends an approximate USD budget at trigger time.
</span> </span>
<span className="block text-[11px] text-[var(--muted-foreground)]"> <span className="ux-helper-text">
Use quantity when you know the units you want. Use amount to budget dollars and let the app derive fractional size at entry. Use quantity when you know the units you want. Use amount to budget dollars and let the app derive fractional size at entry.
</span> </span>
</label> </label>
</> </>
) : ( ) : (
<label className="space-y-2"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-500"> <span className="ux-field-label">
Holding size Holding size
</span> </span>
<Input <Input
@ -1238,8 +1252,8 @@ export function SimpleView() {
</label> </label>
)} )}
<label className="space-y-2"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Notes</span> <span className="ux-field-label">Notes</span>
<Input <Input
value={draft.notes} value={draft.notes}
onChange={(e: ChangeEvent<HTMLInputElement>) => updateDraft('notes', e.target.value)} onChange={(e: ChangeEvent<HTMLInputElement>) => updateDraft('notes', e.target.value)}
@ -1247,11 +1261,13 @@ export function SimpleView() {
/> />
</label> </label>
</div> </div>
</section>
{draft.side === 'buy' && ( {draft.side === 'buy' && (
<div className="grid gap-4 rounded-[1.5rem] border border-[var(--border)] bg-[var(--card-elevated)] p-5 md:grid-cols-[0.55fr_0.45fr]"> <section className="grid gap-4 rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5 md:grid-cols-[0.55fr_0.45fr]">
<div className="space-y-3"> <div className="ux-field-stack">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Drop trigger</p> <p className="text-sm font-bold text-[var(--foreground)]">4. Drop trigger</p>
<p className="ux-helper-text">Set the dip condition that should arm the buy entry.</p>
<Select <Select
value={draft.dropMode} value={draft.dropMode}
onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('dropMode', e.target.value as TriggerMode)} onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('dropMode', e.target.value as TriggerMode)}
@ -1261,8 +1277,8 @@ export function SimpleView() {
]} ]}
/> />
</div> </div>
<label className="space-y-3"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700"> <span className="ux-field-label">
{draft.dropMode === 'dollar' ? 'Drop amount ($)' : 'Drop amount (%)'} {draft.dropMode === 'dollar' ? 'Drop amount ($)' : 'Drop amount (%)'}
</span> </span>
<Input <Input
@ -1271,12 +1287,13 @@ export function SimpleView() {
placeholder={draft.dropMode === 'dollar' ? '0.00' : '0'} placeholder={draft.dropMode === 'dollar' ? '0.00' : '0'}
/> />
</label> </label>
</div> </section>
)} )}
<div className="grid gap-4 rounded-[1.5rem] border border-[var(--border)] bg-[var(--card-elevated)] p-5 md:grid-cols-[0.55fr_0.45fr]"> <section className="grid gap-4 rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5 md:grid-cols-[0.55fr_0.45fr]">
<div className="space-y-3"> <div className="ux-field-stack">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Profit exit</p> <p className="text-sm font-bold text-[var(--foreground)]">{draft.side === 'buy' ? '5. Profit exit' : '4. Profit exit'}</p>
<p className="ux-helper-text">Define when automation should take profit and close or reduce risk.</p>
<Select <Select
value={draft.profitMode} value={draft.profitMode}
onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('profitMode', e.target.value as TriggerMode)} onChange={(e: ChangeEvent<HTMLSelectElement>) => updateDraft('profitMode', e.target.value as TriggerMode)}
@ -1286,8 +1303,8 @@ export function SimpleView() {
]} ]}
/> />
</div> </div>
<label className="space-y-3"> <label className="ux-field-stack">
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700"> <span className="ux-field-label">
{draft.profitMode === 'dollar' ? 'Profit target ($)' : 'Profit target (%)'} {draft.profitMode === 'dollar' ? 'Profit target ($)' : 'Profit target (%)'}
</span> </span>
<Input <Input
@ -1296,13 +1313,13 @@ export function SimpleView() {
placeholder={draft.profitMode === 'dollar' ? '7.50' : '10'} placeholder={draft.profitMode === 'dollar' ? '7.50' : '10'}
/> />
</label> </label>
</div> </section>
{draft.side === 'sell' && ( {draft.side === 'sell' && (
<div className={`rounded-[1.5rem] border px-4 py-4 text-sm ${ <div className={`rounded-[1.5rem] border px-4 py-4 text-sm ${
selectedSellHolding selectedSellHolding
? 'border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300' ? 'border-[var(--bl-success)]/20 bg-[var(--bl-success-muted)] text-[var(--bl-success)]'
: 'border-red-500/20 bg-red-500/10 text-red-700 dark:text-red-300' : 'border-[var(--bl-danger)]/20 bg-[var(--bl-danger-muted)] text-[var(--bl-danger)]'
}`}> }`}>
{selectedSellHolding {selectedSellHolding
? `Holding ready: ${selectedSellHolding.symbol} · ${selectedSellHolding.size} units at ${selectedSellHolding.entryPrice.toFixed(4)}. Executed buys also appear in Portfolio as live positions.` ? `Holding ready: ${selectedSellHolding.symbol} · ${selectedSellHolding.size} units at ${selectedSellHolding.entryPrice.toFixed(4)}. Executed buys also appear in Portfolio as live positions.`
@ -1311,31 +1328,44 @@ export function SimpleView() {
)} )}
{previewText && ( {previewText && (
<div className="rounded-[1.5rem] border border-[var(--border)] bg-[var(--accent-soft)] px-5 py-4 text-sm text-[var(--foreground)]"> <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--accent-soft)] p-5 text-sm leading-6 text-[var(--foreground)]">
{previewText} <div className="mb-1 font-bold">{draft.side === 'buy' ? '6. Review and create' : '5. Review and create'}</div>
</div> <div>{previewText}</div>
<div className="mt-3 text-[var(--muted-foreground)]">
Review the symbol, price source, sizing, and exit target before creating the plan.
</div>
</section>
)} )}
{message && ( {message && (
<div className="rounded-2xl border border-emerald-500/20 bg-emerald-500/10 px-4 py-3 text-sm text-emerald-700 dark:text-emerald-300"> <div className="rounded-2xl border border-[var(--bl-success)]/20 bg-[var(--bl-success-muted)] px-4 py-3 text-sm text-[var(--bl-success)]">
{message} {message}
</div> </div>
)} )}
{error && ( {error && (
<div className="rounded-2xl border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-700 dark:text-red-300"> <div className="rounded-2xl border border-[var(--bl-danger)]/20 bg-[var(--bl-danger-muted)] px-4 py-3 text-sm text-[var(--bl-danger)]">
{error} {error}
</div> </div>
)} )}
<Button <div className="sticky bottom-4 z-[var(--bl-z-sticky)] rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card)]/95 p-4 shadow-[var(--bl-shadow-card)] backdrop-blur">
type="submit" <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
disabled={saveButtonDisabled} <div className="text-sm leading-6 text-[var(--muted-foreground)]">
className="w-full uppercase tracking-[0.24em]" {saveButtonDisabled
size="lg" ? 'Complete the required setup details before creating the plan.'
> : 'Ready to save this plan and arm the next automation step.'}
{submitting ? 'Saving...' : saveButtonLabel} </div>
</Button> <Button
type="submit"
disabled={saveButtonDisabled}
className="w-full md:w-auto"
size="lg"
>
{submitting ? 'Saving...' : saveButtonLabel}
</Button>
</div>
</div>
</form> </form>
</CardContent> </CardContent>
</Card> </Card>