Skip to content

Commit a793420

Browse files
committed
fix
1 parent ddfba21 commit a793420

File tree

7 files changed

+163
-124
lines changed

7 files changed

+163
-124
lines changed

app/coins/[id]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const CoinDetails = async ({ params }: { params: Promise<{ id: string }> }) => {
5050
tickers: coinData.tickers,
5151
};
5252

53+
console.log('==== coinOHLCData:', coinOHLCData);
54+
5355
return (
5456
<main className='coin-details-main'>
5557
<section className='size-full xl:col-span-2'>

components/CandlestickChart.tsx

Lines changed: 47 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useEffect, useRef, useMemo, useState } from 'react';
3+
import { useEffect, useRef, useState } from 'react';
44
import {
55
IChartApi,
66
ISeriesApi,
@@ -13,55 +13,46 @@ import {
1313
PERIOD_BUTTONS,
1414
PERIOD_CONFIG,
1515
} from '@/lib/constants';
16-
import { convertOHLCData } from '@/lib/utils';
16+
import { convertOHLCData, convertOHLCToCandlestickData } from '@/lib/utils';
1717
const { getCoinOHLC } = await import('@/lib/coingecko.actions');
1818

1919
export default function CandlestickChart({
2020
data,
2121
coinId,
2222
height = 360,
2323
children,
24+
liveOhlcv = null,
2425
mode = 'historical',
2526
}: CandlestickChartProps) {
2627
const chartContainerRef = useRef<HTMLDivElement | null>(null);
2728
const chartRef = useRef<IChartApi | null>(null);
2829
const candleSeriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
29-
const resizeObserverRef = useRef<ResizeObserver | null>(null);
30-
31-
const [period, setPeriod] = useState<Period>(
32-
mode === 'live' ? 'daily' : 'monthly'
33-
);
34-
const [ohlcData, setOhlcData] = useState<OHLCData[]>(data);
30+
const [period, setPeriod] = useState<Period>('daily');
31+
const [ohlcData, setOhlcData] = useState<OHLCData[]>(data ?? []);
3532
const [loading, setLoading] = useState(false);
3633

37-
// In live mode, use data prop directly; in historical mode, use state
38-
const activeData = mode === 'live' ? data : ohlcData;
39-
40-
// Memoize converted data to avoid recalculating on every render
41-
const chartData = useMemo(() => convertOHLCData(activeData), [activeData]);
34+
console.log('==== Candlestick Chart Live OHLCV:', liveOhlcv);
4235

36+
// Fetch historical data
4337
const fetchOHLCData = async (selectedPeriod: Period) => {
4438
setLoading(true);
4539
try {
4640
const config = PERIOD_CONFIG[selectedPeriod];
47-
4841
const newData = await getCoinOHLC(
4942
coinId,
5043
config.days,
5144
'usd',
5245
config.interval,
5346
'full'
5447
);
55-
56-
setOhlcData(newData);
57-
} catch (error) {
58-
console.error('Error fetching OHLC data:', error);
48+
setOhlcData(newData ?? []);
49+
} catch (err) {
50+
console.error(err);
5951
} finally {
6052
setLoading(false);
6153
}
6254
};
6355

64-
// Handle period change (only in historical mode)
6556
const handlePeriodChange = (newPeriod: Period) => {
6657
if (mode === 'live' || newPeriod === period) return;
6758
setPeriod(newPeriod);
@@ -73,71 +64,67 @@ export default function CandlestickChart({
7364
const container = chartContainerRef.current;
7465
if (!container) return;
7566

76-
// Show time for shorter periods with hourly interval
7767
const showTime = ['daily', 'weekly', 'monthly'].includes(period);
78-
79-
// Initialize chart
8068
const chart = createChart(container, {
8169
...getChartConfig(height, showTime),
8270
width: container.clientWidth,
8371
});
72+
const series = chart.addSeries(CandlestickSeries, getCandlestickConfig());
8473

85-
// Add candlestick series
86-
const candleSeries = chart.addSeries(
87-
CandlestickSeries,
88-
getCandlestickConfig()
89-
);
90-
candleSeries.setData(chartData);
91-
92-
// Fit content to display all data
74+
series.setData(convertOHLCData(ohlcData));
9375
chart.timeScale().fitContent();
9476

95-
// Store refs
9677
chartRef.current = chart;
97-
candleSeriesRef.current = candleSeries;
78+
candleSeriesRef.current = series;
9879

99-
// Handle responsive resizing using ResizeObserver (more efficient than window resize)
100-
resizeObserverRef.current = new ResizeObserver((entries) => {
101-
if (!entries.length || !chartRef.current) return;
102-
103-
const { width } = entries[0].contentRect;
104-
chartRef.current.applyOptions({ width });
80+
const observer = new ResizeObserver((entries) => {
81+
if (!entries.length) return;
82+
chart.applyOptions({ width: entries[0].contentRect.width });
10583
});
84+
observer.observe(container);
10685

107-
resizeObserverRef.current.observe(container);
108-
109-
// Cleanup function
11086
return () => {
111-
resizeObserverRef.current?.disconnect();
112-
chartRef.current?.remove();
87+
observer.disconnect();
88+
chart.remove();
11389
chartRef.current = null;
11490
candleSeriesRef.current = null;
11591
};
116-
// eslint-disable-next-line react-hooks/exhaustive-deps
11792
}, [height]);
11893

119-
// Update chart data when chartData or period changes
94+
// Update chart when data or liveOhlcv changes
12095
useEffect(() => {
121-
if (candleSeriesRef.current && chartRef.current && chartData.length > 0) {
122-
// Update the chart
123-
candleSeriesRef.current.setData(chartData);
124-
chartRef.current.timeScale().fitContent();
125-
126-
// Update time visibility based on period
127-
const showTime = ['daily', 'weekly', 'monthly'].includes(period);
128-
chartRef.current.applyOptions({
129-
timeScale: {
130-
timeVisible: showTime,
131-
},
132-
});
133-
}
134-
}, [chartData, period, mode]);
96+
if (!candleSeriesRef.current) return;
97+
98+
// Convert timestamps from milliseconds to seconds while keeping full OHLC structure
99+
const convertedToSeconds = ohlcData.map((item) => [
100+
Math.floor(item[0] / 1000), // timestamp in seconds
101+
item[1], // open
102+
item[2], // high
103+
item[3], // low
104+
item[4], // close
105+
] as OHLCData);
106+
console.log('==== Updating convertedToSeconds:', convertedToSeconds);
107+
108+
const merged = liveOhlcv
109+
? [...convertedToSeconds, liveOhlcv]
110+
: [...convertedToSeconds];
111+
112+
console.log('==== Updating merged:', merged);
113+
// Sort ascending by time
114+
merged.sort((a, b) => a[0] - b[0]);
115+
116+
const converted = convertOHLCData(merged);
117+
118+
console.log('==== Updating Candlestick Data:', converted);
119+
120+
candleSeriesRef.current.setData(converted);
121+
chartRef.current?.timeScale().fitContent();
122+
}, [ohlcData, liveOhlcv, period]);
135123

136124
return (
137125
<div className='candlestick-container'>
138126
<div className='candlestick-header'>
139127
<div className='flex-1'>{children}</div>
140-
{/* Only show period buttons in historical mode */}
141128
{mode === 'historical' && (
142129
<div className='candlestick-button-group'>
143130
{PERIOD_BUTTONS.map(({ value, label }) => (
@@ -157,8 +144,6 @@ export default function CandlestickChart({
157144
</div>
158145
)}
159146
</div>
160-
161-
{/* Chart Container */}
162147
<div
163148
ref={chartContainerRef}
164149
className='candlestick-chart-container'

components/LiveDataWrapper.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export default function LiveDataWrapper({
2727
coinOHLCData,
2828
});
2929

30+
// console.log('==== LiveDataWrapper coinOHLCData:', coinOHLCData);
31+
// console.log('==== LiveDataWrapper OHLCV:', ohlcv);
32+
3033
return (
3134
<section className='size-full xl:col-span-2'>
3235
<CoinHeader
@@ -45,7 +48,12 @@ export default function LiveDataWrapper({
4548
{/* Trend Overview */}
4649
<div className='w-full'>
4750
<h4 className='section-title'>Trend Overview</h4>
48-
<CandlestickChart data={ohlcv} coinId={coinId} mode='live' />
51+
<CandlestickChart
52+
data={coinOHLCData}
53+
liveOhlcv={ohlcv}
54+
coinId={coinId}
55+
mode='live'
56+
/>
4957
</div>
5058

5159
<Separator className='my-8 bg-purple-600' />

hooks/useCoinGeckoWebSocket.ts

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,34 @@ export function useCoinGeckoWebSocket({
1313

1414
const [price, setPrice] = useState<ExtendedPriceData | null>(null);
1515
const [trades, setTrades] = useState<TradeData[]>([]);
16-
const [ohlcv, setOhlcv] = useState<OHLCData[]>(coinOHLCData);
16+
// const [ohlcv, setOhlcv] = useState<OHLCData[]>(
17+
// coinOHLCData.map((c) => [
18+
// Math.floor(c[0] / 1000), // normalize ms → seconds
19+
// c[1],
20+
// c[2],
21+
// c[3],
22+
// c[4],
23+
// ])
24+
// );
25+
const [ohlcv, setOhlcv] = useState<OHLCData | null>( null)
26+
27+
1728
const [isWsReady, setIsWsReady] = useState(false);
1829

1930
// Track where historical data ends and live data begins
20-
const historicalDataLength = useRef(coinOHLCData.length);
31+
// const historicalDataLength = useRef(ohlcv.length);
2132

2233
const handleMessage = useCallback((event: MessageEvent) => {
2334
const ws = wsRef.current;
2435
const msg: WebSocketMessage = JSON.parse(event.data);
25-
// Ping/Pong checking to keep connection alive as suggested in their docs
36+
37+
// Ping/Pong to keep connection alive
2638
if (msg.type === 'ping') return ws?.send(JSON.stringify({ type: 'pong' }));
2739

40+
// Confirm subscription
2841
if (msg.type === 'confirm_subscription') {
2942
const { channel } = JSON.parse(msg?.identifier ?? '');
3043
subscribed.current.add(channel);
31-
3244
return;
3345
}
3446

@@ -55,50 +67,23 @@ export function useCoinGeckoWebSocket({
5567
amount: msg.to,
5668
};
5769

58-
setTrades((prev) => {
59-
// Prepend new trade to beginning (most recent first)
60-
const allTrades = [newTrade, ...prev];
61-
return allTrades.slice(0, 10);
62-
});
70+
setTrades((prev) => [newTrade, ...prev].slice(0, 10));
6371
}
64-
65-
// G3: OHLCV updates
72+
console.log('==== Message:', msg);
73+
// G3: OHLCV updates — simple append
6674
if (msg.ch === 'G3') {
67-
setOhlcv((prev) => {
68-
const newTimeMs = (msg.t ?? 0) * 1000;
69-
console.log('Received OHLCV update for time:', newTimeMs);
70-
71-
const newCandle: OHLCData = [
72-
newTimeMs,
75+
76+
setOhlcv([
77+
msg.t || 0, // seconds
7378
Number(msg.o ?? 0),
7479
Number(msg.h ?? 0),
7580
Number(msg.l ?? 0),
7681
Number(msg.c ?? 0),
77-
];
78-
79-
const historicalCount = historicalDataLength.current;
80-
const historical = prev.slice(0, historicalCount);
81-
const live = prev.slice(historicalCount);
82-
83-
// 🔑 Upsert by timestamp
84-
const map = new Map<number, OHLCData>();
85-
for (const candle of live) {
86-
map.set(candle[0], candle);
87-
}
88-
map.set(newTimeMs, newCandle);
89-
90-
// 🔑 Sort by time ASC
91-
const updatedLive = Array.from(map.values())
92-
.sort((a, b) => a[0] - b[0])
93-
.slice(-100);
94-
95-
return [...historical, ...updatedLive];
96-
});
82+
]);
9783
}
98-
9984
}, []);
10085

101-
// WebSocket connection setup
86+
// WebSocket connection
10287
useEffect(() => {
10388
const ws = new WebSocket(WS_BASE);
10489
wsRef.current = ws;
@@ -110,11 +95,12 @@ if (msg.ch === 'G3') {
11095
return () => ws.close();
11196
}, [handleMessage]);
11297

98+
// Subscribe helper
11399
const subscribe = useCallback(
114100
(channel: string, data?: Record<string, any>) => {
115101
const ws = wsRef.current;
116102
if (!ws || !isWsReady || subscribed.current.has(channel)) return;
117-
103+
console.log('==== Subscribing to channel:', channel, data);
118104
ws.send(
119105
JSON.stringify({
120106
command: 'subscribe',
@@ -148,25 +134,34 @@ if (msg.ch === 'G3') {
148134
subscribed.current.clear();
149135
}, []);
150136

151-
// Subscribe to channels when connected
137+
// Subscribe on connection ready
152138
useEffect(() => {
153139
if (!isWsReady) return;
154140

155141
let active = true;
142+
156143
(async () => {
144+
if (!active) return;
145+
146+
// Reset state and normalize historical data
157147
setPrice(null);
158148
setTrades([]);
159-
setOhlcv(coinOHLCData);
160-
historicalDataLength.current = coinOHLCData.length;
161-
162-
if (!active) return;
149+
// setOhlcv(
150+
// coinOHLCData.map((c) => [
151+
// Math.floor(c[0] / 1000),
152+
// c[1],
153+
// c[2],
154+
// c[3],
155+
// c[4],
156+
// ])
157+
// );
158+
// historicalDataLength.current = coinOHLCData.length;
163159

164160
unsubscribeAll();
165161

166-
// Subscribe to price updates
162+
// Subscribe channels
167163
subscribe('CGSimplePrice', { coin_id: [coinId], action: 'set_tokens' });
168164

169-
// Subscribe to trade and OHLCV updates
170165
const wsPools = [poolId.replace('_', ':')];
171166
if (wsPools.length) {
172167
subscribe('OnchainTrade', {

lib/coingecko.actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export async function fetchPools(
166166
}
167167

168168
const data = await res.json();
169-
const pool = data.data[0];
169+
const pool = data.data[1];
170170

171171
return {
172172
id: pool.id as string,

0 commit comments

Comments
 (0)