From ff7d291878bf2a4d31d1d7fc8286d4c21a3af7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=91=ED=98=84?= Date: Fri, 8 Sep 2023 16:22:20 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=ED=83=AD=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=99=84?= =?UTF-8?q?=EC=84=B1=20=ED=9A=8C=EC=9B=90=EC=A0=95=EB=B3=B4=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=99=84=EC=84=B1=20=ED=98=84=EA=B8=88=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=99=84=EC=84=B1=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=ED=83=88=ED=87=B4=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=99=84=EC=84=B1=20Issues=20#=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Logins/OAuthLogin.tsx | 41 ++---- client/src/components/Profile/cashModal.tsx | 123 ++++++++++++++++++ .../components/Profile/memberInfoModal.tsx | 99 ++++++++++++++ .../Profile/memberWithdrawalModal.tsx | 122 +++++++++++++++++ .../src/components/Profile/profileModal.tsx | 97 ++++++++++++++ 5 files changed, 455 insertions(+), 27 deletions(-) create mode 100644 client/src/components/Profile/cashModal.tsx create mode 100644 client/src/components/Profile/memberInfoModal.tsx create mode 100644 client/src/components/Profile/memberWithdrawalModal.tsx create mode 100644 client/src/components/Profile/profileModal.tsx diff --git a/client/src/components/Logins/OAuthLogin.tsx b/client/src/components/Logins/OAuthLogin.tsx index 9ad7da78..b40f5615 100644 --- a/client/src/components/Logins/OAuthLogin.tsx +++ b/client/src/components/Logins/OAuthLogin.tsx @@ -14,51 +14,38 @@ const OAuthLoginModal: React.FC = ({ onClose, onEmailLoginClick const emailLoginText = "이메일로 로그인"; const emailSignupText = "이메일로 회원가입"; - // 구글 로그인 핸들러 + // 카카오 로그인 핸들러 const handleGoogleLogin = async () => { try { - const response = await axios.post('/oauth2/authorization/google'); + const response = await axios.get('/oauth2/authorization/google'); if (response.status === 200) { - const authToken = response.headers['authorization']; - const accessToken = response.headers['accessToken']; - const refreshToken = response.headers['refreshToken']; - - // 토큰들을 로컬 스토리지에 저장 - if(authToken) localStorage.setItem('authToken', authToken); - if(accessToken) localStorage.setItem('accessToken', accessToken); - if(refreshToken) localStorage.setItem('refreshToken', refreshToken); - console.log("Successfully logged in with Google!"); - onClose(); + // 200 상태 코드를 받으면 주소 데이터를 사용하여 리다이렉트 + const redirectUri = response.data.uri; // 백엔드에서 'url' 키로 주소 데이터를 제공한다고 가정 + window.location.href = redirectUri; } else { - console.error("Error logging in with Google"); + console.error("Error logging in with Google, unexpected status code:", response.status); } } catch (error) { console.error("Error logging in with Google:", error); } - }; + }; + // 카카오 로그인 핸들러 const handleKakaoLogin = async () => { try { - const response = await axios.post('/oauth2/authorization/kakao'); + const response = await axios.get('/oauth2/authorization/kakao'); if (response.status === 200) { - const authToken = response.headers['authorization']; - const accessToken = response.headers['accessToken']; - const refreshToken = response.headers['refreshToken']; - - // 토큰들을 로컬 스토리지에 저장 - if(authToken) localStorage.setItem('authToken', authToken); - if(accessToken) localStorage.setItem('accessToken', accessToken); - if(refreshToken) localStorage.setItem('refreshToken', refreshToken); - console.log("Successfully logged in with Kakao!"); - onClose(); + // 200 상태 코드를 받으면 주소 데이터를 사용하여 리다이렉트 + const redirectUri = response.data.uri; // 백엔드에서 'url' 키로 주소 데이터를 제공한다고 가정 + window.location.href = redirectUri; } else { - console.error("Error logging in with Kakao"); + console.error("Error logging in with Kakao, unexpected status code:", response.status); } } catch (error) { console.error("Error logging in with Kakao:", error); } - }; + }; // 모달 반환 return ( diff --git a/client/src/components/Profile/cashModal.tsx b/client/src/components/Profile/cashModal.tsx new file mode 100644 index 00000000..7032295b --- /dev/null +++ b/client/src/components/Profile/cashModal.tsx @@ -0,0 +1,123 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import axios from 'axios'; + +const CashModal: React.FC = ({ onClose, memberId }) => { + const [cash, setCash] = useState(null); + const [cashInput, setCashInput] = useState(''); + + useEffect(() => { + axios.get(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com/cash/${memberId}`) + .then(response => { + if (response.status === 200) { + setCash(response.data.cash); + } else if (response.status === 204) { + // Handle the "No Content" scenario. This could be setting cash to 0 or displaying a message. + setCash(0); // Or handle it in a way that suits your needs + } + }) + .catch(error => { + console.error("Error fetching cash:", error); + }); + }, [memberId]); + + const handleCashReceive = () => { + axios.post(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com/cash/${memberId}`, { + cash: cashInput + }) + .then(response => { + if (response.status === 201) { + // Update the cash display with the new value + setCash(prevCash => prevCash ? prevCash + Number(cashInput) : Number(cashInput)); + } else { + console.warn("Unexpected status code:", response.status); + } + }) + .catch(error => { + console.error("Error updating cash:", error); + }); + }; + + return ( + + + × + 현금 +

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

