diff --git a/README.md b/README.md index 66ace2e..548a6fa 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,154 @@ -# 5-HRTH-ustock-fe -카카오 테크 부트캠프 과정 파이널 프로젝트 프론트엔드 리포지토리 +

+ U'STOCK Logo +

+

U'STOCK

+

뉴스 중심의 차트 분석 서비스 U'STOCK의 frontend repository

+
카카오테크 부트캠프 클라우드 네이티브 제주 1기 2팀 HRTH (2024-07-02 ~ 2024-10-11)
-

-# 💡 **프로젝트 소개** -
-### 핵심 키워드 -1. elle 키워드 -2. veronica 키워드 -3. lucy 키워드 + +

:book: 목차

-### 사용한 데이터 -- 한국투자증권 api 주식 데이터 -- 크롤링한 뉴스 데이터 -- 54 건의 유저 데이터 + 전체 유입 유저 +
+ 목차 +
    +
  1. ➤ 아키텍쳐
  2. +
  3. ➤ 기술 스택
  4. +
  5. ➤ 디렉토리 구조
  6. +
  7. ➤ 트러블 슈팅
  8. +
  9. ➤ 리팩토링
  10. +
  11. ➤ Google OAuth
  12. +
  13. ➤ REST API 개요
  14. +
  15. ➤ /v1/stocks
  16. +
  17. ➤ /v1/portfolio
  18. +
  19. ➤ /v1/news
  20. +
  21. ➤ /v1/game
  22. +
  23. ➤ 팀 소개
  24. +
+
-

-# 🛠️ 아키텍쳐 -(새로이 그릴 예정) +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) -

+ +

🏙️ 아키텍쳐

+스크린샷 2024-10-11 오전 12 47 12 + +
+ +- 배포 파이프라인 + +스크린샷 2024-10-11 오전 12 45 06 + +
+ # 📍 기술스택 -|분류|기술| -|:- |:- | -|Language|| -|Framework| -|Remote Data| -|Data Management| -|Library| -|DevOps| +| 분류 | 기술 | +| :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Language | | +| Framework | | +| Remote Data | | +| Data Management | | +| Library | | +| DevOps | | + +
+ +

🔸 표준 개발 환경

+스크린샷 2024-10-11 오전 12 48 15 + +- 개발 환경 : 유지 보수를 용이하게 하기 위해 역할 명확히 구분 +- 커스텀 훅 : 데이터 fetching과 상태 관리에 집중하여 UI와의 결합도 낮춤 +- 비즈니스 로직 : 서비스 레이어로 분리해 모듈화 +- 이를 통해 코드의 가독성을 높임, 변경 시 영향 범위를 최소화 + +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) + + +

💡 트러블 슈팅 및 최적화

+ +

🔸 Zustand 도입

+ + + + + + +
스크린샷 2024-10-11 오전 12 52 13스크린샷 2024-10-11 오전 12 52 19
+ +``` +--문제-- +- 한 페이지에 컴포넌트와 모달 수가 증가하면서 여러 컴포넌트를 거쳐야 하는 프롭스드릴링 이슈가 발생 +- 자식 컴포넌트에서 부모컴포넌트로 state를 올려줘야 하기 때문에 코드가 복잡해짐 +- 가독성 하락 및 유지보수 불편 + +--해결-- +- 상태관리 라이브러리 zustand 도입 +- state를 전역에서 관리 +- drilling 없이 state를 사용하고 싶은 곳에서 꺼내씀 + +``` + +
+

🔸 커스텀 훅 분리

+스크린샷 2024-10-11 오전 1 13 51 + +``` +--문제-- +- 비즈니스 로직이 컴포넌트 코드에서 많은 부분을 차지 + +--해결-- +- 비즈니스 로직을 커스텀 훅으로 분리 +- 컴포넌트 코드를 화면 렌더링에 집중 +``` + +
+

🔸 페이지 성능 문제

+스크린샷 2024-10-11 오전 1 19 09 + +``` +--문제-- +- 불필요한 리소스의 일괄 로드로 인한 페이지 성능 문제 + +--해결-- +- code spliting +- lazy loading + +--개선-- +- 초기 로딩 시간 단축 +- 번들 크기 단축 +- 성능 64 -> 94 +``` + +
+

🔸 API 중복 요청

+스크린샷 2024-10-11 오전 1 23 34 + +``` +--문제-- +- (루시 여기좀 적어주세요) + +--해결-- +- debounce함수 적용 + +--개선-- +- +``` + +![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) + +

📜 프론트엔드 배포 플로우

+ +

🔸 Cloud front

+ +1. github repository에 코드가 병합 +2. github action을 통해 자동으로 배포가 진행 +3. 이때 깃헙액션에서 리액트 정적 파일을 빌드 +4. 빌드된 파일을 S3에 업로드 +5. CloudFront로 배포 + +

🔸 Github action

+ +- 브랜치에 따라 각각 `개발용 버킷`과 `운영용 버킷`에 업로드 -


