From 35f6506b5692ce4a3ac20c441b7550d5ddb03ea1 Mon Sep 17 00:00:00 2001 From: yzooop Date: Mon, 23 Sep 2024 20:50:28 +0900 Subject: [PATCH] =?UTF-8?q?Feat=20:=20=EA=B1=B0=EB=9E=98=20swipe=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 4 + src/App.tsx | 14 +- src/Component/Button/button.tsx | 24 +- src/Component/Calculator/calculResult.tsx | 278 ++++++------ src/Component/Calculator/calculator.tsx | 449 ++++++++++---------- src/Component/Carousel/EmblaCarousel.tsx | 7 +- src/Component/Dropdown/Dropdown.tsx | 116 +++--- src/Component/Dropdown/gameDropdown.tsx | 134 +++--- src/Component/Game/GameButtons.tsx | 65 ++- src/Component/Game/GameMoney.tsx | 255 ++++++----- src/Component/Game/GameTradeSwipe.tsx | 464 ++++++++++----------- src/Component/Game/GameTradeSwipeStyle.css | 71 ---- src/Component/Game/TradeChoice.tsx | 192 ++++++--- src/Component/Game/TradeChoiceStyle.css | 48 --- src/Component/News/NewsList.tsx | 89 ++-- src/Pages/game/playPage.tsx | 278 ++++++------ src/Router.tsx | 105 ++--- src/index.css | 27 +- 18 files changed, 1310 insertions(+), 1310 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/Component/Game/GameTradeSwipeStyle.css delete mode 100644 src/Component/Game/TradeChoiceStyle.css diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1b6457c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} diff --git a/src/App.tsx b/src/App.tsx index f924a5b..c256044 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,14 @@ import "./App.css"; import { GlobalStyle } from "./Styles/GlobalStyles"; import Router from "./Router"; -import { useAuth } from "./contexts/authContext"; function App() { - const { user } = useAuth(); - return ( - <> - - - - ); + return ( + <> + + + + ); } export default App; diff --git a/src/Component/Button/button.tsx b/src/Component/Button/button.tsx index 05cbbbe..4420703 100644 --- a/src/Component/Button/button.tsx +++ b/src/Component/Button/button.tsx @@ -4,25 +4,23 @@ import { buttonSize, buttonType } from "../../Styles/button"; import { ButtonStyleProps, ButtonProps } from "../../constants/interface"; const ButtonStyle = styled.button` - border-radius: 10px; + border-radius: 10px; - ${(props) => buttonSize[props.$size]} - ${(props) => buttonType[props.$colorType]?.[props.$state]} + ${(props) => buttonSize[props.$size]} + ${(props) => buttonType[props.$colorType]?.[props.$state]} `; const Button = ({ onClick, children, ...styleprops }: ButtonProps) => { - return ( - <> - - {children} - - - ); + return ( + <> + + {children} + + + ); }; export default Button; // 버튼 사용 예시 -{ - /* */ -} +/* */ diff --git a/src/Component/Calculator/calculResult.tsx b/src/Component/Calculator/calculResult.tsx index b05b67d..ce48483 100644 --- a/src/Component/Calculator/calculResult.tsx +++ b/src/Component/Calculator/calculResult.tsx @@ -5,172 +5,196 @@ import Chicken from "../../img/chicken.png"; import Iphone from "../../img/iphone.png"; import styled from "styled-components"; import Skrrr from "../../img/SkerrImg.png"; -import { CalculResultProps, UserProps } from "../../constants/interface"; +import { CalculResultProps } from "../../constants/interface"; import { ValueProps } from "../../constants/interface"; import { formatPrice } from "../../util/util"; const Container = styled.div` - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 10px; - padding: 50px; - border-top: 2px solid rgb(0, 0, 0, 0.2); - margin-bottom: 1rem; - /* background-color: yellow; */ + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + padding: 50px; + border-top: 2px solid rgb(0, 0, 0, 0.2); + margin-bottom: 1rem; + /* background-color: yellow; */ `; const ResultContainer = styled.div` - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 20px; - position: relative; + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 20px; + position: relative; `; const DivContainer = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; `; const ImgStyle = styled.img` - width: 25px; - height: 25px; + width: 25px; + height: 25px; `; const SpanContainer = styled.div` - display: flex; - flex-direction: row; - gap: 3px; - font-size: 20px; - white-space: nowrap; + display: flex; + flex-direction: row; + gap: 3px; + font-size: 20px; + white-space: nowrap; `; const SpanStyle = styled.div` - font-weight: bold; - color: ${(props) => (props.$isNegative ? "#615EFC" : "#FF5759")}; + font-weight: bold; + color: ${(props) => (props.$isNegative ? "#615EFC" : "#FF5759")}; `; const SkrrrBird = styled.img` - position: absolute; - top: 45px; - left: 115px; - width: 260px; - opacity: 0.5; - z-index: -1; + position: absolute; + top: 45px; + left: 115px; + width: 260px; + opacity: 0.5; + z-index: -1; `; const NeverBuySkrrrBird = styled.img` - margin-right: 5rem; - width: 460px; - opacity: 0.5; + margin-right: 5rem; + width: 460px; + opacity: 0.5; `; const SkrrrText = styled.div` - position: absolute; - top: 70px; - left: 270px; - z-index: 3; - color: #ff5759; - font-size: 25px; - white-space: nowrap; + position: absolute; + top: 70px; + left: 270px; + z-index: 3; + color: #ff5759; + font-size: 25px; + white-space: nowrap; `; const NeverBuyText = styled.div` - /* position: absolute; */ - top: 670px; - left: 470px; - z-index: 3; - color: #ff5759; - font-size: 25px; - white-space: nowrap; + /* position: absolute; */ + top: 670px; + left: 470px; + z-index: 3; + color: #ff5759; + font-size: 25px; + white-space: nowrap; `; const BirdContainer = styled.div` - display: flex; - height: 400px; - justify-content: center; - align-items: center; - align-content: center; - flex-direction: column; + display: flex; + height: 400px; + justify-content: center; + align-items: center; + align-content: center; + flex-direction: column; `; const CalculResult: React.FC = ({ - price, - slave, - candy, - soul, - chicken, - iphone, + price, + slave, + candy, + soul, + chicken, + iphone, }) => { - let userName: string = "사용자"; + let userName: string = "사용자"; - const userData = localStorage.getItem("user"); - if (userData) { - userName = JSON.parse(userData).name; - } + const userData = localStorage.getItem("user"); + if (userData) { + userName = JSON.parse(userData).name; + } - return ( - <> - {price !== 0 ? ( - - - {userName} - 님은 - - {formatPrice(Math.abs(price))} - - 원을 {price >= 0 ? "벌었습니다." : "잃었습니다.."} - - - - - - 2024년 최저시급 기준{" "} - {slave}시간 - - - - - - 새콤달콤 {candy} - 개 - - - - - - 국밥{soul}그릇 - - - - - - 치킨 {chicken} - 마리 - - - - - - 아이폰 {iphone}대 - - - - {price >= 0 ? "살껄!@" : "팔껄!@"} - - - ) : ( - - 그 돈으론 1주도 사지 못했~스껄~,,,@ - - - )} - - ); + return ( + <> + {price !== 0 ? ( + + + {userName} + 님은 + + {formatPrice(Math.abs(price))} + + 원을{" "} + + {price >= 0 ? "벌었습니다." : "잃었습니다.."} + + + + + + + 2024년 최저시급 기준{" "} + + {slave} + + 시간 + + + + + + 새콤달콤{" "} + + {candy} + + 개 + + + + + + 국밥 + + {soul} + + 그릇 + + + + + + 치킨{" "} + + {chicken} + + 마리 + + + + + + 아이폰{" "} + + {iphone} + + 대 + + + + + {price >= 0 ? "살껄!@" : "팔껄!@"} + + + + ) : ( + + + 그 돈으론 1주도 사지 못했~스껄~,,,@ + + + + )} + + ); }; export default CalculResult; diff --git a/src/Component/Calculator/calculator.tsx b/src/Component/Calculator/calculator.tsx index 6bcb69f..06e34df 100644 --- a/src/Component/Calculator/calculator.tsx +++ b/src/Component/Calculator/calculator.tsx @@ -9,262 +9,269 @@ import { Input } from "../Input/input"; import axios from "axios"; import { useLocation } from "react-router-dom"; import CalculResult from "./calculResult"; -import swal from "sweetalert"; import { useNavigate } from "react-router-dom"; const Container = styled.div` - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 10px; - padding: 50px; - border-top: 2px solid rgb(0, 0, 0, 0.2); + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + padding: 50px; + border-top: 2px solid rgb(0, 0, 0, 0.2); `; const TitleContainer = styled.div` - display: flex; - flex-direction: row; - justify-content: center; - align-items: end; - gap: 5px; - font-weight: 600; - margin-bottom: 1rem; + display: flex; + flex-direction: row; + justify-content: center; + align-items: end; + gap: 5px; + font-weight: 600; + margin-bottom: 1rem; `; const User = styled.span` - color: ${Colors.main}; + color: ${Colors.main}; `; const DropContainer = styled.div` - display: flex; - flex-direction: row; - gap: 3px; + display: flex; + flex-direction: row; + gap: 3px; `; const Label = styled.div` - margin-bottom: 0.5rem; - margin-left: 1rem; + margin-bottom: 0.5rem; + margin-left: 1rem; `; const Calculator = () => { - const nav = useNavigate(); + const nav = useNavigate(); - // CHECK API 연결할 떄,, 현재 시점 보다 미래의 날짜를 했을 때, 검색 불가하게 막기 필요 - // 없는 날짜를 검색하면 검색 불가하게 막기 필요 - const year = [ - "2014", - "2015", - "2016", - "2017", - "2018", - "2019", - "2020", - "2021", - "2022", - "2023", - "2024", - ]; - const month = [ - "01", - "02", - "03", - "04", - "05", - "06", - "07", - "08", - "09", - "10", - "11", - "12", - ]; - const day = [ - "01", - "02", - "03", - "04", - "05", - "06", - "07", - "08", - "09", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "28", - "29", - "30", - "31", - ]; + // CHECK API 연결할 떄,, 현재 시점 보다 미래의 날짜를 했을 때, 검색 불가하게 막기 필요 + // 없는 날짜를 검색하면 검색 불가하게 막기 필요 + const year = [ + "2014", + "2015", + "2016", + "2017", + "2018", + "2019", + "2020", + "2021", + "2022", + "2023", + "2024", + ]; + const month = [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + ]; + const day = [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + ]; - const [price, setPrice] = useState(""); - const [isValid, setIsValid] = useState(true); - const [selectedYear, setSelectedYear] = useState("2014"); - const [selectedMonth, setSelectedMonth] = useState("01"); - const [selectedDay, setSelectedDay] = useState("01"); - const [result, setResult] = useState(null); - const [errorMsg, setErrorMsg] = useState(""); + const [price, setPrice] = useState(""); + const [isValid, setIsValid] = useState(true); + const [selectedYear, setSelectedYear] = useState("2014"); + const [selectedMonth, setSelectedMonth] = useState("01"); + const [selectedDay, setSelectedDay] = useState("01"); + const [result, setResult] = useState(null); + const [errorMsg, setErrorMsg] = useState(""); - const location = useLocation(); - const code = location.pathname.split("/")[2]; + const location = useLocation(); + const code = location.pathname.split("/")[2]; - const ConfirmHandler = async () => { - axios - .get( - `${process.env.REACT_APP_API_URL}/v1/stocks/${code}/skrrr?date=${ - selectedYear + "/" + selectedMonth + "/" + selectedDay - }&price=${price}` - ) - .then((res) => { - if (res.status === 200) { - const data = res.data; - setResult(data); - } - }) - .catch((error) => { - if (error.response && error.response.data.status === 400) { - const message = error.response.data.message; - setErrorMsg(message); - setIsValid(false); - } else { - nav("/error"); - } - }); - }; + const ConfirmHandler = async () => { + axios + .get( + `${ + process.env.REACT_APP_API_URL + }/v1/stocks/${code}/skrrr?date=${ + selectedYear + "/" + selectedMonth + "/" + selectedDay + }&price=${price}` + ) + .then((res) => { + if (res.status === 200) { + const data = res.data; + setResult(data); + } + }) + .catch((error) => { + if (error.response && error.response.data.status === 400) { + const message = error.response.data.message; + setErrorMsg(message); + setIsValid(false); + } else { + nav("/error"); + } + }); + }; - const handleInputChange = (e: React.ChangeEvent) => { - const value = e.target.value; + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; - if (value.includes("-")) { - setErrorMsg("음수 값은 입력 불가합니다"); - setIsValid(false); - return; - } + if (value.includes("-")) { + setErrorMsg("음수 값은 입력 불가합니다"); + setIsValid(false); + return; + } - setPrice(e.target.value); + setPrice(e.target.value); - if (e.target.value) { - setIsValid(true); - } - if (value === "" || parseInt(value) === 0) { - setErrorMsg("금액을 입력해주세요"); - setIsValid(false); - } else if (isNaN(Number(value))) { - setErrorMsg("숫자를 입력해주세요"); - setIsValid(false); - } - }; + if (e.target.value) { + setIsValid(true); + } + if (value === "" || parseInt(value) === 0) { + setErrorMsg("금액을 입력해주세요"); + setIsValid(false); + } else if (isNaN(Number(value))) { + setErrorMsg("숫자를 입력해주세요"); + setIsValid(false); + } + }; - const handleSelectedYear = (category: number | string) => { - if (typeof category === "string") { - setSelectedYear(category); - } - }; + const handleSelectedYear = (category: number | string) => { + if (typeof category === "string") { + setSelectedYear(category); + } + }; - const handleSelectedMonth = (category: number | string) => { - if (typeof category === "string") { - setSelectedMonth(category); - } - }; + const handleSelectedMonth = (category: number | string) => { + if (typeof category === "string") { + setSelectedMonth(category); + } + }; - const handleSelectedDay = (category: number | string) => { - if (typeof category === "string") { - setSelectedDay(category); - } - }; + const handleSelectedDay = (category: number | string) => { + if (typeof category === "string") { + setSelectedDay(category); + } + }; - const handleRetry = () => { - setResult(null); - }; + const handleRetry = () => { + setResult(null); + }; - // 사용자 이름 적용 - let userName: string = "사용자"; + // 사용자 이름 적용 + let userName: string = "사용자"; - const userData = localStorage.getItem("user"); - if (userData) { - userName = JSON.parse(userData).name; - } + const userData = localStorage.getItem("user"); + if (userData) { + userName = JSON.parse(userData).name; + } - return result ? ( -
- -
- ) : ( - - - 앵무새가 컴퓨터 보는 이미지 - - 만약 {userName}님이 이 때 샀다면? - - -
-
- - - - - - + return result ? ( +
+ +
-
- + ) : ( + + + 앵무새가 컴퓨터 보는 이미지 + + 만약 {userName}님이 이 때 샀다면? + + +
+
+ + + + + + +
+
+ - -
-
-
+
+
+ + {stockData && + stockData.map((item) => ( +
  • handleSelect(item)} + style={{ marginTop: "0.5rem" }} + > + {item.name} +
  • + ))} +
    + + ); }; export default StockItem; diff --git a/src/Component/Game/GameButtons.tsx b/src/Component/Game/GameButtons.tsx index dc63d6f..6408b77 100644 --- a/src/Component/Game/GameButtons.tsx +++ b/src/Component/Game/GameButtons.tsx @@ -5,45 +5,44 @@ import "./GameButtonsStyle.css"; import { useNavigate, useParams } from "react-router-dom"; interface GameButtonsProps { - - openTradeModal: () => void; - openPassModal: () => void; - budget?: number; - setBudget?: (budget: number) => void; + openTradeModal: () => void; + openPassModal: () => void; + budget?: number; + setBudget?: (budget: number) => void; } const GameButtons = ({ - openTradeModal, - openPassModal, - budget, - setBudget, + openTradeModal, + openPassModal, + budget, + setBudget, }: GameButtonsProps) => { - const nav = useNavigate(); - const { year } = useParams<{ year: string }>(); + const nav = useNavigate(); + const { year } = useParams<{ year: string }>(); - // 정보 거래소로 이동 - const goToInfoPage = () => { - if (year) { - nav(`/game/info/${year}`, { state: { budget } }); - } - }; + // 정보 거래소로 이동 + const goToInfoPage = () => { + if (year) { + nav(`/game/info/${year}`, { state: { budget } }); + } + }; - return ( -
    -
    - - 정보거래소 -
    -
    - - 거래하기 -
    -
    - - 넘어가기 -
    -
    - ); + return ( +
    +
    + 정보거래소 + 정보거래소 +
    +
    + 거래하기 + 거래하기 +
    +
    + 넘어가기 + 넘어가기 +
    +
    + ); }; export default GameButtons; diff --git a/src/Component/Game/GameMoney.tsx b/src/Component/Game/GameMoney.tsx index da35afe..a422516 100644 --- a/src/Component/Game/GameMoney.tsx +++ b/src/Component/Game/GameMoney.tsx @@ -1,5 +1,5 @@ -import styled, { keyframes } from "styled-components"; -import { formatPrice, formatChangeRate } from "../../util/gameUtil"; +import styled from "styled-components"; +import { formatPrice } from "../../util/gameUtil"; import { holding } from "../../constants/interface"; import { useState, useEffect } from "react"; import axios from "axios"; @@ -8,159 +8,158 @@ import { useNavigate } from "react-router-dom"; import GameViewTab from "./gameViewTab"; const GameMoney = ({ - setBudget, - budget, + setBudget, + budget, }: { - setBudget: (budget: number) => void; - budget: number; + setBudget: (budget: number) => void; + budget: number; }) => { - const nav = useNavigate(); + const nav = useNavigate(); - const [nickname, setNickname] = useState(""); // 닉네임 - const [total, setTotal] = useState(0); // 총 평가금액 - const [changeFromLast, setChangeFromLast] = useState(0); // 작년 대비 금액 - const [changeFromStart, setChangeFromStart] = useState(0); // 전체 대비 금액 - const [changeRateFromLast, setChangeRateFromLast] = useState(0); // 작년 대비 변동률 - const [changeRateFromStart, setChangeRateFromStart] = useState(0); // 전체 대비 변동률 - const [holdingList, setHoldingList] = useState([]); // 20xx년 주식가격 - const [showTable, setShowTable] = useState(false); - const [tabView, setTabView] = useState("보유주식"); + const [nickname, setNickname] = useState(""); // 닉네임 + const [total, setTotal] = useState(0); // 총 평가금액 + const [changeFromLast, setChangeFromLast] = useState(0); // 작년 대비 금액 + const [changeFromStart, setChangeFromStart] = useState(0); // 전체 대비 금액 + const [changeRateFromLast, setChangeRateFromLast] = useState(0); // 작년 대비 변동률 + const [changeRateFromStart, setChangeRateFromStart] = useState(0); // 전체 대비 변동률 + const [holdingList, setHoldingList] = useState([]); // 20xx년 주식가격 + const [showTable, setShowTable] = useState(false); - const check = usePortfolioStore((state) => state.check); + const check = usePortfolioStore((state) => state.check); - useEffect(() => { - axios - .get(`${process.env.REACT_APP_API_URL}/v1/game/user`, { - withCredentials: true, - headers: { - "Content-Type": "application/json", - }, - }) - .then((res) => { - if (res.status === 200) { - setNickname(res.data.nickname); - setBudget(res.data.budget); - setTotal(res.data.total); - setChangeFromLast(res.data.changeFromLast); - setChangeFromStart(res.data.changeFromStart); - setChangeRateFromLast(res.data.changeRateFromLast); - setChangeRateFromStart(res.data.changeRateFromStart); - setHoldingList(res.data.holdingList); - } else if (res.status === 401) { - alert("401"); - } - }) - .catch((e) => { - nav("/error"); - }); - }, [check]); + useEffect(() => { + axios + .get(`${process.env.REACT_APP_API_URL}/v1/game/user`, { + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + }) + .then((res) => { + if (res.status === 200) { + setNickname(res.data.nickname); + setBudget(res.data.budget); + setTotal(res.data.total); + setChangeFromLast(res.data.changeFromLast); + setChangeFromStart(res.data.changeFromStart); + setChangeRateFromLast(res.data.changeRateFromLast); + setChangeRateFromStart(res.data.changeRateFromStart); + setHoldingList(res.data.holdingList); + } else if (res.status === 401) { + alert("401"); + } + }) + .catch((e) => { + nav("/error"); + }); + }, [check]); - const handleShowTable = () => { - setShowTable((prev) => !prev); - }; + const handleShowTable = () => { + setShowTable((prev) => !prev); + }; - return ( - - - {nickname} 님의 계좌잔고 💰 - - - 거래가능금액 -
    {formatPrice(budget)} 원
    -
    -
    - - 총 평가금액 -
    {formatPrice(total)} 원
    -
    - - 작년 대비 -
    0 - ? "red" - : changeRateFromLast < 0 - ? "blue" - : "black", - }} - > - {formatPrice(changeFromLast)} 원 ({changeRateFromLast}%) -
    -
    - - 전체 대비 -
    0 - ? "red" - : changeRateFromStart < 0 - ? "blue" - : "black", - }} - > - {formatPrice(changeFromStart)} 원 ({changeRateFromStart}%) -
    -
    - - - + return ( + + + {nickname} 님의 계좌잔고 💰 + + + 거래가능금액 +
    {formatPrice(budget)} 원
    +
    +
    + + 총 평가금액 +
    {formatPrice(total)} 원
    +
    + + 작년 대비 +
    0 + ? "red" + : changeRateFromLast < 0 + ? "blue" + : "black", + }} + > + {formatPrice(changeFromLast)} 원 ({changeRateFromLast}%) +
    +
    + + 전체 대비 +
    0 + ? "red" + : changeRateFromStart < 0 + ? "blue" + : "black", + }} + > + {formatPrice(changeFromStart)} 원 ({changeRateFromStart}%) +
    +
    + + + - {showTable && } -
    - ); + {showTable && } +
    + ); }; const GameMoneyContainer = styled.div` - width: 85%; - height: auto; - border-radius: 20px; - box-shadow: 3px 3px 3px rgb(213, 213, 213); - padding: 20px 10px; - position: relative; - margin-top: 20px; - margin-bottom: 20px; + width: 85%; + height: auto; + border-radius: 20px; + box-shadow: 3px 3px 3px rgb(213, 213, 213); + padding: 20px 10px; + position: relative; + margin-top: 20px; + margin-bottom: 20px; `; const GameMoneyTitle = styled.div` - margin-bottom: 30px; + margin-bottom: 30px; `; const GameMoneyBalance = styled.div` - display: flex; - margin: 15px 0; - flex-direction: row; + display: flex; + margin: 15px 0; + flex-direction: row; `; const BalanceDetailButton = styled.div` - position: absolute; - top: 185px; - right: 20px; + position: absolute; + top: 185px; + right: 20px; `; const BalanceTitle = styled.div` - color: #6c6c6c; - width: 110px; + color: #6c6c6c; + width: 110px; `; const Button = styled.button` - width: 78px; - height: 40px; - border-radius: 50px; - border: none; - background-color: #f1f1f1; - cursor: pointer; + width: 78px; + height: 40px; + border-radius: 50px; + border: none; + background-color: #f1f1f1; + cursor: pointer; - &:hover { - background-color: #615efc; - color: white; - font-weight: 600; - } + &:hover { + background-color: #615efc; + color: white; + font-weight: 600; + } `; export default GameMoney; diff --git a/src/Component/Game/GameTradeSwipe.tsx b/src/Component/Game/GameTradeSwipe.tsx index 5e8baa1..1ee0983 100644 --- a/src/Component/Game/GameTradeSwipe.tsx +++ b/src/Component/Game/GameTradeSwipe.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import "./GameTradeSwipeStyle.css"; +import styled from "styled-components"; import TradeChoice from "./TradeChoice"; import TradeConfirmModal from "./TradeConfirmModal"; import axios from "axios"; @@ -9,247 +9,245 @@ import { useStock } from "../../store/stockContext"; import { formatPrice } from "../../util/gameUtil"; interface GameTradeSwipeProps { - onClose: () => void; - isVisible: boolean; - year: string; - budget: number; + onClose: () => void; + isVisible: boolean; + year: string; + budget: number; } const GameTradeSwipe = ({ - onClose, - isVisible, - year, - budget, + onClose, + isVisible, + year, + budget, }: GameTradeSwipeProps) => { - const { stockData } = useStock(); - - const [show, setShow] = useState(isVisible); - const [selectedStock, setSelectedStock] = useState<{ - stockId: number; - name: string; - } | null>(null); // 선택된 종목 상태 - const [quantity, setQuantity] = useState(1); // 수량 상태 - const [acting, setActing] = useState<"BUY" | "SELL">(); // 거래 액션 상태 - const [isModalOpen, setIsModalOpen] = useState(false); // 모달 상태 - const [stockOptions, setStockOptions] = useState< - { stockId: number; name: string }[] - >([]); // 종목 리스트 상태 - - const [currentPrice, setCurrentPrice] = useState(null); - - const check = usePortfolioStore((state) => state.check); - const setCheck = usePortfolioStore((state) => state.setCheck); - - useEffect(() => { - setShow(isVisible); - }, [isVisible]); - - // 종목 선택 핸들러 - const handleStockChange = (direction: "left" | "right") => { - if (!selectedStock || stockOptions.length === 0) return; - - const currentIndex = stockOptions.findIndex( - (stock) => stock.name === selectedStock.name - ); - - if (direction === "left") { - const newIndex = - (currentIndex - 1 + stockOptions.length) % stockOptions.length; - setSelectedStock(stockOptions[newIndex]); - } else { - const newIndex = (currentIndex + 1) % stockOptions.length; - setSelectedStock(stockOptions[newIndex]); - } - }; - - // 수량 선택 핸들러 - const handleQuantityChange = (direction: "left" | "right") => { - if (direction === "left" && quantity > 1) { - setQuantity(quantity - 1); - } else if (direction === "right") { - setQuantity(quantity + 1); - } - }; - - // 선택한 종목의 stockId 찾기 - const getSelectedStockId = () => { - return selectedStock ? selectedStock.stockId : 0; - }; - - // 모달에서 확인 버튼 클릭 시 거래 처리 - const handleTradeConfirm = () => { - const handleTrade = async () => { - const data = { - stockId: getSelectedStockId(), - quantity: quantity, - acting: acting, // 팔기 : SELL, 사기 : BUY - }; - - try { - const res = await axios.post( - `${process.env.REACT_APP_API_URL}/v1/game/stock`, - data, - { - withCredentials: true, - } + const { stockData } = useStock(); + const [show, setShow] = useState(isVisible); + const [selectedStock, setSelectedStock] = useState<{ + stockId: number; + name: string; + } | null>(null); + const [quantity, setQuantity] = useState(1); + const [acting, setActing] = useState<"BUY" | "SELL">(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [stockOptions, setStockOptions] = useState< + { stockId: number; name: string }[] + >([]); + const [currentPrice, setCurrentPrice] = useState(null); + + const check = usePortfolioStore((state) => state.check); + const setCheck = usePortfolioStore((state) => state.setCheck); + + useEffect(() => { + setShow(isVisible); + }, [isVisible]); + + const handleStockChange = (direction: "left" | "right") => { + if (!selectedStock || stockOptions.length === 0) return; + const currentIndex = stockOptions.findIndex( + (stock) => stock.name === selectedStock.name ); + const newIndex = + direction === "left" + ? (currentIndex - 1 + stockOptions.length) % stockOptions.length + : (currentIndex + 1) % stockOptions.length; + setSelectedStock(stockOptions[newIndex]); + }; - if (res.status === 200) { - setCheck(!check); - swal({ - title: "거래 성공", - icon: "success", - }); - setShow(false); - onClose(); - } - } catch (error: any) { - // console.error(error); - // console.error(error.response.data.message); - // catch 블록에서 에러 메시지 출력 - if ( - error.response && - error.response.status === 400 && - error.response.data.message === "종목을 보유하고 있지 않습니다." - ) { - swal({ - title: "거래 오류", - text: error.response.data.message, - icon: "error", - }).then(() => { - setShow(false); - onClose(); - }); - } else if ( - error.response && - error.response.status === 400 && - error.response.data.message === "잔액이 부족합니다." - ) { - swal({ - title: "잔액 부족", - text: error.response.data.message, - icon: "error", - }).then(() => { - setShow(false); - onClose(); - }); - } else { - swal({ - title: "거래 실패", - text: "서버 오류가 발생했습니다. 다시 시도해주세요.", - icon: "error", - }).then(() => { - setShow(false); - onClose(); - }); + const handleQuantityChange = (direction: "left" | "right") => { + setQuantity((prev) => { + if (direction === "left" && prev > 1) { + return prev - 1; + } else if ( + direction === "right" && + currentPrice && + currentPrice * (prev + 1) <= budget + ) { + return prev + 1; + } + return prev; + }); + }; + + const handleTradeConfirm = async () => { + const data = { + stockId: selectedStock?.stockId || 0, + quantity, + acting, + }; + try { + const res = await axios.post( + `${process.env.REACT_APP_API_URL}/v1/game/stock`, + data, + { withCredentials: true } + ); + if (res.status === 200) { + setCheck(!check); + swal({ title: "거래 성공", icon: "success" }); + setShow(false); + onClose(); + } + } catch (error: any) { + const errorMessage = + error.response?.data.message || "서버 오류가 발생했습니다."; + swal({ + title: "거래 실패", + text: errorMessage, + icon: "error", + }).then(() => { + setShow(false); + onClose(); + }); } - } }; - handleTrade(); - setIsModalOpen(false); // 모달 닫기 - }; - - // 모달 열기 - const openTradeConfirmModal = (action: "BUY" | "SELL") => { - setActing(action); // 거래 종류 설정 (구매 or 판매) - setIsModalOpen(true); // 모달 열기 - }; - - // 모달 닫기 핸들러 - const handleClose = () => { - setShow(false); - setTimeout(() => { - onClose(); - }, 700); - }; - - useEffect(() => { - // stockData가 변경될 때 stockOptions를 업데이트 - if (stockData) { - const options = stockData.map((stock) => ({ - stockId: stock.stockId, - name: stock.name, - })); - setStockOptions(options); - - // selectedStock 설정 - if (!selectedStock && options.length > 0) { - setSelectedStock(options[0]); - setCurrentPrice(stockData[0].current); - } - } - - // selectedStock이 변경될 때마다 가격 업데이트 - if (selectedStock && stockData) { - const selectedStockData = stockData.find( - (stock) => stock.stockId === selectedStock.stockId - ); - setCurrentPrice(selectedStockData?.current || null); - } - }, [stockData, selectedStock]); - - const selectedStockName = stockData?.find( - (stock) => stock.stockId === selectedStock?.stockId - ); - - return ( -
    -
    -
    -
    - -
    - 주식 거래하기 -
    거래가능금액 : {formatPrice(budget)}
    -
    - 총 합계 : - {currentPrice && quantity - ? formatPrice(currentPrice * quantity) - : 0} -
    - handleStockChange("left")} - onRightClick={() => handleStockChange("right")} - /> -
    - 현재 가격 :{" "} - {currentPrice !== null - ? `${formatPrice(currentPrice)}원` - : "가격 정보 없음"} -
    - handleQuantityChange("left")} - onRightClick={() => handleQuantityChange("right")} - /> -
    - - -
    + + const openTradeConfirmModal = (action: "BUY" | "SELL") => { + setActing(action); + setIsModalOpen(true); + }; + + const handleQuantityInputChange = (value: number) => { + setQuantity(value); + }; + + useEffect(() => { + if (stockData) { + const options = stockData.map((stock) => ({ + stockId: stock.stockId, + name: stock.name, + })); + setStockOptions(options); + if (!selectedStock && options.length > 0) { + setSelectedStock(options[0]); + setCurrentPrice(stockData[0].current); + } + } + if (selectedStock && stockData) { + const selectedStockData = stockData.find( + (stock) => stock.stockId === selectedStock.stockId + ); + setCurrentPrice(selectedStockData?.current || null); + } + }, [stockData, selectedStock]); + + return ( +
    + + +
    + +
    + 주식 거래하기 + handleStockChange("left")} + onRightClick={() => handleStockChange("right")} + currentPrice={currentPrice} // 현재 가격 전달 + /> + handleQuantityChange("left")} + onRightClick={() => handleQuantityChange("right")} + onQuantityChange={handleQuantityInputChange} // 수량 직접 입력 핸들러 전달 + /> +
    거래가능금액: {formatPrice(budget)}
    +
    + 총 합계:{" "} + {currentPrice && quantity + ? formatPrice(currentPrice * quantity) + : 0} +
    + + openTradeConfirmModal("SELL")} + > + 팔기 + + openTradeConfirmModal("BUY")} + > + 사기 + + +
    +
    + setIsModalOpen(false)} + stock={selectedStock?.name || ""} + quantity={quantity} + acting={acting!} + />
    -
    - - {/* 거래 확인 모달 */} - setIsModalOpen(false)} - stock={selectedStock?.name || ""} - quantity={quantity} - acting={acting!} // acting 값 전달 (BUY or SELL) - /> -
    - ); + ); }; +const SwipeModal = styled.div<{ isOpen: boolean }>` + width: 90%; + position: fixed; + left: 50%; + bottom: ${(props) => (props.isOpen ? "0" : "-100%")}; + max-height: ${(props) => (props.isOpen ? "70vh" : "0")}; + height: 70vh; + max-width: 440px; + box-shadow: 0px -2px 10px rgba(0, 0, 0, 0.1); + transition: bottom 0.7s ease, max-height 0.7s ease; + z-index: 1; + overflow: hidden; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + transform: translateX(-50%); + background-color: #f7f7f7; +`; + +const SwipeContainer = styled.div` + padding: 20px; + background-color: #f7f7f7; + overflow-y: auto; + height: 100%; + display: flex; + flex-direction: column; +`; + +const CloseButton = styled.button` + width: 100px; + height: 10px; + border-radius: 10px; + border: none; + background-color: #dfdfdf; + cursor: pointer; +`; + +const Title = styled.span` + font-size: 28px; + font-weight: 700; + color: #615efc; + margin-top: 30px; +`; + +const TradeButtonGroup = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + margin: 20px 0; +`; + +const TradeButton = styled.button` + width: 100px; + height: 40px; + border-radius: 15px; + background: radial-gradient(circle, #4834d4 0%, #686de0 100%); + color: #fff; + border: none; + font-size: 14px; +`; + export default GameTradeSwipe; diff --git a/src/Component/Game/GameTradeSwipeStyle.css b/src/Component/Game/GameTradeSwipeStyle.css deleted file mode 100644 index 91462c4..0000000 --- a/src/Component/Game/GameTradeSwipeStyle.css +++ /dev/null @@ -1,71 +0,0 @@ -.swipeModal { - width: 90%; - position: fixed; - left: 50%; - bottom: -100%; /* 화면 아래에서 시작 */ - max-height: 0; /* 높이 0으로 시작 */ - height: 80vh; - max-width: 440px; - box-shadow: 0px -2px 10px rgba(0, 0, 0, 0.1); - transition: bottom 0.7s ease, max-height 0.7s ease; /* bottom을 애니메이션에 추가 */ - z-index: 1; - overflow: hidden; /* 내부 컨텐츠가 넘치지 않게 */ - border-top-left-radius: 20px; - border-top-right-radius: 20px; - transform: translateX(-50%); -} - -.swipeContainer { - padding: 20px; - background-color: #f7f7f7; - overflow-y: auto; - height: 100%; - display: flex; - flex-direction: column; -} - -/* 모달이 열릴 때 */ -.swipeModal.open { - bottom: 0; /* 화면 하단에 위치 */ - max-height: 80vh; /* 최대 높이 설정 */ -} - -/* 모달이 닫힐 때 */ -.swipeModal.closed { - bottom: -100%; /* 화면 아래로 이동 */ - max-height: 0; /* 높이를 0으로 줄임 */ -} - -.swipeContainer button { - width: 100px; - height: 10px; - border-radius: 10px; - border: none; - background-color: #dfdfdf; - cursor: pointer; -} - -.swipeContainer span { - font-size: 28px; - font-weight: 700; - color: #615efc; - margin-top: 30px; -} - -.trade-button { - display: flex; - justify-content: center; - align-items: center; - gap: 50px; - margin: 20px 0; -} - -.trade-button button { - width: 100px; - height: 40px; - border-radius: 15px; - background: radial-gradient(circle, #4834d4 0%, #686de0 100%); - color: #fff; - border: none; - font-size: 14px; -} diff --git a/src/Component/Game/TradeChoice.tsx b/src/Component/Game/TradeChoice.tsx index 4dd8e79..a7c9005 100644 --- a/src/Component/Game/TradeChoice.tsx +++ b/src/Component/Game/TradeChoice.tsx @@ -1,13 +1,16 @@ import { useEffect, useRef, useState } from "react"; -import "./TradeChoiceStyle.css"; +import styled from "styled-components"; +import { formatPrice } from "../../util/gameUtil"; interface TradeChoiceProps { title: string; choiceLeft: string; choiceRight: string; - selectedOption: string; + selectedOption: string | number; onLeftClick: () => void; onRightClick: () => void; + currentPrice?: number | null; + onQuantityChange?: (value: number) => void; // 수량 변경 핸들러 추가 } const TradeChoice = ({ @@ -17,6 +20,8 @@ const TradeChoice = ({ selectedOption, onLeftClick, onRightClick, + currentPrice, + onQuantityChange, // 수량 변경 핸들러 }: TradeChoiceProps) => { const [isSelected, setIsSelected] = useState(false); const choiceSectionRef = useRef(null); @@ -35,63 +40,150 @@ const TradeChoice = ({ }; useEffect(() => { - document.addEventListener("click", handleOutsideClick); + document.addEventListener("mousedown", handleOutsideClick); return () => { document.removeEventListener("mousedown", handleOutsideClick); }; }, []); + // input의 값이 직접 입력되면 호출되는 함수 + const handleInputChange = (e: React.ChangeEvent) => { + const newValue = parseInt(e.target.value, 10); + if (!isNaN(newValue) && onQuantityChange) { + onQuantityChange(newValue); + } + }; + return ( -
    -

    - {title} -

    -
    -
    - {choiceLeft} + + {title} + + {title === "종목" && ( +
    + + + {choiceLeft} + + + {selectedOption} + + + {choiceRight} + + + {/* 가격 정보 표시 */} + {currentPrice !== undefined && ( + + {currentPrice !== null + ? `${formatPrice(currentPrice)}원` + : "가격 정보 없음"} + + )}
    -

    - {selectedOption} -

    -
    - {choiceRight} -
    -
    -
    + + {choiceLeft} + + + + {choiceRight} + + + )} + ); }; +const TradeChoiceContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + margin: 30px 0; +`; + +const Title = styled.h2<{ isSelected: boolean }>` + font-size: 15px; + color: ${(props) => (props.isSelected ? "#615EFC" : "#d3d3d3")}; + margin-left: -200px; + margin-bottom: 8px; +`; + +const ChoiceSection = styled.div<{ isSelected: boolean }>` + width: 250px; + height: 80px; + border-radius: 20px; + background-color: #f0f0f0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 20px; + border: ${(props) => (props.isSelected ? "3px solid #615EFC" : "none")}; + font-size: 13px; +`; + +const ChoiceButton = styled.div<{ isSelected: boolean }>` + width: 30px; + height: 30px; + border-radius: 50px; + border: 2px solid ${(props) => (props.isSelected ? "#615EFC" : "#a8a8a8")}; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + font-size: 15px; + font-weight: 700; + color: ${(props) => (props.isSelected ? "#615EFC" : "#a8a8a8")}; +`; + +// 수량을 입력받는 인풋 필드 +const QuantityInput = styled.input<{ isSelected: boolean }>` + width: 60px; + text-align: center; + border: none; + font-size: 16px; + color: ${(props) => (props.isSelected ? "#615EFC" : "#a8a8a8")}; + background-color: transparent; + outline: none; +`; + +const SelectedOption = styled.h3<{ isSelected: boolean }>` + width: 80px; + color: ${(props) => (props.isSelected ? "#615EFC" : "#a8a8a8")}; +`; + +const CurrentPrice = styled.div<{ isSelected: boolean }>` + margin-top: -25px; + font-size: 13px; + color: ${(props) => (props.isSelected ? "#615EFC" : "#a8a8a8")}; +`; + export default TradeChoice; diff --git a/src/Component/Game/TradeChoiceStyle.css b/src/Component/Game/TradeChoiceStyle.css deleted file mode 100644 index 3685d69..0000000 --- a/src/Component/Game/TradeChoiceStyle.css +++ /dev/null @@ -1,48 +0,0 @@ -.TradeChoice { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - cursor: pointer; -} - -.TradeChoice h2 { - font-size: 16px; - color: #d3d3d3; - margin-left: -240px; - margin-bottom: 8px; -} - -.choice-section { - width: 300px; - height: 132px; - border-radius: 20px; - border: none; - background-color: #f0f0f0; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 20px; - color: #a8a8a8; - /* transition: border 0.3s ease, color 0.3 ease; */ -} - -.choice-section h3 { - width: 80px; - color: #a8a8a8; -} - -.choice-button { - width: 35px; - height: 35px; - border-radius: 50px; - border: 3px solid #a8a8a8; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - font-size: 20px; - font-weight: 700; - /* transition: border 0.3s ease, color 0.3s ease; */ -} diff --git a/src/Component/News/NewsList.tsx b/src/Component/News/NewsList.tsx index 251004e..4c141d4 100644 --- a/src/Component/News/NewsList.tsx +++ b/src/Component/News/NewsList.tsx @@ -9,56 +9,57 @@ import { useLocation, useNavigate } from "react-router-dom"; import NoNews from "./\bnoNews"; const ListWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - padding: 20px; - gap: 10px; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + gap: 10px; `; const NewsList: React.FC = () => { - // 나만의 뉴스 데이터 - const [news, setNews] = useState([]); - const navigate = useNavigate(); - const location = useLocation(); - const nav = useNavigate(); - // const change = usePortfolioStore((state) => state.change); + // 나만의 뉴스 데이터 + const [news, setNews] = useState([]); + const navigate = useNavigate(); + const location = useLocation(); + // const change = usePortfolioStore((state) => state.change); - useEffect(() => { - axios - .get( - `${process.env.REACT_APP_API_URL}/v1/news/user`, + useEffect(() => { + axios + .get( + `${process.env.REACT_APP_API_URL}/v1/news/user`, - { - withCredentials: true, - headers: { - "Content-Type": "application/json", - }, - } - ) - .then((res) => { - if (res.status === 200) { - setNews(res.data); - } else if (res.status === 204) { - setNews([]); - } else if (res.status === 401) { - navigate("/noLogin"); - } - }) - .catch((e) => {}); - }, [location]); + { + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + } + ) + .then((res) => { + if (res.status === 200) { + setNews(res.data); + } else if (res.status === 204) { + setNews([]); + } else if (res.status === 401) { + navigate("/noLogin"); + } + }) + .catch((e) => {}); + }, [location, navigate]); - return ( -
    - - {news.length > 0 ? ( - news.map((news: NewsProps) => ) - ) : ( - - )} - -
    - ); + return ( +
    + + {news.length > 0 ? ( + news.map((news: NewsProps) => ( + + )) + ) : ( + + )} + +
    + ); }; export default NewsList; diff --git a/src/Pages/game/playPage.tsx b/src/Pages/game/playPage.tsx index d69260e..5654a6b 100644 --- a/src/Pages/game/playPage.tsx +++ b/src/Pages/game/playPage.tsx @@ -5,159 +5,157 @@ import GameMoney from "../../Component/Game/GameMoney"; import StocksTable from "../../Component/Game/StocksTable"; import styled from "styled-components"; import GameTradeSwipe from "../../Component/Game/GameTradeSwipe"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import PassConfirmModal from "../../Component/Game/PassConfirmModal"; import axios from "axios"; import "./gameStyle.css"; import ExSAm from "../../Game/Tutorial/ex"; -import { StockYearProps } from "../../constants/interface"; import HappyNewYearModal from "./HappyNewYearModal"; import { useStock } from "../../store/stockContext"; import { usePortfolioStore } from "../../store/usePortfolioStore"; const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + position: relative; `; const PlayPage = () => { - const { year } = useParams<{ year: string }>(); - const { stockData, setStockData } = useStock(); - const nav = useNavigate(); - const yearValue = year || "2014"; - const [isTradeModalVisible, setIsTradeModalVisible] = useState(false); - const [isPassModalVisible, setIsPassModalVisible] = useState(false); - const [stockListData, setStockListData] = useState( - null - ); - const [isHappyNewYearModal, setIsHappyNewYearModal] = useState(false); - const [budget, setBudget] = useState(0); - - const check = usePortfolioStore((state) => state.check); - const setCheck = usePortfolioStore((state) => state.setCheck); - - // 튜토리얼을 볼지 결정하는 상태 (첫 번째 튜토리얼 단계 관리) - const [fir, setFir] = useState(true); // 처음엔 true로 설정하여 튜토리얼 표시 - const [sec, setSec] = useState(false); // 두 번째 튜토리얼 단계를 위한 상태 - const [step, setStep] = useState(1); // 현재 튜토리얼 단계 - - // 첫번째 튜토리얼을 닫는 함수 - const closeFirstTutorial = () => { - setFir(false); // 첫 번째 튜토리얼을 닫음 - setSec(true); // 두 번째 튜토리얼도 닫음 - }; - - // 두번째 튜토리얼을 닫는 함수 - const closeSecTutorial = () => { - setSec(false); - }; - - // 거래하기 모달 핸들러 - const openTradeModal = () => { - setIsTradeModalVisible(true); - }; - - const closeTradeModal = () => { - setIsTradeModalVisible(false); - }; - - // 넘어가기 모달 핸들러 - const openPassModal = () => { - setIsPassModalVisible(true); - }; - - const closePassModal = () => { - setIsPassModalVisible(false); - }; - - // 넘어가기 버튼 누르면 중간결과 호출 - const handleConfirmPass = async () => { - try { - if (year === "2023") { - console.log("게임 끝"); - nav("/game/result/total"); - } else { - const response = await axios.get( - `${process.env.REACT_APP_API_URL}/v1/game/interim`, - { - withCredentials: true, - headers: { - "Content-Type": "application/json", - }, - } - ); - if (response.status === 200) { - // 새해 모달을 먼저 보여줌(7초동안 띄워야함) - - setIsHappyNewYearModal(true); - if ( - (parseInt(yearValue, 10) + 1).toString() === - response.data.year.toString() - ) { - console.log(response.data); - const updatedStockList = response.data.stockList; - setStockData(updatedStockList); - } - // 4초 후 페이지를 이동 - setTimeout(() => { - const nextYear = (parseInt(yearValue, 10) + 1).toString(); - - nav(`/game/play/${nextYear}`); - setIsHappyNewYearModal(false); - setCheck(!check); - // window.location.reload(); - }, 500); // 4초 동안 모달을 보여줌 + const { year } = useParams<{ year: string }>(); + const { stockData, setStockData } = useStock(); + const nav = useNavigate(); + const yearValue = year || "2014"; + const [isTradeModalVisible, setIsTradeModalVisible] = useState(false); + const [isPassModalVisible, setIsPassModalVisible] = useState(false); + const [isHappyNewYearModal, setIsHappyNewYearModal] = useState(false); + const [budget, setBudget] = useState(0); + + const check = usePortfolioStore((state) => state.check); + const setCheck = usePortfolioStore((state) => state.setCheck); + + // 튜토리얼을 볼지 결정하는 상태 (첫 번째 튜토리얼 단계 관리) + const [fir, setFir] = useState(true); // 처음엔 true로 설정하여 튜토리얼 표시 + const [sec, setSec] = useState(false); // 두 번째 튜토리얼 단계를 위한 상태 + const [step, setStep] = useState(1); // 현재 튜토리얼 단계 + + // 첫번째 튜토리얼을 닫는 함수 + const closeFirstTutorial = () => { + setFir(false); // 첫 번째 튜토리얼을 닫음 + setSec(true); // 두 번째 튜토리얼도 닫음 + }; + + // 두번째 튜토리얼을 닫는 함수 + const closeSecTutorial = () => { + setSec(false); + }; + + // 거래하기 모달 핸들러 + const openTradeModal = () => { + setIsTradeModalVisible(true); + }; + + const closeTradeModal = () => { + setIsTradeModalVisible(false); + }; + + // 넘어가기 모달 핸들러 + const openPassModal = () => { + setIsPassModalVisible(true); + }; + + const closePassModal = () => { + setIsPassModalVisible(false); + }; + + // 넘어가기 버튼 누르면 중간결과 호출 + const handleConfirmPass = async () => { + try { + if (year === "2023") { + console.log("게임 끝"); + nav("/game/result/total"); + } else { + const response = await axios.get( + `${process.env.REACT_APP_API_URL}/v1/game/interim`, + { + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + } + ); + if (response.status === 200) { + // 새해 모달을 먼저 보여줌(7초동안 띄워야함) + + setIsHappyNewYearModal(true); + if ( + (parseInt(yearValue, 10) + 1).toString() === + response.data.year.toString() + ) { + console.log(response.data); + const updatedStockList = response.data.stockList; + setStockData(updatedStockList); + } + // 4초 후 페이지를 이동 + setTimeout(() => { + const nextYear = ( + parseInt(yearValue, 10) + 1 + ).toString(); + + nav(`/game/play/${nextYear}`); + setIsHappyNewYearModal(false); + setCheck(!check); + // window.location.reload(); + }, 500); // 4초 동안 모달을 보여줌 + } + } + } catch (error) { + console.error(error); + } finally { + closePassModal(); } - } - } catch (error) { - console.error(error); - } finally { - closePassModal(); - } - }; - - return ( - - - - - - - - - - {/* 튜토리얼을 표시 */} - {(fir || sec) && ( - - )} - - ); + }; + + return ( + + + + + + + + + + {/* 튜토리얼을 표시 */} + {(fir || sec) && ( + + )} + + ); }; export default PlayPage; diff --git a/src/Router.tsx b/src/Router.tsx index 61a3365..1e9f87b 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,10 +1,4 @@ -import { - BrowserRouter, - Routes, - Route, - useLocation, - Navigate, -} from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Home from "./Pages/home"; import Layout from "./Component/Layout/Layout"; import SearchStock from "./Pages/searchStock"; @@ -25,57 +19,64 @@ import Rank from "./Pages/game/rank"; import { StockProvider } from "./store/stockContext"; import PreventNavigation from "./Game/Navigation/prevent"; import PreventBackNavigation from "./Game/Navigation/preventBack"; -import { useEffect } from "react"; const Router = () => { - const { user } = useAuth(); - return ( - - - - - } /> - } /> - } /> - } /> - {user ? ( - <> - } /> - } - /> - } /> - - ) : ( - } /> - )} - } /> - } /> - - - - - ); + const { user } = useAuth(); + return ( + + + + + } /> + } /> + } /> + } /> + {user ? ( + <> + } + /> + } + /> + } + /> + + ) : ( + } /> + )} + } + /> + } /> + + + + + ); }; // 게임 라우터 const GameRoutes = () => { - - return ( - - - - - } /> - } /> - } /> - } /> - } /> - } /> - - - ); + return ( + + + + + } /> + } /> + } /> + } /> + } /> + } /> + + + ); }; export default Router; diff --git a/src/index.css b/src/index.css index 9e51584..a6be101 100644 --- a/src/index.css +++ b/src/index.css @@ -1,18 +1,23 @@ body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - height: 100%; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", + "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + height: 100%; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; } -#root{ +#root { height: 100%; -} \ No newline at end of file +} +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +}