임시커밋

This commit is contained in:
2026-02-11 16:31:28 +09:00
parent f650d51f68
commit 3cea3e66d0
45 changed files with 289 additions and 236 deletions

View File

@@ -1 +1 @@
{"mock:7777b1c958636e65539aadf6c4273a0dfa33c17e55d1beb7dbfd033d49457f6e":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b2tlbiIsImF1ZCI6ImRhNWMyYjU5LTA3YTUtNGVhNy1hY2UyLWZlNTMwZTBjM2ZjNCIsInByZHRfY2QiOiIiLCJpc3MiOiJ1bm9ndyIsImV4cCI6MTc3MDc5MzIzOCwiaWF0IjoxNzcwNzA2ODM4LCJqdGkiOiJQUzA2TG12SndjRHl1MDZLVXpPRjVsSHJacWR6ZWJKTXhCVWIifQ.vDnx1Vx-LnzaRETwkR5aQUnl7vV3-KlXG5AVlMRrchjdovJzd5n1vL7YfG_146Swrao2Drw8TwnpdK44-aTrhg","expiresAt":1770793238000},"real:7777b1c958636e65539aadf6c4273a0dfa33c17e55d1beb7dbfd033d49457f6e":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b2tlbiIsImF1ZCI6IjQ1ZTBmYTczLWI3ZmEtNDg5Mi1iYmZkLTJkYzdlNWQ2YTFhOCIsInByZHRfY2QiOiIiLCJpc3MiOiJ1bm9ndyIsImV4cCI6MTc3MDg3NDg1NywiaWF0IjoxNzcwNzg4NDU3LCJqdGkiOiJQUzA2TG12SndjRHl1MDZLVXpPRjVsSHJacWR6ZWJKTXhCVWIifQ.f4XsiK4WgzzBNbGEP5bNnJ9r4yAfGBb8SOwEZ-D0knygsFqSOGsj1QfjjVIBo7lG5AxAwyrIUdoC-rjqIVCc3A","expiresAt":1770874857000}}
{"mock:7777b1c958636e65539aadf6c4273a0dfa33c17e55d1beb7dbfd033d49457f6e":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b2tlbiIsImF1ZCI6ImRhNWMyYjU5LTA3YTUtNGVhNy1hY2UyLWZlNTMwZTBjM2ZjNCIsInByZHRfY2QiOiIiLCJpc3MiOiJ1bm9ndyIsImV4cCI6MTc3MDc5MzIzOCwiaWF0IjoxNzcwNzA2ODM4LCJqdGkiOiJQUzA2TG12SndjRHl1MDZLVXpPRjVsSHJacWR6ZWJKTXhCVWIifQ.vDnx1Vx-LnzaRETwkR5aQUnl7vV3-KlXG5AVlMRrchjdovJzd5n1vL7YfG_146Swrao2Drw8TwnpdK44-aTrhg","expiresAt":1770793238000},"real:7777b1c958636e65539aadf6c4273a0dfa33c17e55d1beb7dbfd033d49457f6e":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b2tlbiIsImF1ZCI6ImViN2M3NzBhLWZjMDgtNDI3MS05YjZiLTkxYmM1OGY0NmM0ZiIsInByZHRfY2QiOiIiLCJpc3MiOiJ1bm9ndyIsImV4cCI6MTc3MDg4MTMyNCwiaWF0IjoxNzcwNzk0OTI0LCJqdGkiOiJQUzA2TG12SndjRHl1MDZLVXpPRjVsSHJacWR6ZWJKTXhCVWIifQ.3NvIylftH8PmMvFQu9CmfZUwULMoKQIAIlJHt5zW3sj70h9d4yLIi5WMbvFp-akUwYEAQMZHhFMeD4B58eP7BA","expiresAt":1770881324493}}

View File

