diff --git a/client/src/components/Logins/GoogleSignin.tsx b/client/src/components/Logins/GoogleSignin.tsx index 8cce7821..ad268ad8 100644 --- a/client/src/components/Logins/GoogleSignin.tsx +++ b/client/src/components/Logins/GoogleSignin.tsx @@ -17,6 +17,9 @@ const GoogleSignIn: React.FC = () => { dispatch(setLoginState()); }; + + + const handleError = () => { console.log("Login Failed"); }; diff --git a/client/src/components/watchlist/Header.tsx b/client/src/components/watchlist/Header.tsx new file mode 100644 index 00000000..7a2f470a --- /dev/null +++ b/client/src/components/watchlist/Header.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import styled from 'styled-components'; +import Menu_icon from "../../asset/images/menu.png"; + +const INTEREST_LIST = "관심목록"; +const INVESTMENT_LIST = "투자목록"; + +// Header 컴포넌트는 다음과 같은 기능을 수행합니다. +// 1. 메뉴 아이콘과 현재 리스트 타입을 표시합니다. +// 2. 메뉴 아이콘을 클릭하면 메뉴의 열림/닫힘 상태를 토글합니다. +// 3. 메뉴가 열려 있으면 SlideMenu를 통해 관심목록과 투자목록을 선택할 수 있습니다. + +const Header: React.FC = ({ currentListType, onChangeListType, isMenuOpen, setMenuOpen }) => { + return ( + + setMenuOpen(!isMenuOpen)} + /> + {currentListType} + {isMenuOpen && ( + + { onChangeListType(INTEREST_LIST); setMenuOpen(false); }}>{INTEREST_LIST} + { onChangeListType(INVESTMENT_LIST); setMenuOpen(false); }}>{INVESTMENT_LIST} + + )} + + ); + }; + +// HeaderProps는 Header 컴포넌트가 받을 props의 타입을 정의합니다. +type HeaderProps = { + currentListType: string; + onChangeListType: (type: "관심목록" | "투자목록") => void; + isMenuOpen: boolean; + setMenuOpen: React.Dispatch>; + + }; + + +const HeaderWrapper = styled.div` + display: flex; + align-items: center; + position: relative; +`; + +const Icon = styled.img` + width: 24px; + height: 24px; + cursor: pointer; + margin-right: 10px; +`; + +const HeaderText = styled.span` + font-size: 18px; +`; + +const SlideMenu = styled.div` + position: absolute; + top: 100%; + left: 0; + width: 248px; + background-color: #f7f7f7; + border: 1px solid #e0e0e0; + display: flex; + flex-direction: column; +`; + +const MenuItem = styled.button` + padding: 8px 16px; + border: none; + background-color: transparent; + cursor: pointer; + text-align: left; +`; + +export default Header; diff --git a/client/src/components/watchlist/StockItem.tsx b/client/src/components/watchlist/StockItem.tsx new file mode 100644 index 00000000..617b687e --- /dev/null +++ b/client/src/components/watchlist/StockItem.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import styled from 'styled-components'; + +// StockItem 컴포넌트는 주식 정보를 나타내는 UI를 구성합니다. +const StockItem: React.FC = ({ stock, setShowChangePrice, showChangePrice }) => { + return ( + {/* 전체 아이템을 감싸는 래퍼 */} + {/* 로고 이미지 */} + {/* 주식의 이름과 코드를 담는 섹션 */} + {stock.name} {/* 주식 이름 */} + {stock.code} {/* 주식 코드 */} + + {/* 주식의 가격과 변동률을 담는 섹션 */} + {stock.price} {/* 주식 가격 */} + setShowChangePrice(true)} + onMouseLeave={() => setShowChangePrice(false)} + > + {showChangePrice ? stock.changePrice : stock.change} {/* 변동률 또는 변동 가격 */} + + + + ); + }; + + type Stock = { + name: string; + code: string; + price: string; + change: string; + changePrice: string; + logo: string; + }; + + type StockItemProps = { + stock: Stock; + showChangePrice: boolean; + setShowChangePrice: React.Dispatch>; + }; + +const StockItemWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + width: 100%; + background-color: transparent; + cursor: pointer; +`; + +const Logo = styled.img` + border-radius: 50%; + width: 40px; + height: 40px; + margin-right: 12px; +`; + +const StockInfo = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + margin-right: 16px; +`; + +const StockName = styled.span` + font-weight: bold; +`; + +const StockCode = styled.span` + color: gray; +`; + +const StockPriceSection = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const StockPrice = styled.span<{ change: string }>` + color: ${props => props.change.startsWith('+') ? 'red' : 'blue'}; +`; + +const StockChange = styled.span<{ change: string }>` + color: ${props => props.change.startsWith('+') ? 'red' : 'blue'}; + cursor: pointer; +`; + +export default StockItem; diff --git a/client/src/components/watchlist/WatchList.tsx b/client/src/components/watchlist/WatchList.tsx index 18ccca84..73f22fbe 100644 --- a/client/src/components/watchlist/WatchList.tsx +++ b/client/src/components/watchlist/WatchList.tsx @@ -1,210 +1,67 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import Samsung_logo from "../../asset/logos/Samsung_logo.svg" -import LG_logo from "../../asset/logos/LG_logo.svg" -import Sk_logo from "../../asset/logos/Sk_logo.png" -import POSCO_logo from "../../asset/logos/POSCO_logo.svg" -import Menu_icon from "../../asset/images/menu.png" +import Header from './Header'; +import StockItem from './StockItem'; -const WatchList: React.FC = ({ currentListType, onChangeListType }) => { +type WatchListProps = { + currentListType: "관심목록" | "투자목록"; + onChangeListType: (type: "관심목록" | "투자목록") => void; +}; +const WatchList: React.FC = ({ currentListType, onChangeListType }) => { const [isMenuOpen, setMenuOpen] = useState(false); - + const [showChangePrice, setShowChangePrice] = useState(false); - const favoriteStocks = [ - { name: "삼성전자", code: "005930", price: "71,000원", change: "+6.13%", changePrice: "+4,100원", logo: Samsung_logo }, - { name: "LG에너지솔루션", code: "373220", price: "522,000원", change: "-4.04%", changePrice: "-22,000원", logo: LG_logo }, - { name: "SK하이닉스", code: "000660", price: "120,000원", change: "-1.48%", changePrice: "-1,800원", logo: Sk_logo }, - { name: "삼성바이오로직스", code: "207940", price: "733,000원", change: "-0.54%", changePrice: "-4,000원", logo: Samsung_logo }, - { name: "POSCO홀딩스", code: "005490", price: "560,000원", change: "-3.28%", changePrice: "-19,000원", logo: POSCO_logo }, - { name: "삼성전자우", code: "005935", price: "56,900원", change: "+5.37%", changePrice: "+2,900원", logo: Samsung_logo }, - { name: "삼성SDI", code: "006400", price: "596,000원", change: "-2.93%", changePrice: "-18,000원", logo: Samsung_logo } + // Sample stocks data + const stocks = [ + { + name: '삼성전자', + code: '005930', + price: '57800', + change: '+0.67%', + changePrice: '+380', + logo: 'https://your_logo_url.com' + }, + { + name: '현대차', + code: '005380', + price: '205000', + change: '-0.24%', + changePrice: '-500', + logo: 'https://your_logo_url.com' + }, ]; - const [showChangePrice, setShowChangePrice] = useState(false); - return ( -
- setMenuOpen(!isMenuOpen)} - /> - {currentListType} - {isMenuOpen && ( - - { onChangeListType('관심목록'); setMenuOpen(false); }}>관심목록 - { onChangeListType('투자목록'); setMenuOpen(false); }}>투자목록 - - )} -
- - { /* 종목 추가 로직 */ }}>종목 추가 - - {favoriteStocks.map(stock => ( - - - - {stock.name} - {stock.code} - - - {stock.price} - setShowChangePrice(true)} - onMouseLeave={() => setShowChangePrice(false)} - > - {showChangePrice ? stock.changePrice : stock.change} - - - - ))} +
+ + {stocks.map((stock, index) => ( + + ))} + ); }; -type WatchListProps = { - currentListType: "관심목록" | "투자목록"; - onChangeListType: (type: "관심목록" | "투자목록") => void; -}; - -const getColorByChange = (change: string) => { - if (change.startsWith('+')) return 'red'; - if (change.startsWith('-')) return 'blue'; - return 'black'; -}; - const WatchListContainer = styled.div` - padding: 8px 0px; -`; - -const Header = styled.div` - display: flex; - align-items: center; - position: relative; -`; - -const Icon = styled.img` - width: 24px; // 너비를 설정합니다. 원하는 크기로 조절 가능합니다. - height: 24px; // 높이를 설정합니다. 원하는 크기로 조절 가능합니다. - cursor: pointer; - margin-right: 10px; -`; - -const HeaderText = styled.span` - font-size: 18px; -`; - -const SlideMenu = styled.div` - position: absolute; - top: 100%; - left: 0; - width: 248px; - background-color: #f7f7f7; - border: 1px solid #e0e0e0; - display: flex; - flex-direction: column; -`; - -const MenuItem = styled.button` - padding: 8px 16px; - border: none; - background-color: transparent; - cursor: pointer; - text-align: left; - - &:hover { - background-color: #e0e0e0; - } -`; - -const StockItem = styled.button` - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 8px 0; - border-bottom: 1px solid #e0e0e0; - width: 100%; - background-color: transparent; - cursor: pointer; - border: none; - text-align: left; -`; - -const Logo = styled.img` - border-radius: 50%; - width: 40px; - height: 40px; - margin-right: 12px; -`; - -const StockInfo = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - margin-right: 16px; -`; - -const StockName = styled.span` - font-weight: bold; -`; - -const StockCode = styled.span` - color: gray; -`; - -const StockPriceSection = styled.div` display: flex; flex-direction: column; align-items: flex-start; `; -const StockPrice = styled.span.attrs<{ change: string }>(({ change }) => ({ - style: { - color: getColorByChange(change), - }, -}))``; - -const StockChange = styled.span.attrs<{ change: string }>(({ change }) => ({ - style: { - color: getColorByChange(change), - }, - }))` - cursor: pointer; - `; - -const Divider1 = styled.div` - margin:0px; - padding:0px; - width: 100%; - height: 11px; - display: flex; - flex-direction: row; - border-bottom: 1px solid #2f4f4f; -`; -const Divider2 = styled.div` - margin:0px; - padding:0px; - width: 100%; - height: 4px; - display: flex; - flex-direction: row; - border-bottom: 1px solid #2f4f4f; -`; - -const AddStockButton = styled.button` - padding: 10px; - border: none; - background-color: transparent; - cursor: pointer; - text-align: left; - color: black; - - &:hover { - background-color: #e0e0e0; - } +const StockList = styled.div` + width: 100%; `; export default WatchList; diff --git a/client/src/page/TabPages/TabContainerPage.tsx b/client/src/page/TabPages/TabContainerPage.tsx index 65b4f237..0754e277 100644 --- a/client/src/page/TabPages/TabContainerPage.tsx +++ b/client/src/page/TabPages/TabContainerPage.tsx @@ -1,7 +1,7 @@ import MarketInfo from "./MarketInfoPage"; import { Routes, Route, Link } from "react-router-dom"; import styled from "styled-components"; -import { DetailStockInformation } from "../../components/stockinfoComponents/DetailStockInformation"; +import { DetailStockInformation } from "../../components/stockListComponents/DetailStockInformation"; import { Community } from "./communityPage"; import { useState } from "react"; import {