서버오류 수정
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
toggleFavorite,
|
toggleFavorite,
|
||||||
FAVORITES_CHANGED_EVENT,
|
FAVORITES_CHANGED_EVENT,
|
||||||
} from '../utils/marketStorage';
|
} from '../utils/marketStorage';
|
||||||
|
import { UPBIT_API } from '../utils/upbitApi';
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
interface MarketInfo {
|
interface MarketInfo {
|
||||||
@@ -21,14 +22,21 @@ interface TickerInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||||
const UPBIT_API = import.meta.env.DEV ? '/upbit-api/v1' : 'https://api.upbit.com/v1';
|
|
||||||
const TICKER_TTL = 3000; // 3초마다 시세 갱신
|
const TICKER_TTL = 3000; // 3초마다 시세 갱신
|
||||||
|
|
||||||
function formatVol(vol: number): string {
|
function formatVol(vol: number | string | null | undefined): string {
|
||||||
if (vol >= 1e12) return `${(vol / 1e12).toFixed(1)}조`;
|
const v = Number(vol);
|
||||||
if (vol >= 1e8) return `${(vol / 1e8).toFixed(0)}억`;
|
if (!Number.isFinite(v)) return '-';
|
||||||
if (vol >= 1e4) return `${(vol / 1e4).toFixed(0)}만`;
|
if (v >= 1e12) return `${(v / 1e12).toFixed(1)}조`;
|
||||||
return vol.toLocaleString();
|
if (v >= 1e8) return `${(v / 1e8).toFixed(0)}억`;
|
||||||
|
if (v >= 1e4) return `${(v / 1e4).toFixed(0)}만`;
|
||||||
|
return v.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatChangeRate(raw: number | string | null | undefined): number | null {
|
||||||
|
const rate = Number(raw);
|
||||||
|
if (!Number.isFinite(rate)) return null;
|
||||||
|
return rate * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Component ───────────────────────────────────────────────────────────────
|
// ─── Component ───────────────────────────────────────────────────────────────
|
||||||
@@ -86,7 +94,10 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetch(`${UPBIT_API}/market/all?isDetails=false`)
|
fetch(`${UPBIT_API}/market/all?isDetails=false`)
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
|
if (!r.ok) throw new Error(`market/all ${r.status}`);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
.then((data: Array<{ market: string; korean_name: string; english_name: string }>) => {
|
.then((data: Array<{ market: string; korean_name: string; english_name: string }>) => {
|
||||||
const krw = data
|
const krw = data
|
||||||
.filter(m => m.market.startsWith('KRW-'))
|
.filter(m => m.market.startsWith('KRW-'))
|
||||||
@@ -129,9 +140,11 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
|||||||
for (let i = 0; i < allCodes.length; i += BATCH) {
|
for (let i = 0; i < allCodes.length; i += BATCH) {
|
||||||
const batch = allCodes.slice(i, i + BATCH).join(',');
|
const batch = allCodes.slice(i, i + BATCH).join(',');
|
||||||
try {
|
try {
|
||||||
const data: Array<{ market: string } & TickerInfo> =
|
const res = await fetch(`${UPBIT_API}/ticker?markets=${batch}`);
|
||||||
await fetch(`${UPBIT_API}/ticker?markets=${batch}`).then(r => r.json());
|
if (!res.ok) continue;
|
||||||
results.push(...data);
|
const data: unknown = await res.json();
|
||||||
|
if (!Array.isArray(data)) continue;
|
||||||
|
results.push(...(data as Array<{ market: string } & TickerInfo>));
|
||||||
} catch { /* ignore partial failures */ }
|
} catch { /* ignore partial failures */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +341,7 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
|||||||
const isFav = favs.has(m.market);
|
const isFav = favs.has(m.market);
|
||||||
const isAdded = addedMarkets?.has(m.market) ?? false;
|
const isAdded = addedMarkets?.has(m.market) ?? false;
|
||||||
const isActive = m.market === currentMarket;
|
const isActive = m.market === currentMarket;
|
||||||
const rate = tk ? tk.signed_change_rate * 100 : null;
|
const rate = tk ? formatChangeRate(tk.signed_change_rate) : null;
|
||||||
const isUp = rate !== null && rate >= 0;
|
const isUp = rate !== null && rate >= 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -373,7 +386,9 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
|||||||
{!embedded && (
|
{!embedded && (
|
||||||
<>
|
<>
|
||||||
<span className="msp-col msp-col-price">
|
<span className="msp-col msp-col-price">
|
||||||
{tk ? tk.trade_price.toLocaleString() : '-'}
|
{tk && Number.isFinite(Number(tk.trade_price))
|
||||||
|
? Number(tk.trade_price).toLocaleString()
|
||||||
|
: '-'}
|
||||||
</span>
|
</span>
|
||||||
<span className={`msp-col msp-col-change ${rate === null ? '' : isUp ? 'up' : 'down'}`}>
|
<span className={`msp-col msp-col-change ${rate === null ? '' : isUp ? 'up' : 'down'}`}>
|
||||||
{rate === null
|
{rate === null
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { setMarketNames } from '../utils/marketNameCache';
|
import { setMarketNames } from '../utils/marketNameCache';
|
||||||
|
import { UPBIT_API } from '../utils/upbitApi';
|
||||||
|
|
||||||
export type ChangeDir = 'RISE' | 'FALL' | 'EVEN';
|
export type ChangeDir = 'RISE' | 'FALL' | 'EVEN';
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ function getMarketCode(raw: UpbitTickerRaw): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAllMarkets(): Promise<MarketInfo[]> {
|
async function fetchAllMarkets(): Promise<MarketInfo[]> {
|
||||||
const res = await fetch('/upbit-api/v1/market/all?isDetails=true');
|
const res = await fetch(`${UPBIT_API}/market/all?isDetails=true`);
|
||||||
if (!res.ok) throw new Error(`market/all ${res.status}`);
|
if (!res.ok) throw new Error(`market/all ${res.status}`);
|
||||||
const data: UpbitMarketAll[] = await res.json();
|
const data: UpbitMarketAll[] = await res.json();
|
||||||
const krw = data.filter(m => m.market.startsWith('KRW-'));
|
const krw = data.filter(m => m.market.startsWith('KRW-'));
|
||||||
@@ -88,7 +89,7 @@ async function fetchAllTickers(markets: string[]): Promise<UpbitTickerRaw[]> {
|
|||||||
for (let i = 0; i < markets.length; i += 100) {
|
for (let i = 0; i < markets.length; i += 100) {
|
||||||
const chunk = markets.slice(i, i + 100);
|
const chunk = markets.slice(i, i + 100);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/upbit-api/v1/ticker?markets=${chunk.join(',')}`);
|
const res = await fetch(`${UPBIT_API}/ticker?markets=${chunk.join(',')}`);
|
||||||
if (res.ok) results.push(...(await res.json()));
|
if (res.ok) results.push(...(await res.json()));
|
||||||
} catch { /* 부분 실패 무시 */ }
|
} catch { /* 부분 실패 무시 */ }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import type { OHLCVBar, Timeframe } from '../types';
|
|||||||
// 개발: Vite proxy (/upbit-api) → https://api.upbit.com
|
// 개발: Vite proxy (/upbit-api) → https://api.upbit.com
|
||||||
// 프로덕션: nginx proxy (/upbit-api) → https://api.upbit.com
|
// 프로덕션: nginx proxy (/upbit-api) → https://api.upbit.com
|
||||||
// 항상 동일한 상대 경로를 사용하므로 CORS 없이 동일 오리진으로 요청됨
|
// 항상 동일한 상대 경로를 사용하므로 CORS 없이 동일 오리진으로 요청됨
|
||||||
const UPBIT_API = '/upbit-api/v1';
|
/** dev: Vite proxy, prod: nginx proxy — 항상 동일 오리진 상대 경로 */
|
||||||
|
export const UPBIT_API = '/upbit-api/v1';
|
||||||
|
|
||||||
export const UPBIT_MARKETS = [
|
export const UPBIT_MARKETS = [
|
||||||
'KRW-BTC', 'KRW-ETH', 'KRW-XRP', 'KRW-SOL', 'KRW-DOGE',
|
'KRW-BTC', 'KRW-ETH', 'KRW-XRP', 'KRW-SOL', 'KRW-DOGE',
|
||||||
|
|||||||
Reference in New Issue
Block a user