@@ -5,12 +5,12 @@
import { redirect } from "next/navigation";
import { createClient } from "@/utils/supabase/server";
import { DashboardContainer } from "@/features/dashboard/components/DashboardContainer";
/**
* 대시보드 페이지
* @returns DashboardContainer UI
* @see features/dashboard/components/DashboardContainer.tsx 클라이언트 상호작용(검색/시세/차트)은 해당 컴포넌트가 담당합니다.
* 대시보드 페이지 (향후 확장용)
* @returns 빈 대시보드 안내 UI
* @see app/(main)/trade/page.tsx 트레이딩 기능은 `/trade` 경로에서 제공합니다.
* @see app/(main)/settings/page.tsx KIS 인증 설정은 `/settings` 경로에서 제공합니다.
*/
export default async function DashboardPage() {
// 상태 정의: 서버에서 세션을 먼저 확인해 비로그인 접근을 차단합니다.
@@ -21,5 +21,17 @@ export default async function DashboardPage() {
if (!user) redirect("/login");
return <DashboardContainer />;
return (
<section className="mx-auto flex h-full w-full max-w-5xl flex-col justify-center p-6">
{/* ========== DASHBOARD PLACEHOLDER ========== */}
<div className="rounded-2xl border border-brand-200 bg-background p-8 shadow-sm dark:border-brand-800/45 dark:bg-brand-900/14">
<h1 className="text-2xl font-semibold tracking-tight text-foreground">
</h1>
<p className="mt-2 text-sm text-muted-foreground">
.
</p>
</div>
</section>
);
}

View File

@@ -0,0 +1,26 @@
/**
* @file app/(main)/settings/page.tsx
* @description 로그인 사용자 전용 설정 페이지(Server Component)
*/
import { redirect } from "next/navigation";
import { SettingsContainer } from "@/features/settings/components/SettingsContainer";
import { createClient } from "@/utils/supabase/server";
/**
* 설정 페이지
* @returns SettingsContainer UI
* @see features/settings/components/SettingsContainer.tsx KIS 인증 설정 UI를 제공합니다.
*/
export default async function SettingsPage() {
// 상태 정의: 서버에서 세션을 먼저 확인해 비로그인 접근을 차단합니다.
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect("/login");
return <SettingsContainer />;
}

26
app/(main)/trade/page.tsx Normal file
View File

@@ -0,0 +1,26 @@
/**
* @file app/(main)/trade/page.tsx
* @description 로그인 사용자 전용 트레이딩 페이지(Server Component)
*/
import { redirect } from "next/navigation";
import { TradeContainer } from "@/features/trade/components/TradeContainer";
import { createClient } from "@/utils/supabase/server";
/**
* 트레이딩 페이지
* @returns TradeContainer UI
* @see features/trade/components/TradeContainer.tsx 종목 검색/차트/호가/주문 기능을 제공합니다.
*/
export default async function TradePage() {
// 상태 정의: 서버에서 세션을 먼저 확인해 비로그인 접근을 차단합니다.
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect("/login");
return <TradeContainer />;
}

View File

@@ -1,7 +1,7 @@
import type {
DashboardChartTimeframe,
DashboardStockChartResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import type { KisCredentialInput } from "@/lib/kis/config";
import { hasKisConfig, normalizeTradingEnv } from "@/lib/kis/config";
import { getDomesticChart } from "@/lib/kis/domestic";

View File

@@ -3,7 +3,7 @@ import { executeOrderCash } from "@/lib/kis/trade";
import {
DashboardStockCashOrderRequest,
DashboardStockCashOrderResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import {
KisCredentialInput,
hasKisConfig,

View File

@@ -3,7 +3,7 @@ import {
getDomesticOrderBook,
KisDomesticOrderBookOutput,
} from "@/lib/kis/domestic";
import { DashboardStockOrderBookResponse } from "@/features/dashboard/types/dashboard.types";
import { DashboardStockOrderBookResponse } from "@/features/trade/types/trade.types";
import {
KisCredentialInput,
hasKisConfig,

View File

@@ -1,5 +1,5 @@
import { KOREAN_STOCK_INDEX } from "@/features/dashboard/data/korean-stocks";
import type { DashboardStockOverviewResponse } from "@/features/dashboard/types/dashboard.types";
import { KOREAN_STOCK_INDEX } from "@/features/trade/data/korean-stocks";
import type { DashboardStockOverviewResponse } from "@/features/trade/types/trade.types";
import type { KisCredentialInput } from "@/lib/kis/config";
import { hasKisConfig, normalizeTradingEnv } from "@/lib/kis/config";
import { getDomesticOverview } from "@/lib/kis/domestic";

View File

@@ -1,9 +1,9 @@
import { KOREAN_STOCK_INDEX } from "@/features/dashboard/data/korean-stocks";
import { KOREAN_STOCK_INDEX } from "@/features/trade/data/korean-stocks";
import type {
DashboardStockSearchItem,
DashboardStockSearchResponse,
KoreanStockIndexItem,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import { NextRequest, NextResponse } from "next/server";
const SEARCH_LIMIT = 10;
@@ -15,7 +15,7 @@ const SEARCH_LIMIT = 10;
* - [레이어] API Route
* - [사용자 행동] 대시보드 검색창 엔터/검색 버튼 클릭 시 호출
* - [데이터 흐름] dashboard-main.tsx -> /api/kis/domestic/search -> KOREAN_STOCK_INDEX 필터/정렬 -> JSON 응답
* - [연관 파일] features/dashboard/data/korean-stocks.ts, features/dashboard/components/dashboard-main.tsx
* - [연관 파일] features/trade/data/korean-stocks.ts, features/trade/components/dashboard-main.tsx
* @author jihoon87.lee
*/
@@ -23,7 +23,7 @@ const SEARCH_LIMIT = 10;
* 국내주식 검색 API
* @param request query string의 q(검색어) 사용
* @returns 종목 검색 결과 목록
* @see features/dashboard/components/dashboard-main.tsx 검색 폼에서 호출합니다.
* @see features/trade/components/dashboard-main.tsx 검색 폼에서 호출합니다.
*/
export async function GET(request: NextRequest) {
// [Step 1] query string에서 검색어(q)를 읽고 공백을 제거합니다.

View File

@@ -1,4 +1,4 @@
import type { DashboardKisRevokeResponse } from "@/features/dashboard/types/dashboard.types";
import type { DashboardKisRevokeResponse } from "@/features/trade/types/trade.types";
import { normalizeTradingEnv } from "@/lib/kis/config";
import {
parseKisCredentialRequest,
@@ -14,7 +14,7 @@ import { NextRequest, NextResponse } from "next/server";
/**
* @description KIS 액세스 토큰 폐기
* @see features/dashboard/components/auth/KisAuthForm.tsx
* @see features/settings/components/KisAuthForm.tsx
*/
export async function POST(request: NextRequest) {
const credentials = await parseKisCredentialRequest(request);

View File

@@ -1,4 +1,4 @@
import type { DashboardKisValidateResponse } from "@/features/dashboard/types/dashboard.types";
import type { DashboardKisValidateResponse } from "@/features/trade/types/trade.types";
import { normalizeTradingEnv } from "@/lib/kis/config";
import {
parseKisCredentialRequest,
@@ -14,7 +14,7 @@ import { NextRequest, NextResponse } from "next/server";
/**
* @description 액세스 토큰 발급 성공 여부로 API 키를 검증합니다.
* @see features/dashboard/components/auth/KisAuthForm.tsx
* @see features/settings/components/KisAuthForm.tsx
*/
export async function POST(request: NextRequest) {
const credentials = await parseKisCredentialRequest(request);

View File

@@ -1,4 +1,4 @@
import type { DashboardKisWsApprovalResponse } from "@/features/dashboard/types/dashboard.types";
import type { DashboardKisWsApprovalResponse } from "@/features/trade/types/trade.types";
import { getKisApprovalKey, resolveKisWebSocketUrl } from "@/lib/kis/approval";
import { normalizeTradingEnv } from "@/lib/kis/config";
import {
@@ -14,7 +14,7 @@ import { NextRequest, NextResponse } from "next/server";
/**
* @description 실시간 웹소켓 연결 정보를 발급합니다.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts
* @see features/trade/hooks/useKisTradeWebSocket.ts
*/
export async function POST(request: NextRequest) {
const credentials = await parseKisCredentialRequest(request);

View File

@@ -1,9 +1,9 @@
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardKisRevokeResponse,
DashboardKisValidateResponse,
DashboardKisWsApprovalResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
interface KisApiBaseResponse {
ok: boolean;

View File

@@ -3,11 +3,11 @@ import { useShallow } from "zustand/react/shallow";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useKisRuntimeStore } from "@/features/dashboard/store/use-kis-runtime-store";
import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store";
import {
revokeKisCredentials,
validateKisCredentials,
} from "@/features/dashboard/apis/kis-auth.api";
} from "@/features/settings/apis/kis-auth.api";
import {
KeyRound,
Shield,

View File

@@ -0,0 +1,51 @@
"use client";
import { useShallow } from "zustand/react/shallow";
import { KisAuthForm } from "@/features/settings/components/KisAuthForm";
import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store";
/**
* @description 설정 페이지 컨테이너입니다. KIS 연결 상태와 인증 폼을 카드 UI로 제공합니다.
* @see app/(main)/settings/page.tsx 로그인 확인 후 이 컴포넌트를 렌더링합니다.
* @see features/settings/components/KisAuthForm.tsx 실제 인증 입력/검증/해제를 담당합니다.
*/
export function SettingsContainer() {
// 상태 정의: 연결 상태 표시용 전역 인증 상태를 구독합니다.
const { verifiedCredentials, isKisVerified } = useKisRuntimeStore(
useShallow((state) => ({
verifiedCredentials: state.verifiedCredentials,
isKisVerified: state.isKisVerified,
})),
);
return (
<section className="mx-auto flex w-full max-w-5xl flex-col gap-5 p-4 md:p-6">
{/* ========== STATUS CARD ========== */}
<article className="rounded-2xl border border-brand-200 bg-muted/35 p-4 dark:border-brand-800/45 dark:bg-brand-900/20">
<h1 className="text-xl font-semibold tracking-tight text-foreground">
KIS API
</h1>
<div className="mt-3 flex flex-wrap items-center gap-2 text-sm">
<span className="font-medium text-foreground"> :</span>
{isKisVerified ? (
<span className="inline-flex items-center rounded-full bg-brand-100 px-2.5 py-1 text-xs font-semibold text-brand-700 dark:bg-brand-900/45 dark:text-brand-200">
<span className="mr-1.5 h-2 w-2 rounded-full bg-brand-500" />
({verifiedCredentials?.tradingEnv === "real" ? "실전" : "모의"})
</span>
) : (
<span className="inline-flex items-center rounded-full bg-brand-50 px-2.5 py-1 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200">
<span className="mr-1.5 h-2 w-2 rounded-full bg-brand-300 dark:bg-brand-500/70" />
</span>
)}
</div>
</article>
{/* ========== AUTH FORM CARD ========== */}
<article className="rounded-2xl border border-brand-200 bg-background p-4 shadow-sm dark:border-brand-800/45 dark:bg-brand-900/14">
<KisAuthForm />
</article>
</section>
);
}

View File

@@ -1,14 +1,14 @@
"use client";
import { fetchKisWebSocketApproval } from "@/features/dashboard/apis/kis-auth.api";
import type { KisTradingEnv } from "@/features/dashboard/types/dashboard.types";
import { fetchKisWebSocketApproval } from "@/features/settings/apis/kis-auth.api";
import type { KisTradingEnv } from "@/features/trade/types/trade.types";
import { createJSONStorage, persist } from "zustand/middleware";
import { create } from "zustand";
/**
* @file features/dashboard/store/use-kis-runtime-store.ts
* @file features/settings/store/use-kis-runtime-store.ts
* @description Stores KIS input, verification, and websocket connection state.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts
* @see features/trade/hooks/useKisTradeWebSocket.ts
*/
export interface KisRuntimeCredentials {
appKey: string;
@@ -73,7 +73,7 @@ let wsConnectionPromise: Promise<KisWsConnection | null> | null = null;
/**
* @description Runtime store for KIS session.
* @see features/dashboard/components/auth/KisAuthForm.tsx
* @see features/settings/components/KisAuthForm.tsx
*/
export const useKisRuntimeStore = create<
KisRuntimeStoreState & KisRuntimeStoreActions

View File

@@ -1,4 +1,4 @@
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardChartTimeframe,
DashboardStockCashOrderRequest,
@@ -7,7 +7,7 @@ import type {
DashboardStockOrderBookResponse,
DashboardStockOverviewResponse,
DashboardStockSearchResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import {
DOMESTIC_KIS_SESSION_OVERRIDE_HEADER,
DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY,

View File

@@ -1,44 +1,41 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
import { useShallow } from "zustand/react/shallow";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { useKisRuntimeStore } from "@/features/dashboard/store/use-kis-runtime-store";
import { KisAuthForm } from "@/features/dashboard/components/auth/KisAuthForm";
import { StockSearchForm } from "@/features/dashboard/components/search/StockSearchForm";
import { StockSearchHistory } from "@/features/dashboard/components/search/StockSearchHistory";
import { StockSearchResults } from "@/features/dashboard/components/search/StockSearchResults";
import { useStockSearch } from "@/features/dashboard/hooks/useStockSearch";
import { useOrderBook } from "@/features/dashboard/hooks/useOrderBook";
import { useKisTradeWebSocket } from "@/features/dashboard/hooks/useKisTradeWebSocket";
import { useStockOverview } from "@/features/dashboard/hooks/useStockOverview";
import { useCurrentPrice } from "@/features/dashboard/hooks/useCurrentPrice";
import { DashboardLayout } from "@/features/dashboard/components/layout/DashboardLayout";
import { StockHeader } from "@/features/dashboard/components/header/StockHeader";
import { OrderBook } from "@/features/dashboard/components/orderbook/OrderBook";
import { OrderForm } from "@/features/dashboard/components/order/OrderForm";
import { StockLineChart } from "@/features/dashboard/components/chart/StockLineChart";
import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store";
import { StockSearchForm } from "@/features/trade/components/search/StockSearchForm";
import { StockSearchHistory } from "@/features/trade/components/search/StockSearchHistory";
import { StockSearchResults } from "@/features/trade/components/search/StockSearchResults";
import { useStockSearch } from "@/features/trade/hooks/useStockSearch";
import { useOrderBook } from "@/features/trade/hooks/useOrderBook";
import { useKisTradeWebSocket } from "@/features/trade/hooks/useKisTradeWebSocket";
import { useStockOverview } from "@/features/trade/hooks/useStockOverview";
import { useCurrentPrice } from "@/features/trade/hooks/useCurrentPrice";
import { DashboardLayout } from "@/features/trade/components/layout/DashboardLayout";
import { StockHeader } from "@/features/trade/components/header/StockHeader";
import { OrderBook } from "@/features/trade/components/orderbook/OrderBook";
import { OrderForm } from "@/features/trade/components/order/OrderForm";
import { StockLineChart } from "@/features/trade/components/chart/StockLineChart";
import type {
DashboardStockOrderBookResponse,
DashboardStockSearchItem,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
/**
* @description
* @see app/(main)/dashboard/page.tsx .
* @see features/dashboard/hooks/useStockSearch.ts // .
* @see features/dashboard/hooks/useStockOverview.ts .
* @description .
* @see app/(main)/trade/page.tsx .
* @see app/(main)/settings/page.tsx .
* @see features/trade/hooks/useStockSearch.ts // .
* @see features/trade/hooks/useStockOverview.ts .
*/
export function DashboardContainer() {
export function TradeContainer() {
const skipNextAutoSearchRef = useRef(false);
const hasInitializedAuthPanelRef = useRef(false);
const searchShellRef = useRef<HTMLDivElement | null>(null);
// 모바일에서는 초기 진입 시 API 패널을 접어 본문(차트/호가)을 먼저 보이게 합니다.
const [isMobileViewport, setIsMobileViewport] = useState(false);
const [isAuthPanelExpanded, setIsAuthPanelExpanded] = useState(true);
// 상태 정의: 검색 패널 열림 상태를 관리합니다.
const [isSearchPanelOpen, setIsSearchPanelOpen] = useState(false);
const { verifiedCredentials, isKisVerified } = useKisRuntimeStore(
@@ -112,11 +109,12 @@ export function DashboardContainer() {
orderBook,
});
const canSearch = isKisVerified && !!verifiedCredentials;
const canTrade = isKisVerified && !!verifiedCredentials;
const canSearch = canTrade;
/**
* @description API .
* @see features/dashboard/components/search/StockSearchForm.tsx .
* @see features/trade/components/search/StockSearchForm.tsx .
*/
const ensureSearchReady = useCallback(() => {
if (canSearch) return true;
@@ -135,7 +133,7 @@ export function DashboardContainer() {
/**
* @description (/) .
* @see features/dashboard/components/search/StockSearchForm.tsx .
* @see features/trade/components/search/StockSearchForm.tsx .
*/
const handleSearchShellBlur = useCallback(
(event: React.FocusEvent<HTMLDivElement>) => {
@@ -155,35 +153,6 @@ export function DashboardContainer() {
[closeSearchPanel],
);
useEffect(() => {
const mediaQuery = window.matchMedia("(max-width: 767px)");
const applyViewportMode = (matches: boolean) => {
setIsMobileViewport(matches);
// 최초 1회: 모바일이면 접힘, 데스크탑이면 펼침.
if (!hasInitializedAuthPanelRef.current) {
setIsAuthPanelExpanded(!matches);
hasInitializedAuthPanelRef.current = true;
return;
}
// 데스크탑으로 돌아오면 항상 펼쳐 사용성을 유지합니다.
if (!matches) {
setIsAuthPanelExpanded(true);
}
};
applyViewportMode(mediaQuery.matches);
const onViewportChange = (event: MediaQueryListEvent) => {
applyViewportMode(event.matches);
};
mediaQuery.addEventListener("change", onViewportChange);
return () => mediaQuery.removeEventListener("change", onViewportChange);
}, []);
useEffect(() => {
if (skipNextAutoSearchRef.current) {
skipNextAutoSearchRef.current = false;
@@ -210,7 +179,7 @@ export function DashboardContainer() {
/**
* @description ( ) .
* @see features/dashboard/components/search/StockSearchForm.tsx onSubmit .
* @see features/trade/components/search/StockSearchForm.tsx onSubmit .
*/
const handleSearchSubmit = useCallback(
(event: React.FormEvent) => {
@@ -223,8 +192,8 @@ export function DashboardContainer() {
/**
* @description / .
* @see features/dashboard/components/search/StockSearchResults.tsx onSelect
* @see features/dashboard/components/search/StockSearchHistory.tsx onSelect
* @see features/trade/components/search/StockSearchResults.tsx onSelect
* @see features/trade/components/search/StockSearchHistory.tsx onSelect
*/
const handleSelectStock = useCallback(
(item: DashboardStockSearchItem) => {
@@ -257,65 +226,30 @@ export function DashboardContainer() {
],
);
if (!canTrade) {
return (
<div className="flex h-full items-center justify-center p-6">
<section className="w-full max-w-xl rounded-2xl border border-brand-200 bg-background p-6 shadow-sm dark:border-brand-800/45 dark:bg-brand-900/18">
{/* ========== UNVERIFIED NOTICE ========== */}
<h2 className="text-lg font-semibold text-foreground">
KIS API .
</h2>
<p className="mt-2 text-sm text-muted-foreground">
App Key/App Secret을
.
</p>
<div className="mt-4">
<Button asChild className="bg-brand-600 hover:bg-brand-700">
<Link href="/settings"> </Link>
</Button>
</div>
</section>
</div>
);
}
return (
<div className="relative h-full flex flex-col">
{/* ========== AUTH STATUS ========== */}
<div className="flex-none border-b bg-muted/40 transition-all duration-300 ease-in-out dark:border-brand-800/45 dark:bg-brand-900/28">
<div className="flex items-center justify-between gap-2 px-3 py-2 text-xs sm:px-4">
<div className="flex items-center gap-2">
<span className="font-semibold">KIS API :</span>
{isKisVerified ? (
<span className="flex items-center font-medium text-brand-700 dark:text-brand-200">
<span className="mr-1.5 h-2 w-2 rounded-full bg-brand-500 ring-2 ring-brand-100 dark:ring-brand-900" />
({verifiedCredentials?.tradingEnv === "real" ? "실전" : "모의"})
</span>
) : (
<span className="text-muted-foreground flex items-center">
<span className="mr-1.5 h-2 w-2 rounded-full bg-brand-200 dark:bg-brand-500/60" />
</span>
)}
</div>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => setIsAuthPanelExpanded((prev) => !prev)}
className={cn(
"h-8 shrink-0 gap-1.5 px-2.5 text-[11px] font-semibold",
"border-brand-200 bg-brand-50 text-brand-700 hover:bg-brand-100 dark:border-brand-700/60 dark:bg-brand-900/30 dark:text-brand-200 dark:hover:bg-brand-900/45",
!isAuthPanelExpanded &&
isMobileViewport &&
"ring-2 ring-brand-200 dark:ring-brand-600/60",
)}
>
{isAuthPanelExpanded ? (
<>
<ChevronUp className="h-3.5 w-3.5" />
API
</>
) : (
<>
<ChevronDown className="h-3.5 w-3.5" />
API
</>
)}
</Button>
</div>
<div
className={cn(
"overflow-hidden transition-[max-height,opacity] duration-300 ease-in-out",
isAuthPanelExpanded ? "max-h-[560px] opacity-100" : "max-h-0 opacity-0",
)}
>
<div className="border-t bg-background p-4 dark:border-brand-800/45 dark:bg-brand-900/14">
<KisAuthForm />
</div>
</div>
</div>
{/* ========== SEARCH ========== */}
<div className="z-30 flex-none border-b bg-background/95 p-4 backdrop-blur-sm dark:border-brand-800/45 dark:bg-brand-900/22">
<div

View File

@@ -13,13 +13,13 @@ import {
import { ChevronDown } from "lucide-react";
import { useTheme } from "next-themes";
import { toast } from "sonner";
import { fetchStockChart } from "@/features/dashboard/apis/kis-stock.api";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import { fetchStockChart } from "@/features/trade/apis/kis-stock.api";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardChartTimeframe,
DashboardRealtimeTradeTick,
StockCandlePoint,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import { cn } from "@/lib/utils";
import {
type ChartBar,
@@ -119,7 +119,7 @@ interface StockLineChartProps {
/**
* @description TradingView , timeframe별 KIS API를 .
* @see features/dashboard/apis/kis-stock.api.ts fetchStockChart
* @see features/trade/apis/kis-stock.api.ts fetchStockChart
* @see lib/kis/domestic.ts getDomesticChart
*/
export function StockLineChart({
@@ -199,7 +199,7 @@ export function StockLineChart({
/**
* @description lightweight-charts OHLCV .
* @see features/dashboard/components/chart/StockLineChart.tsx renderableBars useMemo
* @see features/trade/components/chart/StockLineChart.tsx renderableBars useMemo
*/
const setSeriesData = useCallback((nextBars: ChartBar[]) => {
const candleSeries = candleSeriesRef.current;
@@ -524,8 +524,8 @@ export function StockLineChart({
/**
* @description WebSocket timeframe .
* @see features/dashboard/hooks/useKisTradeWebSocket.ts latestTick
* @see features/dashboard/components/chart/chart-utils.ts toRealtimeTickBar
* @see features/trade/hooks/useKisTradeWebSocket.ts latestTick
* @see features/trade/components/chart/chart-utils.ts toRealtimeTickBar
*/
useEffect(() => {
if (!latestTick) return;
@@ -544,7 +544,7 @@ export function StockLineChart({
/**
* @description (1m/30m/1h) WS .
* @see features/dashboard/apis/kis-stock.api.ts fetchStockChart
* @see features/trade/apis/kis-stock.api.ts fetchStockChart
* @see lib/kis/domestic.ts getDomesticChart
*/
useEffect(() => {

View File

@@ -12,7 +12,7 @@ import type {
DashboardChartTimeframe,
DashboardRealtimeTradeTick,
StockCandlePoint,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
const KRW_FORMATTER = new Intl.NumberFormat("ko-KR");
const KST_TIME_ZONE = "Asia/Seoul";
@@ -228,8 +228,8 @@ export function upsertRealtimeBar(
/**
* @description ChartBar로 . (KST + tickTime )
* @see features/dashboard/hooks/useKisTradeWebSocket.ts latestTick
* @see features/dashboard/components/chart/StockLineChart.tsx
* @see features/trade/hooks/useKisTradeWebSocket.ts latestTick
* @see features/trade/components/chart/StockLineChart.tsx
*/
export function toRealtimeTickBar(
tick: DashboardRealtimeTradeTick,
@@ -260,7 +260,7 @@ export function toRealtimeTickBar(
/**
* @description lightweight-charts X축 KST .
* @see features/dashboard/components/chart/StockLineChart.tsx createChart options.timeScale.tickMarkFormatter
* @see features/trade/components/chart/StockLineChart.tsx createChart options.timeScale.tickMarkFormatter
*/
export function formatKstTickMark(time: Time, tickMarkType: TickMarkType) {
const date = toDateFromChartTime(time);
@@ -275,7 +275,7 @@ export function formatKstTickMark(time: Time, tickMarkType: TickMarkType) {
/**
* @description crosshair KST로 .
* @see features/dashboard/components/chart/StockLineChart.tsx createChart options.localization.timeFormatter
* @see features/trade/components/chart/StockLineChart.tsx createChart options.localization.timeFormatter
*/
export function formatKstCrosshairTime(time: Time) {
const date = toDateFromChartTime(time);

View File

@@ -6,13 +6,13 @@ import {
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { StockLineChart } from "@/features/dashboard/components/chart/StockLineChart";
import { StockPriceBadge } from "@/features/dashboard/components/details/StockPriceBadge";
import { StockLineChart } from "@/features/trade/components/chart/StockLineChart";
import { StockPriceBadge } from "@/features/trade/components/details/StockPriceBadge";
import type {
DashboardStockItem,
DashboardPriceSource,
DashboardMarketPhase,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
const PRICE_FORMATTER = new Intl.NumberFormat("ko-KR");
function formatVolume(value: number) {

View File

@@ -1,6 +1,6 @@
// import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { DashboardStockItem } from "@/features/dashboard/types/dashboard.types";
import { DashboardStockItem } from "@/features/trade/types/trade.types";
import { cn } from "@/lib/utils";
interface StockHeaderProps {

View File

@@ -3,12 +3,12 @@ import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useOrder } from "@/features/dashboard/hooks/useOrder";
import { useKisRuntimeStore } from "@/features/dashboard/store/use-kis-runtime-store";
import { useOrder } from "@/features/trade/hooks/useOrder";
import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardOrderSide,
DashboardStockItem,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
interface OrderFormProps {
stock?: DashboardStockItem;
@@ -16,8 +16,8 @@ interface OrderFormProps {
/**
* @description / .
* @see features/dashboard/hooks/useOrder.ts placeOrder - API
* @see features/dashboard/components/DashboardContainer.tsx OrderForm -
* @see features/trade/hooks/useOrder.ts placeOrder - API
* @see features/trade/components/TradeContainer.tsx OrderForm -
*/
export function OrderForm({ stock }: OrderFormProps) {
const verifiedCredentials = useKisRuntimeStore(
@@ -161,7 +161,7 @@ export function OrderForm({ stock }: OrderFormProps) {
/**
* @description (//) .
* @see features/dashboard/components/order/OrderForm.tsx OrderForm - /
* @see features/trade/components/order/OrderForm.tsx OrderForm - /
*/
function OrderInputs({
type,
@@ -236,7 +236,7 @@ function OrderInputs({
/**
* @description (10/25/50/100%) .
* @see features/dashboard/components/order/OrderForm.tsx setPercent -
* @see features/trade/components/order/OrderForm.tsx setPercent -
*/
function PercentButtons({ onSelect }: { onSelect: (pct: string) => void }) {
return (

View File

@@ -5,7 +5,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import type {
DashboardRealtimeTradeTick,
DashboardStockOrderBookResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import { cn } from "@/lib/utils";
import { AnimatedQuantity } from "./AnimatedQuantity";

View File

@@ -14,7 +14,7 @@ interface StockSearchFormProps {
/**
* @description / .
* @see features/dashboard/components/DashboardContainer.tsx .
* @see features/trade/components/TradeContainer.tsx .
*/
export function StockSearchForm({
keyword,

View File

@@ -1,7 +1,7 @@
import { Clock3, Trash2, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { DashboardStockSearchHistoryItem } from "@/features/dashboard/types/dashboard.types";
import type { DashboardStockSearchHistoryItem } from "@/features/trade/types/trade.types";
interface StockSearchHistoryProps {
items: DashboardStockSearchHistoryItem[];
@@ -13,8 +13,8 @@ interface StockSearchHistoryProps {
/**
* @description , // .
* @see features/dashboard/components/DashboardContainer.tsx UI로 .
* @see features/dashboard/hooks/useStockSearch.ts searchHistory .
* @see features/trade/components/TradeContainer.tsx UI로 .
* @see features/trade/hooks/useStockSearch.ts searchHistory .
*/
export function StockSearchHistory({
items,

View File

@@ -1,7 +1,7 @@
import { Button } from "@/components/ui/button";
// import { Activity, TrendingDown, TrendingUp } from "lucide-react";
import { cn } from "@/lib/utils";
import type { DashboardStockSearchItem } from "@/features/dashboard/types/dashboard.types";
import type { DashboardStockSearchItem } from "@/features/trade/types/trade.types";
interface StockSearchResultsProps {
items: DashboardStockSearchItem[];

View File

@@ -1,5 +1,5 @@
import rawStocks from "@/features/dashboard/data/korean-stocks.json";
import type { KoreanStockIndexItem } from "@/features/dashboard/types/dashboard.types";
import rawStocks from "@/features/trade/data/korean-stocks.json";
import type { KoreanStockIndexItem } from "@/features/trade/types/trade.types";
/**
* (KOSPI + KOSDAQ)

View File

@@ -1,5 +1,5 @@
/**
* @file features/dashboard/data/mock-stocks.ts
* @file features/trade/data/mock-stocks.ts
* @description 1 UI
* @remarks
* - API .
@@ -7,12 +7,12 @@
* - / .
*/
import type { DashboardStockItem } from "@/features/dashboard/types/dashboard.types";
import type { DashboardStockItem } from "@/features/trade/types/trade.types";
/**
*
* @see app/(main)/dashboard/page.tsx DashboardPage가 DashboardMain을 .
* @see features/dashboard/components/dashboard-main.tsx // .
* @see features/trade/components/dashboard-main.tsx // .
*/
export const MOCK_STOCKS: DashboardStockItem[] = [
{

View File

@@ -3,7 +3,7 @@ import type {
DashboardRealtimeTradeTick,
DashboardStockItem,
DashboardStockOrderBookResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
interface UseCurrentPriceParams {
stock?: DashboardStockItem | null;

View File

@@ -2,16 +2,16 @@ import { useEffect, useRef, useState } from "react";
import {
type KisRuntimeCredentials,
useKisRuntimeStore,
} from "@/features/dashboard/store/use-kis-runtime-store";
} from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardRealtimeTradeTick,
DashboardStockOrderBookResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import {
buildKisRealtimeMessage,
parseKisRealtimeOrderbook,
parseKisRealtimeTickBatch,
} from "@/features/dashboard/utils/kis-realtime.utils";
} from "@/features/trade/utils/kis-realtime.utils";
import {
DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY,
resolveDomesticKisSession,
@@ -32,8 +32,8 @@ function resolveTradeTrId(
session: DomesticKisSession,
) {
if (env === "mock") return TRADE_TR_ID;
if (shouldUseAfterHoursSinglePriceTr(session)) return TRADE_TR_ID_OVERTIME;
if (shouldUseExpectedExecutionTr(session)) return TRADE_TR_ID_EXPECTED;
if (shouldUseAfterHoursSinglePriceTr(session)) return TRADE_TR_ID;
if (shouldUseExpectedExecutionTr(session)) return TRADE_TR_ID;
return TRADE_TR_ID;
}
@@ -42,13 +42,14 @@ function resolveOrderBookTrId(
session: DomesticKisSession,
) {
if (env === "mock") return ORDERBOOK_TR_ID;
if (shouldUseAfterHoursSinglePriceTr(session)) return ORDERBOOK_TR_ID_OVERTIME;
if (shouldUseAfterHoursSinglePriceTr(session))
return ORDERBOOK_TR_ID_OVERTIME;
return ORDERBOOK_TR_ID;
}
/**
* @description Subscribes trade ticks and orderbook over one websocket.
* @see features/dashboard/components/DashboardContainer.tsx
* @see features/trade/components/TradeContainer.tsx
* @see lib/kis/domestic-market-session.ts
*/
export function useKisTradeWebSocket(

View File

@@ -1,10 +1,10 @@
import { useState, useCallback } from "react";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardStockCashOrderRequest,
DashboardStockCashOrderResponse,
} from "@/features/dashboard/types/dashboard.types";
import { fetchOrderCash } from "@/features/dashboard/apis/kis-stock.api";
} from "@/features/trade/types/trade.types";
import { fetchOrderCash } from "@/features/trade/apis/kis-stock.api";
export function useOrder() {
const [isLoading, setIsLoading] = useState(false);

View File

@@ -1,15 +1,15 @@
import { useEffect, useRef, useState } from "react";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type { DashboardStockOrderBookResponse } from "@/features/dashboard/types/dashboard.types";
import { fetchStockOrderBook } from "@/features/dashboard/apis/kis-stock.api";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type { DashboardStockOrderBookResponse } from "@/features/trade/types/trade.types";
import { fetchStockOrderBook } from "@/features/trade/apis/kis-stock.api";
import { toast } from "sonner";
/**
* @description REST , .
* DashboardContainer에서 useKisTradeWebSocket을
* TradeContainer에서 useKisTradeWebSocket을
* WebSocket으로 externalRealtimeOrderBook으로 .
* @see features/dashboard/components/DashboardContainer.tsx
* @see features/dashboard/components/orderbook/OrderBook.tsx
* @see features/trade/components/TradeContainer.tsx
* @see features/trade/components/orderbook/OrderBook.tsx
*/
export function useOrderBook(
symbol: string | undefined,

View File

@@ -1,13 +1,13 @@
import { useCallback, useState, useTransition } from "react";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardMarketPhase,
DashboardPriceSource,
DashboardRealtimeTradeTick,
DashboardStockSearchItem,
DashboardStockItem,
} from "@/features/dashboard/types/dashboard.types";
import { fetchStockOverview } from "@/features/dashboard/apis/kis-stock.api";
} from "@/features/trade/types/trade.types";
import { fetchStockOverview } from "@/features/trade/apis/kis-stock.api";
interface OverviewMeta {
priceSource: DashboardPriceSource;
@@ -60,8 +60,8 @@ export function useStockOverview() {
/**
* / .
* StockLineChart API .
* @see features/dashboard/components/DashboardContainer.tsx useKisTradeWebSocket onTick
* @see features/dashboard/components/chart/StockLineChart.tsx fetchStockChart
* @see features/trade/components/TradeContainer.tsx useKisTradeWebSocket onTick
* @see features/trade/components/chart/StockLineChart.tsx fetchStockChart
*/
const updateRealtimeTradeTick = useCallback(
(tick: DashboardRealtimeTradeTick) => {

View File

@@ -1,10 +1,10 @@
import { useCallback, useRef, useState } from "react";
import { fetchStockSearch } from "@/features/dashboard/apis/kis-stock.api";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import { fetchStockSearch } from "@/features/trade/apis/kis-stock.api";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardStockSearchHistoryItem,
DashboardStockSearchItem,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
const SEARCH_HISTORY_STORAGE_KEY = "jurini:stock-search-history:v1";
const SEARCH_HISTORY_LIMIT = 12;
@@ -44,8 +44,8 @@ function writeSearchHistory(items: DashboardStockSearchHistoryItem[]) {
/**
* @description (//) (localStorage) .
* @see features/dashboard/components/DashboardContainer.tsx // .
* @see features/dashboard/components/search/StockSearchHistory.tsx .
* @see features/trade/components/TradeContainer.tsx // .
* @see features/trade/components/search/StockSearchHistory.tsx .
*/
export function useStockSearch() {
// ========== SEARCH STATE ==========
@@ -92,7 +92,7 @@ export function useStockSearch() {
/**
* @description API를 .
* @see features/dashboard/components/DashboardContainer.tsx handleSearchSubmit / .
* @see features/trade/components/TradeContainer.tsx handleSearchSubmit / .
*/
const search = useCallback(
(query: string, credentials: KisRuntimeCredentials | null) => {
@@ -119,7 +119,7 @@ export function useStockSearch() {
/**
* @description .
* @see features/dashboard/components/DashboardContainer.tsx .
* @see features/trade/components/TradeContainer.tsx .
*/
const clearSearch = useCallback(() => {
abortRef.current?.abort();
@@ -130,7 +130,7 @@ export function useStockSearch() {
/**
* @description API setter입니다.
* @see features/dashboard/components/DashboardContainer.tsx ensureSearchReady .
* @see features/trade/components/TradeContainer.tsx ensureSearchReady .
*/
const setSearchError = useCallback((message: string | null) => {
setError(message);
@@ -138,7 +138,7 @@ export function useStockSearch() {
/**
* @description ( ).
* @see features/dashboard/components/DashboardContainer.tsx handleSelectStock .
* @see features/trade/components/TradeContainer.tsx handleSelectStock .
*/
const appendSearchHistory = useCallback((item: DashboardStockSearchItem) => {
setSearchHistory((prev) => {
@@ -155,7 +155,7 @@ export function useStockSearch() {
/**
* @description .
* @see features/dashboard/components/search/StockSearchHistory.tsx .
* @see features/trade/components/search/StockSearchHistory.tsx .
*/
const removeSearchHistory = useCallback((symbol: string) => {
setSearchHistory((prev) => {
@@ -167,7 +167,7 @@ export function useStockSearch() {
/**
* @description .
* @see features/dashboard/components/search/StockSearchHistory.tsx .
* @see features/trade/components/search/StockSearchHistory.tsx .
*/
const clearSearchHistory = useCallback(() => {
setSearchHistory([]);

View File

@@ -1,5 +1,5 @@
/**
* @file features/dashboard/types/dashboard.types.ts
* @file features/trade/types/trade.types.ts
* @description (//)
*/
@@ -75,7 +75,7 @@ export interface DashboardStockSearchItem {
/**
* 1
* @see features/dashboard/hooks/useStockSearch.ts localStorage에 / .
* @see features/trade/hooks/useStockSearch.ts localStorage에 / .
*/
export interface DashboardStockSearchHistoryItem
extends DashboardStockSearchItem {

View File

@@ -1,7 +1,7 @@
import type {
DashboardRealtimeTradeTick,
DashboardStockOrderBookResponse,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
const REALTIME_SIGN_NEGATIVE = new Set(["4", "5"]);
const ALLOWED_REALTIME_TRADE_TR_IDS = new Set([
@@ -32,7 +32,8 @@ const TICK_FIELD_INDEX = {
} as const;
/**
* KIS ??щ/? ??붿€?????
* @description KIS / .
* @see features/trade/hooks/useKisTradeWebSocket.ts / payload .
*/
export function buildKisRealtimeMessage(
approvalKey: string,
@@ -57,9 +58,10 @@ export function buildKisRealtimeMessage(
}
/**
* ?? ??raw)?? ??????
* - ?( ????? ? 붿
* - ?щ ?€?0 ? ?? ?
* @description (raw) .
* - ( ) .
* - 0 .
* @see features/trade/hooks/useKisTradeWebSocket.ts onmessage .
*/
export function parseKisRealtimeTickBatch(raw: string, expectedSymbol: string) {
if (!/^([01])\|/.test(raw)) return [] as DashboardRealtimeTradeTick[];
@@ -152,7 +154,8 @@ export function parseKisRealtimeTickBatch(raw: string, expectedSymbol: string) {
}
/**
* KIS ????(H0STASP0/H0UNASP0/H0STOAA0)?OrderBook ъ?????
* @description KIS (H0STASP0/H0UNASP0/H0STOAA0) OrderBook .
* @see features/trade/hooks/useKisTradeWebSocket.ts onOrderBookMessage .
*/
export function parseKisRealtimeOrderbook(
raw: string,
@@ -224,8 +227,8 @@ export function parseKisRealtimeOrderbook(
}
/**
* @description ? ?? ? ??? 6? ????.
* @see features/dashboard/utils/kis-realtime.utils.ts parseKisRealtimeOrderbook
* @description 6 .
* @see features/trade/utils/kis-realtime.utils.ts parseKisRealtimeOrderbook .
*/
function normalizeDomesticSymbol(value: string) {
const trimmed = value.trim();

View File

@@ -90,7 +90,7 @@ function tryParseApprovalResponse(rawText: string): KisApprovalResponse {
/**
* @description 승인키를 캐시에서 반환하거나 새로 발급합니다.
* @see features/dashboard/store/use-kis-runtime-store.ts
* @see features/settings/store/use-kis-runtime-store.ts
*/
export async function getKisApprovalKey(credentials?: KisCredentialInput) {
const cacheKey = getApprovalCacheKey(credentials);

View File

@@ -29,7 +29,7 @@ const AFTER_HOURS_SINGLE_END_MINUTES = 18 * 60; // 18:00
/**
* @description Converts external string to strict session enum.
* @see lib/kis/domestic.ts getDomesticOrderBook
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveSessionInClient
* @see features/trade/hooks/useKisTradeWebSocket.ts resolveSessionInClient
*/
export function parseDomesticKisSession(value?: string | null) {
if (!value) return null;
@@ -53,7 +53,7 @@ export function parseDomesticKisSession(value?: string | null) {
/**
* @description Returns current session in KST.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts WebSocket TR switching
* @see features/trade/hooks/useKisTradeWebSocket.ts WebSocket TR switching
* @see lib/kis/domestic.ts REST orderbook source switching
*/
export function getDomesticKisSessionInKst(now = new Date()): DomesticKisSession {
@@ -104,7 +104,7 @@ export function getDomesticKisSessionInKst(now = new Date()): DomesticKisSession
/**
* @description If override is valid, use it. Otherwise use real KST time.
* @see app/api/kis/domestic/orderbook/route.ts session override header
* @see features/dashboard/hooks/useKisTradeWebSocket.ts localStorage override
* @see features/trade/hooks/useKisTradeWebSocket.ts localStorage override
*/
export function resolveDomesticKisSession(
override?: string | null,
@@ -143,7 +143,7 @@ export function shouldUseOvertimeOrderBookApi(session: DomesticKisSession) {
/**
* @description Whether trade tick should use expected-execution TR.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveTradeTrId
* @see features/trade/hooks/useKisTradeWebSocket.ts resolveTradeTrId
*/
export function shouldUseExpectedExecutionTr(session: DomesticKisSession) {
return session === "openAuction" || session === "closeAuction";
@@ -151,7 +151,7 @@ export function shouldUseExpectedExecutionTr(session: DomesticKisSession) {
/**
* @description Whether trade tick/orderbook should use after-hours single-price TR.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveTradeTrId
* @see features/trade/hooks/useKisTradeWebSocket.ts resolveTradeTrId
*/
export function shouldUseAfterHoursSinglePriceTr(session: DomesticKisSession) {
return session === "afterHoursSinglePrice";

View File

@@ -2,7 +2,7 @@ import type {
DashboardChartTimeframe,
DashboardStockItem,
StockCandlePoint,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
import type { KisCredentialInput } from "@/lib/kis/config";
import { kisGet } from "@/lib/kis/client";
import {

View File

@@ -3,7 +3,7 @@ import { KisCredentialInput } from "@/lib/kis/config";
import {
DashboardOrderSide,
DashboardOrderType,
} from "@/features/dashboard/types/dashboard.types";
} from "@/features/trade/types/trade.types";
/**
* @file lib/kis/trade.ts