From f1608c3d84e5e7980e8b1cbeffd8592d821ebd72 Mon Sep 17 00:00:00 2001 From: novice1993 Date: Tue, 3 Oct 2023 01:35:14 +0900 Subject: [PATCH] =?UTF-8?q?[Design]=20=EC=B0=BD=20=ED=8C=9D=EC=97=85/?= =?UTF-8?q?=EC=A0=84=ED=99=98=20=EC=8B=9C=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=ED=9A=A8=EA=B3=BC=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인/프로필 창 팝업 및 좌측 창 전환 시 애니메이션 효과 적용 Issues #122 --- .../src/components/EntireList/EntireList.tsx | 59 ++-- .../components/HoldingList/HoldingList.tsx | 67 ++-- client/src/components/Logins/EmailLogin.tsx | 31 +- client/src/components/Logins/OAuthLogin.tsx | 31 +- .../MarketComponents/MarketKospiChart.tsx | 4 +- client/src/components/Profile/CashModal.tsx | 315 +++++++++--------- .../components/Profile/memberInfoModal.tsx | 81 +++-- .../Profile/memberWithdrawalModal.tsx | 150 ++++----- .../src/components/Signups/EmailCertify.tsx | 39 ++- client/src/components/Signups/EmailSignup.tsx | 35 +- client/src/components/Signups/Guide.tsx | 15 +- client/src/components/Signups/Password.tsx | 56 ++-- client/src/components/Signups/Welcome.tsx | 31 +- .../StockOrderSection/OrderResult.tsx | 7 +- client/src/components/watchlist/WatchList.tsx | 45 +-- 15 files changed, 488 insertions(+), 478 deletions(-) diff --git a/client/src/components/EntireList/EntireList.tsx b/client/src/components/EntireList/EntireList.tsx index 0fcf056..aa8957b 100644 --- a/client/src/components/EntireList/EntireList.tsx +++ b/client/src/components/EntireList/EntireList.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; import Header from "./Header"; import StockItem from "./StockItem"; import useCompanyData from "../../hooks/useCompanyData"; @@ -7,19 +8,6 @@ import { useSelector } from "react-redux"; // 👈 추가 import { StateProps } from "../../models/stateProps"; // 👈 추가 import useGetCash from "../../hooks/useGetCash"; -/* - 🔴 수정사항 - 1) 불필요한 컴포넌트 사용 : Divider 컴포넌트 - -> 구분선 (border) 설정위해 생성한 것으로 보이나, 상위 컴포넌트 너비 설정으로 해결 가능 (불필요하여 주석처리) - -> 삭제하는 게 좋을 것 같음 - - 2) 현금 보유량 컴포넌트 조건부 렌더링 변경 (로그인 필요한 서비스 X -> 로그인 해야 화면에 나타나도록) - - 3) header 컨테이너 부분 height 43px로 고정 - - 4) 금액에 회계 단위 (toLocaleString) 적용 -> 이를 위해 useEffect, useState 활용 - */ - const holdingAmountText = "보유 현금"; const amountUnit = "원"; @@ -51,29 +39,28 @@ const EntireList: React.FC = ({ currentListType, onChangeListTy // 🔴 return ( - - -
- - {/* */} - - {/* {isLogin == 0 ? (로그인이 필요한 서비스 입니다.) : (현금 보유량: {holdingsAmount}원)} */} - - {isLogin === 1 && ( - <> -
{holdingAmountText}
-
- {holdingCash} {amountUnit} -
- - )} -
-
- {/* */} - - {isLoading ?
: isError ?
Error fetching data
: companiesList.map((company) => )} -
- + + + +
+ + + + {isLogin === 1 && ( + <> +
{holdingAmountText}
+
+ {holdingCash} {amountUnit} +
+ + )} +
+
+ + {isLoading ?
: isError ?
Error fetching data
: companiesList.map((company) => )} +
+ + ); }; diff --git a/client/src/components/HoldingList/HoldingList.tsx b/client/src/components/HoldingList/HoldingList.tsx index 18d03ea..76c4fa4 100644 --- a/client/src/components/HoldingList/HoldingList.tsx +++ b/client/src/components/HoldingList/HoldingList.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; import Header from "./Header"; import StockItem from "./StockItem"; import useGetStockHolds from "../../hooks/useGetStockholds"; @@ -38,38 +39,40 @@ const HoldingList: React.FC = ({ currentListType, onChangeListTy }; return ( - - -
- - {/* */} - - -
{evalutationProfitText}
-
- {totalEvaluationProfit.toLocaleString()} {profitUnit} -
-
-
- {/* */} - - {isLogin === 0 ? ( - - ) : isLoading || isCompanyDataLoading ? ( -
- ) : isError || isCompanyDataError ? ( -
Error fetching data
- ) : ( - Array.isArray(stockHolds) && - stockHolds.length > 0 && // 여기에 조건을 추가합니다 - stockHolds.map((stockHold: StockItemProps["stockData"]) => { - const matchedCompany = companyData ? companyData.find((company) => company.companyId === stockHold.companyId) : undefined; - - return matchedCompany ? : null; - }) - )} -
- + + + +
+ + {/* */} + + +
{evalutationProfitText}
+
+ {totalEvaluationProfit.toLocaleString()} {profitUnit} +
+
+
+ {/* */} + + {isLogin === 0 ? ( + + ) : isLoading || isCompanyDataLoading ? ( +
+ ) : isError || isCompanyDataError ? ( +
Error fetching data
+ ) : ( + Array.isArray(stockHolds) && + stockHolds.length > 0 && // 여기에 조건을 추가합니다 + stockHolds.map((stockHold: StockItemProps["stockData"]) => { + const matchedCompany = companyData ? companyData.find((company) => company.companyId === stockHold.companyId) : undefined; + + return matchedCompany ? : null; + }) + )} +
+ + ); }; diff --git a/client/src/components/Logins/EmailLogin.tsx b/client/src/components/Logins/EmailLogin.tsx index a62ac21..4e8fc9d 100644 --- a/client/src/components/Logins/EmailLogin.tsx +++ b/client/src/components/Logins/EmailLogin.tsx @@ -1,6 +1,7 @@ import axios from "axios"; import styled from "styled-components"; import React, { useState } from "react"; +import { motion } from "framer-motion"; import { setLoginState } from "../../reducer/member/loginSlice"; import { useDispatch } from "react-redux"; import setAutoLogoutAlarm from "../../utils/setAutoLogoutAlarm"; @@ -72,20 +73,22 @@ const EmailLoginModal: React.FC = ({ onClose, onSignup }) return ( - - × - {titleText} - - handleEnterPress(event, "password")} /> - - handleEnterPress(event, "loginButton")} /> - {generalError && {generalError}} - {findPasswordText} - {loginButtonText} - - {noAccountText} {registerButtonText} - - + + + × + {titleText} + + handleEnterPress(event, "password")} /> + + handleEnterPress(event, "loginButton")} /> + {generalError && {generalError}} + {findPasswordText} + {loginButtonText} + + {noAccountText} {registerButtonText} + + + ); }; diff --git a/client/src/components/Logins/OAuthLogin.tsx b/client/src/components/Logins/OAuthLogin.tsx index 606d6bd..6f284e1 100644 --- a/client/src/components/Logins/OAuthLogin.tsx +++ b/client/src/components/Logins/OAuthLogin.tsx @@ -1,8 +1,8 @@ - import React from "react"; import styled from "styled-components"; import GoogleLoginButton from "./GoogleLoginButton"; import KakaoLoginButton from "./KakaoLoginButton"; +import { motion } from "framer-motion"; const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick, onEmailSignupClick }) => { const titleText = "로그인"; @@ -16,17 +16,19 @@ const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick return ( - - × - {titleText} - - - {orText} - - {emailLoginText} - {emailSignupText} - - + + + × + {titleText} + + + {orText} + + {emailLoginText} + {emailSignupText} + + + ); }; @@ -35,12 +37,11 @@ export default OAuthLoginModal; //변수 타입 interface LoginModalProps { - onClose: () => void; onEmailLoginClick: () => void; onEmailSignupClick: () => void; - onWatchListClick?: () => void; // '?'를 사용하여 선택적으로 만듭니다. - onHoldingsClick?: () => void; // '?'를 사용하여 선택적으로 만듭니다. + onWatchListClick?: () => void; // '?'를 사용하여 선택적으로 만듭니다. + onHoldingsClick?: () => void; // '?'를 사용하여 선택적으로 만듭니다. } // 모달창 띄웠을 때 배경 스타일 diff --git a/client/src/components/MarketComponents/MarketKospiChart.tsx b/client/src/components/MarketComponents/MarketKospiChart.tsx index aa33798..b0e21b1 100644 --- a/client/src/components/MarketComponents/MarketKospiChart.tsx +++ b/client/src/components/MarketComponents/MarketKospiChart.tsx @@ -7,7 +7,9 @@ import axios from "axios"; const MarketkospiChart = () => { const [kospiData, setKospiData] = useState([]); - const { data, isLoading, error } = useQuery("kospi", getKospiData, {}); + const { data, isLoading, error } = useQuery("kospi", getKospiData, { + staleTime: Infinity, + }); useEffect(() => { if (data) { diff --git a/client/src/components/Profile/CashModal.tsx b/client/src/components/Profile/CashModal.tsx index 92df8cc..5912600 100644 --- a/client/src/components/Profile/CashModal.tsx +++ b/client/src/components/Profile/CashModal.tsx @@ -1,141 +1,142 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; -import { useDispatch} from 'react-redux'; // <-- Import useSelector -import { useCreateCash, useResetCash } from '../../hooks/useCash'; -import useGetCash from '../../hooks/useGetCash'; -import useGetCashId from '../../hooks/useGetCashId'; -import { setCashId, setMoney } from '../../reducer/cash/cashSlice'; - - +import React, { useState } from "react"; +import styled from "styled-components"; +import { motion } from "framer-motion"; +import { useDispatch } from "react-redux"; // <-- Import useSelector +import { useCreateCash, useResetCash } from "../../hooks/useCash"; +import useGetCash from "../../hooks/useGetCash"; +import useGetCashId from "../../hooks/useGetCashId"; +import { setCashId, setMoney } from "../../reducer/cash/cashSlice"; const CashModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { - - // 상태 및 변수 초기화 - const titleText = "현금생성/재생성"; - const cashCreationPlaceholder = "1백만원~5억원 사이를 입력하세요"; - const createCashButtonText = "현금 생성"; - const cashInputPlaceholder = "1백만원~5억원 사이를 입력하세요"; - const resetButtonText = "현금 재생성"; - // const refreshButtonText ="새로고침"; - - const [errorMessage, setErrorMessage] = useState(null); // 에러 메시지 상태 변수 추가 - - const dispatch = useDispatch(); - - // useGetCash 훅을 사용하여 현금 보유량 가져오기 - const { cashData: holdingsAmount } = useGetCash(); // 👈 useGetCash 훅을 사용하여 현금 보유량 데이터를 가져옵니다. - - // useGetCashId 훅을 사용하여 cashId 가져오기 - const { cashData: cashId, cashError } = useGetCashId(); - - const createCashMutation = useCreateCash(); - const updateCashMutation = useResetCash(); - - const [cashInput, setCashInput] = useState('0'); - const [initialAmount, setInitialAmount] = useState('0'); // 현금 생성을 위한 상태 변수 - - // 현금 생성 및 cashId 전역 저장 함수 - const handleCreateCash = () => { - createCashMutation.mutate(initialAmount, { - onSuccess: () => { - window.location.reload(); - }, - onError: (error) => { - // 에러 처리 - const err = error as Error; - setErrorMessage(err?.message || "현금 생성에 실패했습니다."); - } - }); - }; - - // 입력한 금액으로 현금 리셋 함수 - const handleCashReset = () => { - if (cashId) { - const numericCashAmount = cashInput; - updateCashMutation.mutate({ money: numericCashAmount }, { - onSuccess: () => { - dispatch(setMoney(numericCashAmount)); - dispatch(setCashId(cashId)); - window.location.reload(); - }, - onError: (error) => { - // 에러 처리 - const err = error as Error; - setErrorMessage(err?.message || "현금 재생성에 실패했습니다."); - } - }); - } else { - console.error("cashId is null or not a valid number."); + // 상태 및 변수 초기화 + const titleText = "현금생성/재생성"; + const cashCreationPlaceholder = "1백만원~5억원 사이를 입력하세요"; + const createCashButtonText = "현금 생성"; + const cashInputPlaceholder = "1백만원~5억원 사이를 입력하세요"; + const resetButtonText = "현금 재생성"; + // const refreshButtonText ="새로고침"; + + const [errorMessage, setErrorMessage] = useState(null); // 에러 메시지 상태 변수 추가 + + const dispatch = useDispatch(); + + // useGetCash 훅을 사용하여 현금 보유량 가져오기 + const { cashData: holdingsAmount } = useGetCash(); // 👈 useGetCash 훅을 사용하여 현금 보유량 데이터를 가져옵니다. + + // useGetCashId 훅을 사용하여 cashId 가져오기 + const { cashData: cashId, cashError } = useGetCashId(); + + const createCashMutation = useCreateCash(); + const updateCashMutation = useResetCash(); + + const [cashInput, setCashInput] = useState("0"); + const [initialAmount, setInitialAmount] = useState("0"); // 현금 생성을 위한 상태 변수 + + // 현금 생성 및 cashId 전역 저장 함수 + const handleCreateCash = () => { + createCashMutation.mutate(initialAmount, { + onSuccess: () => { + window.location.reload(); + }, + onError: (error) => { + // 에러 처리 + const err = error as Error; + setErrorMessage(err?.message || "현금 생성에 실패했습니다."); + }, + }); + }; + + // 입력한 금액으로 현금 리셋 함수 + const handleCashReset = () => { + if (cashId) { + const numericCashAmount = cashInput; + updateCashMutation.mutate( + { money: numericCashAmount }, + { + onSuccess: () => { + dispatch(setMoney(numericCashAmount)); + dispatch(setCashId(cashId)); + window.location.reload(); + }, + onError: (error) => { + // 에러 처리 + const err = error as Error; + setErrorMessage(err?.message || "현금 재생성에 실패했습니다."); + }, } - }; - - const validateCashInput = (inputValue: string) => { - const numericValue = parseInt(inputValue.replace(/,/g, ''), 10); - if (isNaN(numericValue) || numericValue < 1000000 || numericValue > 500000000) { - setErrorMessage("100만에서 5억 사이의 수를 입력하세요"); - } else { - setErrorMessage(null); - } - }; + ); + } else { + console.error("cashId is null or not a valid number."); + } + }; + + const validateCashInput = (inputValue: string) => { + const numericValue = parseInt(inputValue.replace(/,/g, ""), 10); + if (isNaN(numericValue) || numericValue < 1000000 || numericValue > 500000000) { + setErrorMessage("100만에서 5억 사이의 수를 입력하세요"); + } else { + setErrorMessage(null); + } + }; - const handleEnterPress = (event: React.KeyboardEvent, action: () => void) => { - if (event.key === 'Enter') { - action(); - } - }; + const handleEnterPress = (event: React.KeyboardEvent, action: () => void) => { + if (event.key === "Enter") { + action(); + } + }; -return ( + return ( + - × - {titleText} - - {/* cashId가 없거나 cashId 요청에 오류가 있으면 현금 생성 UI 표시 */} - {(!cashId || cashError) && ( -
- { - setInitialAmount(e.target.value); - validateCashInput(e.target.value); // 입력 값 변경 시 유효성 검사 - }} - placeholder={cashCreationPlaceholder} - onKeyDown={(event) => handleEnterPress(event, handleCreateCash)} - /> - - {createCashButtonText} -
- )} - - {/* cashId가 있으면 현금 리셋 UI 표시 */} - {cashId && !cashError && ( -
- { - setCashInput(e.target.value); - validateCashInput(e.target.value); // 입력 값 변경 시 유효성 검사 - }} - placeholder={cashInputPlaceholder} - onKeyDown={(event) => handleEnterPress(event, handleCashReset)} - /> - - {resetButtonText} -
- )} + × + {titleText} + {/* cashId가 없거나 cashId 요청에 오류가 있으면 현금 생성 UI 표시 */} + {(!cashId || cashError) && (
- - 현금 보유량: {holdingsAmount}원 - {errorMessage && {errorMessage}} - + { + setInitialAmount(e.target.value); + validateCashInput(e.target.value); // 입력 값 변경 시 유효성 검사 + }} + placeholder={cashCreationPlaceholder} + onKeyDown={(event) => handleEnterPress(event, handleCreateCash)} + /> + + {createCashButtonText}
+ )} + + {/* cashId가 있으면 현금 리셋 UI 표시 */} + {cashId && !cashError && ( +
+ { + setCashInput(e.target.value); + validateCashInput(e.target.value); // 입력 값 변경 시 유효성 검사 + }} + placeholder={cashInputPlaceholder} + onKeyDown={(event) => handleEnterPress(event, handleCashReset)} + /> + + {resetButtonText} +
+ )} + +
+ + 현금 보유량: {holdingsAmount}원{errorMessage && {errorMessage}} + +
+
-); - + ); }; export default CashModal; @@ -159,7 +160,7 @@ const ModalContainer = styled.div` background-color: white; padding: 20px; width: 400px; - height:230px; + height: 230px; border-radius: 10px; display: flex; flex-direction: column; @@ -176,57 +177,57 @@ const CloseButton = styled.button` position: absolute; top: 10px; right: 10px; - background: #FFFFFF; - border-radius:5px; + background: #ffffff; + border-radius: 5px; border: 1px solid lightgray; font-size: 1.5rem; cursor: pointer; `; const StyledButton = styled.button` - padding: 10px 15px; - background-color: white; - color: darkslategray; - border: 1px solid darkslategray; - border-radius: 5px; - margin-bottom:5px; - cursor: pointer; - - //호버 시 회색 - &:hover { - background-color: #f2f2f2; - } + padding: 10px 15px; + background-color: white; + color: darkslategray; + border: 1px solid darkslategray; + border-radius: 5px; + margin-bottom: 5px; + cursor: pointer; + + //호버 시 회색 + &:hover { + background-color: #f2f2f2; + } `; const CashInput = styled.input` - padding: 10px; - border: 1px solid lightgray; - border-radius: 5px; - margin-right: 10px; + padding: 10px; + border: 1px solid lightgray; + border-radius: 5px; + margin-right: 10px; `; const ReceiveButton = styled(StyledButton)``; const CashCreationInput = styled.input` - padding: 10px; - border: 1px solid lightgray; - border-radius: 5px; - margin-right: 10px; + padding: 10px; + border: 1px solid lightgray; + border-radius: 5px; + margin-right: 10px; `; const CreateCashButton = styled(StyledButton)``; const Content = styled.p` - margin: 15px 0; // 간격 조정 - font-size: 1.1rem; // 폰트 크기 증가 - line-height: 1.5; - color: #555; // 색상 변경 - text-align: center; // 텍스트 중앙 정렬 + margin: 15px 0; // 간격 조정 + font-size: 1.1rem; // 폰트 크기 증가 + line-height: 1.5; + color: #555; // 색상 변경 + text-align: center; // 텍스트 중앙 정렬 `; // 에러 메시지 스타일링 const ErrorMessage = styled.div` - color: red; - font-size: 0.8rem; - margin-top: 5px; + color: red; + font-size: 0.8rem; + margin-top: 5px; `; diff --git a/client/src/components/Profile/memberInfoModal.tsx b/client/src/components/Profile/memberInfoModal.tsx index 84b4645..ababce3 100644 --- a/client/src/components/Profile/memberInfoModal.tsx +++ b/client/src/components/Profile/memberInfoModal.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import styled from 'styled-components'; -import { useGetMemberInfo } from '../../hooks/useGetMemberInfo.ts'; +import React from "react"; +import styled from "styled-components"; +import { motion } from "framer-motion"; +import { useGetMemberInfo } from "../../hooks/useGetMemberInfo.ts"; const MemberInfoModal: React.FC = ({ onClose }) => { - // memberId 값을 useGetMemberInfo 훅에 전달하여 회원 정보를 가져옵니다. - const { data: memberInfo } = useGetMemberInfo(); + const { data: memberInfo } = useGetMemberInfo(); const titleText = "회원정보"; const nameText = "이름: "; @@ -15,38 +15,48 @@ const MemberInfoModal: React.FC = ({ onClose }) => { const formatDate = (dateString: string) => { const date = new Date(dateString); const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 1을 더해줍니다. - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 1을 더해줍니다. + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}`; -} + }; return ( - - - × - {titleText} - {memberInfo ? ( -
- {nameText}{memberInfo.name} - {emailText}{memberInfo.email} - {createdAtText}{formatDate(memberInfo.createdAt)} -
- ) : ( - Data not available - )} -
-
+ + + + × + {titleText} + {memberInfo ? ( +
+ + {nameText} + {memberInfo.name} + + + {emailText} + {memberInfo.email} + + + {createdAtText} + {formatDate(memberInfo.createdAt)} + +
+ ) : ( + Data not available + )} +
+
+
); }; export default MemberInfoModal; - interface MemberInfoModalProps { - onClose: () => void; + onClose: () => void; } // Styled Components Definitions: @@ -68,7 +78,7 @@ const ModalContainer = styled.div` background-color: white; padding: 20px; width: 400px; - height:230px; + height: 230px; border-radius: 10px; display: flex; flex-direction: column; @@ -85,18 +95,17 @@ const CloseButton = styled.button` position: absolute; top: 10px; right: 10px; - background: #FFFFFF; - border-radius:5px; + background: #ffffff; + border-radius: 5px; border: 1px solid lightgray; font-size: 1.5rem; cursor: pointer; `; const Content = styled.p` - margin: 15px 0; // 간격 조정 - font-size: 1.1rem; // 폰트 크기 증가 - line-height: 1.5; - color: #555; // 색상 변경 - text-align: center; // 텍스트 중앙 정렬 + margin: 15px 0; // 간격 조정 + font-size: 1.1rem; // 폰트 크기 증가 + line-height: 1.5; + color: #555; // 색상 변경 + text-align: center; // 텍스트 중앙 정렬 `; - diff --git a/client/src/components/Profile/memberWithdrawalModal.tsx b/client/src/components/Profile/memberWithdrawalModal.tsx index 6c0a3e6..495e425 100644 --- a/client/src/components/Profile/memberWithdrawalModal.tsx +++ b/client/src/components/Profile/memberWithdrawalModal.tsx @@ -1,61 +1,56 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; -import { useDeleteMember } from '../../hooks/useDeleteMembers'; - +import React, { useState } from "react"; +import styled from "styled-components"; +import { motion } from "framer-motion"; +import { useDeleteMember } from "../../hooks/useDeleteMembers"; const MemberWithdrawalModal: React.FC = ({ onClose, onErrorVisibility }) => { + const [inputString, setInputString] = useState(""); + const [errorMsg, setErrorMsg] = useState(null); + const deleteMemberMutation = useDeleteMember(); + + const withdrawalTitle = "탈퇴하시겠습니까?"; + const inputStringLabel = "다음 문자열을 입력해주세요: Codestates"; + const incorrectStringMsg = "문자열이 일치하지 않습니다!"; + const withdrawalButtonText = "회원탈퇴"; + + const handleWithdrawal = () => { + if (inputString === "Codestates") { + deleteMemberMutation.mutate(); + } else { + setErrorMsg(incorrectStringMsg); + onErrorVisibility(true); // 에러가 발생했을 때 높이를 280px로 조절 + } + }; - const [inputString, setInputString] = useState(''); - const [errorMsg, setErrorMsg] = useState(null); - const deleteMemberMutation = useDeleteMember(); - - - const withdrawalTitle = "탈퇴하시겠습니까?"; - const inputStringLabel = "다음 문자열을 입력해주세요: Codestates"; - const incorrectStringMsg = "문자열이 일치하지 않습니다!"; - const withdrawalButtonText = "회원탈퇴"; - - const handleWithdrawal = () => { - if (inputString === "Codestates") { - deleteMemberMutation.mutate(); - } else { - setErrorMsg(incorrectStringMsg); - onErrorVisibility(true); // 에러가 발생했을 때 높이를 280px로 조절 - } - }; - - const handleEnterPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - handleWithdrawal(); - } - }; - - return ( - - - × - {withdrawalTitle} - - setInputString(e.target.value)} onKeyDown={handleEnterPress} /> - - {errorMsg && {errorMsg}} - - {withdrawalButtonText} - - - ); + const handleEnterPress = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + handleWithdrawal(); + } + }; + + return ( + + + + × + {withdrawalTitle} + + setInputString(e.target.value)} onKeyDown={handleEnterPress} /> + {errorMsg && {errorMsg}} + {withdrawalButtonText} + + + + ); }; export default MemberWithdrawalModal; - interface MemberWithdrawalModalProps { - onClose: () => void; - onErrorVisibility: (visibility: boolean) => void; - + onClose: () => void; + onErrorVisibility: (visibility: boolean) => void; } - // Styled Components Definitions: const ModalBackground = styled.div` @@ -76,49 +71,48 @@ const ModalContainer = styled.div` background-color: white; padding: 20px; width: 400px; - height:230px; + height: 230px; border-radius: 10px; display: flex; flex-direction: column; align-items: center; `; - const CloseButton = styled.button` position: absolute; top: 10px; right: 10px; - background: #FFFFFF; - border-radius:5px; + background: #ffffff; + border-radius: 5px; border: 1px solid lightgray; font-size: 1.5rem; cursor: pointer; `; const Title = styled.h2` - font-size: 1.6rem; - margin-bottom: 10px; - font-weight: 400; + font-size: 1.6rem; + margin-bottom: 10px; + font-weight: 400; `; const Label = styled.label` - margin: 15px 0; // 간격 조정 - font-size: 1.1rem; // 폰트 크기 증가 - line-height: 1.5; - color: #555; // 색상 변경 - text-align: center; // 텍스트 중앙 정렬 + margin: 15px 0; // 간격 조정 + font-size: 1.1rem; // 폰트 크기 증가 + line-height: 1.5; + color: #555; // 색상 변경 + text-align: center; // 텍스트 중앙 정렬 `; const PasswordInput = styled.input` - width: 100%; - padding: 5px; - border: 1px solid lightgray; - border-radius: 5px; - margin-bottom: 10px; + width: 100%; + padding: 5px; + border: 1px solid lightgray; + border-radius: 5px; + margin-bottom: 10px; `; const MessageWrapper = styled.div` - height: 10px; // 에러 메시지 공간을 좀 더 확보합니다. + height: 10px; // 에러 메시지 공간을 좀 더 확보합니다. width: 100%; display: flex; justify-content: center; @@ -126,20 +120,20 @@ const MessageWrapper = styled.div` `; const WithdrawalButton = styled.button` - padding: 10px 20px; - background-color: darkslategray; - color: white; - border: 1px solid lightslategray; - border-radius: 5px; - cursor: pointer; - //호버 시 밝게 - &:hover { - background-color: rgba(47, 79, 79, 0.8); - } + padding: 10px 20px; + background-color: darkslategray; + color: white; + border: 1px solid lightslategray; + border-radius: 5px; + cursor: pointer; + //호버 시 밝게 + &:hover { + background-color: rgba(47, 79, 79, 0.8); + } `; const ErrorMessage = styled.p` color: red; font-size: 0.8rem; - margin-bottom:5px; -`; \ No newline at end of file + margin-bottom: 5px; +`; diff --git a/client/src/components/Signups/EmailCertify.tsx b/client/src/components/Signups/EmailCertify.tsx index 8deefb0..08df81c 100644 --- a/client/src/components/Signups/EmailCertify.tsx +++ b/client/src/components/Signups/EmailCertify.tsx @@ -1,6 +1,7 @@ // /client/src/components/Signups/EmailCertify.tsx import axios from "axios"; import React, { useState } from "react"; +import { motion } from "framer-motion"; import styled from "styled-components"; // 고정 문자열을 정의 @@ -24,7 +25,7 @@ const EmailVerificationModal: React.FC = ({ onClose // 엔터키를 눌렀을 때의 핸들러 const handleKeyPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { + if (event.key === "Enter") { handleNextStepClick(); } }; @@ -86,23 +87,25 @@ const EmailVerificationModal: React.FC = ({ onClose return ( - - × - {strings.titleText} - - - - - {strings.codeHintText} - - - - - - {showAgreementError && {strings.agreementError}} - {strings.nextStepText} - {errorMessage && {errorMessage}} - + + + × + {strings.titleText} + + + + + {strings.codeHintText} + + + + + + {showAgreementError && {strings.agreementError}} + {strings.nextStepText} + {errorMessage && {errorMessage}} + + ); }; diff --git a/client/src/components/Signups/EmailSignup.tsx b/client/src/components/Signups/EmailSignup.tsx index 549b942..11bc507 100644 --- a/client/src/components/Signups/EmailSignup.tsx +++ b/client/src/components/Signups/EmailSignup.tsx @@ -2,6 +2,7 @@ import axios from "axios"; import styled from "styled-components"; import React, { useState } from "react"; +import { motion } from "framer-motion"; import { useDispatch } from "react-redux"; import { setEmailForVerification } from "../../reducer/member/memberInfoSlice"; @@ -11,7 +12,7 @@ const strings = { emailLabelText: "이메일", requestVerificationText: "이메일 인증요청", invalidEmailText: "유효하지 않은 이메일입니다", - emailSend : "이메일이 전송되었습니다" + emailSend: "이메일이 전송되었습니다", }; const EmailSignupModal: React.FC = ({ onClose, onRequestVerification }) => { @@ -34,7 +35,7 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV // 이메일 입력창에서 엔터키를 눌렀을 때의 핸들러 const handleKeyPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { + if (event.key === "Enter") { handleVerificationRequest(); } }; @@ -45,7 +46,7 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV setIsInvalidEmail(true); return; } - + try { const response = await axios.post( "http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/send", @@ -61,7 +62,7 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV if (response.status === 200) { dispatch(setEmailForVerification(email)); onRequestVerification(email); - setEmailSent(true); // 이 부분 추가 + setEmailSent(true); // 이 부분 추가 } else if (response.status === 400) { setErrorMessage(response.data.message); } else if (response.status === 500) { @@ -83,16 +84,18 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV return ( - - × - {strings.titleText} - - - {emailSent && {strings.emailSend}} - {isInvalidEmail && {strings.invalidEmailText}} - {strings.requestVerificationText} - {errorMessage && {errorMessage}} - + + + × + {strings.titleText} + + + {emailSent && {strings.emailSend}} + {isInvalidEmail && {strings.invalidEmailText}} + {strings.requestVerificationText} + {errorMessage && {errorMessage}} + + ); }; @@ -191,7 +194,7 @@ const SignupButton = styled.button` } `; const SuccessMessage = styled.p` - color: #e22926; // 빨간색 + color: #e22926; // 빨간색 margin-top: 5px; font-size: 0.8rem; -`; \ No newline at end of file +`; diff --git a/client/src/components/Signups/Guide.tsx b/client/src/components/Signups/Guide.tsx index fd84bf6..ca334a1 100644 --- a/client/src/components/Signups/Guide.tsx +++ b/client/src/components/Signups/Guide.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; const STRINGS = { GUIDE_TITLE: "StockHolm 가이드", @@ -16,12 +17,14 @@ const GuideModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { return ( - - × - {STRINGS.GUIDE_TITLE} - {STRINGS.LOGIN_GUIDE} - {STRINGS.CASH_GUIDE} - + + + × + {STRINGS.GUIDE_TITLE} + {STRINGS.LOGIN_GUIDE} + {STRINGS.CASH_GUIDE} + + ); }; diff --git a/client/src/components/Signups/Password.tsx b/client/src/components/Signups/Password.tsx index c87b93b..1165346 100644 --- a/client/src/components/Signups/Password.tsx +++ b/client/src/components/Signups/Password.tsx @@ -2,6 +2,7 @@ import axios from "axios"; import React, { useState, useRef } from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; import { useDispatch } from "react-redux"; import { setMemberInfo } from "../../reducer/member/memberInfoSlice"; @@ -25,10 +26,10 @@ const PasswordSettingModal: React.FC = ({ onClose, on const [isSubmitting, setIsSubmitting] = useState(false); const dispatch = useDispatch(); - // 각 입력 필드에 대한 ref를 생성합니다. - const passwordRef = useRef(null); - const confirmPasswordRef = useRef(null); - const nameRef = useRef(null); + // 각 입력 필드에 대한 ref를 생성합니다. + const passwordRef = useRef(null); + const confirmPasswordRef = useRef(null); + const nameRef = useRef(null); //비밀번호 입력창 const handlePasswordChange = (e: React.ChangeEvent) => { setPassword(e.target.value); @@ -54,9 +55,7 @@ const PasswordSettingModal: React.FC = ({ onClose, on const [generalError, setGeneralError] = useState(""); const handleEnterPress = (event: React.KeyboardEvent, target?: "confirmPassword" | "name" | "confirmButton") => { - - - if (event.key === 'Enter') { + if (event.key === "Enter") { if (target === "confirmPassword") { confirmPasswordRef.current?.focus(); } else if (target === "name") { @@ -68,12 +67,11 @@ const PasswordSettingModal: React.FC = ({ onClose, on }; const handleConfirmClick = async () => { - if (isSubmitting) { // 이미 요청 중이라면 추가 요청을 방지 return; } - setIsSubmitting(true); // 요청 시작 전에 상태를 '요청 중'으로 설정 + setIsSubmitting(true); // 요청 시작 전에 상태를 '요청 중'으로 설정 //비밀번호 유효성 에러 메시지 if (!isValidPassword(password)) { setPasswordError(strings.passwordError); @@ -122,7 +120,7 @@ const PasswordSettingModal: React.FC = ({ onClose, on // 요청 완료 후 상태를 '요청 중 아님'으로 설정 setIsSubmitting(false); } catch (error) { - setIsSubmitting(false); // 예외 발생 시 상태를 '요청 중 아님'으로 설정 + setIsSubmitting(false); // 예외 발생 시 상태를 '요청 중 아님'으로 설정 if (axios.isAxiosError(error)) { console.error("Error during data submission:", error); if (error.response) { @@ -136,24 +134,26 @@ const PasswordSettingModal: React.FC = ({ onClose, on return ( - - × - {strings.titleText} - - handleEnterPress(event, "confirmPassword")}/> - {passwordError && {passwordError}} - - - handleEnterPress(event, "name")}/> - {confirmPasswordError && {confirmPasswordError}} - - - handleEnterPress(event, "confirmButton")}/> - - {generalError && {generalError}} - - {strings.confirmButtonText} - + + + × + {strings.titleText} + + handleEnterPress(event, "confirmPassword")} /> + {passwordError && {passwordError}} + + + handleEnterPress(event, "name")} /> + {confirmPasswordError && {confirmPasswordError}} + + + handleEnterPress(event, "confirmButton")} /> + + {generalError && {generalError}} + + {strings.confirmButtonText} + + ); }; diff --git a/client/src/components/Signups/Welcome.tsx b/client/src/components/Signups/Welcome.tsx index 041813b..91d29b4 100644 --- a/client/src/components/Signups/Welcome.tsx +++ b/client/src/components/Signups/Welcome.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; import { useSelector } from "react-redux"; import StockHolmLogo from "../../asset/logos/StockHolmLogo.png"; @@ -31,20 +32,22 @@ const Welcome: React.FC = ({ onClose }) => { return ( - - - {WELCOME_TEXT} - {memberInfo?.name}님! - {" "} - {/* Display the member's name */} - - {JOINED_DATE_TEXT} - {memberInfo?.createdAt && formatDate(memberInfo.createdAt)} - {" "} - {/* Display the member's createdAt */} - - {START_TEXT} - + + + + {WELCOME_TEXT} + {memberInfo?.name}님! + {" "} + {/* Display the member's name */} + + {JOINED_DATE_TEXT} + {memberInfo?.createdAt && formatDate(memberInfo.createdAt)} + {" "} + {/* Display the member's createdAt */} + + {START_TEXT} + + ); }; diff --git a/client/src/components/StockOrderSection/OrderResult.tsx b/client/src/components/StockOrderSection/OrderResult.tsx index c773492..b45f396 100644 --- a/client/src/components/StockOrderSection/OrderResult.tsx +++ b/client/src/components/StockOrderSection/OrderResult.tsx @@ -72,12 +72,7 @@ const OrderResult = () => { const orderTime = `${year}-${month}-${date} ${hour}:${minute}`; return ( - + ); diff --git a/client/src/components/watchlist/WatchList.tsx b/client/src/components/watchlist/WatchList.tsx index 916f64e..14838a2 100644 --- a/client/src/components/watchlist/WatchList.tsx +++ b/client/src/components/watchlist/WatchList.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; +import { motion } from "framer-motion"; import StockSearchComponent from "./StockSearchComponent.tsx"; import Header from "./Header.tsx"; import StockItem from "./StockItem.tsx"; @@ -32,27 +33,29 @@ const WatchList: React.FC = ({ currentListType, onChangeListType setStarredCompanyIds((prevState) => prevState.filter((id) => id !== deletedCompanyId)); }; return ( - - -
- - - - - - - - {isLoading ? ( -
Loading...
- ) : isError ? ( -
Error fetching data
- ) : loginStatus === 1 ? ( - companiesList.filter((company) => starredCompanyIds.includes(company.companyId)).map((company) => ) - ) : ( - - )} -
- + + + +
+ + + + + + + + {isLoading ? ( +
Loading...
+ ) : isError ? ( +
Error fetching data
+ ) : loginStatus === 1 ? ( + companiesList.filter((company) => starredCompanyIds.includes(company.companyId)).map((company) => ) + ) : ( + + )} +
+ + ); };