+
+ setCashInput(e.target.value)} + placeholder="현금 입력" + /> + 받기 +
+
+
+ ); +}; + +interface CashModalProps { + onClose: () => void; + memberId: string; +} + +// Styled Components Definitions: + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; + font-weight: 400; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const CashInput = styled.input` + padding: 10px; + border: 1px solid lightgray; + border-radius: 5px; + margin-right: 10px; +`; + +const ReceiveButton = styled.button` + padding: 10px 15px; + background-color: darkgreen; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; + +export default CashModal; diff --git a/client/src/components/Profile/memberInfoModal.tsx b/client/src/components/Profile/memberInfoModal.tsx new file mode 100644 index 00000000..bf0842ef --- /dev/null +++ b/client/src/components/Profile/memberInfoModal.tsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import axios from 'axios'; + +const MemberInfoModal: React.FC = ({ onClose, memberId }) => { + const [memberInfo, setMemberInfo] = useState(null); // Use the MemberData type + + const titleText = "회원정보"; + const loadingText = "Loading..."; + const nameText = "이름: "; + const emailText = "이메일: "; + const createdAtText = "회원 가입 일시: "; + const memberIdText = "회원 ID: "; + + useEffect(() => { + // Fetch member info when the modal is opened + axios.get(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com/members/${memberId}`) + .then(response => { + setMemberInfo(response.data); + }) + .catch(error => { + console.error("Error fetching member info:", error); + }); + }, [memberId]); + + return ( + + + × + {titleText} + {memberInfo ? ( +
+ {/* Display member information */} +

{memberIdText}{memberInfo.memberId}

+

{nameText}{memberInfo.name}

+

{emailText}{memberInfo.email}

+

{createdAtText}{memberInfo.createdAt}

