diff --git a/client/src/components/EntireList/EntireList.tsx b/client/src/components/EntireList/EntireList.tsx index 74e0fc27..6ea208f5 100644 --- a/client/src/components/EntireList/EntireList.tsx +++ b/client/src/components/EntireList/EntireList.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; -import Header from './Header'; -import StockItem from './StockItem'; -import useCompanyData from '../../hooks/useCompanyData'; -import { useGetCash } from '../../hooks/useCash'; // 훅 가져오기 +import React, { useState } from "react"; +import styled from "styled-components"; +import Header from "./Header"; +import StockItem from "./StockItem"; +import useCompanyData from "../../hooks/useCompanyData"; +import { useGetCash } from "../../hooks/useCash"; // 훅 가져오기 const EntireList: React.FC = ({ currentListType, onChangeListType }) => { const [isMenuOpen, setMenuOpen] = useState(false); @@ -15,37 +15,19 @@ const EntireList: React.FC = ({ currentListType, onChangeListTy // 'companies'가 'undefined'인 경우를 처리하기 위해 빈 배열로 초기화 const companiesList = companies || []; - // 현금 보유량 가져오기 - const moneyId = localStorage.getItem('moneyId'); - const { data: cashData } = useGetCash(moneyId); - const holdingsAmount = cashData?.data?.money || "0"; + // 현금 보유량 가져오기 + const moneyId = localStorage.getItem("moneyId"); + const { data: cashData } = useGetCash(moneyId); + const holdingsAmount = cashData?.data?.money || "0"; return ( -
+
현금 보유량: {holdingsAmount}원 {/* 현금 보유량 표시 */} - {isLoading ? ( -
Loading...
- ) : isError ? ( -
Error fetching data
- ) : ( - companiesList.map((company) => ( - - )) - )} + {isLoading ?
Loading...
: isError ?
Error fetching data
: companiesList.map((company) => )}
); @@ -53,50 +35,54 @@ const EntireList: React.FC = ({ currentListType, onChangeListTy // Props와 상태에 대한 타입 정의 type EntireListProps = { - currentListType: '전체종목' | '관심종목' | '보유종목'; - onChangeListType: (type: '전체종목' | '관심종목' | '보유종목') => void; + currentListType: "전체종목" | "관심종목" | "보유종목"; + onChangeListType: (type: "전체종목" | "관심종목" | "보유종목") => void; }; // WatchList 컴포넌트에 대한 스타일드 컴포넌트 정의 const WatchListContainer = styled.div` + height: calc(100vh - 53px); display: flex; flex-direction: column; align-items: flex-start; `; const Divider1 = styled.div` -margin:0px; -padding:0px; -width: 100%; -height: 10px; -display: flex; -flex-direction: row; -border-bottom: 1px solid #2f4f4f; + margin: 0px; + padding: 0px; + width: 100%; + height: 10px; + display: flex; + flex-direction: row; + border-bottom: 1px solid #2f4f4f; `; const HoldingsAmount = styled.div` font-size: 16px; font-weight: bold; - margin: 8px 12px ; + margin: 8px 12px; text-align: center; - color: darkslategray // 현금 보유량을 파란색으로 표시 + color: darkslategray; // 현금 보유량을 파란색으로 표시 `; - const Divider2 = styled.div` -margin:0px; -padding:0px; -width: 100%; -height: 4.5px; -display: flex; -flex-direction: row; -border-bottom: 1px solid #2f4f4f; + margin: 0px; + padding: 0px; + width: 100%; + height: 4.5px; + display: flex; + flex-direction: row; + border-bottom: 1px solid #2f4f4f; `; const StockList = styled.div` width: 100%; - max-height: 740px; /* 스크롤이 발생할 최대 높이를 지정하세요 */ + height: 100%; overflow-y: auto; /* 세로 스크롤을 활성화합니다 */ + + &::-webkit-scrollbar { + display: none; + } `; export default EntireList; diff --git a/client/src/components/Logins/EmailLogin.tsx b/client/src/components/Logins/EmailLogin.tsx index 74da8f17..549b24cc 100644 --- a/client/src/components/Logins/EmailLogin.tsx +++ b/client/src/components/Logins/EmailLogin.tsx @@ -4,9 +4,7 @@ import React, { useState } from "react"; import { setLoginState } from "../../reducer/member/loginSlice"; import { useDispatch } from "react-redux"; -// 이메일 로그인 모달 컴포넌트 const EmailLoginModal: React.FC = ({ onClose, onLogin }) => { - // 상수 문자열 정의 const titleText = "이메일로 로그인"; const emailLabelText = "이메일"; const passwordLabelText = "비밀번호"; @@ -15,55 +13,51 @@ const EmailLoginModal: React.FC = ({ onClose, onLogin }) = const noAccountText = "계정이 없습니까?"; const registerButtonText = "회원가입하기"; - //디스패치 함수 가져오기 const dispatch = useDispatch(); - // 상태 변수 정의 const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [generalError, setGeneralError] = useState(null); - // 이메일 변경 핸들러 const handleEmailChange = (event: React.ChangeEvent) => { setEmail(event.target.value); }; - // 비밀번호 변경 핸들러 const handlePasswordChange = (event: React.ChangeEvent) => { setPassword(event.target.value); }; - // 로그인 버튼 클릭 핸들러 const handleLoginClick = async () => { try { - const response = await axios.post("http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members/login", { - email, - password, - }); + const response = await axios.post( + "http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members/login", + { email, password }, + { validateStatus: (status) => status >= 200 && status < 600 } + ); + if (response.status === 200) { const accessToken = response.headers["authorization"]; - console.log(accessToken); - const refreshToken = response.headers["refresh"]; - // 로그인 상태로 만들기 dispatch(setLoginState()); - - // 토큰들을 로컬 스토리지에 저장 if (accessToken) localStorage.setItem("accessToken", accessToken); if (refreshToken) localStorage.setItem("refreshToken", refreshToken); onLogin(); onClose(); } else { - console.error("로그인 중 오류 발생"); + setGeneralError(response.data.message || JSON.stringify(response.data)); } } catch (error) { - console.error("로그인 중 오류:", error); + if (axios.isAxiosError(error) && error.response) { + setGeneralError(error.response.data.message || JSON.stringify(error.response.data)); + } else { + console.error("로그인 중 오류:", error); + } } }; return ( - // 모달 레이아웃 × @@ -72,6 +66,7 @@ const EmailLoginModal: React.FC = ({ onClose, onLogin }) = + {generalError && {generalError}} {findPasswordText} {loginButtonText} @@ -104,6 +99,7 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; @@ -174,3 +170,9 @@ const RegisterButton = styled.button` color: slategray; cursor: pointer; `; +const ErrorMessage = styled.p` + color: red; + margin-top: 5px; + margin-bottom: 10px; + font-size: 0.8rem; +`; \ No newline at end of file diff --git a/client/src/components/Logins/GoogleLoginButton.tsx b/client/src/components/Logins/GoogleLoginButton.tsx index 5f3efc8a..db06750b 100644 --- a/client/src/components/Logins/GoogleLoginButton.tsx +++ b/client/src/components/Logins/GoogleLoginButton.tsx @@ -1,21 +1,49 @@ -// GoogleLoginButton.tsx - import React from 'react'; +import styled from 'styled-components'; +import { useDispatch } from 'react-redux'; +import { setLoginState } from '../../reducer/member/loginSlice'; +import googleLogo from '../../asset/images/GoogleLogo.svg'; interface Props { backendURL: string; } const GoogleLoginButton: React.FC = ({ backendURL }) => { + const dispatch = useDispatch(); + + const buttonText = "Login with Google"; + const handleLoginClick = () => { window.location.href = `${backendURL}`; + dispatch(setLoginState()); // 로그인 상태를 변경합니다. }; return ( - + + + {buttonText} + ); } +// Styled Components +const GoogleButton = styled.button` + margin: 10px 0; + padding: 10px 20px; + background-color: #FFFFFF; + border: 1px solid lightgray; + border-radius: 5px; + cursor: pointer; + width: 300px; + display: flex; + align-items: center; + justify-content: center; +`; + +const LogoImage = styled.img` + margin-right: 30px; + width: 60px; + height: auto; +`; + export default GoogleLoginButton; diff --git a/client/src/components/Logins/KakaoLoginButton.tsx b/client/src/components/Logins/KakaoLoginButton.tsx index 81ea6ea3..0edd64df 100644 --- a/client/src/components/Logins/KakaoLoginButton.tsx +++ b/client/src/components/Logins/KakaoLoginButton.tsx @@ -1,21 +1,51 @@ // KakaoLoginButton.tsx import React from 'react'; +import styled from 'styled-components'; +import { useDispatch } from 'react-redux'; +import { setLoginState } from '../../reducer/member/loginSlice'; +import kakaoLogo from '../../asset/images/KakaoLogo.svg'; interface Props { backendURL: string; } const KakaoLoginButton: React.FC = ({ backendURL }) => { + const dispatch = useDispatch(); + + const buttonText = "Login with Kakao"; + const handleLoginClick = () => { window.location.href = `${backendURL}`; + dispatch(setLoginState()); // 로그인 상태를 변경합니다. }; return ( - + + + {buttonText} + ); } +// Styled Components +const KakaoButton = styled.button` + margin: 10px 0; + padding: 10px 20px; + background-color: #FFFFFF; + border: 1px solid lightgray; + border-radius: 5px; + cursor: pointer; + width: 300px; + display: flex; + align-items: center; + justify-content: center; +`; + +const LogoImage = styled.img` + margin-right: 30px; + width: 60px; + height: auto; +`; + export default KakaoLoginButton; diff --git a/client/src/components/Logins/LoginConfirmatationModal.tsx b/client/src/components/Logins/LoginConfirmatationModal.tsx index 93ef659b..f4675ee5 100644 --- a/client/src/components/Logins/LoginConfirmatationModal.tsx +++ b/client/src/components/Logins/LoginConfirmatationModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; - +import TokenHandler from './TokenHandler'; const LoginConfirmationModal: React.FC = ({ onClose }) => { const messageText = "로그인이 성공적으로 완료되었습니다!"; @@ -9,7 +9,8 @@ const LoginConfirmationModal: React.FC = ({ onClose }) = return ( - {messageText} + + {messageText} {confirmText} @@ -36,6 +37,7 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; diff --git a/client/src/components/Logins/OAuthLogin.tsx b/client/src/components/Logins/OAuthLogin.tsx index 0cc9e247..7a3aa809 100644 --- a/client/src/components/Logins/OAuthLogin.tsx +++ b/client/src/components/Logins/OAuthLogin.tsx @@ -4,21 +4,22 @@ import GoogleLoginButton from './GoogleLoginButton'; import KakaoLoginButton from './KakaoLoginButton'; import { useSelector } from 'react-redux'; import { RootState } from '../../store/config'; -import TokenHandler from './TokenHandler'; -const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick, onEmailSignupClick }) => { + +const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick, onEmailSignupClick, onLoginSuccess }) => { const titleText = "로그인"; const orText = "또는"; const emailLoginText = "이메일로 로그인"; const emailSignupText = "이메일로 회원가입"; const loginState = useSelector((state: RootState) => state.login); - console.log("Login State:", loginState); + useEffect(() => { if (loginState === 1) { + onLoginSuccess(); onClose(); } }, [loginState, onClose]); @@ -26,7 +27,7 @@ const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick return ( - + × {titleText} @@ -45,6 +46,7 @@ interface LoginModalProps { onClose: () => void; onEmailLoginClick: () => void; onEmailSignupClick: () => void; + onLoginSuccess: () => void; // 추가된 부분 onWatchListClick: () => void; onHoldingsClick: () => void; } @@ -62,6 +64,7 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; diff --git a/client/src/components/Profile/cashModal.tsx b/client/src/components/Profile/cashModal.tsx index c2ecee2b..5eb9572d 100644 --- a/client/src/components/Profile/cashModal.tsx +++ b/client/src/components/Profile/cashModal.tsx @@ -12,6 +12,7 @@ const CashModal: React.FC = ({ onClose }) => { const createCashButtonText = "현금 생성"; const cashInputPlaceholder = "현금 입력"; const resetButtonText = "리셋"; + const refreshButtonText ="새로고침" const dispatch = useDispatch(); const moneyId = useSelector((state: RootState) => state.cash.moneyId); @@ -24,6 +25,12 @@ const CashModal: React.FC = ({ onClose }) => { const [cashInput, setCashInput] = useState('0'); const [initialAmount, setInitialAmount] = useState('0'); // 현금 생성을 위한 상태 변수 + // 현금 정보 재조회 함수 + const refreshCashInfo = () => { + // 여기에 현금 정보를 다시 불러오는 로직을 추가합니다. + // 예: useGetCash() hook을 다시 호출한다던지, 특정 상태를 변경하여 리렌더링을 유발하는 등의 방법이 있습니다. + }; + // 현금 생성 및 cashId 전역 저장 const handleCreateCash = () => { createCashMutation.mutate(initialAmount, { @@ -70,8 +77,12 @@ const CashModal: React.FC = ({ onClose }) => { /> {createCashButtonText} - -

현재 현금: {isLoading ? 'Loading...' : moneyAmount.toLocaleString()}

+
+

+ 현재 현금: {isLoading ? 'Loading...' : moneyAmount.toLocaleString()} +

+ {refreshButtonText} +
= ({ onClose }) => { ); }; +export default CashModal; + interface CashModalProps { onClose: () => void; moneyId: number | null; @@ -106,11 +119,12 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` - z-index: 11; + z-index: 100; position: relative; background-color: white; padding: 20px; width: 400px; + height:230px; border-radius: 10px; display: flex; flex-direction: column; @@ -133,6 +147,15 @@ const CloseButton = styled.button` cursor: pointer; `; +const StyledButton = styled.button` + padding: 10px 15px; + background-color: white; + color: darkslategray; + border: 1px solid darkslategray; + border-radius: 5px; + cursor: pointer; +`; + const CashInput = styled.input` padding: 10px; border: 1px solid lightgray; @@ -140,14 +163,8 @@ const CashInput = styled.input` margin-right: 10px; `; -const ReceiveButton = styled.button` - padding: 10px 15px; - background-color: darkgreen; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; -`; +const ReceiveButton = styled(StyledButton)``; + const CashCreationInput = styled.input` padding: 10px; border: 1px solid lightgray; @@ -155,13 +172,11 @@ const CashCreationInput = styled.input` margin-right: 10px; `; -const CreateCashButton = styled.button` - padding: 10px 15px; - background-color: blue; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; +const CreateCashButton = styled(StyledButton)``; + +const RefreshButton = styled(StyledButton)` + margin-left:50px; `; -export default CashModal; + + diff --git a/client/src/components/Profile/memberInfoModal.tsx b/client/src/components/Profile/memberInfoModal.tsx index 8162766c..a69fd4e9 100644 --- a/client/src/components/Profile/memberInfoModal.tsx +++ b/client/src/components/Profile/memberInfoModal.tsx @@ -52,11 +52,12 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` - z-index: 11; + z-index: 100; position: relative; background-color: white; padding: 20px; width: 400px; + height:230px; border-radius: 10px; display: flex; flex-direction: column; diff --git a/client/src/components/Profile/memberWithdrawalModal.tsx b/client/src/components/Profile/memberWithdrawalModal.tsx index f1c66353..104ae459 100644 --- a/client/src/components/Profile/memberWithdrawalModal.tsx +++ b/client/src/components/Profile/memberWithdrawalModal.tsx @@ -1,42 +1,48 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import { useDeleteMember } from '../../hooks/useDeleteMembers'; // 적절한 경로로 수정 - +import { useDeleteMember } from '../../hooks/useDeleteMembers'; const MemberWithdrawalModal: React.FC = ({ onClose }) => { - const [password, setPassword] = useState(''); + const [inputString, setInputString] = useState(''); + const [errorMsg, setErrorMsg] = useState(null); const deleteMemberMutation = useDeleteMember(); const withdrawalTitle = "StockHolm에서 탈퇴하시겠습니까?"; - const passwordInputLabel = "비밀번호를 입력해주세요."; - const incorrectPasswordMsg = "Incorrect password!"; + const inputStringLabel = "다음 문자열을 입력해주세요: Codestates"; + const incorrectStringMsg = "문자열이 일치하지 않습니다!"; const withdrawalButtonText = "회원탈퇴"; const handleWithdrawal = () => { - deleteMemberMutation.mutate(password, { // 비밀번호를 서버에 전송 - onSuccess: () => { - alert('회원탈퇴 되었습니다!'); - onClose(); - }, - onError: () => { - alert(incorrectPasswordMsg); - } - }); + if (inputString === "Codestates/seb-45_main_008") { + deleteMemberMutation.mutate(inputString, { + onSuccess: () => { + alert('회원탈퇴 되었습니다!'); + onClose(); + } + }); + } else { + setErrorMsg(incorrectStringMsg); + } }; + return ( × {withdrawalTitle} - - setPassword(e.target.value)} /> + + setInputString(e.target.value)} /> + {errorMsg && {errorMsg}} {withdrawalButtonText} ); }; +export default MemberWithdrawalModal; + + interface MemberWithdrawalModalProps { onClose: () => void; @@ -57,11 +63,12 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` - z-index: 11; + z-index: 100; position: relative; background-color: white; padding: 20px; width: 400px; + height:230px; border-radius: 10px; display: flex; flex-direction: column; @@ -99,11 +106,17 @@ const PasswordInput = styled.input` const WithdrawalButton = styled.button` padding: 10px 20px; - background-color: darkred; - color: white; - border: none; + background-color: white; + color: darkslategray; + border: 1px solid lightslategray; border-radius: 5px; cursor: pointer; `; -export default MemberWithdrawalModal; + +const ErrorMessage = styled.p` + color: red; + margin-top: 5px; + margin-bottom: 10px; + font-size: 0.8rem; +`; \ No newline at end of file diff --git a/client/src/components/Profile/profileModal.tsx b/client/src/components/Profile/profileModal.tsx index 1f339f9d..f2c03584 100644 --- a/client/src/components/Profile/profileModal.tsx +++ b/client/src/components/Profile/profileModal.tsx @@ -18,11 +18,11 @@ const ProfileModal: React.FC = ({ onClose }) => { return ( - - setSelectedTab(1)}>{memberInfoText} - setSelectedTab(2)}>{cashText} - setSelectedTab(3)}>{memberWithdrawText} - + + setSelectedTab(1)}>{memberInfoText} + setSelectedTab(2)}>{cashText} + setSelectedTab(3)}>{memberWithdrawText} + {selectedTab === 1 && } {selectedTab === 2 && } @@ -70,6 +70,8 @@ const Tabs = styled.div` justify-content: space-between; width: 100%; margin-bottom: 20px; + position: relative; // 위치를 조절하기 위한 속성 + top: -30px; // 위로 30px 올립니다 z-index: 1002; // 이 값을 추가하여 Tabs를 최상위로 올립니다. `; @@ -84,10 +86,12 @@ const Tabs = styled.div` // cursor: pointer; // `; -const TabButton = styled.button` +// TabButton 컴포넌트 스타일링 +const TabButton = styled.button<{ isActive?: boolean }>` flex: 1; padding: 10px; border: 1px solid lightgray; + border-bottom: ${({ isActive }) => (isActive ? '3px solid darkred' : '1px solid lightgray')}; border-radius: 5px; cursor: pointer; background-color: #FFFFFF; diff --git a/client/src/components/Signups/EmailCertify.tsx b/client/src/components/Signups/EmailCertify.tsx index 2dde0ef6..d2a7f218 100644 --- a/client/src/components/Signups/EmailCertify.tsx +++ b/client/src/components/Signups/EmailCertify.tsx @@ -15,50 +15,63 @@ const strings = { // 이메일 인증 모달 컴포넌트 const EmailVerificationModal: React.FC = ({ onClose, onNextStep, initialEmail }) => { - // 이메일 및 인증코드에 대한 상태를 선언합니다. const [email, setEmail] = useState(initialEmail); const [verificationCode, setVerificationCode] = useState(''); - - // 동의 상태와 알림 상태를 선언합니다. const [hasAgreed, setHasAgreed] = useState(false); const [showAgreementError, setShowAgreementError] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); - // 이메일 입력값을 처리하는 함수 const handleEmailChange = (event: React.ChangeEvent) => { setEmail(event.target.value); }; - // 인증코드 입력값을 처리하는 함수 const handleVerificationCodeChange = (event: React.ChangeEvent) => { setVerificationCode(event.target.value); }; - // 체크박스의 변경을 감지하는 핸들러 const handleAgreementChange = (event: React.ChangeEvent) => { setHasAgreed(event.target.checked); setShowAgreementError(false); // 알림을 숨깁니다. }; - // 다음 단계 버튼 클릭시 이메일 인증을 처리하는 함수 const handleNextStepClick = async () => { - // 동의 확인 if (!hasAgreed) { - setShowAgreementError(true); // 알림을 표시합니다. + setShowAgreementError(true); return; } try { - const response = await axios.post('http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/confirm', { email, code: verificationCode }); + const response = await axios.post( + 'http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/confirm', + { email, code: verificationCode }, + { + validateStatus: function (status) { + return status >= 200 && status < 600; + } + } + ); + if (response.status === 200) { onNextStep(); - } else { + } else if (response.status === 400) { + setErrorMessage(response.data.message); + } else if (response.status === 500) { + setErrorMessage(JSON.stringify(response.data)); + } else { console.error('Error during email confirmation'); } } catch (error) { - console.error('Error during email confirmation:', error); + if (axios.isAxiosError(error)) { + console.error('Error during email confirmation:', error); + if (error.response) { + console.error('Detailed server response:', error.response.data); + } + } else { + console.error('An unknown error occurred:', error); + } } }; - + return ( @@ -73,17 +86,19 @@ const EmailVerificationModal: React.FC = ({ onClose - + {showAgreementError && 동의하지 않으면 진행할 수 없습니다} {strings.nextStepText} + {errorMessage && {errorMessage}} ); -}; + }; + + export default EmailVerificationModal; -export default EmailVerificationModal; // 이메일 인증 모달의 Props 타입 type EmailVerificationModalProps = { @@ -107,7 +122,8 @@ const ModalBackground = styled.div` // 모달의 컨테이너 스타일 const ModalContainer = styled.div` -position: relative; + z-index:4000; + position: relative; background-color: white; padding: 20px; width: 400px; diff --git a/client/src/components/Signups/EmailSignup.tsx b/client/src/components/Signups/EmailSignup.tsx index da3ccaee..ed986ea0 100644 --- a/client/src/components/Signups/EmailSignup.tsx +++ b/client/src/components/Signups/EmailSignup.tsx @@ -15,42 +15,58 @@ const strings = { const EmailSignupModal: React.FC = ({ onClose, onRequestVerification }) => { const dispatch = useDispatch(); - // 사용자가 입력한 이메일 상태 const [email, setEmail] = useState(''); - const [isInvalidEmail, setIsInvalidEmail] = useState(false); // 이메일 유효성 상태 - + const [isInvalidEmail, setIsInvalidEmail] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); - // 이메일 입력 핸들러 const handleEmailChange = (event: React.ChangeEvent) => { setEmail(event.target.value); setIsInvalidEmail(false); }; - //이메일 유효성 검사 const validateEmail = (email: string) => { return email.includes('@') && email.includes('.com'); }; - // 이메일 인증 요청 핸들러 const handleVerificationRequest = async () => { if (!validateEmail(email)) { setIsInvalidEmail(true); return; } - + try { - const response = await axios.post('http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/send', { email }); + const response = await axios.post( + 'http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/send', + { email }, + { + validateStatus: function (status) { + return status >= 200 && status < 600; // Reject only if status code is greater than or equal to 600 + } + } + ); + if (response.status === 200) { - // 여기서 Redux store의 emailForVerification에 데이터 저장 dispatch(setEmailForVerification(email)); onRequestVerification(email); + } else if (response.status === 400) { + setErrorMessage(response.data.message); + } else if (response.status === 500) { + setErrorMessage(JSON.stringify(response.data)); } else { console.error('Error sending verification email'); } } catch (error) { - console.error('Error sending verification email:', error); + if (axios.isAxiosError(error)) { + console.error('Error sending verification email:', error); + if (error.response) { + console.error('Detailed server response:', error.response.data); + } + } else { + console.error('An unknown error occurred:', error); + } } }; + return ( @@ -63,6 +79,7 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV {strings.requestVerificationText} + {errorMessage && {errorMessage}} ); @@ -70,10 +87,9 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV export default EmailSignupModal; -// 프롭 타입 정의 type EmailSignupModalProps = { onClose: () => void; - onRequestVerification: (email: string) => void; + onRequestVerification: (email: string) => void; }; @@ -92,6 +108,7 @@ const ModalBackground = styled.div` //모달 컨테이너 const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; diff --git a/client/src/components/Signups/Password.tsx b/client/src/components/Signups/Password.tsx index 4b1224f9..25ec5b01 100644 --- a/client/src/components/Signups/Password.tsx +++ b/client/src/components/Signups/Password.tsx @@ -41,6 +41,9 @@ const PasswordSettingModal: React.FC = ({ onClose, on return passwordRegex.test(password); }; + // 일반적인 오류 메시지 상태 추가 + const [generalError, setGeneralError] = useState(''); + const handleConfirmClick = async () => { if (!isValidPassword(password)) { setPasswordError(strings.passwordError); @@ -57,47 +60,69 @@ const PasswordSettingModal: React.FC = ({ onClose, on } try { - const response = await axios.post('http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members', { - email, - name, - password, - confirmPassword, - }); - - if (response.status === 201) { - console.log('Data sent successfully'); - - // 여기서 Redux store의 memberInfo에 데이터 저장 - dispatch(setMemberInfo(response.data)); - onClose(); - onNext(); - } else { - console.error('Error sending data'); - } - } catch (error) { - console.error('Error sending data:', error); - } - }; - - return ( - - - × - {strings.titleText} - - - {passwordError && {passwordError}} - - - - {confirmPasswordError && {confirmPasswordError}} - - - - {strings.confirmButtonText} - - - ); + const response = await axios.post( + 'http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members', + { + email, + name, + password, + confirmPassword, + }, + { + validateStatus: function (status) { + return status >= 200 && status < 600; + } + } + ); + + if (response.status === 201) { + console.log('Data sent successfully'); + dispatch(setMemberInfo(response.data)); + onClose(); + onNext(); + } else if (response.status === 400 && response.data.code === "EMAIL_DUPLICATION") { + setGeneralError(response.data.message); + } else if (response.status === 404 && response.data.code === "INVALID_PASSWORD") { + setGeneralError(JSON.stringify(response.data)); + } else if (response.status === 500) { + setGeneralError(JSON.stringify(response.data)); + } else { + console.error('Error sending data'); + } + } catch (error) { + if (axios.isAxiosError(error)) { + console.error('Error during data submission:', error); + if (error.response) { + console.error('Detailed server response:', error.response.data); + } + } else { + console.error('An unknown error occurred:', error); + } + } + }; + + return ( + + + × + {strings.titleText} + + + {passwordError && {passwordError}} + + + + {confirmPasswordError && {confirmPasswordError}} + + + + + {generalError && {generalError}} {/* 일반 오류 메시지 표시 */} + + {strings.confirmButtonText} + + + ); }; interface PasswordSettingModalProps { @@ -122,6 +147,7 @@ const ModalBackground = styled.div` // 모달 컨테이너 스타일 const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; diff --git a/client/src/components/Signups/Welcome.tsx b/client/src/components/Signups/Welcome.tsx index 057c9b37..a20ca6f2 100644 --- a/client/src/components/Signups/Welcome.tsx +++ b/client/src/components/Signups/Welcome.tsx @@ -58,6 +58,7 @@ const ModalBackground = styled.div` `; const ModalContainer = styled.div` + z-index:4000; position: relative; background-color: white; padding: 20px; diff --git a/client/src/page/MainPage.tsx b/client/src/page/MainPage.tsx index af3bf85b..8302348f 100644 --- a/client/src/page/MainPage.tsx +++ b/client/src/page/MainPage.tsx @@ -101,12 +101,16 @@ const MainPage = () => { // 🔴 페이지 로드 시 로컬 스토리지의 토큰을 기반으로 로그인 상태를 확인합니다. useEffect(() => { - const authToken = localStorage.getItem("Authorization"); - if (authToken !== null) { + const acessToken = localStorage.getItem("acessToken"); + if (acessToken !== null) { dispatch(setLoginState()); } }, [dispatch]); + const handleOAuthLoginSuccess = useCallback(() => { + setLoginConfirmationModalOpen(true); // 로그인 확인 모달 열기 +}, []); + //프로필 모달 열고닫는 매커니즘 const openProfileModal = useCallback(() => { setProfileModalOpen(true); @@ -158,8 +162,15 @@ const MainPage = () => { {!expandScreen.right && } {isOAuthModalOpen && ( - handleMenuChange("관심종목")} onHoldingsClick={() => handleMenuChange("보유종목")} /> - )} + handleMenuChange("관심종목")} + onHoldingsClick={() => handleMenuChange("보유종목")} + /> + )} {isEmailLoginModalOpen && } {isLoginConfirmationModalOpen && }