+![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) diff --git a/src/Component/Game/GameMoney.tsx b/src/Component/Game/GameMoney.tsx index c0bdb9a..1d89224 100644 --- a/src/Component/Game/GameMoney.tsx +++ b/src/Component/Game/GameMoney.tsx @@ -10,175 +10,175 @@ import GameViewTab from "./gameViewTab"; import { useSwipeStore } from "../../store/useSwipeStore"; const GameMoney = ({ - setBudget, - budget, + setBudget, + budget, }: { - setBudget: (budget: number) => void; - budget: number; + setBudget: (budget: number) => void; + budget: number; }) => { - 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(true); - const { holdingList, setHoldingList } = useSwipeStore(); // 내가 산거 - - const check = usePortfolioStore((state) => state.check); - - // 페이지 유효성 검사 - const setCheckYear = useGameStore((state) => state.setCheckYear); - - const { currentRank } = useGameStore((state) => ({ - currentRank: state.currentRank, - })); - - 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) { - setCheckYear(res.data.year); - 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, currentRank]); - - const handleShowTable = () => { - setShowTable((prev) => !prev); - }; - - const { fetchRank } = useGameStore((state) => ({ - fetchRank: state.fetchRank, - })); - - useEffect(() => { - fetchRank(); - }, []); - - return ( - - - {nickname} 님의 계좌잔고 💰 - - - 거래가능금액 -
{formatPrice(budget)} 원
-
-
- - 총 평가금액 -
{formatPrice(total)} 원
-
- - 작년 대비 -
0 - ? "red" - : changeRateFromLast < 0 - ? "blue" - : "black", - }} - > - {formatPrice(changeFromLast)} 원 ({changeRateFromLast}%) -
-
- - 전체 대비 -
0 - ? "red" - : changeRateFromStart < 0 - ? "blue" - : "black", - }} - > - {formatPrice(changeFromStart)} 원 ({changeRateFromStart}%) -
-
- - - -
나는 현재 {currentRank} 등!
- {showTable && } -
- ); + 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(true); + const { holdingList, setHoldingList } = useSwipeStore(); // 내가 산거 + + const check = usePortfolioStore((state) => state.check); + + // 페이지 유효성 검사 + const setCheckYear = useGameStore((state) => state.setCheckYear); + + const { currentRank } = useGameStore((state) => ({ + currentRank: state.currentRank, + })); + + 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) { + setCheckYear(res.data.year); + 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, currentRank]); + + const handleShowTable = () => { + setShowTable((prev) => !prev); + }; + + const { fetchRank } = useGameStore((state) => ({ + fetchRank: state.fetchRank, + })); + + useEffect(() => { + fetchRank(); + }, []); + + return ( + + + {nickname} 님의 계좌잔고 💰 + + + 거래가능금액 +
{formatPrice(budget)} 원
+
+
+ + 총 평가금액 +
{formatPrice(total)} 원
+
+ + 작년 대비 +
0 + ? "red" + : changeRateFromLast < 0 + ? "blue" + : "black", + }} + > + {formatPrice(changeFromLast)} 원 ({changeRateFromLast}%) +
+
+ + 전체 대비 +
0 + ? "red" + : changeRateFromStart < 0 + ? "blue" + : "black", + }} + > + {formatPrice(changeFromStart)} 원 ({changeRateFromStart}%) +
+
+ + + +
나는 현재 {currentRank} 등!
+ {showTable && } +
+ ); }; const GameMoneyContainer = styled.div` - width: 85%; - height: auto; - border-radius: 20px; - box-shadow: 0px 0px 17px rgb(213, 213, 213); - padding: 20px 10px; - position: relative; - margin-top: 20px; - margin-bottom: 20px; + width: 85%; + height: auto; + border-radius: 20px; + box-shadow: 0px 0px 17px 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: 118px; - right: 20px; + position: absolute; + top: 60px; + 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; - - &:hover { - background-color: #615efc; - color: white; - font-weight: 600; - } + width: 78px; + height: 40px; + border-radius: 50px; + border: none; + background-color: #f1f1f1; + cursor: pointer; + + &:hover { + background-color: #615efc; + color: white; + font-weight: 600; + } `; export default GameMoney; diff --git a/src/Component/Game/StocksTable.tsx b/src/Component/Game/StocksTable.tsx index 9cfe34f..5784342 100644 --- a/src/Component/Game/StocksTable.tsx +++ b/src/Component/Game/StocksTable.tsx @@ -5,65 +5,66 @@ import "./StocksTableStyle.css"; import styled, { keyframes } from "styled-components"; const StocksTable: React.FC = ({ stocks, year }) => { - const header = ["번호", "종목", "전년", "올해", "등락"]; - console.log(stocks, year); + const header = ["번호", "종목", "전년", "올해", "등락"]; - return ( -
- - - - {header.map((item, index) => ( - - ))} - - - - {stocks && - stocks.map((stock, index) => ( - - - - - - - - ))} - -
{item}
{index + 1}{stock.name} - {formatPriceWithYear(stock.prev, year)} - 0 - ? "red" - : stock.changeRate < 0 - ? "blue" - : "black", - textAlign: "right", - }} - > - {formatPriceWithYear(stock.current, year)} - 0 - ? "red" - : stock.changeRate < 0 - ? "blue" - : "black", - }} - > - {formatChangeRate(stock.changeRate)} -
-
- ); + return ( +
+ + + + {header.map((item, index) => ( + + ))} + + + + {stocks && + stocks.map((stock, index) => ( + + + + + + + + ))} + +
{item}
{index + 1}{stock.name} + {formatPriceWithYear(stock.prev, year)} + 0 + ? "red" + : stock.changeRate < 0 + ? "blue" + : "black", + textAlign: "right", + }} + > + {formatPriceWithYear(stock.current, year)} + 0 + ? "red" + : stock.changeRate < 0 + ? "blue" + : "black", + }} + > + {formatChangeRate(stock.changeRate)} +
+
+ ); }; // 부드럽게 나타나는 애니메이션 정의 @@ -79,9 +80,9 @@ const fadeIn = keyframes` `; const TableRow = styled.tr<{ index: number }>` - animation: ${fadeIn} 0.5s ease-out forwards; - animation-delay: ${({ index }) => index * 0.1}s; - opacity: 0; + animation: ${fadeIn} 0.5s ease-out forwards; + animation-delay: ${({ index }) => index * 0.1}s; + opacity: 0; `; export default StocksTable; diff --git a/src/Component/Game/gameHoldingsView.tsx b/src/Component/Game/gameHoldingsView.tsx index d300122..226f5c6 100644 --- a/src/Component/Game/gameHoldingsView.tsx +++ b/src/Component/Game/gameHoldingsView.tsx @@ -28,7 +28,7 @@ const GameHoldingsView = ({ holdingList }: any) => { ) : ( <> - + {header.map((item, index) => ( {item} ))} @@ -43,6 +43,7 @@ const GameHoldingsView = ({ holdingList }: any) => { index % 2 === 0 ? "#ededed" : "white", + fontSize: "11px", }} > {holding.stockName} diff --git a/src/Component/SearchBar/SearchBar.tsx b/src/Component/SearchBar/SearchBar.tsx index 87c92cb..0d35e9a 100644 --- a/src/Component/SearchBar/SearchBar.tsx +++ b/src/Component/SearchBar/SearchBar.tsx @@ -19,86 +19,86 @@ const SearchBar: React.FC = () => { }; // debounce 함수 : 1000ms 디바운스를 걸고, 마지막 keyword로만 api 요청 - // const handelDebounce = useCallback( - // debounce((input: string) => { - // if (input.trim() === "") { - // setList([]); - // return; - // } - // axios - // .get( - // `${process.env.REACT_APP_API_URL}/v1/stocks/search?query=${input}`, - // { - // withCredentials: true, - // headers: { - // "Content-Type": "application/json", - // }, - // } - // ) - // .then((res) => { - // if (res.status === 200) { - // const result = res.data; - // setList(result); - // } else if (res.status === 401) { - // nav("/login"); - // } - // }) - // .catch((error) => {}); - // }, 300), - // [] - // ); + const handelDebounce = useCallback( + debounce((input: string) => { + if (input.trim() === "") { + setList([]); + return; + } + axios + .get( + `${process.env.REACT_APP_API_URL}/v1/stocks/search?query=${input}`, + { + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + } + ) + .then((res) => { + if (res.status === 200) { + const result = res.data; + setList(result); + } else if (res.status === 401) { + nav("/login"); + } + }) + .catch((error) => {}); + }, 300), + [] + ); - // // input 태그에 입력되는 값으로 keyword 업데이트, debounce 함수에 keyword 전달 - // const handleSearch = useCallback( - // (e: React.ChangeEvent) => { - // let input = e.target.value; - // const regExp = /[ \{\}\[\]\/?.,;:|\)*~`!^\-_+┼<>@\#$%&\'\"\\\(\=]/gi; - // if (regExp.test(input)) { - // input = input.replace(regExp, ""); - // } - // setKeyword(input); - // handelDebounce(input); - // }, - // [handelDebounce] - // ); + // input 태그에 입력되는 값으로 keyword 업데이트, debounce 함수에 keyword 전달 + const handleSearch = useCallback( + (e: React.ChangeEvent) => { + let input = e.target.value; + const regExp = /[ \{\}\[\]\/?.,;:|\)*~`!^\-_+┼<>@\#$%&\'\"\\\(\=]/gi; + if (regExp.test(input)) { + input = input.replace(regExp, ""); + } + setKeyword(input); + handelDebounce(input); + }, + [handelDebounce] + ); - // // # Debounce 적용 전 ver + // # Debounce 적용 전 ver // input 태그에 입력되는 값으로 keyword 업데이트, debounce 함수에 keyword 전달 - const handleSearch = (e: React.ChangeEvent) => { - let input = e.target.value; - const regExp = /[ \{\}\[\]\/?.,;:|\)*~`!^\-_+┼<>@\#$%&\'\"\\\(\=]/gi; - if (regExp.test(input)) { - input = input.replace(regExp, ""); - } - setKeyword(input); - }; + // const handleSearch = (e: React.ChangeEvent) => { + // let input = e.target.value; + // const regExp = /[ \{\}\[\]\/?.,;:|\)*~`!^\-_+┼<>@\#$%&\'\"\\\(\=]/gi; + // if (regExp.test(input)) { + // input = input.replace(regExp, ""); + // } + // setKeyword(input); + // }; - useEffect(() => { - if (keyword.trim() === "") { - setList([]); - return; - } - axios - .get( - `${process.env.REACT_APP_API_URL}/v1/stocks/search?query=${keyword}`, - { - withCredentials: true, - headers: { - "Content-Type": "application/json", - }, - } - ) - .then((res) => { - if (res.status === 200) { - const result = res.data; - setList(result); - } else if (res.status === 401) { - nav("/login"); - } - }) - .catch((error) => {}); - }, [keyword]); + // useEffect(() => { + // if (keyword.trim() === "") { + // setList([]); + // return; + // } + // axios + // .get( + // `${process.env.REACT_APP_API_URL}/v1/stocks/search?query=${keyword}`, + // { + // withCredentials: true, + // headers: { + // "Content-Type": "application/json", + // }, + // } + // ) + // .then((res) => { + // if (res.status === 200) { + // const result = res.data; + // setList(result); + // } else if (res.status === 401) { + // nav("/login"); + // } + // }) + // .catch((error) => {}); + // }, [keyword]); const handleSelectStock = (event: StockDataProps) => { nav(`/stocks/${event.code}`); diff --git a/src/Game/Main/mainStyle.css b/src/Game/Main/mainStyle.css index 9a922d6..c6959d3 100644 --- a/src/Game/Main/mainStyle.css +++ b/src/Game/Main/mainStyle.css @@ -1,512 +1,528 @@ .container { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100vh; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100vh; } + +@media (max-width: 768px) { + .container { + flex-direction: column; /* 세로 방향으로 정렬 */ + height: auto; /* 높이를 자동으로 조정 */ + padding: 20px; /* 여백 추가 */ + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } +} + .macbook { - position: relative; - width: 228px; - height: 260px; + position: relative; + width: 228px; + height: 260px; } .macbook__topBord { - position: absolute; - z-index: 0; - top: 34px; - left: 0; - width: 128px; - height: 116px; - border-radius: 6px; - transform-origin: center; - background: linear-gradient(-135deg, #c8c9c9 52%, #8c8c8c 56%); - transform: scale(0) skewY(-30deg); - animation: topbord 0.4s 1.7s ease-out; - animation-fill-mode: forwards; + position: absolute; + z-index: 0; + top: 34px; + left: 0; + width: 128px; + height: 116px; + border-radius: 6px; + transform-origin: center; + background: linear-gradient(-135deg, #c8c9c9 52%, #8c8c8c 56%); + transform: scale(0) skewY(-30deg); + animation: topbord 0.4s 1.7s ease-out; + animation-fill-mode: forwards; } .macbook__topBord::before { - content: ""; - position: absolute; - z-index: 2; - top: 8px; - left: 6px; - width: 100%; - height: 100%; - border-radius: 6px; - background: #000; + content: ""; + position: absolute; + z-index: 2; + top: 8px; + left: 6px; + width: 100%; + height: 100%; + border-radius: 6px; + background: #000; } .macbook__topBord::after { - content: ""; - position: absolute; - z-index: 1; - bottom: -7px; - left: 8px; - width: 168px; - height: 12px; - transform-origin: left bottom; - transform: rotate(-42deg) skew(-4deg); - background: linear-gradient(-135deg, #c8c9c9 52%, #8c8c8c 56%); + content: ""; + position: absolute; + z-index: 1; + bottom: -7px; + left: 8px; + width: 168px; + height: 12px; + transform-origin: left bottom; + transform: rotate(-42deg) skew(-4deg); + background: linear-gradient(-135deg, #c8c9c9 52%, #8c8c8c 56%); } .macbook__display { - position: absolute; - z-index: 10; - top: 17px; - left: 12px; - z-index: 2; - width: calc(100% - 12px); - height: calc(100% - 18px); - background: linear-gradient(45deg, #3ba9ff, #c82aff); + position: absolute; + z-index: 10; + top: 17px; + left: 12px; + z-index: 2; + width: calc(100% - 12px); + height: calc(100% - 18px); + background: linear-gradient(45deg, #3ba9ff, #c82aff); } .macbook__display::before { - content: ""; - position: absolute; - z-index: 5; - top: -9px; - left: -6px; - width: calc(100% + 12px); - height: calc(100% + 18px); - border-radius: 6px; - background: linear-gradient( - 60deg, - rgba(255, 255, 255, 0) 60%, - rgba(255, 255, 255, 0.3) 60% - ); + content: ""; + position: absolute; + z-index: 5; + top: -9px; + left: -6px; + width: calc(100% + 12px); + height: calc(100% + 18px); + border-radius: 6px; + background: linear-gradient( + 60deg, + rgba(255, 255, 255, 0) 60%, + rgba(255, 255, 255, 0.3) 60% + ); } .macbook__load { - position: relative; - width: 100%; - height: 100%; - background: #222; - animation: display 0.4s 4.3s ease; - opacity: 1; - animation-fill-mode: forwards; + position: relative; + width: 100%; + height: 100%; + background: #222; + animation: display 0.4s 4.3s ease; + opacity: 1; + animation-fill-mode: forwards; } .macbook__load:before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 80px; - height: 6px; - border-radius: 3px; - box-sizing: border-box; - border: solid 1px #fff; + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 80px; + height: 6px; + border-radius: 3px; + box-sizing: border-box; + border: solid 1px #fff; } .macbook__load:after { - content: ""; - position: absolute; - top: 0; - left: 18px; - bottom: 0; - margin: auto; - width: 0; - height: 6px; - border-radius: 3px; - background: #fff; - animation: load 2s 2s ease-out; - animation-fill-mode: forwards; + content: ""; + position: absolute; + top: 0; + left: 18px; + bottom: 0; + margin: auto; + width: 0; + height: 6px; + border-radius: 3px; + background: #fff; + animation: load 2s 2s ease-out; + animation-fill-mode: forwards; } .macbook__image { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 100px; - height: auto; - opacity: 0; - /* visibility: hidden; */ - transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 100px; + height: auto; + opacity: 0; + /* visibility: hidden; */ + transition: opacity 0.5s ease-in-out; } .macbook__image.visible { - /* visibility: hidden; */ - opacity: 1; + /* visibility: hidden; */ + opacity: 1; } .macbook__underBord { - position: relative; - left: 42px; - bottom: -145px; - width: 150px; - height: 90px; - border-radius: 6px; - transform-origin: center; - transform: rotate(-30deg) skew(30deg); - background: linear-gradient(-45deg, #c8c9c9 61%, #8c8c8c 66%); - animation: modal 0.5s 1s ease-out; - opacity: 0; - animation-fill-mode: forwards; + position: relative; + left: 42px; + bottom: -145px; + width: 150px; + height: 90px; + border-radius: 6px; + transform-origin: center; + transform: rotate(-30deg) skew(30deg); + background: linear-gradient(-45deg, #c8c9c9 61%, #8c8c8c 66%); + animation: modal 0.5s 1s ease-out; + opacity: 0; + animation-fill-mode: forwards; } .macbook__underBord::before { - content: ""; - position: absolute; - z-index: 3; - top: -8px; - left: 8px; - width: 100%; - height: 100%; - border-radius: 6px; - background: #dcdede; + content: ""; + position: absolute; + z-index: 3; + top: -8px; + left: 8px; + width: 100%; + height: 100%; + border-radius: 6px; + background: #dcdede; } .macbook__underBord::after { - content: ""; - position: absolute; - z-index: 2; - top: -8px; - left: 12px; - width: 170px; - height: 15px; - transform-origin: top left; - background: linear-gradient(-45deg, #c8c9c9 61%, #8c8c8c 66%); - transform: rotate(31deg) skew(-16deg); + content: ""; + position: absolute; + z-index: 2; + top: -8px; + left: 12px; + width: 170px; + height: 15px; + transform-origin: top left; + background: linear-gradient(-45deg, #c8c9c9 61%, #8c8c8c 66%); + transform: rotate(31deg) skew(-16deg); } .macbook__keybord { - position: relative; - top: 0; - left: 16px; - z-index: 3; - border-radius: 3px; - width: calc(100% - 16px); - height: 45px; - background: #c8c9c9; + position: relative; + top: 0; + left: 16px; + z-index: 3; + border-radius: 3px; + width: calc(100% - 16px); + height: 45px; + background: #c8c9c9; } .macbook__keybord::before { - content: ""; - position: absolute; - bottom: -30px; - left: 0; - right: 0; - margin: 0 auto; - width: 40px; - height: 25px; - border-radius: 3px; - background: #c8c9c9; + content: ""; + position: absolute; + bottom: -30px; + left: 0; + right: 0; + margin: 0 auto; + width: 40px; + height: 25px; + border-radius: 3px; + background: #c8c9c9; } .keybord { - position: relative; - top: 2px; - left: 2px; - display: flex; - flex-direction: column; - width: calc(100% - 3px); - height: calc(100% - 4px); + position: relative; + top: 2px; + left: 2px; + display: flex; + flex-direction: column; + width: calc(100% - 3px); + height: calc(100% - 4px); } .keybord__touchbar { - width: 100%; - height: 6px; - border-radius: 3px; - background: #000; + width: 100%; + height: 6px; + border-radius: 3px; + background: #000; } .keybord__keyBox { - display: grid; - grid-template-rows: 3fr 1fr; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; - width: 100%; - height: 24px; - margin: 1px 0 0 0; - padding: 0 0 0 1px; - box-sizing: border-box; - list-style: none; + display: grid; + grid-template-rows: 3fr 1fr; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + width: 100%; + height: 24px; + margin: 1px 0 0 0; + padding: 0 0 0 1px; + box-sizing: border-box; + list-style: none; } .keybord__key { - position: relative; - width: 8px; - height: 7px; - margin: 1px; - background: #000; + position: relative; + width: 8px; + height: 7px; + margin: 1px; + background: #000; } .keybord__keyBox .keybord__key { - transform: translate(60px, -60px); - animation: key 0.2s 1.4s ease-out; - animation-fill-mode: forwards; - opacity: 0; + transform: translate(60px, -60px); + animation: key 0.2s 1.4s ease-out; + animation-fill-mode: forwards; + opacity: 0; } .keybord__keyBox .keybord__key::before, .keybord__keyBox .keybord__key::after { - content: ""; - position: absolute; - left: 0; - width: 100%; - height: 100%; - background: #000; + content: ""; + position: absolute; + left: 0; + width: 100%; + height: 100%; + background: #000; } .keybord__key::before { - top: 8px; - transform: translate(20px, -20px); - animation: key1 0.2s 1.5s ease-out; - animation-fill-mode: forwards; + top: 8px; + transform: translate(20px, -20px); + animation: key1 0.2s 1.5s ease-out; + animation-fill-mode: forwards; } .keybord__key::after { - top: 16px; - transform: translate(40px, -40px); - animation: key2 0.2s 1.6s ease-out; - animation-fill-mode: forwards; + top: 16px; + transform: translate(40px, -40px); + animation: key2 0.2s 1.6s ease-out; + animation-fill-mode: forwards; } .keybord__keyBox .key--12::before { - width: 10px; + width: 10px; } .keybord__keyBox .key--13::before { - height: 10px; + height: 10px; } .key--01 { - grid-row: 1 / 2; - grid-column: 1 / 2; + grid-row: 1 / 2; + grid-column: 1 / 2; } .key--02 { - grid-row: 1 / 2; - grid-column: 2 / 3; + grid-row: 1 / 2; + grid-column: 2 / 3; } .key--03 { - grid-row: 1 / 2; - grid-column: 3 / 4; + grid-row: 1 / 2; + grid-column: 3 / 4; } .key--04 { - grid-row: 1 / 2; - grid-column: 4 / 5; + grid-row: 1 / 2; + grid-column: 4 / 5; } .key--05 { - grid-row: 1 / 2; - grid-column: 5 / 6; + grid-row: 1 / 2; + grid-column: 5 / 6; } .key--06 { - grid-row: 1 / 2; - grid-column: 6 / 7; + grid-row: 1 / 2; + grid-column: 6 / 7; } .key--07 { - grid-row: 1 / 2; - grid-column: 7 / 8; + grid-row: 1 / 2; + grid-column: 7 / 8; } .key--08 { - grid-row: 1 / 2; - grid-column: 8 / 9; + grid-row: 1 / 2; + grid-column: 8 / 9; } .key--09 { - grid-row: 1 / 2; - grid-column: 9 / 10; + grid-row: 1 / 2; + grid-column: 9 / 10; } .key--10 { - grid-row: 1 / 2; - grid-column: 10 / 11; + grid-row: 1 / 2; + grid-column: 10 / 11; } .key--11 { - grid-row: 1 / 2; - grid-column: 11 / 12; + grid-row: 1 / 2; + grid-column: 11 / 12; } .key--12 { - grid-row: 1 / 2; - grid-column: 12 / 13; + grid-row: 1 / 2; + grid-column: 12 / 13; } .key--13 { - grid-row: 1 / 2; - grid-column: 13 / 14; + grid-row: 1 / 2; + grid-column: 13 / 14; } .keybord__keyBox--under { - margin: 0; - padding: 0 0 0 1px; - box-sizing: border-box; - list-style: none; - display: flex; + margin: 0; + padding: 0 0 0 1px; + box-sizing: border-box; + list-style: none; + display: flex; } .keybord__keyBox--under .keybord__key { - transform: translate(80px, -80px); - animation: key3 0.3s 1.6s linear; - animation-fill-mode: forwards; - opacity: 0; + transform: translate(80px, -80px); + animation: key3 0.3s 1.6s linear; + animation-fill-mode: forwards; + opacity: 0; } .key--19 { - width: 28px; + width: 28px; } @keyframes topbord { - 0% { - transform: scale(0) skewY(-30deg); - } - 30% { - transform: scale(1.1) skewY(-30deg); - } - 45% { - transform: scale(0.9) skewY(-30deg); - } - 60% { - transform: scale(1.05) skewY(-30deg); - } - 75% { - transform: scale(0.95) skewY(-30deg); - } - 90% { - transform: scale(1.02) skewY(-30deg); - } - 100% { - transform: scale(1) skewY(-30deg); - } + 0% { + transform: scale(0) skewY(-30deg); + } + 30% { + transform: scale(1.1) skewY(-30deg); + } + 45% { + transform: scale(0.9) skewY(-30deg); + } + 60% { + transform: scale(1.05) skewY(-30deg); + } + 75% { + transform: scale(0.95) skewY(-30deg); + } + 90% { + transform: scale(1.02) skewY(-30deg); + } + 100% { + transform: scale(1) skewY(-30deg); + } } @keyframes display { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } } @keyframes load { - 0% { - width: 0; - } - 20% { - width: 40px; - } - 30% { - width: 40px; - } - 60% { - width: 60px; - } - 90% { - width: 60px; - } - 100% { - width: 80px; - } + 0% { + width: 0; + } + 20% { + width: 40px; + } + 30% { + width: 40px; + } + 60% { + width: 60px; + } + 90% { + width: 60px; + } + 100% { + width: 80px; + } } @keyframes modal { - 0% { - transform: scale(0) rotate(-30deg) skew(30deg); - opacity: 0; - } - 30% { - transform: scale(1.1) rotate(-30deg) skew(30deg); - opacity: 1; - } - 45% { - transform: scale(0.9) rotate(-30deg) skew(30deg); - opacity: 1; - } - 60% { - transform: scale(1.05) rotate(-30deg) skew(30deg); - opacity: 1; - } - 75% { - transform: scale(0.95) rotate(-30deg) skew(30deg); - opacity: 1; - } - 90% { - transform: scale(1.02) rotate(-30deg) skew(30deg); - opacity: 1; - } - 100% { - transform: scale(1) rotate(-30deg) skew(30deg); - opacity: 1; - } + 0% { + transform: scale(0) rotate(-30deg) skew(30deg); + opacity: 0; + } + 30% { + transform: scale(1.1) rotate(-30deg) skew(30deg); + opacity: 1; + } + 45% { + transform: scale(0.9) rotate(-30deg) skew(30deg); + opacity: 1; + } + 60% { + transform: scale(1.05) rotate(-30deg) skew(30deg); + opacity: 1; + } + 75% { + transform: scale(0.95) rotate(-30deg) skew(30deg); + opacity: 1; + } + 90% { + transform: scale(1.02) rotate(-30deg) skew(30deg); + opacity: 1; + } + 100% { + transform: scale(1) rotate(-30deg) skew(30deg); + opacity: 1; + } } @keyframes key { - 0% { - transform: translate(60px, -60px); - opacity: 0; - } - 100% { - transform: translate(0px, 0px); - opacity: 1; - } + 0% { + transform: translate(60px, -60px); + opacity: 0; + } + 100% { + transform: translate(0px, 0px); + opacity: 1; + } } @keyframes key1 { - 0% { - transform: translate(20px, -20px); - opacity: 0; - } - 100% { - transform: translate(0px, 0px); - opacity: 1; - } + 0% { + transform: translate(20px, -20px); + opacity: 0; + } + 100% { + transform: translate(0px, 0px); + opacity: 1; + } } @keyframes key2 { - 0% { - transform: translate(40px, -40px); - opacity: 0; - } - 100% { - transform: translate(0px, 0px); - opacity: 1; - } + 0% { + transform: translate(40px, -40px); + opacity: 0; + } + 100% { + transform: translate(0px, 0px); + opacity: 1; + } } @keyframes key3 { - 0% { - transform: translate(80px, -80px); - opacity: 0; - } - 100% { - transform: translate(0px, 0px); - opacity: 1; - } + 0% { + transform: translate(80px, -80px); + opacity: 0; + } + 100% { + transform: translate(0px, 0px); + opacity: 1; + } } @keyframes load { - 0% { - width: 0; - } - 100% { - width: 80px; - } + 0% { + width: 0; + } + 100% { + width: 80px; + } } @keyframes flyOut { - 0% { - transform: translate(0, 0) scale(1); - opacity: 1; - } - 50% { - transform: translate(250px, -150px) scale(1.5); /* Increase size halfway */ - opacity: 1; - } - 100% { - transform: translate(500px, -300px); /* Adjust the flight path as needed */ - opacity: 0; - } + 0% { + transform: translate(0, 0) scale(1); + opacity: 1; + } + 50% { + transform: translate(250px, -150px) scale(1.5); /* Increase size halfway */ + opacity: 1; + } + 100% { + transform: translate( + 500px, + -300px + ); /* Adjust the flight path as needed */ + opacity: 0; + } } .macbook__image { - visibility: hidden; - opacity: 0; - transition: opacity 1s ease-in; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + visibility: hidden; + opacity: 0; + transition: opacity 1s ease-in; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } .macbook__image.visible { - visibility: visible; - opacity: 1; + visibility: visible; + opacity: 1; } .macbook__image.fly { - animation: flyOut 2s ease-out forwards; /* Adjust animation duration */ + animation: flyOut 2s ease-out forwards; /* Adjust animation duration */ } .macbook__game { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 24px; - color: white; - text-align: center; - opacity: 0; - animation: fadeIn 1s forwards; - width: 100%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 24px; + color: white; + text-align: center; + opacity: 0; + animation: fadeIn 1s forwards; + width: 100%; } @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { + opacity: 0; + } + to { + opacity: 1; + } } diff --git a/src/Game/Main/styleMain.ts b/src/Game/Main/styleMain.ts index 4075f42..e803f5f 100644 --- a/src/Game/Main/styleMain.ts +++ b/src/Game/Main/styleMain.ts @@ -1,16 +1,19 @@ import { styled } from "styled-components"; export const ButtonDiv = styled.div` - display: flex; - margin-top: 2rem; - margin-left: 2rem; + display: flex; + margin-top: 2rem; + margin-left: 2rem; + @media (max-width: 768px) { + margin-left: 0; + } `; export const GameTitle = styled.p` - display: flex; - align-items: center; - justify-content: center; - font-size: 25px; - font-weight: 900; - margin-bottom: 1rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 25px; + font-weight: 900; + margin-bottom: 1rem; `; diff --git a/src/Pages/game/playPage.tsx b/src/Pages/game/playPage.tsx index a73c139..4dc5d58 100644 --- a/src/Pages/game/playPage.tsx +++ b/src/Pages/game/playPage.tsx @@ -1,214 +1,3 @@ -// import { useNavigate, useParams } from "react-router-dom"; -// import GameButtons from "../../Component/Game/GameButtons"; -// import GameHeader from "../../Component/Game/GameHeader"; -// import GameMoney from "../../Component/Game/GameMoney"; -// import StocksTable from "../../Component/Game/StocksTable"; -// import styled from "styled-components"; -// import GameTradeSwipe from "../../Component/Game/GameTradeSwipe"; -// import { useState } from "react"; -// import PassConfirmModal from "../../Component/Game/PassConfirmModal"; -// import axios from "axios"; -// import "./gameStyle.css"; -// import HappyNewYearModal from "./HappyNewYearModal"; -// import { useStock } from "../../store/stockContext"; -// import { usePortfolioStore } from "../../store/usePortfolioStore"; -// import { useGameStore } from "../../store/useGameStore"; -// import ProgressBar from "../../Game/Loading/progressBar"; -// import { GoAlert } from "react-icons/go"; -// import { Colors } from "../../Styles/Colors"; -// import Button from "../../Component/Button/button"; -// import { useHintStore } from "../../store/hintStore"; - -// const Container = styled.div` -// display: flex; -// flex-direction: column; -// justify-content: center; -// align-items: center; -// text-align: center; -// `; - -// const PlayPage = () => { -// const { year } = useParams<{ year?: string }>(); -// const { stockData, setStockData } = useStock(); -// const nav = useNavigate(); -// const yearValue = year || "2014"; -// // 페이지 유효성 검사를 위한 변수 -// const yearNumber = parseInt(yearValue, 10); -// const [isTradeModalVisible, setIsTradeModalVisible] = useState(false); -// const [isPassModalVisible, setIsPassModalVisible] = useState(false); -// const [isHappyNewYearModal, setIsHappyNewYearModal] = useState(false); -// const [budget, setBudget] = useState(0); -// const [, setLoading] = useState(false); - -// const { check, setCheck } = usePortfolioStore((state) => ({ -// check: state.check, -// setCheck: state.setCheck, -// })); - -// const { checkYear, setCheckGameDone } = useGameStore((state) => ({ -// checkYear: state.checkYear, -// setCheckGameDone: state.setCheckGameDone, -// })); - -// const startYear = 2014; -// const lastYear = 2023; - -// // 년도 진행률 표시 -// const calculateProgress = () => { -// return ( -// ((parseInt(yearValue, 10) - startYear) / (lastYear - startYear)) * -// 100 -// ); -// }; - -// const [progress, setProgress] = useState(calculateProgress); - -// // 거래하기 모달 핸들러 -// const openTradeModal = () => { -// setIsTradeModalVisible(true); -// }; - -// const closeTradeModal = () => { -// setIsTradeModalVisible(false); -// }; - -// // 넘어가기 모달 핸들러 -// const openPassModal = () => { -// setIsPassModalVisible(true); -// }; - -// const closePassModal = () => { -// setIsPassModalVisible(false); -// }; - -// // 넘어가기 버튼 누르면 중간결과 호출 -// const handleConfirmPass = async () => { -// try { -// setLoading(true); -// setIsHappyNewYearModal(true); -// setIsPassModalVisible(false); - -// if (year === "2023") { -// setCheckGameDone(true); -// 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) { -// if ( -// (parseInt(yearValue, 10) + 1).toString() === -// response.data.year.toString() -// ) { -// const updatedStockList = response.data.stockList; -// setStockData(updatedStockList); -// } - -// // API 완료 후 모달 닫고 페이지 이동 -// setIsHappyNewYearModal(false); -// const nextYear = (parseInt(yearValue, 10) + 1).toString(); -// nav(`/game/play/${nextYear}`); -// setCheck(!check); -// setProgress( -// ((parseInt(nextYear, 10) - startYear) / -// (lastYear - startYear)) * -// 100 -// ); -// // 로컬 스토리지에서 삭제 -// localStorage.removeItem("hint-storage"); -// // 주스탠드 스토어 상태 초기화 -// useHintStore.getState().resetPurchaseHints(); -// } -// } -// } catch (error) { -// console.error(error); -// } finally { -// setLoading(false); -// closePassModal(); -// } -// }; - -// return ( -// <> -// {yearNumber !== checkYear ? ( -//
-// -//

