From 20fae21723c66595c8cc1ab23ba53c2afd2f1a05 Mon Sep 17 00:00:00 2001 From: yzooop Date: Mon, 30 Sep 2024 15:26:23 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Fix=20:=20=EC=9D=B8=ED=8A=B8=EB=A1=9C=20ski?= =?UTF-8?q?p=20=EB=B2=84=ED=8A=BC=20fixed=EB=A1=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=20#244?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Game/StocksTable.tsx | 6 +- src/Game/Main/gameStart.tsx | 505 +++++++++++++++-------------- 2 files changed, 259 insertions(+), 252 deletions(-) diff --git a/src/Component/Game/StocksTable.tsx b/src/Component/Game/StocksTable.tsx index 6770da9..e390477 100644 --- a/src/Component/Game/StocksTable.tsx +++ b/src/Component/Game/StocksTable.tsx @@ -1,10 +1,6 @@ import React from "react"; import { StocksTableProps } from "../../constants/interface"; -import { - formatPrice, - formatChangeRate, - formatPriceWithYear, -} from "../../util/gameUtil"; +import { formatChangeRate, formatPriceWithYear } from "../../util/gameUtil"; import "./StocksTableStyle.css"; import styled, { keyframes } from "styled-components"; diff --git a/src/Game/Main/gameStart.tsx b/src/Game/Main/gameStart.tsx index 0a7632c..a8fdebe 100644 --- a/src/Game/Main/gameStart.tsx +++ b/src/Game/Main/gameStart.tsx @@ -12,270 +12,281 @@ import { useStock } from "../../store/stockContext"; import Loading from "../Loading/loading"; const GameMain: React.FC = () => { - const { setStockData } = useStock(); - const [isLoadingFinished, setIsLoadingFinished] = useState(false); // skip 로딩 - const [loading, setLoading] = useState(false); // axios 요청 로딩 - const [isFlying, setIsFlying] = useState(false); - const [isGame, setIsGame] = useState(false); - const [nickname, setNickname] = useState(""); - const navigate = useNavigate(); - const [isValidQuantity, setIsValidQuantity] = useState(true); - const [isSkipped, setIsSkipped] = useState(false); + const { setStockData } = useStock(); + const [isLoadingFinished, setIsLoadingFinished] = useState(false); // skip 로딩 + const [loading, setLoading] = useState(false); // axios 요청 로딩 + const [isFlying, setIsFlying] = useState(false); + const [isGame, setIsGame] = useState(false); + const [nickname, setNickname] = useState(""); + const navigate = useNavigate(); + const [isValidQuantity, setIsValidQuantity] = useState(true); + const [isSkipped, setIsSkipped] = useState(false); - const handleRank = () => { - navigate("/game/rank"); - }; + const handleRank = () => { + navigate("/game/rank"); + }; - const handleConfirm = () => { - if (!nickname || !nickname.trim()) { - setIsValidQuantity(false); - setNickname(""); - return; - } + const handleConfirm = () => { + if (!nickname || !nickname.trim()) { + setIsValidQuantity(false); + setNickname(""); + return; + } - // 로딩 시작 - setLoading(true); + // 로딩 시작 + setLoading(true); - axios - .get(`${process.env.REACT_APP_API_URL}/v1/game/start`, { - params: { nickname }, - withCredentials: true, - }) - .then((res) => { - if (res.status == 200) { - const stockData = res.data; - navigate(`/game/play/2014`); - setStockData(stockData); - } else { - swal({ - icon: "error", - title: "닉네임을 입력해주세요!", - }); - setNickname(""); - } - setLoading(false); - }) - .catch((error) => { - console.error("서버 에러 : ", error); - setNickname(""); - setLoading(false); - }); - }; + axios + .get(`${process.env.REACT_APP_API_URL}/v1/game/start`, { + params: { nickname }, + withCredentials: true, + }) + .then((res) => { + if (res.status == 200) { + const stockData = res.data; + navigate(`/game/play/2014`); + setStockData(stockData); + } else { + swal({ + icon: "error", + title: "닉네임을 입력해주세요!", + }); + setNickname(""); + } + setLoading(false); + }) + .catch((error) => { + console.error("서버 에러 : ", error); + setNickname(""); + setLoading(false); + }); + }; - const handleChangeQuantity = (e: React.ChangeEvent) => { - const value = e.target.value; - // setNickname(value); - // 공백을 허용하지 않도록 필터링 - if (value.includes(" ")) { - swal({ - icon: "error", - title: "닉네임에 공백을 포함할 수 없습니다.", - }); - return; - } - setNickname(value); - setIsValidQuantity(true); - }; + const handleChangeQuantity = (e: React.ChangeEvent) => { + const value = e.target.value; + // setNickname(value); + // 공백을 허용하지 않도록 필터링 + if (value.includes(" ")) { + swal({ + icon: "error", + title: "닉네임에 공백을 포함할 수 없습니다.", + }); + return; + } + setNickname(value); + setIsValidQuantity(true); + }; - const handleSkip = () => { - setIsSkipped(true); - setIsLoadingFinished(false); - setIsFlying(false); - setIsGame(true); - }; + const handleSkip = () => { + setIsSkipped(true); + setIsLoadingFinished(false); + setIsFlying(false); + setIsGame(true); + }; - useEffect(() => { - if (!isSkipped) { - const timer = setTimeout(() => { - setIsLoadingFinished(true); + useEffect(() => { + if (!isSkipped) { + const timer = setTimeout(() => { + setIsLoadingFinished(true); - const flyTimer = setTimeout(() => { - setIsFlying(true); + const flyTimer = setTimeout(() => { + setIsFlying(true); - const gameTimer = setTimeout(() => { - setIsGame(true); - }, 2000); + const gameTimer = setTimeout(() => { + setIsGame(true); + }, 2000); - return () => clearTimeout(gameTimer); - }, 2000); + return () => clearTimeout(gameTimer); + }, 2000); - return () => clearTimeout(flyTimer); - }, 5000); + return () => clearTimeout(flyTimer); + }, 5000); - return () => clearTimeout(timer); - } - }, [isSkipped]); + return () => clearTimeout(timer); + } + }, [isSkipped]); - return ( - <> - {loading ? ( + return ( <> - - - ) : ( - <> -
-
-
-
-
- {isLoadingFinished && !isSkipped && ( - - )} - {isGame && ( -
-

- U'STOCK{" "} -

-

- 모의투자 -

-

- {" "} - 게임해볼래? -

-
- )} -
-
-
-
-
-
-
    -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
