diff --git a/web/src/components/StrategyWizard.tsx b/web/src/components/StrategyWizard.tsx
index 6ea880e..c8e1e4b 100644
--- a/web/src/components/StrategyWizard.tsx
+++ b/web/src/components/StrategyWizard.tsx
@@ -1,19 +1,19 @@
-import React, { useState } from 'react';
-import {
- ShieldCheck,
- Scale,
- Zap,
- DollarSign,
- Clock,
- ChevronRight,
- ChevronLeft,
- CheckCircle2,
- AlertTriangle,
- Info,
- Wallet,
- Target,
- Lock
-} from 'lucide-react';
+import React, { useState } from 'react';
+import {
+ ShieldCheck,
+ Scale,
+ Zap,
+ DollarSign,
+ Clock,
+ ChevronRight,
+ ChevronLeft,
+ CheckCircle2,
+ AlertTriangle,
+ Info,
+ Wallet,
+ Target,
+ Lock
+} from 'lucide-react';
import { RISK_STYLE_TEMPLATES } from '../lib/RiskStyleTemplates';
import type { RiskStyleTemplate } from '../lib/RiskStyleTemplates';
import { getUserTier, TIER_POLICIES, isFeatureAllowed } from '../lib/TierPolicy';
@@ -24,22 +24,22 @@ import { createTradeProfile, updateTradeProfile } from '../lib/profileApi';
import { Button } from './ui/button';
import { Card } from './ui/card';
import { Input } from './ui/input';
-
-interface WizardState {
- step: number;
- riskStyle: RiskStyleTemplate | null;
- assets: string[];
- capital: number;
- profitTarget: number;
- hours: '24/7' | 'London + New York' | 'Asia only';
-}
-
-const ASSETS = [
- { id: 'BTC/USDT', label: 'Bitcoin (BTC)' },
- { id: 'ETH/USDT', label: 'Ethereum (ETH)' },
- { id: 'SOL/USDT', label: 'Solana (SOL)' },
-];
-
+
+interface WizardState {
+ step: number;
+ riskStyle: RiskStyleTemplate | null;
+ assets: string[];
+ capital: number;
+ profitTarget: number;
+ hours: '24/7' | 'London + New York' | 'Asia only';
+}
+
+const ASSETS = [
+ { id: 'BTC/USDT', label: 'Bitcoin (BTC)' },
+ { id: 'ETH/USDT', label: 'Ethereum (ETH)' },
+ { id: 'SOL/USDT', label: 'Solana (SOL)' },
+];
+
const SESSION_MAP = {
'24/7': 'LDN,NY,TOK,SYD',
'London + New York': 'LDN,NY',
@@ -75,89 +75,89 @@ const buildStrategyConfig = (state: WizardState) => ({
entryMode: 'both'
}
});
-
+
export const StrategyWizard: React.FC<{
onComplete: () => void;
editingProfile?: any;
profile?: any;
previewAsCustomer?: boolean;
}> = ({ onComplete, editingProfile, profile: userProfile, previewAsCustomer = false }) => {
- const { user } = useAuth();
- const tier = getUserTier(userProfile);
- const policy = TIER_POLICIES[tier];
-
- // Initialize state from existing profile if editing
- const getInitialState = (): WizardState => {
- if (editingProfile) {
- const config = editingProfile.strategy_config;
- const passRatio = config?.execution?.minRulePassRatio || 1.0;
- const style = RISK_STYLE_TEMPLATES.find(t => t.minRulePassRatio === passRatio) || RISK_STYLE_TEMPLATES[1];
-
- let hours: WizardState['hours'] = '24/7';
- const sessions = config?.rules?.find((r: any) => r.ruleId === 'SessionRule')?.params?.sessions;
- if (sessions === 'LDN,NY') hours = 'London + New York';
- if (sessions === 'TOK,SYD') hours = 'Asia only';
-
- return {
- step: 1,
- riskStyle: style,
- assets: String(editingProfile.symbols).split(','),
- capital: editingProfile.allocated_capital,
- profitTarget: config?.riskLimits?.dailyProfitTargetUsd || 100,
- hours
- };
- }
- return {
- step: 1,
- riskStyle: null,
- assets: ['BTC/USDT'],
- capital: 1000,
- profitTarget: 100,
- hours: '24/7'
- };
- };
-
+ const { user } = useAuth();
+ const tier = getUserTier(userProfile);
+ const policy = TIER_POLICIES[tier];
+
+ // Initialize state from existing profile if editing
+ const getInitialState = (): WizardState => {
+ if (editingProfile) {
+ const config = editingProfile.strategy_config;
+ const passRatio = config?.execution?.minRulePassRatio || 1.0;
+ const style = RISK_STYLE_TEMPLATES.find(t => t.minRulePassRatio === passRatio) || RISK_STYLE_TEMPLATES[1];
+
+ let hours: WizardState['hours'] = '24/7';
+ const sessions = config?.rules?.find((r: any) => r.ruleId === 'SessionRule')?.params?.sessions;
+ if (sessions === 'LDN,NY') hours = 'London + New York';
+ if (sessions === 'TOK,SYD') hours = 'Asia only';
+
+ return {
+ step: 1,
+ riskStyle: style,
+ assets: String(editingProfile.symbols).split(','),
+ capital: editingProfile.allocated_capital,
+ profitTarget: config?.riskLimits?.dailyProfitTargetUsd || 100,
+ hours
+ };
+ }
+ return {
+ step: 1,
+ riskStyle: null,
+ assets: ['BTC/USDT'],
+ capital: 1000,
+ profitTarget: 100,
+ hours: '24/7'
+ };
+ };
+
const [state, setState] = useState(getInitialState());
const [loading, setLoading] = useState(false);
const [showBacktest, setShowBacktest] = useState(false);
const { enabled: backtestEnabled, loading: backtestGateLoading } = useBacktestFeatureGate({ previewAsCustomer });
-
- const next = () => setState(s => ({ ...s, step: s.step + 1 }));
- const back = () => setState(s => ({ ...s, step: s.step - 1 }));
-
- const handleSave = async () => {
- if (!user || !state.riskStyle) return;
- setLoading(true);
-
+
+ const next = () => setState(s => ({ ...s, step: s.step + 1 }));
+ const back = () => setState(s => ({ ...s, step: s.step - 1 }));
+
+ const handleSave = async () => {
+ if (!user || !state.riskStyle) return;
+ setLoading(true);
+
const strategy_config = buildStrategyConfig(state);
-
- const payload = {
- name: editingProfile?.name || `${state.riskStyle.label.split(' ')[1]} Bot (${state.assets.join(',')})`,
- user_id: user.id,
- allocated_capital: state.capital,
- risk_per_trade_percent: state.riskStyle.riskPerTrade,
- symbols: state.assets.join(','),
- is_active: editingProfile ? editingProfile.is_active : false, // Preserve status if editing
- strategy_config
- };
-
+
+ const payload = {
+ name: editingProfile?.name || `${state.riskStyle.label.split(' ')[1]} Bot (${state.assets.join(',')})`,
+ user_id: user.id,
+ allocated_capital: state.capital,
+ risk_per_trade_percent: state.riskStyle.riskPerTrade,
+ symbols: state.assets.join(','),
+ is_active: editingProfile ? editingProfile.is_active : false, // Preserve status if editing
+ strategy_config
+ };
+
let result;
if (editingProfile) {
result = await updateTradeProfile(editingProfile.id, payload).then(() => ({ error: null as any })).catch((error) => ({ error }));
} else {
result = await createTradeProfile(payload).then(() => ({ error: null as any })).catch((error) => ({ error }));
}
-
- setLoading(false);
-
- if (result.error) {
- alert('Error saving strategy: ' + result.error.message);
- } else {
- onComplete();
- }
- };
-
- return (
+
+ setLoading(false);
+
+ if (result.error) {
+ alert('Error saving strategy: ' + result.error.message);
+ } else {
+ onComplete();
+ }
+ };
+
+ return (
{/* Progress Bar */}
@@ -168,62 +168,61 @@ export const StrategyWizard: React.FC<{
className={`relative z-10 flex h-10 w-10 items-center justify-center rounded-full text-sm font-bold transition-all duration-300 ${state.step >= i ? 'scale-110 bg-[var(--primary)] text-[var(--primary-foreground)]' : 'border border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)]'
}`}
>
- {state.step > i ? : i}
-
- ))}
-
-
- {/* Step 1: Risk Style */}
- {state.step === 1 && (
-
+ {state.step > i ? : i}
+
+ ))}
+
+
+ {/* Step 1: Risk Style */}
+ {state.step === 1 && (
+
How should this bot behave?
Select a pre-configured risk style. High-frequency options seek more opportunities but require more flexibility.
-
-
- {RISK_STYLE_TEMPLATES.map(style => {
- const isLocked = !isFeatureAllowed(tier, 'risk_style', style.id);
- return (
-
-
+
+ {RISK_STYLE_TEMPLATES.map(style => {
+ const isLocked = !isFeatureAllowed(tier, 'risk_style', style.id);
+ return (
+
+
setState({ ...state, riskStyle: style })}
- className={`${optionBaseClass} ${isLocked ? 'cursor-not-allowed opacity-40 grayscale' : 'hover:scale-[1.01] active:scale-[0.99]'
+ className={`card-button ${optionBaseClass} ${isLocked ? 'cursor-not-allowed opacity-40 grayscale' : 'hover:scale-[1.01] active:scale-[0.99]'
} ${state.riskStyle?.id === style.id
? optionSelectedClass
: optionIdleClass
}`}
- >
-
-
+
+
- {style.id === 'safe' ? : style.id === 'balanced' ? : }
-
-
-
+ {style.id === 'safe' ? : style.id === 'balanced' ? : }
+
+
+
{style.label}
{isLocked && }
- {style.tradeFrequency}
-
+
{style.tradeFrequency}
+
{style.description}
-
- {isLocked && (
-
- Pro/Elite Only
-
- )}
-
- );
- })}
-
-
+
+ {isLocked && (
+
+ Pro/Elite Only
+
+ )}
+
+ );
+ })}
+
+
Continue
-
-
- )}
-
- {/* Step 2: Assets & Capital */}
- {state.step === 2 && (
-
+
+
+ )}
+
+ {/* Step 2: Assets & Capital */}
+ {state.step === 2 && (
+
Assets & Capital
Define what to trade and how much capital to use.
-
-
-
-
+
+
+
+
Select Trading Assets
-
- {ASSETS.map(asset => (
+
+ {ASSETS.map(asset => (
{
const newAssets = state.assets.includes(asset.id)
- ? state.assets.filter(a => a !== asset.id)
- : [...state.assets, asset.id];
- if (newAssets.length > 0) setState({ ...state, assets: newAssets });
- }}
+ ? state.assets.filter(a => a !== asset.id)
+ : [...state.assets, asset.id];
+ if (newAssets.length > 0) setState({ ...state, assets: newAssets });
+ }}
className={`rounded-xl border-2 px-5 py-3 text-sm font-bold transition-all ${state.assets.includes(asset.id)
? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--foreground)]'
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border-strong)]'
@@ -265,12 +264,12 @@ export const StrategyWizard: React.FC<{
>
{asset.label}
- ))}
-
-
-
-
+
+
+
Capital Allocation
@@ -284,109 +283,108 @@ export const StrategyWizard: React.FC<{
/>
Total USD balance this bot is allowed to manage.
-
-
-
+
+
+
- Daily Profit Target
- {!isFeatureAllowed(tier, 'profit_target', policy.maxDailyProfitTargetUsd + 1) && (
- Limited to ${policy.maxDailyProfitTargetUsd}
- )}
-
-
+ Daily Profit Target
+ {!isFeatureAllowed(tier, 'profit_target', policy.maxDailyProfitTargetUsd + 1) && (
+
Limited to ${policy.maxDailyProfitTargetUsd}
+ )}
+
+
{
- const val = Number(e.target.value);
- if (isFeatureAllowed(tier, 'profit_target', val)) {
- setState({ ...state, profitTarget: val });
- }
- }}
+ type="number"
+ disabled={tier === 'free'}
+ value={state.profitTarget}
+ onChange={e => {
+ const val = Number(e.target.value);
+ if (isFeatureAllowed(tier, 'profit_target', val)) {
+ setState({ ...state, profitTarget: val });
+ }
+ }}
className={`h-14 pl-10 font-bold ${tier === 'free' ? 'cursor-not-allowed opacity-50' : ''}`}
/>
-
-
+
+
Once this profit is reached, the bot automatically pauses for the day to lock in gains.
-
-
-
-
-
-
+
+
+
+
+
+
Back
Continue
-
-
- )}
-
- {/* Step 3: Trading Hours */}
- {state.step === 3 && (
-
+ )}
+
+ {/* Step 3: Trading Hours */}
+ {state.step === 3 && (
+
+
Trading Hours
When should the bot look for signals?
-
-
-
- {(['24/7', 'London + New York', 'Asia only'] as const).map(option => (
-
+
+
+ {(['24/7', 'London + New York', 'Asia only'] as const).map(option => (
+
setState({ ...state, hours: option })}
- className={`rounded-2xl border-2 p-6 text-left transition-all ${state.hours === option
+ className={`card-button rounded-2xl border-2 p-6 text-left transition-all ${state.hours === option
? optionSelectedClass
: optionIdleClass
}`}
- >
-
+ >
+
-
-
-
+
+
+
{option}
- {option === '24/7' ? 'Universal coverage, trades any time signals appear.' :
- option === 'London + New York' ? 'Focuses on the most liquid market overlap (07:00 - 21:00 UTC).' :
- 'Optimized for Tokoyo and Sydney sessions.'}
-
+ {option === '24/7' ? 'Universal coverage, trades any time signals appear.' :
+ option === 'London + New York' ? 'Focuses on the most liquid market overlap (07:00 - 21:00 UTC).' :
+ 'Optimized for Tokoyo and Sydney sessions.'}
+
-
+
))}
-
-
-
+
+
+
Back
Continue
-
-
- )}
-
- {/* Step 4: Safety Confirmation */}
- {state.step === 4 && (
-
+ )}
+
+ {/* Step 4: Safety Confirmation */}
+ {state.step === 4 && (
+
+
Safety Confirmation
Review the built-in safeguards protecting your capital.
-
-
+
+
-
+
Auto-Protective Stop Loss
Enabled (Mandatory)
@@ -397,36 +395,36 @@ export const StrategyWizard: React.FC<{
Daily Recovery Halt
- -${Math.floor(state.capital * 0.05)} (5%)
-
-
+ -${Math.floor(state.capital * 0.05)} (5%)
+
+
Market Volatility Guard
- Smart ATR Check
-
-
-
-
-
- Risk management and safety rules are coded into the engine core. These cannot be disabled or bypassed, ensuring your bot always operates within safety boundaries.
+ Smart ATR Check
+
+
+
+
+
+ Risk management and safety rules are coded into the engine core. These cannot be disabled or bypassed, ensuring your bot always operates within safety boundaries.
-
-
+
+
Back
I Understand
-
-
- )}
-
- {/* Step 5: Review & Create */}
- {state.step === 5 && (
-
+ )}
+
+ {/* Step 5: Review & Create */}
+ {state.step === 5 && (
+
+
Ready to Deploy
Verify your bot strategy one last time.
@@ -444,41 +442,41 @@ export const StrategyWizard: React.FC<{
))}
-
-
-
-
+
+
+
+
Target Capital
- $ {state.capital}
-
-
-
+ $ {state.capital}
+
+
+
Profit Threshold
- $ {state.profitTarget}/day
-
-
-
+ $ {state.profitTarget}/day
+
+
+
Risk Level
{(state.riskStyle?.riskPerTrade || 0)}% per trade
-
-
+
+
Active Schedule
{state.hours}
-
-
-
+
+
+
-
-
-
+
+
+
Bots are created in PAUSED mode by default. You must manually enable trading from your dashboard.
-
+
Back
diff --git a/web/src/layout-fixes.css b/web/src/layout-fixes.css
index fe9ae1a..1ba4360 100644
--- a/web/src/layout-fixes.css
+++ b/web/src/layout-fixes.css
@@ -493,3 +493,36 @@
word-break: break-word;
overflow-wrap: anywhere;
}
+
+/* ---------------------------------------------------------------------------
+ Section 25 — card-button utility (UI audit Pattern A)
+ Use on a native when you need a button-shaped CARD with stacked
+ block content. The @bytelyst/ui Button primitive has whitespace-nowrap +
+ fixed h-{size} that collapses multi-line content. This class restores the
+ focus ring + reset behavior without those constraints.
+--------------------------------------------------------------------------- */
+.card-button {
+ appearance: none;
+ -webkit-appearance: none;
+ background: transparent;
+ border: 0;
+ font: inherit;
+ color: inherit;
+ cursor: pointer;
+ display: block;
+ width: 100%;
+ text-align: left;
+ white-space: normal;
+ height: auto;
+ transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
+}
+.card-button:disabled {
+ cursor: not-allowed;
+ opacity: 0.4;
+}
+.card-button:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 2px var(--bl-bg-canvas, #0b0f17),
+ 0 0 0 4px var(--bl-focus-ring, var(--bl-accent, #5A8CFF));
+ border-radius: inherit;
+}
diff --git a/web/src/tabs/MyStrategiesTab.tsx b/web/src/tabs/MyStrategiesTab.tsx
index d82a565..5b97450 100644
--- a/web/src/tabs/MyStrategiesTab.tsx
+++ b/web/src/tabs/MyStrategiesTab.tsx
@@ -5,21 +5,21 @@ import { getUserTier } from '../lib/TierPolicy';
import {
Play,
Pause,
- Trash2,
- Activity,
- TrendingUp,
- Plus,
- Shield,
- Zap,
- Scale,
- Settings,
- ChevronDown,
- ChevronUp,
- Lightbulb,
+ Trash2,
+ Activity,
+ TrendingUp,
+ Plus,
+ Shield,
+ Zap,
+ Scale,
+ Settings,
+ ChevronDown,
+ ChevronUp,
+ Lightbulb,
Cpu,
- Fingerprint,
- Target,
- DollarSign,
+ Fingerprint,
+ Target,
+ DollarSign,
Lock
} from 'lucide-react';
import { StrategyWizard } from '../components/StrategyWizard';
@@ -35,8 +35,8 @@ function getStrategyKindLabel(config: any) {
}
const ActiveStrategyCard: React.FC<{
- profile: any;
- botState: any;
+ profile: any;
+ botState: any;
tier: string;
onToggle: (p: any) => void;
onEdit: (p: any) => void;
@@ -49,57 +49,57 @@ const ActiveStrategyCard: React.FC<{
const isAggressive = config?.execution?.minRulePassRatio < 0.9;
const isSafe = config?.execution?.minRulePassRatio >= 1.0;
const strategyKindLabel = getStrategyKindLabel(config);
-
- const explanation = getStrategyExplanation(profile, botState);
-
- return (
+
+ const explanation = getStrategyExplanation(profile, botState);
+
+ return (
-
- {/* 1. Direct Status Strip */}
-
+
+ {/* 1. Direct Status Strip */}
+
-
- {/* 2. Header Area */}
-
-
-
+
+ {/* 2. Header Area */}
+
+
+
- {isSafe ? : isAggressive ? : }
-
-
+ }}>
+ {isSafe ? : isAggressive ? : }
+
+
Active Strategy
-
- {profile.is_active ? 'Running' : 'Paused'}
-
-
+
+ {profile.is_active ? 'Running' : 'Paused'}
+
+
{onBacktest && (
@@ -108,128 +108,128 @@ const ActiveStrategyCard: React.FC<{
} onClick={() => onEdit(profile)} style={{ width: '38px', height: '38px', borderRadius: '10px', background: 'var(--bl-surface-highlight)', border: '1px solid var(--bl-border-subtle)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--bl-text-quiet)', cursor: 'pointer' }} className="icon-btn-hover" />
} onClick={() => onDelete(profile.id)} style={{ width: '38px', height: '38px', borderRadius: '10px', background: 'var(--bl-surface-highlight)', border: '1px solid var(--bl-border-subtle)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--bl-text-quiet)', cursor: 'pointer' }} className="icon-btn-hover" />
-
-
- {/* 3. Identity */}
-
-
{profile.name}
-
+
+
+ {/* 3. Identity */}
+
+
{profile.name}
+
- {profile.symbols}
+ {profile.symbols}
{strategyKindLabel}
-
- {/* 4. Operational DNA (Specs) */}
-
- {[
+
+ {/* 4. Operational DNA (Specs) */}
+
+ {[
{ label: 'Allocation', value: `$${profile.allocated_capital.toLocaleString()}`, icon:
, color: 'var(--bl-success)' },
{ label: 'PnL (Global)', value: '$0.00', icon:
, color: 'var(--bl-info-strong)' },
{ label: 'Target', value: `$${config?.riskLimits?.dailyProfitTargetUsd || 0}`, icon:
, color: 'var(--bl-warning)' },
{ label: 'Latency', value: '5ms', icon:
, color: 'var(--bl-danger)' }
- ].map((spec, i) => (
-
(
+
+ padding: '16px',
+ borderRadius: '20px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '6px'
+ }}>
- {spec.icon} {spec.label}
-
-
{spec.value}
-
- ))}
-
-
- {/* 5. Health Diagnostic (Education Layer) */}
-
+ ))}
+
+
+ {/* 5. Health Diagnostic (Education Layer) */}
+
+
onToggleExpand(profile.id)}
- variant="ghost"
+ className="card-button"
style={{
- width: '100%',
- padding: '16px',
- borderRadius: '20px',
+ width: '100%',
+ padding: '16px',
+ borderRadius: '20px',
background: 'var(--bl-surface-highlight)',
border: '1px solid var(--bl-border-subtle)',
- display: 'flex',
- flexDirection: 'column',
- gap: '8px',
- cursor: 'pointer',
- textAlign: 'left'
- }}
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '8px',
+ cursor: 'pointer',
+ textAlign: 'left'
+ }}
>
-
+
- Diagnostic Intelligence {tier === 'free' && }
-
+
Diagnostic Intelligence {tier === 'free' &&
}
+
{isExpanded ?
:
}
-
+
- {explanation.reason}
-
- {isExpanded && explanation.recommendation && (
-
+ {isExpanded && explanation.recommendation && (
+
+ borderRadius: '12px'
+ }}>
- Optimization
-
+
Optimization
+
{explanation.recommendation}
-
- )}
-
-
-
- {/* 6. Action */}
-
+
+ )}
+
+
+
+ {/* 6. Action */}
+
onToggle(profile)}
variant={profile.is_active ? 'outline' : 'primary'}
style={{
- width: '100%',
- height: '56px',
+ width: '100%',
+ height: '56px',
background: profile.is_active ? 'var(--bl-surface-highlight)' : 'var(--bl-success)',
color: profile.is_active ? 'var(--bl-text-secondary)' : 'black',
- borderRadius: '18px',
+ borderRadius: '18px',
border: profile.is_active ? '1px solid var(--bl-border-subtle)' : 'none',
- fontWeight: 900,
- fontSize: '12px',
- textTransform: 'uppercase',
- cursor: 'pointer',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- gap: '12px',
+ fontWeight: 900,
+ fontSize: '12px',
+ textTransform: 'uppercase',
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: '12px',
boxShadow: profile.is_active ? 'none' : '0 12px 36px -12px color-mix(in oklab, var(--bl-success) 40%, transparent)',
- transition: 'all 0.2s',
- letterSpacing: '1.5px'
- }}
- className="action-btn-hover"
+ transition: 'all 0.2s',
+ letterSpacing: '1.5px'
+ }}
+ className="action-btn-hover"
>
- {profile.is_active ? (
- <> PAUSE TRADING>
- ) : (
- <> START TRADING>
- )}
+ {profile.is_active ? (
+ <> PAUSE TRADING>
+ ) : (
+ <> START TRADING>
+ )}
-
-
-
-
- );
-};
-
+ .action-btn-hover:hover {
+ filter: brightness(1.1);
+ transform: scale(1.02);
+ }
+ `}
+
+ );
+};
+
export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewAsCustomer?: boolean }> = ({
botState,
alerts = [],
previewAsCustomer = false
}) => {
const { user, profile: userProfile } = useAuth();
- const [profiles, setProfiles] = useState
([]);
- const [isLoading, setIsLoading] = useState(true);
+ const [profiles, setProfiles] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
const [showWizard, setShowWizard] = useState(false);
const [editingProfile, setEditingProfile] = useState(null);
const [expandedExplanations, setExpandedExplanations] = useState>({});
@@ -263,7 +263,7 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
const tier = getUserTier(userProfile);
const { enabled: backtestEnabled, loading: backtestGateLoading } = useBacktestFeatureGate({ previewAsCustomer });
-
+
const fetchProfiles = async () => {
if (!user) return;
setIsLoading(true);
@@ -271,13 +271,13 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
setProfiles(data || []);
setIsLoading(false);
};
-
- useEffect(() => {
- fetchProfiles();
- window.addEventListener('profiles-updated', fetchProfiles);
- return () => window.removeEventListener('profiles-updated', fetchProfiles);
- }, [user]);
-
+
+ useEffect(() => {
+ fetchProfiles();
+ window.addEventListener('profiles-updated', fetchProfiles);
+ return () => window.removeEventListener('profiles-updated', fetchProfiles);
+ }, [user]);
+
const toggleBot = async (profile: any) => {
try {
await setTradeProfileActive(profile.id, !profile.is_active);
@@ -296,11 +296,11 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
// existing UI remains silent on delete failure
}
};
-
- if (showWizard) {
- return (
-
-
+
+ if (showWizard) {
+ return (
+
+
{
@@ -312,7 +312,7 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
>
← Back to My Strategies
-
+
-
- );
- }
-
+ }}
+ />
+
+ );
+ }
+
return (
@@ -353,67 +353,67 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
-
- {/* Contextual Intelligence Row: Recent Activity + Symbol Volatility */}
- {(() => {
- const activeSymbols = [...new Set(profiles.flatMap(p => p.symbols?.split(',').map((s: string) => s.trim()) || []))];
- const recentAlerts = [...alerts].reverse().slice(0, 5);
- const symbolVolatility = activeSymbols
- .filter(s => botState?.symbols?.[s])
- .map(s => ({ symbol: s, change: botState.symbols[s].change24h || 0 }))
- .sort((a, b) => Math.abs(b.change) - Math.abs(a.change));
-
- return (
-
- {/* Recent Activity */}
+
+ {/* Contextual Intelligence Row: Recent Activity + Symbol Volatility */}
+ {(() => {
+ const activeSymbols = [...new Set(profiles.flatMap(p => p.symbols?.split(',').map((s: string) => s.trim()) || []))];
+ const recentAlerts = [...alerts].reverse().slice(0, 5);
+ const symbolVolatility = activeSymbols
+ .filter(s => botState?.symbols?.[s])
+ .map(s => ({ symbol: s, change: botState.symbols[s].change24h || 0 }))
+ .sort((a, b) => Math.abs(b.change) - Math.abs(a.change));
+
+ return (
+
+ {/* Recent Activity */}
-
+
-
- {recentAlerts.map((alert, i) => {
+
+
+ {recentAlerts.map((alert, i) => {
const mins = Math.floor((Date.now() - alert.timestamp) / 60000);
- const timeAgo = mins < 1 ? 'just now' : mins < 60 ? `${mins}m ago` : `${Math.floor(mins / 60)}h ago`;
- return (
+ const timeAgo = mins < 1 ? 'just now' : mins < 60 ? `${mins}m ago` : `${Math.floor(mins / 60)}h ago`;
+ return (
{alert.symbol}
{alert.message}
{timeAgo}
-
- );
- })}
+
+ );
+ })}
{recentAlerts.length === 0 &&
No activity yet...
}
-
-
-
- {/* Symbol-Specific Volatility */}
+
+
+
+ {/* Symbol-Specific Volatility */}
-
+
Your Markets (24h)
-
-
- {symbolVolatility.map(({ symbol, change }) => (
+
+
+ {symbolVolatility.map(({ symbol, change }) => (
-
{symbol}
-
+
{symbol}
+
= 0 ? 'var(--bl-success)' : 'var(--bl-danger)', borderRadius: '99px' }} />
= 0 ? 'var(--bl-success)' : 'var(--bl-danger)', minWidth: '55px', textAlign: 'right' }}>
- {change >= 0 ? '+' : ''}{change.toFixed(2)}%
-
-
-
- ))}
+ {change >= 0 ? '+' : ''}{change.toFixed(2)}%
+
+
+
+ ))}
{symbolVolatility.length === 0 &&
Deploy a strategy to see its market data
}
-
-
-
- );
- })()}
+
+
+
+ );
+ })()}
setExpandedExplanations(prev => ({ ...prev, [id]: !prev[id] }))}
/>
))}
-
- {profiles.length === 0 && !isLoading && (
-
+ borderRadius: '40px',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ textAlign: 'center'
+ }}>
No strategies yet
Create your first strategy to start monitoring markets and testing automated execution.
@@ -474,16 +474,16 @@ export const MyStrategiesTab: React.FC<{ botState: any; alerts?: any[]; previewA
>
GET STARTED
-
- )}
-
-
-
-
- );
-};
+
+ )}
+
+
+
+
+ );
+};
diff --git a/web/src/views/SimpleView.tsx b/web/src/views/SimpleView.tsx
index 297b4cf..5525f42 100644
--- a/web/src/views/SimpleView.tsx
+++ b/web/src/views/SimpleView.tsx
@@ -1006,15 +1006,14 @@ export function SimpleView() {
-
{
dispatch({ type: 'clear-feedback' });
dispatch({ type: 'set-selected-holding-trade-id', value: null });
updateDraft('side', 'buy');
}}
- variant="ghost"
- className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
+ className={`card-button h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
draft.side === 'buy'
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
: 'border-[var(--border)] bg-[var(--card-elevated)]'
@@ -1024,8 +1023,8 @@ export function SimpleView() {
New short-term buy plan
Arm a dip-buy trigger and let the app manage the profit exit after fill.
-
-
+ {
dispatch({ type: 'clear-feedback' });
@@ -1035,8 +1034,7 @@ export function SimpleView() {
updateDraft('side', 'sell');
}
}}
- variant="ghost"
- className={`h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
+ className={`card-button h-auto justify-start rounded-[1.25rem] border px-5 py-5 text-left transition ${
draft.side === 'sell'
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
: 'border-[var(--border)] bg-[var(--card-elevated)]'
@@ -1046,7 +1044,7 @@ export function SimpleView() {
Manage an existing holding
Choose a filled holding and place it back under managed profit-taking.
-
+