swimminginthecode DIVE!

Dazz6/ABOUT

고마워써 프로젝트 - #2-4. 메뉴

dazz6 2025. 1. 9. 18:00

 

고마워써 프로젝트에서는 왼쪽 상단 햄버거 메뉴에서 여러 경로에 접근 가능하다.

햄버거 메뉴는 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 알림이 출력된다.