-
    -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
-
-
-
-
- {/* 스킵 버튼 */} - {!isGame && ( - - )} - {isGame && ( - <> -
-
- - Skrrr  모의 투자 - 게임 - -
- + {loading ? ( + <> + + + ) : ( + <> +
+
+
+
+
+ {isLoadingFinished && !isSkipped && ( + + )} + {isGame && ( +
+

+ U'STOCK{" "} +

+

+ 모의투자 +

+

+ {" "} + 게임해볼래? +

+
+ )} +
+
+
+
+
+
+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+
+
+
+
+ {/* 스킵 버튼 */} + {!isGame && ( + + )} + {isGame && ( + <> +
+
+ + + Skrrr   + + 모의 투자 게임 + +
+ +
+ + +
+ +
+
+
+ + )}
- - -
- -
-
-
- + )} -
- )} - - ); + ); }; export default GameMain; From 88be3fa610c7ed6abe1500d87d17580d44a82d6c Mon Sep 17 00:00:00 2001 From: yzooop Date: Mon, 30 Sep 2024 15:44:09 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Fix:=20=EB=B2=84=ED=8A=BC=20=ED=98=B8?= =?UTF-8?q?=EB=B2=84=ED=95=98=EB=A9=B4=20=EC=BB=A4=EC=84=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#239?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Game/GameTradeSwipe.tsx | 459 +++++++++++++------------- src/Component/Modal/modal.tsx | 268 +++++++-------- src/Game/Tutorial/rule.tsx | 388 +++++++++++----------- src/Pages/game/playPage.tsx | 348 +++++++++---------- 4 files changed, 737 insertions(+), 726 deletions(-) diff --git a/src/Component/Game/GameTradeSwipe.tsx b/src/Component/Game/GameTradeSwipe.tsx index 241b746..59e22bf 100644 --- a/src/Component/Game/GameTradeSwipe.tsx +++ b/src/Component/Game/GameTradeSwipe.tsx @@ -9,270 +9,271 @@ import { formatPrice } from "../../util/gameUtil"; import styled from "styled-components"; 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 [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 { stockData } = useStock(); + 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); + const check = usePortfolioStore((state) => state.check); + const setCheck = usePortfolioStore((state) => state.setCheck); - // 모달 외부 클릭 감지하기 위해 ref 생성 - const modalRef = useRef(null); + // 모달 외부 클릭 감지하기 위해 ref 생성 + const modalRef = useRef(null); - // 모달 외부 클릭 시 닫기 처리 - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - modalRef.current && - !modalRef.current.contains(event.target as Node) - ) { - onClose(); - } - }; + // 모달 외부 클릭 시 닫기 처리 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + modalRef.current && + !modalRef.current.contains(event.target as Node) + ) { + onClose(); + } + }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [onClose]); + + 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]); }; - }, [onClose]); - 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]); - }; + const handleQuantityChange = (direction: "left" | "right") => { + setQuantity((prev) => { + if (direction === "left" && prev > 1) { + return prev - 1; + } else if (direction === "right") { + return prev + 1; + } + return prev; + }); + }; - const handleQuantityChange = (direction: "left" | "right") => { - setQuantity((prev) => { - if (direction === "left" && prev > 1) { - return prev - 1; - } else if (direction === "right") { - return prev + 1; - } - return prev; - }); - }; + const handleTradeConfirm = async () => { + const data = { + stockId: selectedStock?.stockId || 0, + quantity, + acting, + }; - 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" }); + setIsModalOpen(false); + setSelectedStock(stockOptions[0]); + setQuantity(1); + onClose(); + } + } catch (error: any) { + const errorMessage = + error.response?.data.message || "서버 오류가 발생했습니다."; + swal({ + title: "거래 실패", + text: errorMessage, + icon: "error", + }).then(() => { + setIsModalOpen(false); + // 모달을 닫지 않음, 에러 후 닫지 않는 로직 유지 + }); + } }; - 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" }); - setIsModalOpen(false); - setSelectedStock(stockOptions[0]); - setQuantity(1); - onClose(); - } - } catch (error: any) { - const errorMessage = - error.response?.data.message || "서버 오류가 발생했습니다."; - swal({ - title: "거래 실패", - text: errorMessage, - icon: "error", - }).then(() => { - setIsModalOpen(false); - // 모달을 닫지 않음, 에러 후 닫지 않는 로직 유지 - }); - } - }; - - const openTradeConfirmModal = (action: "BUY" | "SELL") => { - setActing(action); - setIsModalOpen(true); - }; + const openTradeConfirmModal = (action: "BUY" | "SELL") => { + setActing(action); + setIsModalOpen(true); + }; - const handleQuantityInputChange = (value: number) => { - setQuantity(value); - }; + 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]); + 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 ( -
- - -
- { - setSelectedStock(stockOptions[0]); - setQuantity(1); - onClose(); - }} - > -
- 주식 거래하기 - handleStockChange("left")} - onRightClick={() => handleStockChange("right")} - currentPrice={currentPrice} // 현재 가격 전달 - /> - handleQuantityChange("left")} - onRightClick={() => handleQuantityChange("right")} - onQuantityChange={handleQuantityInputChange} // 수량 직접 입력 핸들러 전달 - /> -
거래가능금액: {formatPrice(budget)}
-
- 총 합계:{" "} - {currentPrice && quantity - ? formatPrice(currentPrice * quantity) - : 0} -
- - openTradeConfirmModal("SELL")} - disabled={quantity === 0} // 수량이 0이거나 빈 값일 때 비활성화 - > - 팔기 - - openTradeConfirmModal("BUY")} - disabled={quantity === 0} // 수량이 0이거나 빈 값일 때 비활성화 - > - 사기 - - -
-
- setIsModalOpen(false)} - stock={selectedStock?.name || ""} - quantity={quantity} - acting={acting!} - /> -
- ); + return ( +
+ + +
+ { + setSelectedStock(stockOptions[0]); + setQuantity(1); + onClose(); + }} + > +
+ 주식 거래하기 + handleStockChange("left")} + onRightClick={() => handleStockChange("right")} + currentPrice={currentPrice} // 현재 가격 전달 + /> + handleQuantityChange("left")} + onRightClick={() => handleQuantityChange("right")} + onQuantityChange={handleQuantityInputChange} // 수량 직접 입력 핸들러 전달 + /> +
거래가능금액: {formatPrice(budget)}
+
+ 총 합계:{" "} + {currentPrice && quantity + ? formatPrice(currentPrice * quantity) + : 0} +
+ + openTradeConfirmModal("SELL")} + disabled={quantity === 0} // 수량이 0이거나 빈 값일 때 비활성화 + > + 팔기 + + openTradeConfirmModal("BUY")} + disabled={quantity === 0} // 수량이 0이거나 빈 값일 때 비활성화 + > + 사기 + + +
+
+ setIsModalOpen(false)} + stock={selectedStock?.name || ""} + quantity={quantity} + acting={acting!} + /> +
+ ); }; 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; + 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; + 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; + 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; + 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; + 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: ${({ disabled }) => - disabled - ? "radial-gradient(circle, #cccccc 0%, #e0e0e0 100%)" - : "radial-gradient(circle, #4834d4 0%, #686de0 100%)"}; - color: #fff; - border: none; - font-size: 14px; + width: 100px; + height: 40px; + border-radius: 15px; + background: ${({ disabled }) => + disabled + ? "radial-gradient(circle, #cccccc 0%, #e0e0e0 100%)" + : "radial-gradient(circle, #4834d4 0%, #686de0 100%)"}; + color: #fff; + border: none; + font-size: 14px; + cursor: pointer; `; export default GameTradeSwipe; diff --git a/src/Component/Modal/modal.tsx b/src/Component/Modal/modal.tsx index af418a0..673d834 100644 --- a/src/Component/Modal/modal.tsx +++ b/src/Component/Modal/modal.tsx @@ -5,151 +5,151 @@ import { ModalProps } from "../../constants/interface"; import ReactModal, { Styles } from "react-modal"; const getModalStyles = (isSmallScreen: boolean): Styles => { - return { - overlay: { - backgroundColor: "rgb(255 255 255 / 60%)", - width: isSmallScreen ? "100%" : "90%", - height: "100%", - zIndex: 100, - top: "0", - left: "0", - }, - content: { - width: isSmallScreen ? "85%" : "400px", - height: isSmallScreen ? "400px" : "450px", - zIndex: "11", - position: "fixed" as const, - scrollbarWidth: "none", - marginLeft: "-15px", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - overflow: "auto", - padding: isSmallScreen ? "20px" : "40px", - }, - }; + return { + overlay: { + backgroundColor: "rgb(255 255 255 / 60%)", + width: isSmallScreen ? "100%" : "90%", + height: "100%", + zIndex: 100, + top: "0", + left: "0", + }, + content: { + width: isSmallScreen ? "85%" : "400px", + height: isSmallScreen ? "400px" : "450px", + zIndex: "11", + position: "fixed" as const, + scrollbarWidth: "none", + marginLeft: "-15px", + top: "50%", + left: "50%", + transform: "translate(-46%, -50%)", + overflow: "auto", + padding: isSmallScreen ? "20px" : "40px", + }, + }; }; const ModalOpen: React.FC = ({ - title, - isOpen, - onRequestClose, - onConfirm, - confirmLabel = "확인", - cancelLabel = "취소", - showConfirmButton = true, - showCancelButton = true, - showOneConfirmBtn = false, - text, - children, - icon, + title, + isOpen, + onRequestClose, + onConfirm, + confirmLabel = "확인", + cancelLabel = "취소", + showConfirmButton = true, + showCancelButton = true, + showOneConfirmBtn = false, + text, + children, + icon, }) => { - const [isSmallScreen, setIsSamllScreen] = useState( - window.matchMedia("(max-width: 768px)").matches - ); + const [isSmallScreen, setIsSamllScreen] = useState( + window.matchMedia("(max-width: 768px)").matches + ); - useEffect(() => { - const handleResize = () => { - setIsSamllScreen(window.matchMedia("(max-width: 768px)").matches); - }; + useEffect(() => { + const handleResize = () => { + setIsSamllScreen(window.matchMedia("(max-width: 768px)").matches); + }; - window.addEventListener("resize", handleResize); + window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); - useEffect(() => { - if (isOpen) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = "auto"; - } - return () => { - document.body.style.overflow = "auto"; - }; - }, [isOpen]); + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + return () => { + document.body.style.overflow = "auto"; + }; + }, [isOpen]); - const modalStyles = getModalStyles(isSmallScreen); + const modalStyles = getModalStyles(isSmallScreen); - return ( - - - {icon ? ( -
- -
- ) : ( - <> - {title &&

{title}

} - × - - )} -
- {children} - {showOneConfirmBtn ? ( - // 하나의 텍스트 버튼만 있는 모달 -
- {onConfirm && ( - - )} -
- ) : ( - // 확인/취소 버튼이 있는 모달 -
- {showCancelButton && ( -
- -
- )} - {showConfirmButton && onConfirm && ( - - )} -
- )} -
- ); + + {icon ? ( +
+ +
+ ) : ( + <> + {title &&

{title}

} + × + + )} +
+ {children} + {showOneConfirmBtn ? ( + // 하나의 텍스트 버튼만 있는 모달 +
+ {onConfirm && ( + + )} +
+ ) : ( + // 확인/취소 버튼이 있는 모달 +
+ {showCancelButton && ( +
+ +
+ )} + {showConfirmButton && onConfirm && ( + + )} +
+ )} + + ); }; export default ModalOpen; diff --git a/src/Game/Tutorial/rule.tsx b/src/Game/Tutorial/rule.tsx index 5a308cb..26bb623 100644 --- a/src/Game/Tutorial/rule.tsx +++ b/src/Game/Tutorial/rule.tsx @@ -5,216 +5,222 @@ import Role from "../../img/role.png"; import styled from "styled-components"; const Overlay = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: rgba(163, 152, 152, 0.8); /* 반투명한 검정색 배경 */ - z-index: 10; /* 모달 뒤에 위치, 모달보다 낮은 z-index */ + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(163, 152, 152, 0.8); /* 반투명한 검정색 배경 */ + z-index: 10; /* 모달 뒤에 위치, 모달보다 낮은 z-index */ `; const Container = styled.div` - /* width: 400px; */ - height: 850px; - z-index: 11; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - overflow: auto; - display: flex; - justify-content: center; /* 수평 중앙 정렬 */ - align-items: center; /* 수직 중앙 정렬 */ - padding: 20px; - box-sizing: border-box; /* 패딩 포함 계산 */ + /* width: 400px; */ + height: 850px; + z-index: 11; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + overflow: auto; + display: flex; + justify-content: center; /* 수평 중앙 정렬 */ + align-items: center; /* 수직 중앙 정렬 */ + padding: 20px; + box-sizing: border-box; /* 패딩 포함 계산 */ `; const Box = styled.div` - background-color: white; - border-radius: 10px; - padding: 20px; - display: flex; - flex-direction: column; - justify-content: center; /* 수직 중앙 정렬 */ - align-items: center; /* 수평 중앙 정렬 */ + background-color: white; + border-radius: 10px; + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; /* 수직 중앙 정렬 */ + align-items: center; /* 수평 중앙 정렬 */ `; const ModalContainer = styled.div` - display: flex; - margin-top: 1rem; - margin-left: 390px; - flex-direction: row; - width: 100%; - align-items: center; + display: flex; + margin-top: 1rem; + margin-left: 390px; + flex-direction: row; + width: 100%; + align-items: center; `; const Button = styled.button` - background: none; - border: none; + background: none; + border: none; + cursor: pointer; `; const RuleModal = () => { - const [onModal, setOnModal] = useState(false); + const [onModal, setOnModal] = useState(false); - // 모달 외부를 클릭하면 모달을 닫기 위한 함수 - const handleClickOutside = (e: React.MouseEvent) => { - // e.target이 모달 콘텐츠가 아닌 경우에만 모달 닫기 - if (e.target === e.currentTarget) { - setOnModal(false); - } - }; + // 모달 외부를 클릭하면 모달을 닫기 위한 함수 + const handleClickOutside = (e: React.MouseEvent) => { + // e.target이 모달 콘텐츠가 아닌 경우에만 모달 닫기 + if (e.target === e.currentTarget) { + setOnModal(false); + } + }; - return ( - setOnModal(true)}> -

- 룰 설명 -

- - {onModal && ( - - - e.stopPropagation()}> -
setOnModal(true)}> +

- -

-
- -

- 2014년부터 게임이 시작됩니다. -
게임 한 회당 1년단위로 진행됩니다! -

-

- 게임 시작 시 총 50만원을 지급받습니다.
- 50만원으로 자신의 자산을 불려보세요! -

-

- 더보기를 누르면 자신이 보유한
- 종목 정보/실시간 랭킹을 확인 할 수 있습니다.{" "} -

-

- 전년도 대비 수익금과 수익률을 종목별로 확인할 수 있어요. -

-

- 매 회 정보거래소에서 원하는 종목의 뉴스를 구매해서 확인할 수 - 있습니다. -
정보는 매 회 단계별 1개씩, 총 3번만 확인할 수 있습니다! -

-

- 원하는 종목을 사기/팔기 할 수 있습니다. -

-

- 넘어가기 버튼을 통해 다음 턴으로 넘어가세요. -

-
-
-
-
- )} -
- ); + fontSize: "15px", + marginRight: "0.6rem", + marginTop: "1rem", + }} + > + 룰 설명 +

+ + {onModal && ( + + + e.stopPropagation()}> +
+ +
+
+ +

+ 2014년부터 게임이 시작됩니다. +
게임 한 회당 1년단위로 진행됩니다! +

+

+ 게임 시작 시 총 50만원을 지급받습니다.{" "} +
+ 50만원으로 자신의 자산을 불려보세요! +

+

+ 더보기를 누르면 자신이 보유한
+ 종목 정보/실시간 랭킹을 확인 할 수 있습니다.{" "} +

+

+ 전년도 대비 수익금과 수익률을 종목별로 + 확인할 수 있어요. +

+

+ 매 회 정보거래소에서 원하는 종목의 뉴스를 + 구매해서 확인할 수 있습니다. +
정보는 매 회 단계별 1개씩, 총 3번만 + 확인할 수 있습니다! +

+

+ 원하는 종목을 사기/팔기 할 수 있습니다. +

+

+ 넘어가기 버튼을 통해 다음 턴으로 넘어가세요. +

+
+
+
+
+ )} + + ); }; export default RuleModal; diff --git a/src/Pages/game/playPage.tsx b/src/Pages/game/playPage.tsx index 73beac1..63cd080 100644 --- a/src/Pages/game/playPage.tsx +++ b/src/Pages/game/playPage.tsx @@ -20,155 +20,159 @@ 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(); - 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 [loading, setLoading] = useState(false); - - const checkYear = useGameStore((state) => state.checkYear); - const check = usePortfolioStore((state) => state.check); - const setCheck = usePortfolioStore((state) => state.setCheck); - - 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") { - 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 [loading, setLoading] = useState(false); + + const checkYear = useGameStore((state) => state.checkYear); + const check = usePortfolioStore((state) => state.check); + const setCheck = usePortfolioStore((state) => state.setCheck); + + 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") { + 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 ? ( -
- -

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

-
- ) : ( - - - -
- {/*
*/} - {/*

+ {yearNumber !== checkYear ? ( +

+ +

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

+
+ ) : ( + + + +
+ {/*
*/} + {/*

{ > 게임 진행도

*/} - {/*
*/} - -
- - - - - - -
- )} - - ); + {/*
*/} + +
+ + + + + + +
+ )} + + ); }; export default PlayPage; From ae72fa1f4df141c68190d8cff9ee89db28bc6cd5 Mon Sep 17 00:00:00 2001 From: yzooop Date: Mon, 30 Sep 2024 20:24:32 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Fix:=20=ED=95=B4=EB=8B=B9=20=EC=A2=85?= =?UTF-8?q?=EB=AA=A9=20=EB=B3=B4=EC=9C=A0=EC=88=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#234?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Game/GameMoney.tsx | 274 +++++++++++++------------- src/Component/Game/GameTradeSwipe.tsx | 149 +++++++++----- src/Component/Game/TradeChoice.tsx | 183 +++++++++++------ src/store/useSwipeStore.tsx | 12 ++ 4 files changed, 381 insertions(+), 237 deletions(-) create mode 100644 src/store/useSwipeStore.tsx diff --git a/src/Component/Game/GameMoney.tsx b/src/Component/Game/GameMoney.tsx index f9cef8f..32fe91e 100644 --- a/src/Component/Game/GameMoney.tsx +++ b/src/Component/Game/GameMoney.tsx @@ -7,164 +7,166 @@ import { usePortfolioStore } from "../../store/usePortfolioStore"; import { useGameStore } from "../../store/useGameStore"; import { useNavigate } from "react-router-dom"; 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(false); - - const check = usePortfolioStore((state) => state.check); - - // 페이지 유효성 검사 - const setCheckYear = useGameStore((state) => state.setCheckYear); - - 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]); - - 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}%) -
-
- - - - - {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(false); + const { holdingList, setHoldingList } = useSwipeStore(); // 내가 산거 + + const check = usePortfolioStore((state) => state.check); + + // 페이지 유효성 검사 + const setCheckYear = useGameStore((state) => state.setCheckYear); + + 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]); + + 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}%) +
+
+ + + + + {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: 118px; - right: 20px; + position: absolute; + top: 118px; + 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/GameTradeSwipe.tsx b/src/Component/Game/GameTradeSwipe.tsx index 59e22bf..06a4a23 100644 --- a/src/Component/Game/GameTradeSwipe.tsx +++ b/src/Component/Game/GameTradeSwipe.tsx @@ -7,6 +7,7 @@ import { usePortfolioStore } from "../../store/usePortfolioStore"; import { useStock } from "../../store/stockContext"; import { formatPrice } from "../../util/gameUtil"; import styled from "styled-components"; +import { useSwipeStore } from "../../store/useSwipeStore"; interface GameTradeSwipeProps { onClose: () => void; @@ -26,7 +27,7 @@ const GameTradeSwipe = ({ stockId: number; name: string; } | null>(null); - const [quantity, setQuantity] = useState(1); + const [quantity, setQuantity] = useState(0); const [acting, setActing] = useState<"BUY" | "SELL">(); const [isModalOpen, setIsModalOpen] = useState(false); const [stockOptions, setStockOptions] = useState< @@ -36,6 +37,7 @@ const GameTradeSwipe = ({ const check = usePortfolioStore((state) => state.check); const setCheck = usePortfolioStore((state) => state.setCheck); + const { holdingList } = useSwipeStore(); // 모달 외부 클릭 감지하기 위해 ref 생성 const modalRef = useRef(null); @@ -67,6 +69,16 @@ const GameTradeSwipe = ({ ? (currentIndex - 1 + stockOptions.length) % stockOptions.length : (currentIndex + 1) % stockOptions.length; setSelectedStock(stockOptions[newIndex]); + + // 내가 가지고 있는 주식이면, 수량 업데이트 + const holdingStock = holdingList.find( + (holding) => holding.stockId === stockOptions[newIndex].stockId + ); + if (holdingStock) { + setQuantity(holdingStock.quantity); + } else { + setQuantity(0); + } }; const handleQuantityChange = (direction: "left" | "right") => { @@ -98,7 +110,7 @@ const GameTradeSwipe = ({ swal({ title: "거래 성공", icon: "success" }); setIsModalOpen(false); setSelectedStock(stockOptions[0]); - setQuantity(1); + setQuantity(0); onClose(); } } catch (error: any) { @@ -124,58 +136,97 @@ const GameTradeSwipe = ({ setQuantity(value); }; + // 최대구매수량 계산 함수 + const handleMaxPurchaseQuantity = () => { + if (currentPrice && budget) { + const maxQuantity = Math.floor(budget / currentPrice); + setQuantity(maxQuantity > 0 ? maxQuantity : 0); + } + }; + + // 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]); useEffect(() => { - if (stockData) { + if (stockData && stockData.length > 0) { 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); + setCurrentPrice(stockData[0].current || 0); // 초기값을 0으로 설정 + } else if (selectedStock) { + // 선택한 종목에 맞는 가격을 설정 + const selectedStockData = stockData.find( + (stock) => stock.stockId === selectedStock.stockId + ); + setCurrentPrice(selectedStockData?.current || 0); // undefined 방지 } } - if (selectedStock && stockData) { - const selectedStockData = stockData.find( - (stock) => stock.stockId === selectedStock.stockId - ); - setCurrentPrice(selectedStockData?.current || null); - } }, [stockData, selectedStock]); return (
-
+
{ setSelectedStock(stockOptions[0]); - setQuantity(1); + setQuantity(0); onClose(); }} - > + > + X + + + {/* 주식 거래하기 */} + handleStockChange("left")} + onRightClick={() => handleStockChange("right")} + currentPrice={currentPrice || 0} // undefined 방지 + handleMaxPurchaseQuantity={ + handleMaxPurchaseQuantity + } + /> + handleQuantityChange("left")} + onRightClick={() => handleQuantityChange("right")} + onQuantityChange={handleQuantityInputChange} // 수량 직접 입력 핸들러 전달 + currentPrice={currentPrice || 0} // undefined 방지 + handleMaxPurchaseQuantity={ + handleMaxPurchaseQuantity + } + />
- 주식 거래하기 - handleStockChange("left")} - onRightClick={() => handleStockChange("right")} - currentPrice={currentPrice} // 현재 가격 전달 - /> - handleQuantityChange("left")} - onRightClick={() => handleQuantityChange("right")} - onQuantityChange={handleQuantityInputChange} // 수량 직접 입력 핸들러 전달 - />
거래가능금액: {formatPrice(budget)}
총 합계:{" "} @@ -183,6 +234,7 @@ const GameTradeSwipe = ({ ? formatPrice(currentPrice * quantity) : 0}
+ openTradeConfirmModal("SELL")} @@ -216,9 +268,9 @@ const SwipeModal = styled.div<{ isOpen: boolean }>` position: fixed; left: 50%; bottom: ${(props) => (props.isOpen ? "0" : "-100%")}; - max-height: ${(props) => (props.isOpen ? "70vh" : "0")}; - height: 70vh; - max-width: 440px; + max-height: ${(props) => (props.isOpen ? "65vh" : "0")}; + height: 65vh; + max-width: 426px; box-shadow: 0px -2px 10px rgba(0, 0, 0, 0.1); transition: bottom 0.7s ease, max-height 0.7s ease; z-index: 1; @@ -227,6 +279,8 @@ const SwipeModal = styled.div<{ isOpen: boolean }>` border-top-right-radius: 20px; transform: translateX(-50%); background-color: #f7f7f7; + display: flex; + justify-content: center; `; const SwipeContainer = styled.div` @@ -239,20 +293,23 @@ const SwipeContainer = styled.div` `; const CloseButton = styled.button` - width: 100px; - height: 10px; - border-radius: 10px; - border: none; - background-color: #dfdfdf; + width: 30px; + height: 30px; cursor: pointer; + position: absolute; + font-size: 20px; + top: 0; + right: 0; + border: none; + background-color: transparent; `; -const Title = styled.span` - font-size: 28px; - font-weight: 700; - color: #615efc; - margin-top: 30px; -`; +// const Title = styled.span` +// font-size: 25px; +// font-weight: 700; +// color: #615efc; +// margin-top: 30px; +// `; const TradeButtonGroup = styled.div` display: flex; diff --git a/src/Component/Game/TradeChoice.tsx b/src/Component/Game/TradeChoice.tsx index fc7af19..c30b4ac 100644 --- a/src/Component/Game/TradeChoice.tsx +++ b/src/Component/Game/TradeChoice.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { formatPrice } from "../../util/gameUtil"; +import { useSwipeStore } from "../../store/useSwipeStore"; interface TradeChoiceProps { title: string; @@ -11,6 +12,7 @@ interface TradeChoiceProps { onRightClick: () => void; currentPrice?: number | null; onQuantityChange?: (value: number) => void; // 수량 변경 핸들러 추가 + handleMaxPurchaseQuantity: () => void; } const TradeChoice = ({ @@ -22,13 +24,16 @@ const TradeChoice = ({ onRightClick, currentPrice, onQuantityChange, // 수량 변경 핸들러 + handleMaxPurchaseQuantity, }: TradeChoiceProps) => { const [isSelected, setIsSelected] = useState(false); const choiceSectionRef = useRef(null); + const { holdingList } = useSwipeStore(); const handleClick = () => { setIsSelected(true); }; + console.log(currentPrice); const handleOutsideClick = (e: MouseEvent) => { if ( @@ -62,83 +67,133 @@ const TradeChoice = ({ return ( - {title} - + {/* 종목 부분 */} {title === "종목" && ( -
- +
- - {choiceLeft} - - - {selectedOption} - - 종목 선택 +
+
+ - {choiceRight} - - - {/* 가격 정보 표시 */} - {currentPrice !== undefined && ( - - {currentPrice !== null - ? `${formatPrice(currentPrice)}원` - : "가격 정보 없음"} - - )} -
+ + {choiceLeft} + + + {selectedOption} + {currentPrice && ( + + {formatPrice(currentPrice)} 원 + + )} + + + + {choiceRight} + +
+ + {/* 가격 정보 표시 */} + {/* {currentPrice ? ( + + {formatPrice(currentPrice)}원 + + ) : ( +
가격 정보 없음
// 혹은 기본값으로 처리 + )} */} +
+ )} + {/* 수량 부분 */} {title === "수량" && ( - - - {choiceLeft} - - - +
- {choiceRight} - - + 수량 선택 + + 보유수량 : {selectedOption}주 + +
+
+ + + {choiceLeft} + + + + {choiceRight} + + +
+
+ + 최대로 구매하기 + +
+ )}
); }; - const TradeChoiceContainer = styled.div` display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; - margin: 30px 0; + margin-bottom: 40px; `; const Title = styled.h2<{ isSelected: boolean }>` font-size: 15px; color: ${(props) => (props.isSelected ? "#615EFC" : "#656565")}; - margin-left: -200px; margin-bottom: 8px; `; @@ -154,6 +209,7 @@ const ChoiceSection = styled.div<{ isSelected: boolean }>` gap: 20px; border: ${(props) => (props.isSelected ? "3px solid #615EFC" : "none")}; font-size: 13px; + position: relative; `; const ChoiceButton = styled.div<{ isSelected: boolean }>` @@ -187,9 +243,26 @@ const SelectedOption = styled.h3<{ isSelected: boolean }>` `; const CurrentPrice = styled.div<{ isSelected: boolean }>` - margin-top: -25px; + margin-top: 5px; font-size: 13px; color: ${(props) => (props.isSelected ? "#615EFC" : "#656565")}; `; +const CurrentQuantity = styled.div<{ isSelected: boolean }>` + font-size: 13px; + color: ${(props) => (props.isSelected ? "#615EFC" : "#656565")}; +`; + +const MaxPurchaseBtn = styled.button<{ isSelected: boolean }>` + width: 110px; + height: 25px; + cursor: pointer; + border-radius: 10px; + margin-top: 10px; + border: ${(props) => + props.isSelected ? "2.5px solid #615EFC" : "1px solid #f0f0f0"}; + background-color: ${(props) => (props.isSelected ? "#615EFC" : "#f0f0f0")}; + color: ${(props) => (props.isSelected ? "white" : "black")}; +`; + export default TradeChoice; diff --git a/src/store/useSwipeStore.tsx b/src/store/useSwipeStore.tsx new file mode 100644 index 0000000..00f2439 --- /dev/null +++ b/src/store/useSwipeStore.tsx @@ -0,0 +1,12 @@ +import create from "zustand"; +import { holding } from "../constants/interface"; + +interface SwipeStore { + holdingList: holding[]; + setHoldingList: (list: holding[]) => void; +} + +export const useSwipeStore = create((set) => ({ + holdingList: [], + setHoldingList: (list: holding[]) => set({ holdingList: list }), +}));