+
+ ) : ( +

{loadingText}

+ )} +
+
+ ); +}; + +interface MemberInfoModalProps { + onClose: () => void; + memberId: string; +} +interface MemberData { + memberId: number; + email: string; + name: string; + createdAt: string; +} + +// Styled Components Definitions: + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const Title = styled.h2` + margin-bottom: 20px; + font-size: 1.6rem; + font-weight: 400; +`; + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +export default MemberInfoModal; diff --git a/client/src/components/Profile/memberWithdrawalModal.tsx b/client/src/components/Profile/memberWithdrawalModal.tsx new file mode 100644 index 00000000..09cd9721 --- /dev/null +++ b/client/src/components/Profile/memberWithdrawalModal.tsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import axios from 'axios'; + +const MemberWithdrawalModal: React.FC = ({ onClose, memberId }) => { + const [password, setPassword] = useState(''); + const [memberPassword, setMemberPassword] = useState(null); + + const withdrawalTitle = "StockHolm에서 탈퇴하시겠습니까?"; + const passwordInputLabel = "비밀번호를 입력해주세요."; + const incorrectPasswordMsg = "Incorrect password!"; + const withdrawalButtonText = "회원탈퇴"; + + useEffect(() => { + axios.get(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com/members/${memberId}`) + .then(response => { + setMemberPassword(response.data.password); + }) + .catch(error => { + console.error("Error fetching member password:", error); + }); + }, [memberId]); + + const handleWithdrawal = () => { + if (password === memberPassword) { + axios.delete(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com/members/${memberId}`) + .then(response => { + if (response.status === 204) { + alert('회원탈퇴 되었습니다!'); + onClose(); + } + }) + .catch(error => { + console.error('Error deleting member:', error); + }); + } else { + alert(incorrectPasswordMsg); + } + }; + + return ( + + + × + {withdrawalTitle} + + setPassword(e.target.value)} /> + {withdrawalButtonText} + + + ); +}; + +interface MemberWithdrawalModalProps { + onClose: () => void; + memberId: string; +} + +// Styled Components Definitions: + +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; + + +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +const Title = styled.h3` + font-size: 1.2rem; + margin-bottom: 20px; +`; + +const Label = styled.label` + display: block; + margin-bottom: 10px; +`; + +const PasswordInput = styled.input` + width: 100%; + padding: 10px; + border: 1px solid lightgray; + border-radius: 5px; + margin-bottom: 20px; +`; + +const WithdrawalButton = styled.button` + padding: 10px 20px; + background-color: darkred; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +`; + +export default MemberWithdrawalModal; diff --git a/client/src/components/Profile/profileModal.tsx b/client/src/components/Profile/profileModal.tsx new file mode 100644 index 00000000..73b37bca --- /dev/null +++ b/client/src/components/Profile/profileModal.tsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const ProfileModal: React.FC = ({ onClose }) => { + const [selectedTab, setSelectedTab] = useState(1); + + const handleTabChange = (tabNumber: number) => { + setSelectedTab(tabNumber); + }; + + return ( + + + × + + handleTabChange(1)}>회원정보 + + handleTabChange(2)}>현금 + handleTabChange(3)}>회원탈퇴 + + + {selectedTab === 1 &&
회원정보 Content
} + {selectedTab === 2 &&
현금 Content
} + {selectedTab === 3 &&
회원탈퇴 Content
} +
+
+
+ ); +}; + +interface ProfileModalProps { + onClose: () => void; +} + + +// 모달 배경 스타일 +const ModalBackground = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +`; + +// 모달 컨테이너 스타일 +const ModalContainer = styled.div` + position: relative; + background-color: white; + padding: 20px; + width: 400px; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; +`; +const Tabs = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + margin-bottom: 20px; +`; + +// 모달 닫기 버튼 스타일 +const CloseButton = styled.button` + position: absolute; + top: 10px; + right: 10px; + background: #FFFFFF; + border: 1px solid lightgray; + font-size: 1.5rem; + cursor: pointer; +`; + +interface TabButtonProps { + active?: boolean; +} + +const TabButton = styled.button` + flex: 1; + padding: 10px; + border: 1px solid lightgray; + border-radius: 5px; + cursor: pointer; + background-color: ${({ active }) => (active ? 'darkslategray' : '#FFFFFF')}; + color: ${({ active }) => (active ? '#FFFFFF' : 'darkslategray')}; +`; + +const TabContent = styled.div` + width: 100%; +`; + + +export default ProfileModal; \ No newline at end of file From 5b00216c3f282e45368a96efafddaaa1fd306a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=91=ED=98=84?= Date: Fri, 8 Sep 2023 17:10:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Headers/LogoutHeader.tsx | 9 +++-- client/src/components/Signups/EmailSignup.tsx | 6 +++ client/src/components/Signups/Password.tsx | 7 ++++ client/src/reducer/member/memberInfoSlice.ts | 37 +++++++++++++++++++ client/src/store/config.ts | 2 + 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 client/src/reducer/member/memberInfoSlice.ts diff --git a/client/src/components/Headers/LogoutHeader.tsx b/client/src/components/Headers/LogoutHeader.tsx index fd7b155e..a34756d1 100644 --- a/client/src/components/Headers/LogoutHeader.tsx +++ b/client/src/components/Headers/LogoutHeader.tsx @@ -32,6 +32,11 @@ const LogoutHeader: React.FC = ({ onLoginClick }) => { }; export default LogoutHeader; +// 프롭스 타입 정의 +interface LogoutHeaderProps { + onLoginClick: () => void; +} + // 스타일드 컴포넌트 정의 @@ -84,8 +89,4 @@ const LoginButton = styled.button` } `; -// 프롭스 타입 정의 -interface LogoutHeaderProps { - onLoginClick: () => void; -} diff --git a/client/src/components/Signups/EmailSignup.tsx b/client/src/components/Signups/EmailSignup.tsx index 1c173c5b..da3ccaee 100644 --- a/client/src/components/Signups/EmailSignup.tsx +++ b/client/src/components/Signups/EmailSignup.tsx @@ -2,6 +2,8 @@ import axios from 'axios'; import styled from 'styled-components'; import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { setEmailForVerification } from '../../reducer/member/memberInfoSlice'; // 문자열 상수 정의 const strings = { @@ -12,9 +14,11 @@ const strings = { }; const EmailSignupModal: React.FC = ({ onClose, onRequestVerification }) => { + const dispatch = useDispatch(); // 사용자가 입력한 이메일 상태 const [email, setEmail] = useState(''); const [isInvalidEmail, setIsInvalidEmail] = useState(false); // 이메일 유효성 상태 + // 이메일 입력 핸들러 const handleEmailChange = (event: React.ChangeEvent) => { @@ -37,6 +41,8 @@ const EmailSignupModal: React.FC = ({ onClose, onRequestV try { const response = await axios.post('http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/email/send', { email }); if (response.status === 200) { + // 여기서 Redux store의 emailForVerification에 데이터 저장 + dispatch(setEmailForVerification(email)); onRequestVerification(email); } else { console.error('Error sending verification email'); diff --git a/client/src/components/Signups/Password.tsx b/client/src/components/Signups/Password.tsx index af485a29..4b1224f9 100644 --- a/client/src/components/Signups/Password.tsx +++ b/client/src/components/Signups/Password.tsx @@ -3,6 +3,9 @@ import axios from 'axios'; import React, { useState } from 'react'; import styled from 'styled-components'; +import { useDispatch } from 'react-redux'; +import { setMemberInfo } from '../../reducer/member/memberInfoSlice'; + const strings = { titleText: "비밀번호 설정", passwordLabelText: "비밀번호", @@ -14,6 +17,7 @@ const strings = { }; const PasswordSettingModal: React.FC = ({ onClose, onNext, email }) => { + const dispatch = useDispatch(); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [name, setName] = useState(''); @@ -62,6 +66,9 @@ const PasswordSettingModal: React.FC = ({ onClose, on if (response.status === 201) { console.log('Data sent successfully'); + + // 여기서 Redux store의 memberInfo에 데이터 저장 + dispatch(setMemberInfo(response.data)); onClose(); onNext(); } else { diff --git a/client/src/reducer/member/memberInfoSlice.ts b/client/src/reducer/member/memberInfoSlice.ts new file mode 100644 index 00000000..0fc1a056 --- /dev/null +++ b/client/src/reducer/member/memberInfoSlice.ts @@ -0,0 +1,37 @@ +// memberInfoSlice.ts + +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface MemberInfo { + email: string; + name: string; + password: string; + confirmPassword: string; +} + +interface MemberInfoState { + memberInfo: MemberInfo | null; + emailForVerification: string | null; // 이메일 상태 추가 +} + +const initialState: MemberInfoState = { + memberInfo: null, + emailForVerification: null, // 초기값 설정 +}; + +const memberInfoSlice = createSlice({ + name: 'memberInfo', + initialState, + reducers: { + setMemberInfo: (state, action: PayloadAction) => { + state.memberInfo = action.payload; + }, + setEmailForVerification: (state, action: PayloadAction) => { + state.emailForVerification = action.payload; // 액션 추가 + }, + }, +}); + +export const { setMemberInfo, setEmailForVerification } = memberInfoSlice.actions; + +export default memberInfoSlice.reducer; \ No newline at end of file diff --git a/client/src/store/config.ts b/client/src/store/config.ts index 5a3b3d55..188ef5e2 100644 --- a/client/src/store/config.ts +++ b/client/src/store/config.ts @@ -5,6 +5,7 @@ import { stockOrderPriceReducer } from "../reducer/StockOrderPrice-Reducer"; import { expandScreenReducer } from "../reducer/ExpandScreen-Reducer"; import { stockOrderSetReducer } from "../reducer/StockOrderSet-Reducer"; import { companyIdReducer } from "../reducer/CompanyId-Reducer"; +import memberInfoReducer from '../reducer/member/memberInfoSlice'; const store = configureStore({ reducer: { @@ -14,6 +15,7 @@ const store = configureStore({ expandScreen: expandScreenReducer, stockOrderSet: stockOrderSetReducer, companyId: companyIdReducer, + memberInfo: memberInfoReducer, }, });