Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"stop": [
{
"command": ".cursor/hooks/stop-quality-check.sh",
"timeout": 600,
"loop_limit": 0
"timeout": 600
}
]
}
Expand Down
3 changes: 3 additions & 0 deletions .cursor/hooks/after-file-edit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

set -euo pipefail

# Cursor hook processes often inherit a minimal PATH; bun is usually in ~/.bun/bin.
export PATH="${HOME}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:${PATH:-}"

input=$(cat)
file_path=$(
printf '%s' "$input" | python3 -c "import sys, json; print(json.load(sys.stdin).get('file_path', ''))"
Expand Down
3 changes: 3 additions & 0 deletions .cursor/hooks/stop-quality-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

set -euo pipefail

# Cursor hook processes often inherit a minimal PATH; bun is usually in ~/.bun/bin.
export PATH="${HOME}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:${PATH:-}"

cat >/dev/null

project_root="${CURSOR_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
Expand Down
140 changes: 75 additions & 65 deletions app/(main)/home-page-client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import dynamic from "next/dynamic";
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
SymbolData,
Expand All @@ -23,6 +23,12 @@ import { EXPLANATIONS_PROVIDER_CHANGED_EVENT } from "@/lib/explanation-provider"
import { fetchAIPredictionForCurrentProvider } from "@/lib/local-ollama-ai-prediction";
import { fetchStockOfTheDayForCurrentProvider } from "@/lib/local-ollama-stock-of-the-day";
import { MARKET_UI_COPY } from "@/lib/market-ui-copy";
import {
fetchHistoricalData,
fetchPrimarySymbolData,
fetchSecondarySymbolData,
} from "@/lib/home-page-symbol-fetch";
import { normalizeMarketSymbol } from "@/lib/market-symbol";
import { AIPredictionPanel } from "@/components/AIPredictionPanel";
import { HomeHub } from "@/components/HomeHub";
import { StockOfTheDayPanel } from "@/components/StockOfTheDayPanel";
Expand Down Expand Up @@ -122,6 +128,8 @@ export function HomePageClient() {
const [activeTab, setActiveTab] = useState<TabType>("overview");
const [symbolData, setSymbolData] = useState<SymbolData | null>(null);
const [historicalData, setHistoricalData] = useState<PriceData[]>([]);
const [loadedHistoryRange, setLoadedHistoryRange] = useState<TimeRange>("1M");
const [historyLoading, setHistoryLoading] = useState(false);
const [timeRange, setTimeRange] = useState<TimeRange>("1M");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand All @@ -147,89 +155,89 @@ export function HomePageClient() {
);

useEffect(() => {
if (symbolFromUrl && symbolFromUrl.trim()) {
setSelectedSymbol(symbolFromUrl.trim().toUpperCase());
} else {
setSelectedSymbol(null);
}
setSelectedSymbol(normalizeMarketSymbol(symbolFromUrl));
}, [symbolFromUrl]);

const clearSymbol = () => {
setSelectedSymbol(null);
router.replace("/", { scroll: false });
};

useEffect(() => {
const fetchSymbolData = async () => {
if (!selectedSymbol) return;

setLoading(true);
setError(null);
const loadedSymbolRef = useRef<string | null>(null);

try {
const symbolResponse = await fetch(
`/api/market/symbol/${selectedSymbol}`
);
if (!symbolResponse.ok) {
const body = (await symbolResponse.json().catch(() => ({}))) as {
error?: string;
};
throw new Error(body.error ?? MARKET_UI_COPY.load.symbolData);
}
const symbolResult = await symbolResponse.json();
setSymbolData(symbolResult.data);

const historicalResponse = await fetch(
`/api/market/historical/${selectedSymbol}?range=${timeRange}`
);
if (!historicalResponse.ok) {
throw new Error(MARKET_UI_COPY.load.historicalData);
}
const historicalResult = await historicalResponse.json();
setHistoricalData(historicalResult.data);
useEffect(() => {
if (!selectedSymbol) {
loadedSymbolRef.current = null;
return;
}

const indicatorsResponse = await fetch(
`/api/market/indicators/${selectedSymbol}`
);
if (indicatorsResponse.ok) {
const indicatorsResult = await indicatorsResponse.json();
setTechnicalIndicators(indicatorsResult.data);
let cancelled = false;
const symbolChanged = loadedSymbolRef.current !== selectedSymbol;
loadedSymbolRef.current = selectedSymbol;

const load = async () => {
if (symbolChanged) {
setLoading(true);
setError(null);
setSymbolData(null);
setHistoricalData([]);
setTechnicalIndicators(null);
setForecastData(null);
setSeasonalData(null);
setFinancialData(null);

try {
const primary = await fetchPrimarySymbolData(
selectedSymbol,
timeRange
);
if (cancelled) return;
setSymbolData(primary.symbolData);
setHistoricalData(primary.historicalData);
setLoadedHistoryRange(timeRange);
} catch (err) {
if (cancelled) return;
console.error("Error fetching symbol data:", err);
setError(
err instanceof Error ? err.message : MARKET_UI_COPY.load.symbolData
);
} finally {
if (!cancelled) setLoading(false);
}

const forecastResponse = await fetch(
`/api/market/forecast/${selectedSymbol}`
);
if (forecastResponse.ok) {
const forecastResult = await forecastResponse.json();
setForecastData(forecastResult.data);
try {
const secondary = await fetchSecondarySymbolData(selectedSymbol);
if (cancelled) return;
setTechnicalIndicators(secondary.technicalIndicators);
setForecastData(secondary.forecastData);
setSeasonalData(secondary.seasonalData);
setFinancialData(secondary.financialData);
} catch (err) {
console.warn("Error fetching secondary symbol data:", err);
}
return;
}

const seasonalResponse = await fetch(
`/api/market/seasonal/${selectedSymbol}`
);
if (seasonalResponse.ok) {
const seasonalResult = await seasonalResponse.json();
setSeasonalData(seasonalResult.data);
}
setHistoryLoading(true);
setHistoricalData([]);

const financialsResponse = await fetch(
`/api/market/financials/${selectedSymbol}`
);
if (financialsResponse.ok) {
const financialsResult = await financialsResponse.json();
setFinancialData(financialsResult.data);
}
try {
const historical = await fetchHistoricalData(selectedSymbol, timeRange);
if (cancelled) return;
setHistoricalData(historical);
setLoadedHistoryRange(timeRange);
} catch (err) {
console.error("Error fetching symbol data:", err);
setError(
err instanceof Error ? err.message : MARKET_UI_COPY.load.symbolData
);
console.warn("Error fetching historical data for range:", err);
} finally {
setLoading(false);
if (!cancelled) setHistoryLoading(false);
}
};

fetchSymbolData();
void load();

return () => {
cancelled = true;
};
}, [selectedSymbol, timeRange]);

useEffect(() => {
Expand Down Expand Up @@ -410,6 +418,8 @@ export function HomePageClient() {
symbolData={symbolData}
historicalData={historicalData}
timeRange={timeRange}
dataTimeRange={loadedHistoryRange}
historyLoading={historyLoading}
onTimeRangeChange={handleTimeRangeChange}
/>
)}
Expand Down
Loading
Loading