
고마워써 프로젝트에서는 왼쪽 상단 햄버거 메뉴에서 여러 경로에 접근 가능하다.
햄버거 메뉴는 react-burger-menu 라이브러리를 이용했다.
import { slide as Menu } from "react-burger-menu";
//...
// jsx
<Menu
customBurgerIcon={<GiHamburgerMenu size={"30"} />}
customCrossIcon={<FaArrowLeft size={"30"} />}
width={"250px"}
isOpen={isMenuOpen}
onStateChange={({ isOpen }) => setIsMenuOpen(isOpen)}
styles={MenuStyles}
>
<StyledMenuItem to="/update-info" onClick={closeMenu}>
회원정보 수정
</StyledMenuItem>
<StyledMenuItem to="/yearly-presents" onClick={closeMenu}>
나의 편지함
</StyledMenuItem>
<StyledMenuItem to="/yearly-presents" onClick={closeMenu} $isSubMenu>
<CustomIcon>✶</CustomIcon>올해 받은 연하장
</StyledMenuItem>
<StyledMenuItem to="/all-presents" onClick={closeMenu} $isSubMenu>
<CustomIcon>✷</CustomIcon>전체 수신 목록
</StyledMenuItem>
<StyledMenuItem to="/create-form">편지 보내기</StyledMenuItem>
<StyledMenuItem to="/create-form" onClick={closeMenu} $isSubMenu>
<CustomIcon>✶</CustomIcon>신청받기
</StyledMenuItem>
<StyledMenuItem to="/receiver-list" onClick={closeMenu} $isSubMenu>
<CustomIcon>✷</CustomIcon>작성하기
</StyledMenuItem>
<StyledMenuItem to="/design" onClick={closeMenu} $isSubMenu>
<CustomIcon>✸</CustomIcon>디자인하기
</StyledMenuItem>
<StyledMenuItem to="" onClick={logoutProc}>
로그아웃
</StyledMenuItem>
</Menu>
$isSubmenu는 css에서 차이를 주려고 넣은 옵션이다.
항목을 클릭하면 이동과 동시에 햄버거 메뉴가 닫히게 onClick 이벤트도 넣어 줬다.
로그인이 필요한 경로의 경우 privateRouter 패턴을 사용해서 구현했다.
interface PrivateRouterProps extends RouteProps {
component: ComponentType<any>;
}
const PrivateRouter: React.FC<PrivateRouterProps> = ({
component: Component,
...rest
}) => {
const userId = parseInt(sessionStorage.getItem("userId") || "0");
const [validate, setValidate] = useState<boolean | null>(null);
const nav = useHistory();
useEffect(() => {
let isMounted = true;
const validateToken = async () => {
try {
const res = await axios.get(`/api/users/token/validate/${userId}`);
if (isMounted) {
setValidate(res.data as boolean);
if (!res.data) {
toast.error("인증 만료. 다시 로그인해 주세요.");
nav.push("/");
}
}
} catch (error) {
console.error(error);
if (isMounted) {
setValidate(false);
toast.error("인증 실패. 다시 시도해 주세요.");
}
}
};
if (userId) {
validateToken();
} else {
setValidate(false);
toast.error("로그인 후 이용해 주세요!");
}
return () => {
isMounted = false;
};
}, [userId]);
if (validate === null) {
return <div>Loading...</div>;
}
return (
<Route
{...rest}
render={(props) =>
validate ? <Component {...props} /> : <Redirect to="/" />
}
/>
);
};
axios로 검증할 때는, userId와 token 안에 있는 정보를 대조하는 방식을 사용한다.
@GetMapping("/token/validate/{userId}")
@Operation(summary = "token validate", description = "토큰 검증")
public ResponseEntity<Boolean> validateToken(@PathVariable Integer userId, HttpServletRequest request) {
return ResponseEntity.ok(authService.validateToken(userId, request));
}
public boolean validateToken(Integer userId, HttpServletRequest request) {
String token = jwtTokenProvider.resolveToken(request);
// 토큰이 null이거나 유효하지 않으면 false 반환
if (token == null || !jwtTokenProvider.validateToken(token)) {
return false;
}
// 토큰에서 kakaoId 추출 후 DB 조회 및 검증
String kakaoId = jwtTokenProvider.getUserId(token);
return userRepository.findByKakaoId(kakaoId)
.map(user -> user.getId().equals(userId))
.orElse(false); // 사용자가 없으면 false 반환
}
검증이 성공하면 해당 경로의 데이터를 출력하고,
검증이 실패하면 home 경로로 이동한 뒤 로그인 후 이용하라는 toast 알림이 출력된다.
'Dazz6 > ABOUT' 카테고리의 다른 글
| 고마워써 프로젝트 - #2-6. 연하장 신청받기 (0) | 2025.01.13 |
|---|---|
| 고마워써 프로젝트 - #2-5. 연하장 디자인하기 (0) | 2025.01.10 |
| 고마워써 프로젝트 - #2-3. 회원정보 수정 (0) | 2025.01.08 |
| 고마워써 프로젝트 - #2-2. 홈 화면 (0) | 2025.01.07 |
| 고마워써 프로젝트 - #2-1. ERD (0) | 2025.01.06 |