비로그인 사용자의 접근 관리하기
이슈
현재 우리의 앱은 로그인을 해야만 이용할 수 있다.
그러나 이 방식은 서비스 측면에서 좋지 않다고 생각한다.
앱의 서비스와 콘텐츠가 어떤 것이 있는지 보여주지도 않고,
로그인과 회원가입을 먼저 요구하는 것은 이상하지 않은가?
앱을 처음 방문한 사용자의 대부분은 그냥 떠나버릴 것이라고 생각한다.
그래서 비로그인 사용자도 앱의 주요 기능을 이용할 수 있도록 제한을 완화해보았다.
로그인한 유저의 정보가 꼭 필요한 Private 기능과,
그렇지 않은 Public 기능으로 나누어 권한을 관리해야 할 것이다. 이를 정리하면 다음과 같다.
비로그인 사용자의 권한
원래 가능했던 것
- 로그인 페이지에 접근할 수 있다. 로그인할 수 있다.
- 회원가입 페이지에 접근할 수 있다. 회원가입할 수 있다.
- 404 페이지에 접근할 수 있다.
허용 (Public)
- 메인페이지에 접근할 수 있다. 게시물 목록을 볼 수 있다.
- 게시물 상세 페이지에 접근할 수 있다.
- 태그 페이지에 접근할 수 있다.
- 검색 페이지에 접근할 수 있다. 검색할 수 있다.
- 다른 유저의 마이페이지에 접근할 수 있다.
- 팔로우 페이지에 접근할 수 있다 (팔로우 신청은 할 수 없다)
제한 (Private)
- 게시물에 좋아요를 누를 수 있다 (그러나 저장은 안 된다)
- 게시물에 댓글을 달 수 없다.
- 게시물 등록 페이지에 접근할 수 없다. 게시물을 등록하거나 수정할 수 없다.
- 자신의 마이페이지에 접근할 수 없다.
- 자신의 정보 페이지에 접근할 수 없다.
- 자신의 정보 수정 페이지에 접근할 수 없다.
- 다른 유저에게 팔로우를 신청할 수 없다.
- 알림 페이지에 접근할 수 없다. (헤더에 알림 아이콘이 표시되지 않음)
모달 만들기
비로그인 사용자가 제한된 기능에 접근할 경우 다음과 같은 모달을 띄워 보여준다.
이를 위해 LoginRequireModal이라는 컴포넌트를 만들었다.
import { Modal } from 'components';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import theme from 'styles/theme';
const MODAL_TITLE_LOGIN_REQUIRED = '로그인이 필요한 서비스입니다.';
const MODAL_DESCRIPTION_LOGIN_REQUIRED = '로그인 화면으로 이동하시겠어요?';
const LoginRequireModal = ({ visible, onClose }) => {
const navigate = useNavigate();
const handleClick = useCallback(() => {
navigate('/login', { replace: true });
}, [navigate]);
return (
<Modal visible={visible} onClose={onClose}>
<Modal.Content
title={MODAL_TITLE_LOGIN_REQUIRED}
description={MODAL_DESCRIPTION_LOGIN_REQUIRED}
/>
<Modal.Button onClick={handleClick}>예</Modal.Button>
<Modal.Button onClick={onClose} style={modalButtonStyle}>
아니오
</Modal.Button>
</Modal>
);
};
const modalButtonStyle = {
color: theme.color.fontBlack,
backgroundColor: theme.color.backgroundNormal,
};
export default LoginRequireModal;
사전에 만들어두었던 Modal 컴포넌트를 기반으로 만든 것이다.
Modal 컴포넌트에 관해서는 아래의 깃허브 코드를 참고해주시기 바란다.
여기서는 간략히만 설명하겠다.
‘예’ 버튼을 누를 경우 로그인 화면으로 이동하도록 처리한다.
‘아니오’ 버튼을 누르거나 모달 바깥을 클릭했을 때 모달이 종료되는데,
이에 대한 처리는 prop으로 받는 onClose를 통해서 한다.
navigate(-1)
을 통해 뒤로가거나, 원래의 화면 그대로 있거나 둘 중 하나이다.
로그인이 필요한 페이지 접근 처리
Router 폴더에 PrivateWrapper 컴포넌트를 추가하였다.
import useLocalToken from 'hooks/useLocalToken';
import { useNavigate, Outlet } from 'react-router-dom';
import LoginRequireModal from 'components/Modal/customs/LoginRequireModal';
import { useCallback } from 'react';
const PrivateWrapper = () => {
const [token] = useLocalToken();
const navigate = useNavigate();
const handleCloseModal = useCallback(() => {
navigate(-1);
}, [navigate]);
return token ? (
<Outlet />
) : (
<>
<Outlet />
<LoginRequireModal visible={true} onClose={handleCloseModal} />
</>
);
};
export default PrivateWrapper;
사전에 만들어 둔 useLocalToken으로 로컬스토리지에서 token을 꺼내온다.
useLocalToken 코드
token이 존재한다면 로그인이 된 것이므로 페이지를 그대로 보여준다.
여기서 Outlet은 React-Router v6에서 도입된 기능으로,
부모 경로 요소에서 자식 경로 요소를 렌더링하는 데 사용된다.
간단히 말해 {children}을 사용하는 것과 같은 효과가 난다.
힌편, token이 존재한다면 로그인이 되지 않은 것이므로
페이지를 보여주되, LoginRequireModal을 띄운다.
이를 Router에 적용한다. 로그인이 필요한 Private 페이지일 경우 다음과 같이 적용한다.
<Route element={<PrivateWrapper />}>
<Route path="/post/create" element={<PostEditPage />} />
</Route>
참고로, 로그인이 불필요한 Public 페이지는 아래와 같다.
<Route path="/" element={<PostEditPage />} />
로그인이 필요한 기능 제한 처리
좋아요, 댓글, 팔로우 신청 시 LoginRequireModal을 띄운다.
이것들은 각 페이지에서 작업해야 한다.
예를 들어, 댓글 작성은 PostDetailPage에 있으므로 해당 페이지에서 처리한다.
우선, 다음과 같이 LoginRequireModal의 렌더링 여부를 상태로 관리한다.
const [loginModalOn, setLoginModalOn] = useState(false);
비로그인한 유저가 댓글을 작성한 경우 아래와 같이 처리한다.
if (!token) {
setLoginModalOn(true);
return;
}
그러면 LoginRequireModal이 렌더링된다.
<LoginRequireModal visible={loginModalOn} onClose={handleCloseModal} />
LoginRequireModal이 닫힌 경우(아니오를 누르거나 모달 바깥을 클릭한 경우)
handleCloseModal을 통해 처리한다.
const handleCloseModal = useCallback(() => {
setLoginModalOn(false);
}, []);
전체 코드는 아래 링크를 참고해주시기 바란다.
결과물
학습을 진행하면서 작성한 글 입니다.
정확하지 않은 지식이 있을 수 있습니다. 참고로만 봐주시기 바랍니다.