refactor(ui): replace one-off visual language with shared Badge components
- Replace .stat-chip with Badge in PresetMarketplace, BacktestTab, MembershipTab - Replace .saved-setup-id-chip with Badge in SimpleView - Replace .screener-sector-chip with Badge in ScreenerView - Replace .health-pill with Badge in TradeProfileManager - Remove CSS definitions for one-off classes from index.css - All components now use shared Badge from product adapter - Verify: audit:ui (0 findings), audit:ui:strict (0 findings), typecheck, build
This commit is contained in:
parent
a17de130c7
commit
94ce743bd0
@ -19,6 +19,7 @@ import { STRATEGY_PRESETS } from '../lib/PresetRegistry';
|
|||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
||||||
import { PageHeader } from './ui/page-header';
|
import { PageHeader } from './ui/page-header';
|
||||||
|
import { Badge } from './ui/Primitives';
|
||||||
|
|
||||||
interface PresetMarketplaceProps {
|
interface PresetMarketplaceProps {
|
||||||
onSelect: (preset: StrategyPreset) => void;
|
onSelect: (preset: StrategyPreset) => void;
|
||||||
@ -74,7 +75,7 @@ const StrategyMarketplaceCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-chip">V{index + 1}.4</div>
|
<Badge variant="neutral">V{index + 1}.4</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@ -185,7 +186,7 @@ export const PresetMarketplace: React.FC<PresetMarketplaceProps> = ({ onSelect,
|
|||||||
description="Browse reusable strategy profiles with preconfigured risk posture and execution bias."
|
description="Browse reusable strategy profiles with preconfigured risk posture and execution bias."
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="stat-chip">{allPresets.length} presets</div>
|
<Badge variant="neutral">{allPresets.length} presets</Badge>
|
||||||
{onClose ? (
|
{onClose ? (
|
||||||
<Button variant="outline" onClick={onClose}>
|
<Button variant="outline" onClick={onClose}>
|
||||||
Return
|
Return
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { Button } from './ui/button';
|
|||||||
import { Card } from './ui/card';
|
import { Card } from './ui/card';
|
||||||
import { Input } from './ui/input';
|
import { Input } from './ui/input';
|
||||||
import { Select } from './ui/select';
|
import { Select } from './ui/select';
|
||||||
import { Textarea } from './ui/Primitives';
|
import { Textarea, Badge } from './ui/Primitives';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
// ChatControl is now rendered globally in App.tsx
|
// ChatControl is now rendered globally in App.tsx
|
||||||
|
|
||||||
@ -642,7 +642,6 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
const bp = botState.accountSnapshot?.buying_power ?? 0;
|
const bp = botState.accountSnapshot?.buying_power ?? 0;
|
||||||
const isCovered = (p.allocated_capital || 0) <= bp;
|
const isCovered = (p.allocated_capital || 0) <= bp;
|
||||||
const coverageStatus = isCovered ? 'Covered' : 'Insufficient funds';
|
const coverageStatus = isCovered ? 'Covered' : 'Insufficient funds';
|
||||||
const coverageColor = isCovered ? 'ok' : 'drift'; // reuse 'drift' style for warning
|
|
||||||
|
|
||||||
const lifecycleStatus = stats.tradeCount > 0 ? 'OK' : (p.is_active ? 'Blocked' : 'Idle');
|
const lifecycleStatus = stats.tradeCount > 0 ? 'OK' : (p.is_active ? 'Blocked' : 'Idle');
|
||||||
const reconciliationDrift = (botState.health?.reconciliationMismatchCount || 0) > 0 ? 'Drift' : 'Clean';
|
const reconciliationDrift = (botState.health?.reconciliationMismatchCount || 0) > 0 ? 'Drift' : 'Clean';
|
||||||
@ -752,15 +751,15 @@ export const TradeProfileManager = ({ botState = DEFAULT_BOT_STATE }: TradeProfi
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="profile-health-strip flex flex-wrap gap-1 px-4 py-2">
|
<div className="profile-health-strip flex flex-wrap gap-1 px-4 py-2">
|
||||||
<span className={`health-pill ${capitalHealth === 'Issue' ? 'issue' : 'ok'}`}>Capital: {capitalHealth}</span>
|
<Badge variant={capitalHealth === 'Issue' ? 'error' : 'success'}>Capital: {capitalHealth}</Badge>
|
||||||
{botState.accountSnapshot && (
|
{botState.accountSnapshot && (
|
||||||
<span className={`health-pill ${coverageColor}`} title={`Buying Power: $${bp.toFixed(2)}`}>
|
<Badge variant={isCovered ? 'success' : 'warning'} title={`Buying Power: $${bp.toFixed(2)}`}>
|
||||||
LP: {coverageStatus}
|
LP: {coverageStatus}
|
||||||
</span>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<span className={`health-pill ${lifecycleStatus === 'OK' ? 'ok' : 'blocked'}`}>Lifecycle: {lifecycleStatus}</span>
|
<Badge variant={lifecycleStatus === 'OK' ? 'success' : 'error'}>Lifecycle: {lifecycleStatus}</Badge>
|
||||||
<span className={`health-pill ${reconciliationDrift === 'Drift' ? 'drift' : 'clean'}`}>Reconciliation: {reconciliationDrift}</span>
|
<Badge variant={reconciliationDrift === 'Drift' ? 'warning' : 'success'}>Reconciliation: {reconciliationDrift}</Badge>
|
||||||
<span className={`health-pill ${lockStatus === 'Contended' ? 'drift' : 'ok'}`}>Locks: {lockStatus}</span>
|
<Badge variant={lockStatus === 'Contended' ? 'warning' : 'success'}>Locks: {lockStatus}</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Win rate section */}
|
{/* Win rate section */}
|
||||||
|
|||||||
@ -1080,26 +1080,6 @@ body {
|
|||||||
box-shadow: var(--card-shadow);
|
box-shadow: var(--card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--card-elevated);
|
|
||||||
color: var(--muted-foreground);
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-chip[data-tone="success"] {
|
|
||||||
color: #16a34a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-chip[data-tone="danger"] {
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trading-sidebar-nav {
|
.trading-sidebar-nav {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -2238,11 +2218,6 @@ body {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved-setup-id-chip,
|
|
||||||
.saved-setup-updated {
|
|
||||||
text-transform: none !important;
|
|
||||||
letter-spacing: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.saved-setup-timeline {
|
.saved-setup-timeline {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -2332,19 +2307,6 @@ body {
|
|||||||
gap: 7px;
|
gap: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screener-sector-chip {
|
|
||||||
min-height: 32px !important;
|
|
||||||
border-radius: 999px !important;
|
|
||||||
padding: 0 12px !important;
|
|
||||||
font-size: 11px !important;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.screener-sector-chip[data-active="true"] {
|
|
||||||
border-color: var(--accent) !important;
|
|
||||||
background: var(--accent-soft) !important;
|
|
||||||
color: var(--accent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.screener-more-select {
|
.screener-more-select {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { fetchTradeProfiles } from '../lib/profileApi';
|
|||||||
import { PageHeader } from '../components/ui/page-header';
|
import { PageHeader } from '../components/ui/page-header';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
|
import { Badge } from '../components/ui/Primitives';
|
||||||
import { Select } from '../components/ui/select';
|
import { Select } from '../components/ui/select';
|
||||||
|
|
||||||
interface BacktestTabProps {
|
interface BacktestTabProps {
|
||||||
@ -84,18 +85,18 @@ export const BacktestTab: React.FC<BacktestTabProps> = ({ previewAsCustomer = fa
|
|||||||
<Card style={{ marginBottom: 16 }}>
|
<Card style={{ marginBottom: 16 }}>
|
||||||
<CardContent style={{ padding: '14px 16px' }}>
|
<CardContent style={{ padding: '14px 16px' }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '10px' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '10px' }}>
|
||||||
<span className="stat-chip">
|
<Badge variant="neutral">
|
||||||
Role: {isAdminAccount ? 'admin' : 'customer'}
|
Role: {isAdminAccount ? 'admin' : 'customer'}
|
||||||
</span>
|
</Badge>
|
||||||
<span className="stat-chip">
|
<Badge variant="neutral">
|
||||||
Preview Mode: {previewAsCustomer ? 'customer view' : 'off'}
|
Preview Mode: {previewAsCustomer ? 'customer view' : 'off'}
|
||||||
</span>
|
</Badge>
|
||||||
<span className="stat-chip" data-tone={runtimeFlags.enableBacktest ? 'success' : 'danger'}>
|
<Badge variant={runtimeFlags.enableBacktest ? 'success' : 'error'}>
|
||||||
ENABLE_BACKTEST: {String(runtimeFlags.enableBacktest)}
|
ENABLE_BACKTEST: {String(runtimeFlags.enableBacktest)}
|
||||||
</span>
|
</Badge>
|
||||||
<span className="stat-chip" data-tone={runtimeFlags.customerEnabled ? 'success' : 'danger'}>
|
<Badge variant={runtimeFlags.customerEnabled ? 'success' : 'error'}>
|
||||||
BACKTEST_CUSTOMER_ENABLED: {String(runtimeFlags.customerEnabled)}
|
BACKTEST_CUSTOMER_ENABLED: {String(runtimeFlags.customerEnabled)}
|
||||||
</span>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{!backtestGateLoading && !backtestEnabled && (
|
{!backtestGateLoading && !backtestEnabled && (
|
||||||
<p style={{ margin: 0, fontSize: '12px', color: 'var(--bl-danger-muted)' }}>
|
<p style={{ margin: 0, fontSize: '12px', color: 'var(--bl-danger-muted)' }}>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { TIER_POLICIES } from '../lib/TierPolicy';
|
|||||||
import { PageHeader } from '../components/ui/page-header';
|
import { PageHeader } from '../components/ui/page-header';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
|
import { Badge } from '../components/ui/Primitives';
|
||||||
|
|
||||||
const PlanCard: React.FC<{
|
const PlanCard: React.FC<{
|
||||||
id: string;
|
id: string;
|
||||||
@ -66,9 +67,9 @@ const PlanCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isElite && (
|
{isElite && (
|
||||||
<span className="stat-chip" data-tone="success">
|
<Badge variant="success">
|
||||||
Peak
|
Peak
|
||||||
</span>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -77,10 +78,10 @@ const PlanCard: React.FC<{
|
|||||||
<h3 style={{ fontSize: 42, fontWeight: 950, color: 'var(--foreground)', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
|
<h3 style={{ fontSize: 42, fontWeight: 950, color: 'var(--foreground)', letterSpacing: '-0.04em', margin: 0 }}>{price}</h3>
|
||||||
<span style={{ fontSize: 14, color: 'var(--muted-foreground)', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
|
<span style={{ fontSize: 14, color: 'var(--muted-foreground)', fontWeight: 900, textTransform: 'uppercase' }}>/mo</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-chip">
|
<Badge variant="neutral">
|
||||||
<Sparkles size={14} style={{ color: 'var(--bl-warning)' }} />
|
<Sparkles size={14} style={{ color: 'var(--bl-warning)' }} />
|
||||||
Professional grade DNA
|
Professional grade DNA
|
||||||
</div>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style={{
|
<p style={{
|
||||||
@ -163,9 +164,9 @@ export const MembershipTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 28 }}>
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 28 }}>
|
||||||
<span className="stat-chip">3 tiers</span>
|
<Badge variant="neutral">3 tiers</Badge>
|
||||||
<span className="stat-chip">Preview pricing</span>
|
<Badge variant="neutral">Preview pricing</Badge>
|
||||||
<span className="stat-chip">Product direction only</span>
|
<Badge variant="neutral">Product direction only</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { tradingRuntime } from '../lib/runtime';
|
|||||||
import { createRequestId } from '../../../shared/request-id.js';
|
import { createRequestId } from '../../../shared/request-id.js';
|
||||||
import { SkeletonBlock } from '../components/Skeleton';
|
import { SkeletonBlock } from '../components/Skeleton';
|
||||||
import { PageHeader } from '../components/ui/page-header';
|
import { PageHeader } from '../components/ui/page-header';
|
||||||
import { Button, Input, Select } from '../components/ui/Primitives';
|
import { Button, Input, Select, Badge } from '../components/ui/Primitives';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
@ -200,16 +200,14 @@ export function ScreenerView() {
|
|||||||
<div className="screener-sector-row">
|
<div className="screener-sector-row">
|
||||||
<SlidersHorizontal size={13} color="var(--muted-foreground)" />
|
<SlidersHorizontal size={13} color="var(--muted-foreground)" />
|
||||||
{SECTORS.slice(0, 6).map(s => (
|
{SECTORS.slice(0, 6).map(s => (
|
||||||
<Button
|
<Badge
|
||||||
key={s}
|
key={s}
|
||||||
|
variant={sector === s ? 'info' : 'neutral'}
|
||||||
|
className="cursor-pointer"
|
||||||
onClick={() => setSector(s)}
|
onClick={() => setSector(s)}
|
||||||
variant={sector === s ? 'secondary' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
className="screener-sector-chip"
|
|
||||||
data-active={sector === s}
|
|
||||||
>
|
>
|
||||||
{s}
|
{s}
|
||||||
</Button>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
<Select
|
<Select
|
||||||
aria-label="More sectors"
|
aria-label="More sectors"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
import { fetchTradeProfiles, type TradeProfilePayload } from '../lib/profileApi';
|
import { fetchTradeProfiles, type TradeProfilePayload } from '../lib/profileApi';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
||||||
import { PageHeader } from '../components/ui/page-header';
|
import { PageHeader } from '../components/ui/page-header';
|
||||||
import { Button, Input, Select } from '../components/ui/Primitives';
|
import { Button, Input, Select, Badge } from '../components/ui/Primitives';
|
||||||
import {
|
import {
|
||||||
DEFAULT_TRADE_PLANS_UI_STATE,
|
DEFAULT_TRADE_PLANS_UI_STATE,
|
||||||
reduceTradePlansUiState,
|
reduceTradePlansUiState,
|
||||||
@ -1515,30 +1515,18 @@ export function SimpleView() {
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{(runtimeSnapshot?.tradeId || entry.linked_trade_id) ? (
|
{(runtimeSnapshot?.tradeId || entry.linked_trade_id) ? (
|
||||||
<Button
|
<Badge variant="neutral" className="cursor-pointer" onClick={() => void copyIdentifier('trade', String(runtimeSnapshot?.tradeId || entry.linked_trade_id))}>
|
||||||
type="button"
|
|
||||||
variant="subtle"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => void copyIdentifier('trade', String(runtimeSnapshot?.tradeId || entry.linked_trade_id))}
|
|
||||||
className="saved-setup-id-chip"
|
|
||||||
>
|
|
||||||
{copiedKey === `trade:${String(runtimeSnapshot?.tradeId || entry.linked_trade_id)}`
|
{copiedKey === `trade:${String(runtimeSnapshot?.tradeId || entry.linked_trade_id)}`
|
||||||
? 'Trade copied'
|
? 'Trade copied'
|
||||||
: `Trade ${String(runtimeSnapshot?.tradeId || entry.linked_trade_id).slice(0, 18)}…`}
|
: `Trade ${String(runtimeSnapshot?.tradeId || entry.linked_trade_id).slice(0, 18)}…`}
|
||||||
</Button>
|
</Badge>
|
||||||
) : null}
|
) : null}
|
||||||
{runtimeSnapshot?.orderId ? (
|
{runtimeSnapshot?.orderId ? (
|
||||||
<Button
|
<Badge variant="neutral" className="cursor-pointer" onClick={() => void copyIdentifier('order', runtimeSnapshot.orderId)}>
|
||||||
type="button"
|
|
||||||
variant="subtle"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => void copyIdentifier('order', runtimeSnapshot.orderId)}
|
|
||||||
className="saved-setup-id-chip"
|
|
||||||
>
|
|
||||||
{copiedKey === `order:${runtimeSnapshot.orderId}`
|
{copiedKey === `order:${runtimeSnapshot.orderId}`
|
||||||
? 'Order copied'
|
? 'Order copied'
|
||||||
: `Order ${runtimeSnapshot.orderId.slice(0, 12)}…`}
|
: `Order ${runtimeSnapshot.orderId.slice(0, 12)}…`}
|
||||||
</Button>
|
</Badge>
|
||||||
) : null}
|
) : null}
|
||||||
{updatedAt ? (
|
{updatedAt ? (
|
||||||
<span className="saved-setup-updated">
|
<span className="saved-setup-updated">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user