Skip to content

Commit 94c69bc

Browse files
committed
Improve UI for mobile device
1 parent 2a6f2e4 commit 94c69bc

File tree

7 files changed

+188
-64
lines changed

7 files changed

+188
-64
lines changed

src/components/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ const Footer = () => {
1616
<img
1717
src="/goose.png"
1818
alt="TradingGoose Logo"
19-
className="h-5 w-5 sm:h-10 sm:w-10"
19+
className="h-8 w-8 sm:h-10 sm:w-10"
2020
/>
2121
</div>
2222
<div>
2323
<h1 className="text-xl sm:text-2xl font-bold" style={{ color: '#FFCC00' }}>TradingGoose</h1>
24-
<p className="text-xs sm:text-sm text-muted-foreground hidden sm:block">
24+
<p className="text-xs sm:text-sm text-muted-foreground">
2525
AI-Powered Portfolio Management
2626
</p>
2727
</div>

src/components/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function Header() {
5353
if (!hasAiConfig && hasAlpacaConfig) {
5454
return { dotClass: 'bg-yellow-500', message: 'AI Config Required' };
5555
}
56-
return { dotClass: 'bg-red-500', message: 'Require API Configurations' };
56+
return { dotClass: 'bg-red-500', message: 'Configurations Required' };
5757
}, [hasAiConfig, hasAlpacaConfig]);
5858
const primaryRole = getPrimaryRole();
5959

@@ -125,11 +125,11 @@ export default function Header() {
125125
<div className="flex items-center gap-2 sm:gap-4">
126126
<div className="flex items-center gap-2 sm:gap-3">
127127
<div className="p-2 rounded-lg">
128-
<img src="/goose.png" alt="TradingGoose Logo" className="h-5 w-5 sm:h-10 sm:w-10" />
128+
<img src="/goose.png" alt="TradingGoose Logo" className="h-8 w-8 sm:h-10 sm:w-10" />
129129
</div>
130130
<div>
131131
<h1 className="text-xl sm:text-2xl font-bold" style={{ color: '#FFCC00' }}>TradingGoose</h1>
132-
<p className="text-xs sm:text-sm text-muted-foreground hidden sm:block">AI-Powered Portfolio Management</p>
132+
<p className="text-xs sm:text-sm text-muted-foreground">AI-Powered Portfolio Management</p>
133133
</div>
134134
</div>
135135

src/components/PerformanceChart.tsx

Lines changed: 131 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"
22
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
33
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
44
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from "recharts";
5-
import { X, Loader2, AlertCircle, Settings } from "lucide-react";
5+
import { X, Loader2, AlertCircle, Settings, Eye } from "lucide-react";
66
import { Button } from "@/components/ui/button";
77
import { Badge } from "@/components/ui/badge";
88
import { Alert, AlertDescription } from "@/components/ui/alert";
@@ -11,9 +11,11 @@ import { useAuth, isSessionValid, hasAlpacaCredentials } from "@/lib/auth";
1111
import { useToast } from "@/hooks/use-toast";
1212
import { fetchPortfolioDataForPeriod, fetchStockDataForPeriod, type PortfolioData, type StockData, type PortfolioDataPoint } from "@/lib/portfolio-data";
1313
import { useNavigate } from "react-router-dom";
14+
import StockTickerAutocomplete from "@/components/StockTickerAutocomplete";
1415

1516
interface PerformanceChartProps {
1617
selectedStock?: string;
18+
selectedStockDescription?: string;
1719
onClearSelection?: () => void;
1820
}
1921

@@ -57,7 +59,7 @@ const formatValue = (value: number | undefined, isMobile: boolean = false): stri
5759

5860
// Remove the old hardcoded function - we'll create a dynamic one in the component
5961

60-
const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: PerformanceChartProps) => {
62+
const PerformanceChart = React.memo(({ selectedStock: propSelectedStock, selectedStockDescription, onClearSelection }: PerformanceChartProps) => {
6163
const navigate = useNavigate();
6264
const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>("1D");
6365
const [loading, setLoading] = useState(false);
@@ -71,12 +73,38 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
7173
const hasConfiguredAlpaca = useMemo(() => hasAlpacaCredentials(apiSettings), [apiSettings]);
7274
const [hasAlpacaConfig, setHasAlpacaConfig] = useState(hasConfiguredAlpaca);
7375
const { toast } = useToast();
76+
77+
// Internal state for selected stock (can be from prop or from search)
78+
const [internalSelectedStock, setInternalSelectedStock] = useState<string | undefined>(propSelectedStock);
79+
const [tickerInput, setTickerInput] = useState<string>("");
80+
const [stockDescription, setStockDescription] = useState<string>(selectedStockDescription || "");
81+
82+
// Use prop or internal state
83+
const selectedStock = propSelectedStock || internalSelectedStock;
7484

7585
// Track if we've already fetched for current apiSettings and selectedStock
7686
const fetchedRef = useRef<string>('');
7787
const lastFetchTimeRef = useRef<number>(0);
7888
const metricsLoaded = useRef(false);
7989

90+
// Handle viewing a stock from the search bar
91+
const handleViewStock = useCallback(() => {
92+
if (tickerInput.trim()) {
93+
setInternalSelectedStock(tickerInput.trim().toUpperCase());
94+
setTickerInput("");
95+
}
96+
}, [tickerInput]);
97+
98+
// Handle clearing the selection
99+
const handleClearSelection = useCallback(() => {
100+
if (onClearSelection) {
101+
onClearSelection();
102+
}
103+
setInternalSelectedStock(undefined);
104+
setTickerInput("");
105+
setStockDescription("");
106+
}, [onClearSelection]);
107+
80108
const fetchData = useCallback(async (period: string) => {
81109
// Debounce fetches - don't fetch if we just fetched less than 2 seconds ago
82110
const now = Date.now();
@@ -110,6 +138,22 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
110138
}
111139
}));
112140
}
141+
142+
// Fetch stock description if not already fetched
143+
if (!stockDescription && hasConfiguredAlpaca) {
144+
try {
145+
const assetInfo = await alpacaAPI.getAsset(selectedStock).catch(err => {
146+
console.warn(`Could not fetch asset info for ${selectedStock}:`, err);
147+
return null;
148+
});
149+
150+
if (assetInfo?.name) {
151+
setStockDescription(assetInfo.name);
152+
}
153+
} catch (err) {
154+
console.warn(`Could not fetch description for ${selectedStock}:`, err);
155+
}
156+
}
113157
} else {
114158
// Fetch portfolio data for this period if not cached
115159
if (!portfolioData[period]) {
@@ -250,6 +294,15 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
250294
setHasAlpacaConfig(hasConfiguredAlpaca);
251295
}, [hasConfiguredAlpaca]);
252296

