diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index ad6e4ec..1dc60e2 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -1,17 +1,24 @@ import { Button } from "@/components/ui/button"; import LogoIcon from "@/assets/icons/Logo.svg?react"; import { useNavigate } from "react-router-dom"; +import { useAuthStatus } from "@/hooks/login/query/useAuthStatus"; const Header = () => { const navigate = useNavigate(); + const { data: auth } = useAuthStatus(); const handleLogin = () => { navigate("/login"); - } + }; const handleSignup = () => { navigate("/signup"); - } + }; + + const goMyPage = () => { + navigate("/portfolio"); + }; + return (
@@ -28,7 +35,7 @@ const Header = () => { 지표 - 퀀트 + 퀀트 서비스 @@ -37,14 +44,30 @@ const Header = () => {
- - + {auth?.isAuthenticated ? ( + + ) : ( + <> + + + + )}
diff --git a/src/hooks/login/query/useAuthStatus.ts b/src/hooks/login/query/useAuthStatus.ts index dcd90eb..be506f8 100644 --- a/src/hooks/login/query/useAuthStatus.ts +++ b/src/hooks/login/query/useAuthStatus.ts @@ -2,6 +2,18 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; const checkAuthStatus = async (): Promise => { + const isLocalhost = typeof window !== 'undefined' && window.location.hostname === 'localhost'; + const enableLocalAuto = (import.meta as any)?.env?.VITE_ENABLE_LOCAL_AUTOLOGIN !== 'false'; + + if (isLocalhost && enableLocalAuto) { + const mockUser = { + id: 'local-dev-user', + email: 'dev@local.test', + name: 'Local Developer', + }; + return { isAuthenticated: true, user: mockUser }; + } + try { const response = await axios.get('YOUR_BACKEND_API_URL/auth/status', { withCredentials: true @@ -26,6 +38,6 @@ export const useAuthStatus = () => { queryKey: ['auth'], queryFn: checkAuthStatus, retry: false, - staleTime: 5 * 60 * 1000, + staleTime: 5 * 60 * 1000, }); }; \ No newline at end of file diff --git a/src/pages/account/AccountPage.tsx b/src/pages/account/AccountPage.tsx index b9ee254..e8f9898 100644 --- a/src/pages/account/AccountPage.tsx +++ b/src/pages/account/AccountPage.tsx @@ -1,15 +1,87 @@ -import React from "react"; +import React, { useState } from "react"; +import { useAuthStatus } from "@/hooks/login/query/useAuthStatus"; import PortfolioHeader from "@/pages/portfoliio/components/PortfolioHeader"; -const PortfolioPage = () => { - return ( -
-
- +const AccountPage = () => { + const { data: auth } = useAuthStatus(); + const userName = auth?.user?.name || "크레임"; + const userEmail = auth?.user?.email || "crame25@gmail.com"; + const [notificationsEnabled, setNotificationsEnabled] = useState(true); + + return ( +
+
+ + + {/* Profile info card */} +
+
+

내 정보

+ +
+
+
+
+
이름
+
{userName}
+
+
+
생년월일
+
0000.00.00
+
+
+
아이디
+
crame25
+
+
+
이메일
+
{userEmail}
+
+
+
전화번호
+
000-0000-0000
+
+
+
+
+ + {/* Account settings */} +
+ {/* 알림 설정 */} +
+
+
알림 설정
+
거래 및 시장 알림을 받습니다
+ +
+ + {/* 소셜 연동 */} +
+
+
+ Google +
+
ID : {userEmail}
+
연결일자 : 2000.00.00
+
+
+ +
+
- ); +
+
+ ); }; - -export default PortfolioPage; \ No newline at end of file +export default AccountPage; \ No newline at end of file diff --git a/src/pages/login/GoogleLoginPage.tsx b/src/pages/login/GoogleLoginPage.tsx index 49b58d8..072eea9 100644 --- a/src/pages/login/GoogleLoginPage.tsx +++ b/src/pages/login/GoogleLoginPage.tsx @@ -13,7 +13,7 @@ const GoogleLoginPage = () => { const location = useLocation(); const state = location.state as LocationState; const errorMessage = state?.error; - + const CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; const REDIRECT_URI = "http://localhost:5173/login/auth/google"; const SCOPE = "email profile openid"; @@ -24,9 +24,12 @@ const GoogleLoginPage = () => { } useEffect(() => { - // 이미 로그인된 사용자는 홈 페이지로 리디렉션 - if (authData?.isAuthenticated) { - navigate('/home'); + const isLocalhost = typeof window !== 'undefined' && window.location.hostname === 'localhost'; + const enableLocalAuto = (import.meta as any)?.env?.VITE_ENABLE_LOCAL_AUTOLOGIN !== 'false'; + + // 로컬 자동로그인 상태에서는 로그인 페이지에서 자동 리다이렉트하지 않음 + if (authData?.isAuthenticated && !(isLocalhost && enableLocalAuto)) { + navigate('/'); } }, [authData, navigate]); @@ -50,6 +53,14 @@ const GoogleLoginPage = () => {
+ {window.location.hostname === 'localhost' && ( + + )}
) } diff --git a/src/pages/portfoliio/ApiKeyPage.tsx b/src/pages/portfoliio/ApiKeyPage.tsx new file mode 100644 index 0000000..0e690ca --- /dev/null +++ b/src/pages/portfoliio/ApiKeyPage.tsx @@ -0,0 +1,149 @@ +import React, { useMemo, useState } from "react"; +import PortfolioHeader from "@/pages/portfoliio/components/PortfolioHeader"; + +type ApiKeyItem = { + id: string; + name: string; + publicKey: string; + secretKey: string; + createdAt: string; + manager: string; +}; + +const initialRows: ApiKeyItem[] = [ + { id: "1", name: "Key1", publicKey: "Public Key1", secretKey: "Secret Key1", createdAt: "생성일1", manager: "관리1" }, + { id: "2", name: "Key2", publicKey: "Public Key2", secretKey: "Secret Key2", createdAt: "생성일2", manager: "관리2" }, + { id: "3", name: "Key3", publicKey: "Public Key3", secretKey: "Secret Key3", createdAt: "생성일3", manager: "관리3" }, + { id: "4", name: "Key4", publicKey: "Public Key4", secretKey: "Secret Key4", createdAt: "생성일4", manager: "관리4" }, +]; + +const ApiKeyPage = () => { + const [rows, setRows] = useState(initialRows); + const [open, setOpen] = useState(false); + const [form, setForm] = useState({ name: "", publicKey: "", secretKey: "" }); + + const isValid = useMemo(() => form.name && form.publicKey && form.secretKey, [form]); + + const onConfirm = () => { + if (!isValid) return; + const now = new Date(); + const createdAt = `${now.getFullYear()}.${String(now.getMonth() + 1).padStart(2, "0")}.${String(now.getDate()).padStart(2, "0")}`; + setRows(prev => [ + ...prev, + { + id: String(prev.length + 1), + name: form.name, + publicKey: form.publicKey, + secretKey: form.secretKey, + createdAt, + manager: `관리${prev.length + 1}`, + }, + ]); + setForm({ name: "", publicKey: "", secretKey: "" }); + setOpen(false); + }; + + const onDelete = (id: string) => { + const ok = window.confirm("해당 API Key를 삭제하시겠습니까?"); + if (!ok) return; + setRows(prev => prev.filter(r => r.id !== id)); + }; + + return ( +
+
+ + + {/* 안내 */} +
+

API Key 설정 안내

+

거래소 API를 등록하시면 자동매매 기능을 이용하실 수 있습니다. API Key는 안전하게 암호화되어 저장됩니다.

+
+ + {/* 표 */} +
+
+
API Key 관리
+ +
+
+ + + + + + + + + + + {rows.map((r) => ( + + + + + + + ))} + +
Public KeySecret Key생성일관리
{r.publicKey}{r.secretKey}{r.createdAt} +
+ {r.manager} + +
+
+
+
+
+ + {/* 모달 */} + {open && ( +
+
+

API key 입력

+
+
+ + setForm({ ...form, name: e.target.value })} + placeholder="Key 이름을 입력해주세요" + className="w-full rounded-md border border-[#E0D7C8] px-3 py-3 focus:outline-none focus:ring-2 focus:ring-[#F4B224]" + /> +
+
+ + setForm({ ...form, publicKey: e.target.value })} + placeholder="Public Key를 입력해주세요" + className="w-full rounded-md border border-[#E0D7C8] px-3 py-3 focus:outline-none focus:ring-2 focus:ring-[#F4B224]" + /> +
+
+ + setForm({ ...form, secretKey: e.target.value })} + placeholder="Secret Key를 입력해주세요" + className="w-full rounded-md border border-[#E0D7C8] px-3 py-3 focus:outline-none focus:ring-2 focus:ring-[#F4B224]" + /> +
+
+
+ + +
+
+
+ )} +
+ ); +}; + +export default ApiKeyPage; diff --git a/src/pages/quant/QuantPage.tsx b/src/pages/quant/QuantPage.tsx index ebec03c..7fff9a2 100644 --- a/src/pages/quant/QuantPage.tsx +++ b/src/pages/quant/QuantPage.tsx @@ -26,7 +26,7 @@ const QuantPage = () => { system.id === "algorithm-trading" ? () => navigate("/quant/algo") : system.id === "ai-trading" - ? undefined + ? () => navigate("/ai-trade") : undefined } /> diff --git a/src/pages/quant/components/StrategyCard.tsx b/src/pages/quant/components/StrategyCard.tsx index 3c5a5be..7880ce2 100644 --- a/src/pages/quant/components/StrategyCard.tsx +++ b/src/pages/quant/components/StrategyCard.tsx @@ -48,7 +48,7 @@ const StrategyCard = ({

{title}

{subtitle && {subtitle}}
-

간단설명간단설 명간단설명간 간단설 명 간단설명간간 설명 간 담설

+

트레이딩 전략 소개


diff --git a/src/pages/quant/data/mockData.ts b/src/pages/quant/data/mockData.ts index c41af82..ca2c46d 100644 --- a/src/pages/quant/data/mockData.ts +++ b/src/pages/quant/data/mockData.ts @@ -64,7 +64,7 @@ export const tradingSystems: TradingSystem[] = [ percentage: 72, } ], - buttonText: '현재 나의 플랜', + buttonText: '프리미엄 구독하기', isDisabled: false, } ]; \ No newline at end of file diff --git a/src/router/AppRouter.tsx b/src/router/AppRouter.tsx index 12235c5..6515aba 100644 --- a/src/router/AppRouter.tsx +++ b/src/router/AppRouter.tsx @@ -13,6 +13,7 @@ import AITradingPage from "@pages/ai-trade/aiTradingPage.tsx"; import SignupPage from "@pages/signup/SignupPage.tsx"; import PortfolioPage from "@pages/portfoliio/PortfolioPage.tsx"; import Account from "@pages/account/AccountPage.tsx"; +import PortfolioApiKeyPage from "@/pages/portfoliio/ApiKeyPage"; const AppRouter = () => { return ( @@ -32,6 +33,7 @@ const AppRouter = () => { } /> } /> + } /> }> } />