diff --git a/package.json b/package.json index 1e67312..1ab9cc8 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "scripts": { "start": "react-scripts start", - "start:windows": "set HTTPS=true&&set SSL_CRT_FILE=cert/localhost.pem&&set SSL_KEY_FILE=cert/localhost-key.pem&&npm run start", + "start:windows": "set HTTPS=true&&set SSL_CRT_FILE=./localhost.pem&&set SSL_KEY_FILE=./localhost-key.pem&&npm run start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", diff --git a/src/components/userProfile/userInfoBarCard/EmptyUSerInfobar.module.scss b/src/components/userProfile/userInfoBarCard/EmptyUSerInfobar.module.scss new file mode 100644 index 0000000..5765612 --- /dev/null +++ b/src/components/userProfile/userInfoBarCard/EmptyUSerInfobar.module.scss @@ -0,0 +1,118 @@ +@font-face { + font-family: Freesentation; + src: url('../../../assets/fonts/Freesentation-Bold.woff'); + } + + @font-face { + font-family: Freesentation; + src: url('../../../assets/fonts/Freesentation-Regular.woff'); + } + + @font-face { + font-family: Freesentation; + src: url('../../../assets/fonts/Freesentation-SemiBold.woff'); + } + + .UserInfoBar { + width: 300px; + background-color: var(--white); + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + border-right: 1px solid var(--gray_50); + } + + .UserPhoto { + width: 120px; + height: 120px; + background-color: var(--gray_100); + margin-top: 72px; + border-radius: 760px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + } + + .UserName { + margin-top: 4px; + min-width: 111px; + width: auto; + min-height: 64px; + height: auto; + padding: 10px; + background-color: white; + text-align: center; + } + + .UserName h1 { + font-size: 22px; + font-weight: 600; + line-height: 33px; + font: 'Freesentation'; + } + + .UserName span { + size: 18px; + font-weight: 400; + line-height: 27px; + font: 'Freesentation'; + color: var(--gray_500); + } + + .UserProfileNumber { + margin-top: 12px; + min-width: 216px; + width: auto; + min-height: 72px; + height: auto; + background-color: white; + display: flex; + justify-content: space-around; + gap: 10px; + align-items: center; + cursor: pointer; + } + + .UserProfilBox { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + } + + .UserProfileBox span { + display: block; + text-align: center; + margin-bottom: 5px; + } + + .ProfileBottom { + margin-top: 20px; + width: 260px; + height: 32px; + background-color: var(--gray_50); + text-align: center; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + cursor: pointer; + } + + .authContainer { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + \ No newline at end of file diff --git a/src/components/userProfile/UserInfoBar.tsx b/src/components/userProfile/userInfoBarCard/EmptyUserInfoBar.tsx similarity index 67% rename from src/components/userProfile/UserInfoBar.tsx rename to src/components/userProfile/userInfoBarCard/EmptyUserInfoBar.tsx index cce5467..6cad641 100644 --- a/src/components/userProfile/UserInfoBar.tsx +++ b/src/components/userProfile/userInfoBarCard/EmptyUserInfoBar.tsx @@ -1,127 +1,99 @@ -import React, { useState, useEffect } from 'react'; -import AuthContainer from '../login/AuthContainer'; -import useRegisterStore from '../../stores/registerStore'; -import { RegisterStatus } from '../../types/enum/RegisterStatus'; - -import styles from './UserInfoBar.module.scss'; -import { ReactComponent as ProfilePerson } from '../../assets/img_user_default_profile.svg'; -import Following from './followModal/Following'; -import Follower from './followModal/Follower'; - -import instance from '../../apis/instance'; - -const UserInfoBar = (props: { children?: React.ReactNode }) => { - const [isFollowingOpen, setIsFollowingOpen] = useState(false); - const [isFollowerOpen, setIsFollowerOpen] = useState(false); - const [isLog, setIsLog] = useState(false); - const [isOverlayVisible, setIsOverlayVisible] = useState(false); - const [userData, setUserData] = useState({ - profileImage:'', - profileId:'', - mapCnt:0, - followerCnt:0, - followingCnt:0, - }) - - useEffect(() => { - const fetchUserData = async () => { - try { - const response = await instance.get('/user'); - const data = response.data; - - setUserData({ - profileImage: data.profileImage, - mapCnt:data.mapCnt, - profileId: data.profileId, - followerCnt: data.followerCnt, - followingCnt: data.followingCnt, - }); - } catch (error) { - console.error('Failed to fetch user data', error); - } - }; - - fetchUserData(); - }, [userData]); - - const { loginNeeded, registerStatus, setLoginNeededStatus } = - useRegisterStore(); - - const openFollowing = () => { - setIsFollowingOpen(true); - }; - - const closeFollowing = () => { - setIsFollowingOpen(false); - }; - - const openFollower = () => { - setIsFollowerOpen(true); - }; - - const closeFollower = () => { - setIsFollowerOpen(false); - }; - - useEffect(() => { - if (registerStatus !== RegisterStatus.LOG_IN && loginNeeded) { - setIsLog(false); - setIsOverlayVisible(true); - console.log('setDimmed(true'); - } else { - setIsLog(true); - setIsOverlayVisible(false); - console.log('setDimmed(false)'); - } - }, [loginNeeded, registerStatus]); - - const handleClose = () => { - setLoginNeededStatus(false); - setIsOverlayVisible(false); - }; - - const handleLoginClick = () => { - setLoginNeededStatus(true); - setIsOverlayVisible(true); - }; - - return ( -
-
- -
-
-

환영해요!

- 로그인이 필요해요 -
-
-
-
내 지도
- 0 -
-
-
팔로워
- {userData.followerCnt} -
-
-
팔로잉
- {userData.followingCnt} -
-
-
- 로그인하기 -
- - {isFollowingOpen && } - {isFollowerOpen && } - {isOverlayVisible && ( - <> -
- - - )} -
- ); -}; - -export default UserInfoBar; +import React, { useState, useEffect } from 'react'; +import AuthContainer from '../../login/AuthContainer'; +import useRegisterStore from '../../../stores/registerStore'; +import { RegisterStatus } from '../../../types/enum/RegisterStatus'; + +import styles from './UserInfoBar.module.scss'; +import { ReactComponent as ProfilePerson } from '../../../assets/img_user_default_profile.svg'; +import Following from '../followModal/Following'; +import Follower from '../followModal/Follower'; + +import instance from '../../../apis/instance'; + +const UserInfoBar = (props: { children?: React.ReactNode }) => { + const [isFollowingOpen, setIsFollowingOpen] = useState(false); + const [isFollowerOpen, setIsFollowerOpen] = useState(false); + const [isLog, setIsLog] = useState(false); + const [isOverlayVisible, setIsOverlayVisible] = useState(false); + + const { loginNeeded, registerStatus, setLoginNeededStatus } = + useRegisterStore(); + + const openFollowing = () => { + setIsFollowingOpen(true); + }; + + const closeFollowing = () => { + setIsFollowingOpen(false); + }; + + const openFollower = () => { + setIsFollowerOpen(true); + }; + + const closeFollower = () => { + setIsFollowerOpen(false); + }; + + useEffect(() => { + if (registerStatus !== RegisterStatus.LOG_IN && loginNeeded) { + setIsLog(false); + setIsOverlayVisible(true); + console.log('setDimmed(true'); + } else { + setIsLog(true); + setIsOverlayVisible(false); + console.log('setDimmed(false)'); + } + }, [loginNeeded, registerStatus]); + + const handleClose = () => { + setLoginNeededStatus(false); + setIsOverlayVisible(false); + }; + + const handleLoginClick = () => { + setLoginNeededStatus(true); + setIsOverlayVisible(true); + }; + + return ( +
+
+ +
+
+

환영해요!

+ 로그인이 필요해요 +
+
+
+
내 지도
+ 0 +
+
+
팔로워
+ 0 +
+
+
팔로잉
+ 0 +
+
+
+ 로그인하기 +
+ + {isFollowingOpen && } + {isFollowerOpen && } + {isOverlayVisible && ( + <> +
+ + + )} +
+ ); +}; + +export default UserInfoBar; diff --git a/src/components/userProfile/UserInfoBar.module.scss b/src/components/userProfile/userInfoBarCard/GetUserInfoBar.module.scss similarity index 90% rename from src/components/userProfile/UserInfoBar.module.scss rename to src/components/userProfile/userInfoBarCard/GetUserInfoBar.module.scss index 6932483..9d43d59 100644 --- a/src/components/userProfile/UserInfoBar.module.scss +++ b/src/components/userProfile/userInfoBarCard/GetUserInfoBar.module.scss @@ -1,16 +1,16 @@ @font-face { font-family: Freesentation; - src: url('../../assets/fonts/Freesentation-Bold.woff'); + src: url('../../../assets/fonts/Freesentation-Bold.woff'); } @font-face { font-family: Freesentation; - src: url('../../assets/fonts/Freesentation-Regular.woff'); + src: url('../../../assets/fonts/Freesentation-Regular.woff'); } @font-face { font-family: Freesentation; - src: url('../../assets/fonts/Freesentation-SemiBold.woff'); + src: url('../../../assets/fonts/Freesentation-SemiBold.woff'); } .UserInfoBar { diff --git a/src/components/userProfile/userInfoBarCard/GetUserInfoBar.tsx b/src/components/userProfile/userInfoBarCard/GetUserInfoBar.tsx new file mode 100644 index 0000000..f5344be --- /dev/null +++ b/src/components/userProfile/userInfoBarCard/GetUserInfoBar.tsx @@ -0,0 +1,167 @@ +import React, { useState, useEffect } from 'react'; +import AuthContainer from '../../login/AuthContainer'; +import useRegisterStore from '../../../stores/registerStore'; +import { RegisterStatus } from '../../../types/enum/RegisterStatus'; + +import styles from './GetUserInfoBar.module.scss'; +import { ReactComponent as ProfilePerson } from '../../../assets/img_user_default_profile.svg'; +import Following from '../followModal/Following'; +import Follower from '../followModal/Follower'; + +import instance from '../../../apis/instance'; + +const UserInfoBar = (props: { children?: React.ReactNode }) => { + const [isFollowingOpen, setIsFollowingOpen] = useState(false); + const [isFollowerOpen, setIsFollowerOpen] = useState(false); + const [isLog, setIsLog] = useState(false); + const [isOverlayVisible, setIsOverlayVisible] = useState(false); + const [userData, setUserData] = useState({ + nickname:'', + profileId:'', + imgUrl:'', + mapCnt:0, + followerCnt:0, + followingCnt:0, + }); + + const {setAccessToken, clearAccessToken} = useRegisterStore(); + useEffect(() => { + const reIssueToken = async () => { + try { + const response = await instance.get('/jwt/reissue'); + const data = response.data; + + if (data && data.accessToken) { + setAccessToken(data.accessToken); + instance.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`; + } + } catch (error) { + console.error('Failed to reissue token', error); + clearAccessToken(); // 실패 시 토큰 초기화 (로그아웃 처리) + } + }; + + const responseInterceptor = instance.interceptors.response.use( + (response) => response, // 성공적인 응답 그대로 전달 + async (error) => { + const originalRequest = error.config; + + if (!originalRequest._retry) { + originalRequest._retry = true; // 무한 루프 방지 + await reIssueToken(); // 토큰 재발급 시도 + return instance(originalRequest); // 재발급 후 원래 요청 재시도 + } + + return Promise.reject(error); + } + ); + + return () => { + // 컴포넌트 언마운트 시 인터셉터 해제 + instance.interceptors.response.eject(responseInterceptor); + }; + }, []); + + useEffect(() => { + const fetchUserData = async () => { + try { + const response = await instance.get('/user'); + const data = response.data.result; + + setUserData({ + nickname: data.nickname, + profileId: data.profileId, + imgUrl: data.imgUrl, + mapCnt:data.mapCnt, + followerCnt: data.followerCnt, + followingCnt: data.followingCnt, + }); + } catch (error) { + console.error('Failed to fetch user data', error); + } + }; + + fetchUserData(); + }, []); + + const { loginNeeded, registerStatus, setLoginNeededStatus } = + useRegisterStore(); + + const openFollowing = () => { + setIsFollowingOpen(true); + }; + + const closeFollowing = () => { + setIsFollowingOpen(false); + }; + + const openFollower = () => { + setIsFollowerOpen(true); + }; + + const closeFollower = () => { + setIsFollowerOpen(false); + }; + + useEffect(() => { + if (registerStatus !== RegisterStatus.LOG_IN && loginNeeded) { + setIsLog(false); + setIsOverlayVisible(true); + console.log('setDimmed(true'); + } else { + setIsLog(true); + setIsOverlayVisible(false); + console.log('setDimmed(false)'); + } + }, [loginNeeded, registerStatus]); + + const handleClose = () => { + setLoginNeededStatus(false); + setIsOverlayVisible(false); + }; + + const handleLoginClick = () => { + setLoginNeededStatus(true); + setIsOverlayVisible(true); + }; + + return ( +
+
+ +
+
+

{userData.nickname}

+ {userData.profileId} +
+
+
+
내 지도
+ {userData.mapCnt} +
+
+
팔로워
+ {userData.followerCnt} +
+
+
팔로잉
+ {userData.followingCnt} +
+
+
+ 로그인하기 +
+ + {isFollowingOpen && } + {isFollowerOpen && } + {isOverlayVisible && ( + <> +
+ + + )} +
+ ); +}; + +export default UserInfoBar; diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx index 87029e1..52725c7 100644 --- a/src/pages/UserProfile/UserProfile.tsx +++ b/src/pages/UserProfile/UserProfile.tsx @@ -3,8 +3,8 @@ import { Outlet, useParams } from 'react-router-dom'; import GlobalNavigationBar from '../../components/global/GlobalNavigationBar'; import styles1 from '../../components/global/GlobalNavigationBar.module.scss'; -import UserInfoBar from '../../components/userProfile/UserInfoBar'; -import styles2 from '../components/userProfile/UserInfoBar.module.scss'; +import UserInfoBar from '../../components/userProfile/userInfoBarCard/GetUserInfoBar'; +import styles2 from '../components/userProfile/GetUserInfoBar.module.scss'; import GetUser from '../../components/userProfile/GetUser'; import EmptyUser from '../../components/userProfile/EmptyUser'; @@ -39,8 +39,7 @@ const UserProfile = () => {
- {/*{RegisterStatus.LOG_IN ? : } 로그인구현 정상 되면 주석 제거*/} - + {RegisterStatus.LOG_IN ? : }
diff --git a/src/stores/registerStore.ts b/src/stores/registerStore.ts index 9bb7978..f8d1474 100644 --- a/src/stores/registerStore.ts +++ b/src/stores/registerStore.ts @@ -1,6 +1,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { RegisterStatus } from '../types/enum/RegisterStatus'; +import { access } from 'fs'; interface State { registerStatus: RegisterStatus; @@ -12,6 +13,8 @@ interface State { resetStatus: () => void; //로그인 필요한 상태로 초기화 - 로그아웃으로 사용 가능ㅋ setRegisterStatus: (status: RegisterStatus) => void; //미로그인/회원가입 상태 세팅 setLoginNeededStatus: (status: boolean) => void; + setAccessToken: (accessToken: string) => void; //새로운 액세스 토큰 설정 + clearAccessToken: () => void; //액세스 토큰 초기화 } const useRegisterStore = create( @@ -40,6 +43,12 @@ const useRegisterStore = create( setLoginNeededStatus: (status) => { set({ loginNeeded: status }); }, + setAccessToken: (accessToken) => { + set({accessToken:accessToken}); + }, + clearAccessToken: () => { + set({accessToken:undefined}); + } }), { name: 'registerStatusStorage' }, ),