297+
// Sync internal state with prop changes
298+
useEffect(() => {
299+
if (propSelectedStock !== internalSelectedStock) {
300+
setInternalSelectedStock(propSelectedStock);
301+
// Update description if provided, otherwise clear it
302+
setStockDescription(selectedStockDescription || "");
303+
}
304+
}, [propSelectedStock, selectedStockDescription]);
305+
253306
// Get real stock metrics from positions
254307
const getStockMetrics = useCallback((symbol: string) => {
255308
const position = positions.find((p: any) => p.symbol === symbol);
@@ -434,58 +487,89 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
434487
<Card>
435488
<CardHeader className="pb-4">
436489
<div className="space-y-2">
437-
<div className="flex items-center justify-between">
438-
<div className="flex items-center gap-2">
439-
<CardTitle>Performance</CardTitle>
440-
{selectedStock && (
441-
<div className="flex items-center gap-2">
442-
<Badge variant="default">{selectedStock}</Badge>
443-
<Button
444-
variant="ghost"
445-
size="icon"
446-
className="h-6 w-6"
447-
onClick={onClearSelection}
448-
>
449-
<X className="h-3 w-3" />
450-
</Button>
451-
</div>
452-
)}
453-
</div>
454-
</div>
455-
<div className="text-xs text-muted-foreground">
456-
Data may be incomplete or delayed.{' '}
457-
<a
458-
href={selectedStock
459-
? `https://app.alpaca.markets/trade/${selectedStock}`
460-
: 'https://app.alpaca.markets/dashboard/overview'
461-
}
462-
target="_blank"
463-
rel="noopener noreferrer"
464-
className="text-primary hover:underline"
465-
>
466-
View on Alpaca →
467-
</a>
468-
</div>
469-
</div>
470-
</CardHeader>
471-
<CardContent>
472-
{!hasAlpacaConfig && (
473-
<Alert className="mb-4">
474-
<AlertCircle className="h-4 w-4" />
475-
<AlertDescription className="flex items-center justify-between">
476-
<span>Connect your Alpaca account to view live performance data</span>
490+
{!hasAlpacaConfig ? (
491+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 rounded-lg border p-4 bg-background">
492+
<div className="flex items-center gap-2">
493+
<AlertCircle className="h-4 w-4 flex-shrink-0" />
494+
<span className="text-sm">Connect your Alpaca account to view live performance data</span>
495+
</div>
477496
<Button
478497
variant="outline"
479498
size="sm"
480-
onClick={() => navigate('/settings?tab=trading')}
481-
className="ml-4"
499+
onClick={() => navigate('/settings')}
500+
className="w-full sm:w-auto"
482501
>
483502
<Settings className="h-4 w-4 mr-2" />
484503
Configure API
485504
</Button>
486-
</AlertDescription>
487-
</Alert>
488-
)}
505+
</div>
506+
) : (
507+
<>
508+
{selectedStock ? (
509+
<Badge variant="default" className="flex items-center justify-between w-full pr-1.5 py-2 px-4">
510+
<div className="flex-1" /> {/* Spacer */}
511+
<span className="text-base">
512+
<span className="font-bold">{selectedStock}</span>
513+
{stockDescription && (
514+
<span className="font-medium text-muted-foreground ml-2">
515+
{stockDescription}
516+
</span>
517+
)}
518+
</span>
519+
<div className="flex-1 flex justify-end"> {/* Right-aligned container */}
520+
<button
521+
className="ml-4 rounded-full hover:bg-primary/30 p-0.5 transition-colors"
522+
onClick={handleClearSelection}
523+
type="button"
524+
aria-label="Clear stock selection"
525+
>
526+
<X className="h-4 w-4" />
527+
</button>
528+
</div>
529+
</Badge>
530+
) : (
531+
<div className="flex items-center gap-2 w-full">
532+
<StockTickerAutocomplete
533+
value={tickerInput}
534+
onChange={setTickerInput}
535+
onEnterPress={handleViewStock}
536+
onSelect={(suggestion) => {
537+
setInternalSelectedStock(suggestion.symbol);
538+
setStockDescription(suggestion.description || "");
539+
setTickerInput("");
540+
}}
541+
placeholder="Search stock..."
542+
className="flex-1"
543+
/>
544+
<Button
545+
size="sm"
546+
onClick={handleViewStock}
547+
disabled={!tickerInput.trim()}
548+
>
549+
<Eye className="h-4 w-4 mr-1" />
550+
View
551+
</Button>
552+
</div>
553+
)}
554+
<div className="text-xs text-muted-foreground">
555+
Data may be incomplete or delayed.{' '}
556+
<a
557+
href={selectedStock
558+
? `https://app.alpaca.markets/trade/${selectedStock}`
559+
: 'https://app.alpaca.markets/dashboard/overview'
560+
}
561+
target="_blank"
562+
rel="noopener noreferrer"
563+
className="text-primary hover:underline"
564+
>
565+
View on Alpaca →
566+
</a>
567+
</div>
568+
</>
569+
)}
570+
</div>
571+
</CardHeader>
572+
<CardContent>
489573
<Tabs value={selectedPeriod} onValueChange={(value) => setSelectedPeriod(value as TimePeriod)} className="space-y-4">
490574
<TabsList className="grid w-full grid-cols-8 max-w-5xl mx-auto">
491575
{periods.map((period) => (

src/components/PortfolioPositions.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ interface Position {
4141
unrealizedPL: number;
4242
unrealizedPLPct: number;
4343
dayChange: number;
44+
description?: string;
4445
}
4546

4647
interface PortfolioPositionsProps {
47-
onSelectStock?: (symbol: string) => void;
48+
onSelectStock?: (symbol: string, description?: string) => void;
4849
selectedStock?: string;
4950
}
5051

@@ -165,15 +166,16 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
165166
return;
166167
}
167168

168-
// Get batch data for all positions to get today's open prices
169+
// Get batch data for all positions to get today's open prices and asset info
169170
const symbols = alpacaPositions.map((pos: any) => pos.symbol);
170171
let batchData: any = {};
171172

172173
if (symbols.length > 0) {
173174
try {
174175
batchData = await alpacaAPI.getBatchData(symbols, {
175176
includeQuotes: true,
176-
includeBars: true
177+
includeBars: true,
178+
includeAssets: true // Add this to get company names
177179
});
178180
} catch (err) {
179181
console.warn('Could not fetch batch data for daily changes:', err);
@@ -198,6 +200,9 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
198200
dayChangePercent = previousClose > 0 ? (priceChange / previousClose) * 100 : 0;
199201
}
200202

203+
// Get company description from asset data
204+
const description = stockData?.asset?.name || pos.symbol;
205+
201206
return {
202207
symbol: pos.symbol,
203208
shares: parseFloat(pos.qty),
@@ -206,7 +211,8 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
206211
marketValue: parseFloat(pos.market_value),
207212
unrealizedPL: parseFloat(pos.unrealized_pl),
208213
unrealizedPLPct: parseFloat(pos.unrealized_plpc) * 100,
209-
dayChange: dayChangePercent
214+
dayChange: dayChangePercent,
215+
description: description
210216
};
211217
});
212218

@@ -600,7 +606,7 @@ export default function PortfolioPositions({ onSelectStock, selectedStock }: Por
600606
key={position.symbol}
601607
className={`cursor-pointer hover:bg-muted/50 transition-colors ${selectedStock === position.symbol ? 'bg-muted' : ''
602608
}`}
603-
onClick={() => onSelectStock?.(position.symbol)}
609+
onClick={() => onSelectStock?.(position.symbol, position.description)}
604610
>
605611
<TableCell className="font-medium w-[60px]">
606612
<Badge variant={selectedStock === position.symbol ? 'default' : 'outline'}>

src/components/StandaloneWatchlist.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ interface WatchlistItem {
4343
}
4444

4545
interface StandaloneWatchlistProps {
46-
onSelectStock?: (ticker: string) => void;
46+
onSelectStock?: (ticker: string, description?: string) => void;
4747
selectedStock?: string;
4848
}
4949

@@ -728,7 +728,7 @@ export default function StandaloneWatchlist({ onSelectStock, selectedStock }: St
728728
onClick={(e) => {
729729
// Only trigger selection if not clicking on buttons
730730
if ((e.target as HTMLElement).closest('button')) return;
731-
onSelectStock?.(item.ticker);
731+
onSelectStock?.(item.ticker, item.description);
732732
}}
733733
>
734734
{/* Mobile and Desktop: Left side - Stock info */}

0 commit comments

Comments
 (0)