-// 정상적인 접근 경로가 아닙니다 -//

-//
-// ) : ( -// -// - -//
-// -//
-// -// -// -// -// -// -//
-// )} -// -// ); -// }; - -// export default PlayPage; - import { useNavigate, useParams } from "react-router-dom"; import GameButtons from "../../Component/Game/GameButtons"; import GameHeader from "../../Component/Game/GameHeader"; @@ -231,188 +20,191 @@ import Button from "../../Component/Button/button"; import { useHintStore } from "../../store/hintStore"; const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; `; const PlayPage = () => { - const { year } = useParams<{ year?: string }>(); - const { stockData, setStockData } = useStock(); - console.log("스톡데이터",stockData); - const nav = useNavigate(); - const yearValue = year || "2014"; - // 페이지 유효성 검사를 위한 변수 - const yearNumber = parseInt(yearValue, 10); - const [isTradeModalVisible, setIsTradeModalVisible] = useState(false); - const [isPassModalVisible, setIsPassModalVisible] = useState(false); - const [isHappyNewYearModal, setIsHappyNewYearModal] = useState(false); - const [budget, setBudget] = useState(0); - const [, setLoading] = useState(false); - - const { check, setCheck } = usePortfolioStore((state) => ({ - check: state.check, - setCheck: state.setCheck, - })); - - const { checkYear, setCheckGameDone } = useGameStore((state) => ({ - checkYear: state.checkYear, - setCheckGameDone: state.setCheckGameDone, - })); - - const startYear = 2014; - const lastYear = 2023; - - // 년도 진행률 표시 - const calculateProgress = () => { - return ( - ((parseInt(yearValue, 10) - startYear) / (lastYear - startYear)) * 100 - ); - }; - - const [progress, setProgress] = useState(calculateProgress); - - // 거래하기 모달 핸들러 - const openTradeModal = () => { - setIsTradeModalVisible(true); - }; - - const closeTradeModal = () => { - setIsTradeModalVisible(false); - }; - - // 넘어가기 모달 핸들러 - const openPassModal = () => { - setIsPassModalVisible(true); - }; - - const closePassModal = () => { - setIsPassModalVisible(false); - }; - - // 넘어가기 버튼 누르면 중간결과 호출 - const handleConfirmPass = async () => { - try { - setLoading(true); - setIsHappyNewYearModal(true); - setIsPassModalVisible(false); - - if (year === "2023") { - setCheckGameDone(true); - 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", - }, - } + const { year } = useParams<{ year?: string }>(); + const { stockData, setStockData } = useStock(); + const nav = useNavigate(); + const yearValue = year || "2014"; + // 페이지 유효성 검사를 위한 변수 + const yearNumber = parseInt(yearValue, 10); + const [isTradeModalVisible, setIsTradeModalVisible] = useState(false); + const [isPassModalVisible, setIsPassModalVisible] = useState(false); + const [isHappyNewYearModal, setIsHappyNewYearModal] = useState(false); + const [budget, setBudget] = useState(0); + const [, setLoading] = useState(false); + + const { check, setCheck } = usePortfolioStore((state) => ({ + check: state.check, + setCheck: state.setCheck, + })); + + const { checkYear, setCheckGameDone } = useGameStore((state) => ({ + checkYear: state.checkYear, + setCheckGameDone: state.setCheckGameDone, + })); + + const startYear = 2014; + const lastYear = 2023; + + // 년도 진행률 표시 + const calculateProgress = () => { + return ( + ((parseInt(yearValue, 10) - startYear) / (lastYear - startYear)) * + 100 ); - if (response.status === 200) { - if ( - (parseInt(yearValue, 10) + 1).toString() === - response.data.year.toString() - ) { - const updatedStockList = response.data.stockList; - setStockData(updatedStockList); - } - - // API 완료 후 모달 닫고 페이지 이동 - setIsHappyNewYearModal(false); - const nextYear = (parseInt(yearValue, 10) + 1).toString(); - nav(`/game/play/${nextYear}`); - setCheck(!check); - setProgress( - ((parseInt(nextYear, 10) - startYear) / (lastYear - startYear)) * - 100 - ); - // 로컬 스토리지에서 삭제 - localStorage.removeItem("hint-storage"); - // 주스탠드 스토어 상태 초기화 - useHintStore.getState().resetPurchaseHints(); + }; + + const [progress, setProgress] = useState(calculateProgress); + + // 거래하기 모달 핸들러 + const openTradeModal = () => { + setIsTradeModalVisible(true); + }; + + const closeTradeModal = () => { + setIsTradeModalVisible(false); + }; + + // 넘어가기 모달 핸들러 + const openPassModal = () => { + setIsPassModalVisible(true); + }; + + const closePassModal = () => { + setIsPassModalVisible(false); + }; + + // 넘어가기 버튼 누르면 중간결과 호출 + const handleConfirmPass = async () => { + try { + setLoading(true); + setIsHappyNewYearModal(true); + setIsPassModalVisible(false); + + if (year === "2023") { + setCheckGameDone(true); + 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) { + if ( + (parseInt(yearValue, 10) + 1).toString() === + response.data.year.toString() + ) { + const updatedStockList = response.data.stockList; + setStockData(updatedStockList); + } + + // API 완료 후 모달 닫고 페이지 이동 + setIsHappyNewYearModal(false); + const nextYear = (parseInt(yearValue, 10) + 1).toString(); + nav(`/game/play/${nextYear}`); + setCheck(!check); + setProgress( + ((parseInt(nextYear, 10) - startYear) / + (lastYear - startYear)) * + 100 + ); + // 로컬 스토리지에서 삭제 + localStorage.removeItem("hint-storage"); + // 주스탠드 스토어 상태 초기화 + useHintStore.getState().resetPurchaseHints(); + } + } + } catch (error) { + console.error(error); + } finally { + setLoading(false); + closePassModal(); } - } - } catch (error) { - console.error(error); - } finally { - setLoading(false); - closePassModal(); - } - }; - - return ( - <> - {yearNumber !== checkYear ? ( -
- -

정상적인 접근 경로가 아닙니다

-
- ) : ( - - + }; -
- -
- - - - - - -
- )} - - ); + return ( + <> + {yearNumber !== checkYear ? ( +
+ +

+ 정상적인 접근 경로가 아닙니다 +

+
+ ) : ( + + + +
+ +
+ + + + + + +
+ )} + + ); }; export default PlayPage;