From 72b59dbc5b11192713ffae4fcdb70337b00491d2 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 7 Jun 2025 20:20:34 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20api=20interceptor=20=EB=A6=AC?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=A0=89=EC=85=98=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/api.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/api.tsx b/src/apis/api.tsx index 2aeafeb..4610ffd 100644 --- a/src/apis/api.tsx +++ b/src/apis/api.tsx @@ -75,7 +75,7 @@ api.interceptors.response.use( } // 로그인 페이지로 리다이렉트 - window.location.href = '/login'; + window.location.href = '/'; return Promise.reject(refreshError); } From 29a8a6c13a70dda652bdcf478a229fbcfa4c9427 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 7 Jun 2025 21:05:27 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=B0=8F=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManagePage/components/SideComponents.tsx | 291 +++++++++++++++++- 1 file changed, 275 insertions(+), 16 deletions(-) diff --git a/src/pages/ManagePage/components/SideComponents.tsx b/src/pages/ManagePage/components/SideComponents.tsx index e28fbea..f5b7ad0 100644 --- a/src/pages/ManagePage/components/SideComponents.tsx +++ b/src/pages/ManagePage/components/SideComponents.tsx @@ -1,12 +1,84 @@ -import { FC, useState } from 'react'; +import { FC, useState, useEffect } from 'react'; import styled from 'styled-components'; import { SideComponentsProps } from '../interface'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import { userAPI, User } from '../../../apis/User'; +import { authAPI } from '../../../apis/Auth'; +import { useNotificationStore } from '../../../hooks/notificationStore'; const SideComponents: FC = ({ selectPage, ChooseIndex, }) => { const [toggle, setToggle] = useState([false, false, false]); + const [showProfileModal, setShowProfileModal] = useState(false); + const [isModalClicked, setIsModalClicked] = useState(false); + const [userInfo, setUserInfo] = useState({ + _id: 0, + name: '', + organization: '', + phone: '', + isAdmin: false, + isApproved: false, + }); + + // 로딩 상태 추가 + const [isLoading, setIsLoading] = useState(true); + + // 컴포넌트 마운트 시 사용자 정보 가져오기 + useEffect(() => { + const fetchUserInfo = async () => { + try { + setIsLoading(true); + // localStorage나 context에서 사용자 ID를 가져와야 함 + const userId = localStorage.getItem('_id'); // 또는 다른 방법으로 ID 획득 + + if (userId) { + const userData = await userAPI.getUser(userId); + setUserInfo(userData); + } + } catch (error) { + console.error('사용자 정보 조회 실패:', error); + } finally { + setIsLoading(false); + } + }; + + fetchUserInfo(); + }, []); + + //프로필 영역 hover와 active기능 구현 + const handleProfileClick = () => { + setIsModalClicked(true); + setShowProfileModal(true); + }; + + const handleProfileMouseEnter = () => { + if (!isModalClicked) { + setShowProfileModal(true); + } + }; + + const handleProfileMouseLeave = () => { + if (!isModalClicked) { + setShowProfileModal(false); + } + }; + + const handleOutsideClick = () => { + setIsModalClicked(false); + setShowProfileModal(false); + }; + + const showNotification = useNotificationStore( + (state) => state.showNotification + ); + const handleLogout = async () => { + await authAPI.logout(); + + showNotification('로그아웃 되었습니다.', { severity: 'success' }); + window.location.href = '/'; + }; return ( @@ -108,9 +180,64 @@ const SideComponents: FC = ({ 지도 - - © 2025. SMC101LAB - + + + © 2025. SMC101LAB + + + {/* 프로필 영역 */} + + {isLoading ? ( + + 사용자 정보 로딩 중... + + ) : ( + + + {userInfo.name} + {/* 프로필 모달 */} + {showProfileModal && ( + + + + 이름 : + {userInfo.name} + + + 권한 : + + {userInfo.isAdmin ? ( + 관리자 + ) : ( + 일반 사용자 + )} + + + + 소속 : + {userInfo.organization} + + + 로그아웃 + + )} + + )} + + + + {/* 모달이 클릭으로 열린 상태일 때 외부 클릭으로 닫기 */} + {isModalClicked && } ); }; @@ -145,10 +272,6 @@ const LogoWrapper = styled.div` justify-content: center; align-items: center; `; -const IndexWrapper = styled.div` - width: 100%; - height: 50%; -`; const ToggleIndexContainer = styled.div` width: 100%; @@ -248,21 +371,157 @@ const IndexText = styled.div<{ $isSelect: boolean }>` padding-left: 30px; `; +const CopyrightText = styled.div` + @media ${({ theme }) => theme.device.tablet} { + font-size: ${({ theme }) => theme.fonts.sizes.ms}; + } + color: ${({ theme }) => theme.colors.grey[500]}; + font-size: ${({ theme }) => theme.fonts.sizes.ms}; + text-align: center; +`; + +const ProfileContainer = styled.div` + position: relative; + display: flex; + align-items: center; + gap: 10px; + padding: 10px; + border-radius: 8px; + transition: all 0.2s ease-in-out; + + &:hover { + background-color: ${({ theme }) => theme.colors.grey[100]}; + } +`; + +const ProfileName = styled.div` + color: ${({ theme }) => theme.colors.grey[700]}; + font-size: ${({ theme }) => theme.fonts.sizes.ms}; + font-weight: ${({ theme }) => theme.fonts.weights.medium}; +`; + +const ProfileModal = styled.div` + position: absolute; + bottom: 100%; + left: 0; + right: 0; + background: white; + border: 1px solid ${({ theme }) => theme.colors.grey[300]}; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 16px; + margin-bottom: 8px; + z-index: 1000; + + &::after { + content: ''; + position: absolute; + top: 100%; + left: 20px; + border: 6px solid transparent; + border-top-color: white; + } +`; + +const ProfileInfo = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 12px; +`; + +const InfoItem = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const InfoLabel = styled.span` + color: ${({ theme }) => theme.colors.grey[600]}; + font-size: ${({ theme }) => theme.fonts.sizes.ms}; + font-weight: ${({ theme }) => theme.fonts.weights.medium}; +`; + +const InfoValue = styled.span` + color: ${({ theme }) => theme.colors.grey[800]}; + font-size: ${({ theme }) => theme.fonts.sizes.ms}; +`; + +const AdminBadge = styled.span` + background-color: ${({ theme }) => theme.colors.primary}; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: ${({ theme }) => theme.fonts.sizes.xs}; + font-weight: ${({ theme }) => theme.fonts.weights.bold}; +`; + +const UserBadge = styled.span` + background-color: ${({ theme }) => theme.colors.grey[400]}; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: ${({ theme }) => theme.fonts.sizes.xs}; + font-weight: ${({ theme }) => theme.fonts.weights.bold}; +`; + +const LogoutButton = styled.button` + width: 100%; + padding: 8px 12px; + background-color: ${({ theme }) => theme.colors.primary}; + color: white; + border: none; + border-radius: 4px; + font-size: ${({ theme }) => theme.fonts.sizes.ms}; + font-weight: ${({ theme }) => theme.fonts.weights.medium}; + cursor: pointer; + transition: all 0.2s ease-in-out; + + &:hover { + background-color: ${({ theme }) => theme.colors.primaryDark}; + } +`; +const IndexWrapper = styled.div` + width: 100%; + flex: 1; +`; + +const BottomWrapper = styled.div` + width: 100%; + margin-top: auto; +`; + const CopyrightWrapper = styled.div` width: 100%; - padding: 20px; + padding: 10px; display: flex; justify-content: center; align-items: center; - position: absolute; +`; + +const ProfileWrapper = styled.div` + width: 100%; + padding: 8px; + border-top: 1px solid ${({ theme }) => theme.colors.grey[300]}; +`; +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; bottom: 0; + z-index: 999; + background: transparent; +`; +const LoadingWrapper = styled.div` + width: 100%; + flex: 1; + display: flex; + justify-content: center; + align-items: center; `; -const CopyrightText = styled.div` - @media ${({ theme }) => theme.device.tablet} { - font-size: ${({ theme }) => theme.fonts.sizes.ms}; - } - color: ${({ theme }) => theme.colors.grey[500]}; +const LoadingText = styled.div` + color: ${({ theme }) => theme.colors.grey[600]}; font-size: ${({ theme }) => theme.fonts.sizes.ms}; - text-align: center; `; From f988947e24f20108c1ca87d3cd1fed35b82de3f4 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 7 Jun 2025 21:11:01 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20smc101lab=20->=20smc101=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MapPage/components/map/PrivacyPolicyModal.tsx | 4 ++-- src/pages/MapPage/components/map/TermsofUseModal.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx b/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx index 3b3103e..ff13627 100644 --- a/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx +++ b/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx @@ -32,8 +32,8 @@ const PrivacyPolicyModal = ({ isOpen, onClose }: PrivacyPolicyModalProps) => { SMC101LAB 개인정보 처리방침 - SMC101LAB(이하 '개인정보처리자')은(는) 정보주체의 자유와 권리 보호를 - 위해 「개인정보 보호법」 및 관계 법령이 정한 바를 준수하여, 적법하게 + SMC101(이하 '개인정보처리자')은(는) 정보주체의 자유와 권리 보호를 위해 + 「개인정보 보호법」 및 관계 법령이 정한 바를 준수하여, 적법하게 개인정보를 처리하고 안전하게 관리하고 있습니다. 이에 「개인정보 보호법」 제30조에 따라 정보주체에게 개인정보 처리에 관한 절차 및 기준을 안내하고, 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 diff --git a/src/pages/MapPage/components/map/TermsofUseModal.tsx b/src/pages/MapPage/components/map/TermsofUseModal.tsx index 9035447..17e8a13 100644 --- a/src/pages/MapPage/components/map/TermsofUseModal.tsx +++ b/src/pages/MapPage/components/map/TermsofUseModal.tsx @@ -275,7 +275,7 @@ const TermsofUseModal = ({ isOpen, onClose }: TermsofUseModalProps) => { 없습니다. - 저작권 표시 "Copyright © 2025 SMC101"은 웹 사이트 하단에 상시 + 저작권 표시 "Copyright © 2025 SMC101LAB"은 웹 사이트 하단에 상시 노출됩니다.