diff --git a/client/package-lock.json b/client/package-lock.json index 4a719d1c..0ba4f0e5 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,9 @@ "name": "vite-project", "version": "0.0.0", "dependencies": { + "axios": "^1.5.0", + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": "^6.0.7" @@ -2833,6 +2836,21 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", + "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", @@ -3052,6 +3070,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3141,6 +3170,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3165,6 +3202,33 @@ "node": ">=6.0.0" } }, + "node_modules/echarts": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", + "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.4.4" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.2.tgz", + "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, "node_modules/electron-to-chromium": { "version": "1.4.504", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.504.tgz", @@ -3496,8 +3560,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.1", @@ -3608,6 +3671,38 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -3990,6 +4085,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4216,6 +4330,11 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -4509,6 +4628,11 @@ "node": ">=8" } }, + "node_modules/size-sensor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz", + "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4896,6 +5020,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zrender": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", + "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } } diff --git a/client/package.json b/client/package.json index 7745298f..204529df 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.5.0", + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": "^6.0.7" diff --git a/client/src/asset/CentralSectionMenu-BookmarkOff.png b/client/src/asset/CentralSectionMenu-BookmarkOff.png new file mode 100644 index 00000000..1f3bec98 Binary files /dev/null and b/client/src/asset/CentralSectionMenu-BookmarkOff.png differ diff --git a/client/src/asset/CentralSectionMenu-BookmarkOn.png.png b/client/src/asset/CentralSectionMenu-BookmarkOn.png.png new file mode 100644 index 00000000..af97fcf3 Binary files /dev/null and b/client/src/asset/CentralSectionMenu-BookmarkOn.png.png differ diff --git a/client/src/asset/CentralSectionMenu-compareChart.png b/client/src/asset/CentralSectionMenu-compareChart.png new file mode 100644 index 00000000..9e3a34e1 Binary files /dev/null and b/client/src/asset/CentralSectionMenu-compareChart.png differ diff --git a/client/src/asset/images/GoogleLogo.svg b/client/src/asset/images/GoogleLogo.svg new file mode 100644 index 00000000..3790851d --- /dev/null +++ b/client/src/asset/images/GoogleLogo.svg @@ -0,0 +1,2 @@ + + diff --git a/client/src/asset/images/KakaoLogo.svg b/client/src/asset/images/KakaoLogo.svg new file mode 100644 index 00000000..e5f5d11b --- /dev/null +++ b/client/src/asset/images/KakaoLogo.svg @@ -0,0 +1,21 @@ + +image/svg+xml diff --git a/client/src/components/CentralChartSection/Index.tsx b/client/src/components/CentralChartSection/Index.tsx new file mode 100644 index 00000000..ebc00abe --- /dev/null +++ b/client/src/components/CentralChartSection/Index.tsx @@ -0,0 +1,24 @@ +import { styled } from "styled-components"; + +import UpperMenuBar from "../CentralSectionMenu/Index"; +import StockChart from "./StockChart"; + +const CentralChartSection = () => { + return ( + + + + + ); +}; + +export default CentralChartSection; + +const Container = styled.div` + flex: 6.7 0 0; + min-width: 630px; + height: 100%; + + display: flex; + flex-direction: column; +`; diff --git a/client/src/components/CentralChartSection/StockChart.tsx b/client/src/components/CentralChartSection/StockChart.tsx new file mode 100644 index 00000000..11cfaf19 --- /dev/null +++ b/client/src/components/CentralChartSection/StockChart.tsx @@ -0,0 +1,122 @@ +import { styled } from "styled-components"; +import EChartsReact from "echarts-for-react"; + +const StockChart = () => { + return ( + + + + ); +}; + +export default StockChart; + +const options = { + // title: { + // text: "Stock Chart with Separate Y Axes on the Right Side", + // }, + xAxis: { + type: "category", + data: [ + new Date("2023-08-31 14:00").toLocaleDateString(), + "2023-08-31 14:00", + "Day 1", + "Day 2", + "Day 3", + "Day 4", + "Day 5", + "Day 6", + "Day 7", + "Day 8", + "Day 9", + "Day 10", + "Day 11", + "Day 12", + "Day 13", + "Day 14", + "Day 15", + "Day 16", + "Day 17", + "Day 18", + "Day 19", + "Day 20", + "Day 21", + "Day 22", + "Day 23", + "Day 24", + "Day 25", + "Day 26", + "Day 27", + "Day 28", + ], // X 축 라벨 + }, + yAxis: [ + { + type: "value", + // name: "Price", // 주가 Y 축 라벨 + position: "right", // 오른쪽에 위치 + }, + ], + dataZoom: [ + { + type: "inside", // 마우스 스크롤을 통한 줌 인/아웃 지원 + }, + ], + tooltip: { + trigger: "axis", + axisPointer: { + type: "cross", // 마우스 위치에 눈금 표시 + }, + }, + series: [ + { + name: "주가", + type: "candlestick", // 캔들스틱 시리즈 + data: [ + [100, 120, 80, 90], // 시가, 종가, 저가, 주가 + [110, 130, 100, 120], + [90, 110, 70, 100], + [95, 105, 85, 110], + [105, 125, 95, 120], + [110, 120, 100, 130], + [120, 140, 110, 150], + [130, 150, 120, 160], + [140, 160, 130, 170], + [150, 170, 140, 180], + [150, 170, 140, 180], + [160, 180, 150, 190], + [170, 190, 160, 200], + [170, 200, 170, 210], + [170, 140, 130, 130], + [150, 160, 120, 160], + [140, 160, 130, 170], + [150, 170, 140, 180], + [140, 125, 95, 120], + [110, 120, 100, 130], + [120, 140, 110, 150], + [130, 150, 120, 160], + [140, 160, 130, 170], + [150, 170, 140, 180], + [160, 180, 150, 190], + [170, 190, 160, 200], + [180, 200, 170, 210], + [190, 210, 180, 220], + ], + yAxisIndex: 0, // 첫 번째 Y 축 사용 + }, + ], +}; + +// 사이즈 조절을 위한 스타일 설정 +const chartStyle = { + width: "100%", + height: "100%", +}; + +const Container = styled.div` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; diff --git a/client/src/components/CentralChartSection/TransactionVolumeChart.tsx b/client/src/components/CentralChartSection/TransactionVolumeChart.tsx new file mode 100644 index 00000000..8855348a --- /dev/null +++ b/client/src/components/CentralChartSection/TransactionVolumeChart.tsx @@ -0,0 +1,15 @@ +import { styled } from "styled-components"; + +const TransactionVolumeChart = () => { + return TransactionVolumeChart; +}; + +export default TransactionVolumeChart; + +const Container = styled.div` + flex: 1 0 0; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid darkgray; +`; diff --git a/client/src/components/CentralSectionMenu/FLineBookmarkBtn.tsx b/client/src/components/CentralSectionMenu/FLineBookmarkBtn.tsx new file mode 100644 index 00000000..8eaebd92 --- /dev/null +++ b/client/src/components/CentralSectionMenu/FLineBookmarkBtn.tsx @@ -0,0 +1,39 @@ +import { styled } from "styled-components"; + +import bookmarkOffImg from "../../asset/CentralSectionMenu-BookmarkOff.png"; +// import bookmarkOnImg from "../../asset/CentralSectionMenu-BookmarkOn.png.png"; + +const buttonText: string = "관심"; + +const BookmarkBtn = () => { + return ( + + ; + } + + if (direction === "right") { + return ; + } +}; + +export default ExpandScreenBtn; + +// type 정의 +interface OwnProps { + direction: string; +} + +// component 생성 +const Button = styled.div` + width: 43px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 22px; + border-right: ${(props) => + props.direction === "left" && "1px solid darkgray"}; + border-left: ${(props) => + props.direction === "right" && "1px solid darkgray"}; +`; diff --git a/client/src/components/CentralSectionMenu/FLineStockOverview.tsx b/client/src/components/CentralSectionMenu/FLineStockOverview.tsx new file mode 100644 index 00000000..e998aba5 --- /dev/null +++ b/client/src/components/CentralSectionMenu/FLineStockOverview.tsx @@ -0,0 +1,102 @@ +import { styled } from "styled-components"; + +// dummyData -> 일부 재활용 예정 (변수명 다시 생각) +import dummyImg from "../../asset/CentralSectionMenu-dummyImg.png"; +const corpName: string = "카카오"; +const stockCode: string = "035720"; +const stockCodeAdditionalInfo: string = "코스피"; +const stockPrice: string = "48,600"; +const changeRate: string = "+1.25%"; +const chageAmount: string = "▲600"; +const transactionVolume: string = "864,728"; +const volumeAdditionalInfo: string = "거래량"; +const transactionValue: string = "419억 1,468만"; +const valueAdditionalInfo: string = "거래대금"; + +const StockOverview = () => { + return ( + + + {corpName} + + {stockCode} {stockCodeAdditionalInfo} + + {stockPrice} + {changeRate} + {chageAmount} + + {volumeAdditionalInfo} + {transactionVolume} + + + {valueAdditionalInfo} + {transactionValue} + + + ); +}; + +export default StockOverview; + +const Container = styled.div` + flex: 7 0 0; + + display: flex; + flex-direction: row; + align-items: center; + padding-left: 12px; + padding-right: 12px; + gap: 8px; +`; + +const CorpLogo = styled.img` + width: 24px; + height: 24px; + border-radius: 50%; +`; + +const CorpName = styled.div` + font-size: 18px; + font-weight: 530; +`; + +const StockCode = styled.div` + font-size: 14px; + color: #999999; +`; + +const StockPrice = styled.div` + font-size: 18px; + color: #ed2926; + font-weight: 530; +`; + +const PriceChangeRate = styled.div` + font-size: 14px; + color: #ed2926; +`; + +const PriceChangeAmount = styled.div` + font-size: 14px; + color: #ed2926; +`; + +const TransactionVolume = styled.div` + font-size: 14px; + color: #525252; + + & span { + color: #999999; + padding-right: 5px; + } +`; + +const TransactionValue = styled.div` + font-size: 14px; + color: #525252; + + & span { + color: #999999; + padding-right: 5px; + } +`; diff --git a/client/src/components/CentralSectionMenu/FLineTradeBtn.tsx b/client/src/components/CentralSectionMenu/FLineTradeBtn.tsx new file mode 100644 index 00000000..b0936391 --- /dev/null +++ b/client/src/components/CentralSectionMenu/FLineTradeBtn.tsx @@ -0,0 +1,44 @@ +import { styled } from "styled-components"; + +const TradeBtn = (props: OwnProps) => { + const { type } = props; + const buttonText: string = type === "buying" ? "매수" : "매도"; + + return ( + + + + ); +}; + +export default TradeBtn; + +// type 정의 +interface OwnProps { + type: string; +} + +// component 생성 +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding-right: 12px; +`; + +const Button = styled.div` + width: 44px; + height: 24px; + + display: flex; + justify-content: center; + align-items: center; + + font-size: 12px; + color: ${(props) => (props.type === "buying" ? "#cc3c3a" : "#4479c2")}; + background-color: ${(props) => + props.type === "buying" ? "#fcdddb" : "#dce9fc"}; + + padding-top: 2px; + border-radius: 0.2rem; +`; diff --git a/client/src/components/CentralSectionMenu/FirstLineMenu.tsx b/client/src/components/CentralSectionMenu/FirstLineMenu.tsx new file mode 100644 index 00000000..bec35fdc --- /dev/null +++ b/client/src/components/CentralSectionMenu/FirstLineMenu.tsx @@ -0,0 +1,29 @@ +import { styled } from "styled-components"; + +import ExpandScreenBtn from "./FLineExpandScreenBtn"; +import StockOverview from "./FLineStockOverview"; +import BookmarkBtn from "./FLineBookmarkBtn"; +import TradeBtn from "./FLineTradeBtn"; + +const FirstLineMenu = () => { + return ( + + + + + + + + + ); +}; + +export default FirstLineMenu; + +const Container = styled.div` + width: 100%; + height: 44px; + display: flex; + flex-direction: row; + border-bottom: 1px solid darkgray; +`; diff --git a/client/src/components/CentralSectionMenu/Index.tsx b/client/src/components/CentralSectionMenu/Index.tsx new file mode 100644 index 00000000..a14acaa0 --- /dev/null +++ b/client/src/components/CentralSectionMenu/Index.tsx @@ -0,0 +1,20 @@ +import { styled } from "styled-components"; + +import FirstLineMenu from "./FirstLineMenu"; +import SecondLineMenu from "./SecondLineMenu"; + +const UpperMenuBar = () => { + return ( + + + + + ); +}; + +export default UpperMenuBar; + +const Container = styled.div` + width: 100%; + text-align: center; +`; diff --git a/client/src/components/CentralSectionMenu/SLineChangeChartCycleBox.tsx b/client/src/components/CentralSectionMenu/SLineChangeChartCycleBox.tsx new file mode 100644 index 00000000..77e8eb48 --- /dev/null +++ b/client/src/components/CentralSectionMenu/SLineChangeChartCycleBox.tsx @@ -0,0 +1,22 @@ +import { styled } from "styled-components"; +import ChangeChartCycleBtn from "./SLineChangeChartCycleBtn"; + +const ChangeChartCycleBox = () => { + return ( + + + + + + + + ); +}; + +export default ChangeChartCycleBox; + +const Container = styled.div` + display: flex; + flex-direction: row; + gap: 4px; +`; diff --git a/client/src/components/CentralSectionMenu/SLineChangeChartCycleBtn.tsx b/client/src/components/CentralSectionMenu/SLineChangeChartCycleBtn.tsx new file mode 100644 index 00000000..fff89b54 --- /dev/null +++ b/client/src/components/CentralSectionMenu/SLineChangeChartCycleBtn.tsx @@ -0,0 +1,22 @@ +import { styled } from "styled-components"; + +const ChangeChartCycleBtn = (props: OwnProps) => { + const { cycle } = props; + return {cycle}; +}; + +export default ChangeChartCycleBtn; + +// type 정의 +interface OwnProps { + cycle: string; +} + +// component 생성 +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding-right: 14px; + color: #999999; +`; diff --git a/client/src/components/CentralSectionMenu/SLineCompareChartBtn.tsx b/client/src/components/CentralSectionMenu/SLineCompareChartBtn.tsx new file mode 100644 index 00000000..1fe3be1e --- /dev/null +++ b/client/src/components/CentralSectionMenu/SLineCompareChartBtn.tsx @@ -0,0 +1,37 @@ +import { styled } from "styled-components"; + +import IconImg from "../../asset/CentralSectionMenu-compareChart.png"; + +const buttonText: string = "비교차트"; + +const CompareChartBtn = () => { + return ( + + +
{buttonText}
+
+ ); +}; + +export default CompareChartBtn; + +const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding-left: 12px; + + .BtntextContainer { + display: flex; + justify-content: center; + align-items: center; + padding-top: 3px; + } +`; + +const Icon = styled.img` + width: auto; + height: 28px; + border-radius: 50%; + padding-right: 4px; +`; diff --git a/client/src/components/CentralSectionMenu/SecondLineMenu.tsx b/client/src/components/CentralSectionMenu/SecondLineMenu.tsx new file mode 100644 index 00000000..37640766 --- /dev/null +++ b/client/src/components/CentralSectionMenu/SecondLineMenu.tsx @@ -0,0 +1,25 @@ +import { styled } from "styled-components"; + +import CompareChartBtn from "./SLineCompareChartBtn"; +import ChangeChartCycleBox from "./SLineChangeChartCycleBox"; + +const SecondLineMenu = () => { + return ( + + + + + ); +}; + +export default SecondLineMenu; + +const Container = styled.div` + width: 100%; + height: 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid darkgray; +`; diff --git a/client/src/components/Headers/LoginHeader.tsx b/client/src/components/Headers/LoginHeader.tsx index af416b2e..4ddd3947 100644 --- a/client/src/components/Headers/LoginHeader.tsx +++ b/client/src/components/Headers/LoginHeader.tsx @@ -4,7 +4,7 @@ import StockHolmLogo from "../../asset/images/StockHolmLogo.png"; import SampleProfile from "../../asset/images/ProfileSample.png"; import AlarmImage from "../../asset/images/alarm.png"; -const LoginHeader: React.FC = () => { +const LoginHeader: React.FC = ({ onLogoutClick }) => { const [searchValue, setSearchValue] = useState(''); const handleSearchChange = (event: React.ChangeEvent) => { @@ -30,12 +30,18 @@ const LoginHeader: React.FC = () => { - {logoutText} + {logoutText} {/* 로그아웃 버튼 클릭 시 onLogoutClick 실행 */} ); }; +export default LoginHeader; + +interface LoginHeaderProps { + onLogoutClick: () => void; // 로그아웃 클릭 핸들러 타입 정의 +} + const HeaderContainer = styled.div` display: flex; justify-content: space-between; @@ -124,4 +130,3 @@ const LogoutButton = styled.button` -export default LoginHeader; diff --git a/client/src/components/Headers/LogoutHeader.tsx b/client/src/components/Headers/LogoutHeader.tsx index 04418fba..484475fe 100644 --- a/client/src/components/Headers/LogoutHeader.tsx +++ b/client/src/components/Headers/LogoutHeader.tsx @@ -1,10 +1,9 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import StockHolmLogo from "../../asset/images/StockHolmLogo.png"; -import React, { useState } from 'react'; -import styled from 'styled-components'; -import StockHolmLogo from "../../asset/images/StockHolmLogo.png" - -const LogoutHeader: React.FC = () => { - const [searchValue, setSearchValue] = useState(''); +const LogoutHeader: React.FC = ({ onLoginClick }) => { + const [searchValue, setSearchValue] = useState(""); const handleSearchChange = (event: React.ChangeEvent) => { setSearchValue(event.target.value); @@ -22,7 +21,7 @@ const LogoutHeader: React.FC = () => { - {loginText} + {loginText} ); }; @@ -33,10 +32,9 @@ const HeaderContainer = styled.div` align-items: center; padding: 1rem 2rem; background-color: #fff; - border-bottom: 1px solid #2F4F4F; // 다크 슬레이트 그레이 줄 + border-bottom: 1px solid #2f4f4f; // 다크 슬레이트 그레이 줄 width: 100%; height: 51px; - `; const LogoButton = styled.button` @@ -44,7 +42,7 @@ const LogoButton = styled.button` border: none; cursor: pointer; padding: 0; - + &:focus { outline: none; } @@ -56,8 +54,8 @@ const LogoImage = styled.img` `; const SearchBar = styled.input.attrs({ - type: 'text', - placeholder: '검색...' + type: "text", + placeholder: "검색...", })` width: 50%; padding: 0.5rem; @@ -67,8 +65,8 @@ const SearchBar = styled.input.attrs({ const LoginButton = styled.button` background-color: #fff; // 흰색 배경 - color: #2F4F4F; // 글자색 - border: 1px solid #2F4F4F; // 회색 테두리 + color: #2f4f4f; // 글자색 + border: 1px solid #2f4f4f; // 회색 테두리 padding: 0.5rem 1rem; border-radius: 5px; // 5px 둥글게 cursor: pointer; @@ -78,5 +76,8 @@ const LoginButton = styled.button` background-color: #f2f2f2; // 호버 시 약간 어두운 흰색으로 변경 } `; +interface LogoutHeaderProps { + onLoginClick: () => void; +} export default LogoutHeader; diff --git a/client/src/components/Logins/EmailLogin.tsx b/client/src/components/Logins/EmailLogin.tsx new file mode 100644 index 00000000..f70866e3 --- /dev/null +++ b/client/src/components/Logins/EmailLogin.tsx @@ -0,0 +1,162 @@ +import axios from "axios"; +import styled from "styled-components"; +import React, { useState } from "react"; + +const EmailLoginModal = ({ + onClose, + onLogin, +}: { + onClose: () => void; + onLogin: () => void; +}) => { + // 문자열 변수 정의 + const titleText = "이메일로 로그인"; + const emailLabelText = "이메일"; + const passwordLabelText = "비밀번호"; + const findPasswordText = "비밀번호 찾기"; + const loginButtonText = "로그인"; + const noAccountText = "계정이 없습니까?"; + const registerButtonText = "회원가입하기"; + + const [email, setEmail] = useState(""); // 입력된 이메일 상태 + const [password, setPassword] = useState(""); // 입력된 비밀번호 상태 + + const handleEmailChange = (event: React.ChangeEvent) => { + setEmail(event.target.value); + }; + + const handlePasswordChange = (event: React.ChangeEvent) => { + setPassword(event.target.value); + }; + + const handleLoginClick = async () => { + try { + const response = await axios.post("http://localhost:8080/login", { + email, + password, + }); + if (response.status === 200) { + onLogin(); + onClose(); + } else { + console.error("Error during login"); + } + } catch (error) { + console.error("Error during login:", error); + } + }; + + return ( + + + × + {titleText} + + + + + {findPasswordText} + {loginButtonText} + + {noAccountText} {registerButtonText} + + + + ); +}; + +export default EmailLoginModal; + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #ffffff; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; // 크기를 줄입니다. + font-weight: 400; // 굵기를 줄입니다. +`; + +const Label = styled.label` + align-self: flex-start; + margin-top: 10px; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-top: 5px; + border: 1px solid lightgray; + border-radius: 5px; +`; + +const RightAlignedButton = styled.button` + align-self: flex-end; + margin-top: 5px; + background: none; + border: none; + color: slategray; // 글자색을 변경합니다. + cursor: pointer; +`; + +const LoginButton = styled.button` + width: 100%; + padding: 10px; + margin-top: 10px; + background-color: darkslategray; // 배경색을 변경합니다. + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; + +const BottomText = styled.div` + margin-top: 10px; + font-size: 0.9rem; // 크기를 줄입니다. +`; + +const RegisterButton = styled.button` + background: none; + border: none; + color: slategray; // 글자색을 변경합니다. + cursor: pointer; +`; diff --git a/client/src/components/Logins/OAuthLogin.tsx b/client/src/components/Logins/OAuthLogin.tsx new file mode 100644 index 00000000..665385ee --- /dev/null +++ b/client/src/components/Logins/OAuthLogin.tsx @@ -0,0 +1,171 @@ +import styled from 'styled-components'; +import googleLogo from '../../asset/images/GoogleLogo.svg'; +import kakaoLogo from '../../asset/images/KakaoLogo.svg'; +import axios from 'axios'; + + + +const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick, onEmailSignupClick }) => { + const titleText = "로그인"; + const googleLoginText = "구글로 로그인"; + const kakaoLoginText = "카카오로 로그인"; + const orText = "또는"; + const emailLoginText = "이메일로 로그인"; + const emailSignupText = "이메일로 회원가입"; + + + const handleGoogleLogin = async () => { + try { + const response = await axios.post('http://localhost:8080/auth/google'); + if (response.status === 200) { + // 로그인 처리 + console.log("Successfully logged in with Google!"); + onClose(); + } else { + console.error("Error logging in with Google"); + } + } catch (error) { + console.error("Error logging in with Google:", error); + } + }; + + const handleKakaoLogin = async () => { + try { + const response = await axios.post('http://localhost:8080/auth/kakao'); + if (response.status === 200) { + // 로그인 처리 + console.log("Successfully logged in with Kakao!"); + onClose(); + } else { + console.error("Error logging in with Kakao"); + } + } catch (error) { + console.error("Error logging in with Kakao:", error); + } + }; + + return ( + + + × + {titleText} + + + {googleLoginText} + + + + + {kakaoLoginText} + + {orText} + + {emailLoginText} + {emailSignupText} + + + + ); +}; + +export default OAuthLoginModal; + +interface LoginModalProps { + onClose: () => void; + onEmailLoginClick: () => void; + onEmailSignupClick: () => void; // 추가 +} + +const OrText = styled.span` + margin: 20px 0; + color: grey; +`; + + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; // 크기를 줄입니다. + font-weight: 400; // 굵기를 줄입니다. +`; + + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const OAuthButton = styled.button` + margin: 10px 0; + padding: 10px 20px; + background-color: #FFFFFF; + border: 1px solid lightgray; + border-radius: 5px; + cursor: pointer; + width: 300px; // 버튼의 가로 너비를 동일하게 설정합니다. + display: flex; + align-items: center; + justify-content: center; +`; +const GoogleButton = styled(OAuthButton)` + display: flex; + align-items: center; + justify-content: center; +`; + +const KakaoButton = styled(OAuthButton)` + display: flex; + align-items: center; + justify-content: center; +`; + +const EmailButtonsContainer = styled.div` + display: flex; + justify-content: space-around; + width: 100%; + margin: 5px 0; +`; + + +const EmailButton = styled.button` + margin: 5px 0; + padding: 10px 20px; + background-color: #FFFFFF; + border: 1px solid lightgray; + border-radius: 5px; + cursor: pointer; +`; + + +const LogoImage = styled.img` + margin-right: 30px; + width: 60px; // 로고의 가로 크기를 조정합니다. 필요에 따라 값을 조절할 수 있습니다. + height: auto; // 가로 크기에 맞춰 세로 크기를 자동으로 조절합니다. +`; \ No newline at end of file diff --git a/client/src/components/README.md b/client/src/components/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/client/src/components/Signups/EmailCertify.tsx b/client/src/components/Signups/EmailCertify.tsx new file mode 100644 index 00000000..722cd87d --- /dev/null +++ b/client/src/components/Signups/EmailCertify.tsx @@ -0,0 +1,149 @@ +import axios from 'axios'; +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const strings = { + titleText: "이메일 인증요청", + emailLabelText: "인증할 이메일", + codeLabelText: "인증코드", + nextStepText: "인증 후 다음단계", + codeHintText: "이메일로 전송된 코드를 입력하세요", + termsText: "개인정보 처리방침 및 서비스 이용약관에 동의합니다" +}; + +const EmailVerificationModal: React.FC = ({ onClose, onNextStep }) => { + const [email, setEmail] = useState('sample@example.com'); // 이메일 상태 + const [verificationCode, setVerificationCode] = useState(''); // 인증코드 상태 + + const handleEmailChange = (event: React.ChangeEvent) => { + setEmail(event.target.value); + }; + + const handleVerificationCodeChange = (event: React.ChangeEvent) => { + setVerificationCode(event.target.value); + }; + + const handleNextStepClick = async () => { + try { + const response = await axios.post('http://localhost:8080/email/confirm', { email, code: verificationCode }); + if (response.status === 200) { + onNextStep(); + } else { + console.error('Error during email confirmation'); + } + } catch (error) { + console.error('Error during email confirmation:', error); + } + }; + + return ( + + + × + {strings.titleText} + + + + + {strings.codeHintText} + + + + + + {strings.nextStepText} + + + + ); +}; + +export default EmailVerificationModal; + +type EmailVerificationModalProps = { + onClose: () => void; + onNextStep: () => void; +}; + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` +position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; // 크기를 줄입니다. + font-weight: 400; // 굵기를 줄입니다. +`; + +const Label = styled.label` + align-self: flex-start; + margin-top: 10px; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-top: 5px; + border: 1px solid lightgray; + border-radius: 5px; +`; + +const SignupButton = styled.button` + width: 100%; + padding: 10px; + margin-top: 10px; + background-color: darkslategray; // 배경색을 변경합니다. + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; + +const HintText = styled.p` + color: red; + font-size: 0.8rem; + margin-top: 5px; +`; + +const TermsCheckbox = styled.div` + margin-top: 10px; + display: flex; + align-items: center; + + input[type="checkbox"] { + margin-right: 5px; + } + + label { + font-size: 0.9rem; + } +`; \ No newline at end of file diff --git a/client/src/components/Signups/EmailSignup.tsx b/client/src/components/Signups/EmailSignup.tsx new file mode 100644 index 00000000..c5483f73 --- /dev/null +++ b/client/src/components/Signups/EmailSignup.tsx @@ -0,0 +1,115 @@ +import axios from 'axios'; +import styled from 'styled-components'; +import React, { useState } from 'react'; + +const strings = { + titleText: "이메일로 회원가입", + emailLabelText: "이메일", + requestVerificationText: "이메일 인증요청" +}; + +const EmailSignupModal: React.FC = ({ onClose, onRequestVerification }) => { + const [email, setEmail] = useState(''); // 입력된 이메일을 관리하는 상태 + + const handleEmailChange = (event: React.ChangeEvent) => { + setEmail(event.target.value); + }; + + const handleVerificationRequest = async () => { + try { + const response = await axios.post('http://localhost:8080/email/send', { email }); + if (response.status === 200) { + onRequestVerification(); + } else { + console.error('Error sending verification email'); + } + } catch (error) { + console.error('Error sending verification email:', error); + } + }; + + return ( + + + × + {strings.titleText} + + + + {strings.requestVerificationText} + + + + ); +}; + +export default EmailSignupModal; + + +type EmailSignupModalProps = { + onClose: () => void; + onRequestVerification: () => void; +}; + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; // 크기를 줄입니다. + font-weight: 400; // 굵기를 줄입니다. +`; + +const Label = styled.label` + align-self: flex-start; + margin-top: 10px; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-top: 5px; + border: 1px solid lightgray; + border-radius: 5px; +`; + +const SignupButton = styled.button` + width: 100%; + padding: 10px; + margin-top: 10px; + background-color: darkslategray; // 배경색을 변경합니다. + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; diff --git a/client/src/components/Signups/Password.tsx b/client/src/components/Signups/Password.tsx new file mode 100644 index 00000000..1a135960 --- /dev/null +++ b/client/src/components/Signups/Password.tsx @@ -0,0 +1,131 @@ +import axios from 'axios'; +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const strings = { + titleText: "비밀번호 설정", + passwordLabelText: "비밀번호", + confirmPasswordLabelText: "비밀번호 확인", + nicknameLabelText: "닉네임", + confirmButtonText: "확인" + }; + + const PasswordSettingModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [nickname, setNickname] = useState(''); + + const handlePasswordChange = (e: React.ChangeEvent) => { + setPassword(e.target.value); + }; + + const handleConfirmPasswordChange = (e: React.ChangeEvent) => { + setConfirmPassword(e.target.value); + }; + + const handleNicknameChange = (e: React.ChangeEvent) => { + setNickname(e.target.value); + }; + + const handleConfirmClick = async () => { + try { + const response = await axios.post('http://localhost:8080/members', { + password, + confirmPassword, + nickname + }); + + if (response.status === 200) { + console.log('Data sent successfully'); + onClose(); + } else { + console.error('Error sending data'); + } + } catch (error) { + console.error('Error sending data:', error); + } + }; + + return ( + + + × + {strings.titleText} + + + + + + + {strings.confirmButtonText} + + + ); +}; + + export default PasswordSettingModal; + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; + font-weight: 400; +`; + + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const Label = styled.label` + align-self: flex-start; + margin-top: 10px; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-top: 5px; + border: 1px solid lightgray; + border-radius: 5px; +`; + + +const ConfirmButton = styled.button` + width: 100%; + padding: 10px; + margin-top: 10px; + background-color: darkslategray; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; \ No newline at end of file diff --git a/client/src/components/StockOrderSection/Index.tsx b/client/src/components/StockOrderSection/Index.tsx index 2112a12d..687f9bbe 100644 --- a/client/src/components/StockOrderSection/Index.tsx +++ b/client/src/components/StockOrderSection/Index.tsx @@ -5,8 +5,6 @@ import StockName from "./StockName"; import OrderRequest from "./OrderRequest"; import OrderResult from "./OrderResult"; -// test - const StockOrderSection = () => { return ( @@ -21,11 +19,9 @@ const StockOrderSection = () => { export default StockOrderSection; const Container = styled.aside` - // 우측 슬라이드 관련 설정 position: fixed; right: 0px; transition: right 0.3s ease-in-out; - // display: flex; flex-direction: column; diff --git a/client/src/page/MainPage.tsx b/client/src/page/MainPage.tsx index 884ed941..02189d03 100644 --- a/client/src/page/MainPage.tsx +++ b/client/src/page/MainPage.tsx @@ -1,18 +1,130 @@ -import { styled } from "styled-components"; - +import { useState, useCallback } from "react"; +import styled from "styled-components"; +import LogoutHeader from "../components/Headers/LogoutHeader"; import LoginHeader from "../components/Headers/LoginHeader"; +import OAuthLoginModal from "../components/Logins/OAuthLogin"; +import EmailLoginModal from "../components/Logins/EmailLogin"; +import EmailSignupModal from "../components/Signups/EmailSignup"; +import EmailVerificationModal from "../components/Signups/EmailCertify"; +import PasswordSettingModal from "../components/Signups/Password"; + +import CentralChartSection from "../components/CentralChartSection/Index"; import StockOrderSection from "../components/StockOrderSection/Index"; const MainPage = () => { + const [isOAuthModalOpen, setOAuthModalOpen] = useState(false); + const [isEmailLoginModalOpen, setEmailLoginModalOpen] = useState(false); + const [isEmailSignupModalOpen, setEmailSignupModalOpen] = useState(false); + + const openOAuthModal = useCallback(() => { + setOAuthModalOpen(true); + }, []); + + const closeOAuthModal = useCallback(() => { + setOAuthModalOpen(false); + }, []); + + const openEmailLoginModal = useCallback(() => { + setOAuthModalOpen(false); + setEmailLoginModalOpen(true); + }, []); + + const closeEmailLoginModal = useCallback(() => { + setEmailLoginModalOpen(false); + }, []); + + const openEmailSignupModal = useCallback(() => { + setOAuthModalOpen(false); + setEmailSignupModalOpen(true); + }, []); + + const closeEmailSignupModal = useCallback(() => { + setEmailSignupModalOpen(false); + }, []); + + const [isEmailVerificationModalOpen, setEmailVerificationModalOpen] = + useState(false); + + const openEmailVerificationModal = useCallback(() => { + setEmailSignupModalOpen(false); // 이메일 회원가입 모달 닫기 + setEmailVerificationModalOpen(true); // 이메일 인증 모달 열기 + }, []); + + const closeEmailVerificationModal = useCallback(() => { + setEmailVerificationModalOpen(false); + }, []); + + const [isPasswordSettingModalOpen, setPasswordSettingModalOpen] = + useState(false); + + const openPasswordSettingModal = useCallback(() => { + setEmailVerificationModalOpen(false); // 이메일 인증 모달 닫기 + setPasswordSettingModalOpen(true); // 비밀번호 설정 모달 열기 + }, []); + + const closePasswordSettingModal = useCallback(() => { + setPasswordSettingModalOpen(false); + }, []); + + const [isLoggedIn, setIsLoggedIn] = useState(false); // 로그인 상태 관리 + + const handleLogin = () => { + setIsLoggedIn(true); + }; + + const handleLogout = () => { + setIsLoggedIn(false); + }; + return ( - + {isLoggedIn ? ( + // 로그아웃 버튼 클릭 핸들러 추가 + ) : ( + + )}
- +
+ {isOAuthModalOpen && ( + + )} + {isEmailLoginModalOpen && ( + + )} + + {isEmailSignupModalOpen && ( + + )} + + {isEmailVerificationModalOpen && ( + + )} + + {isPasswordSettingModalOpen && ( + { + handleLogin(); + closePasswordSettingModal(); + }} + /> + )}
); }; @@ -45,9 +157,3 @@ export const RightSection = styled.section` height: 100%; border: 1px solid black; `; - -const CentralSection = styled.section` - flex: 6.7 0 0; - min-width: 630px; - height: 100%; -`;