[TIL] 인증 서비스가 들어간 팬레터함 만들기

·2023년 12월 4일
1

TIL

목록 보기
48/85
post-thumbnail

이번 심화주차 개인과제는 지난 번 숙련주차 개인과제를 발전시켜 홈 화면과 상세화면에 인증시스템을 적용한 서비스로 발전시키는 것이었다.

[과제를 통해 배울 수 있는 것]

  • redux-toolkit을 이용한 전역 상태 관리
  • redux-toolkit의 thunk 기능을 이용한 서버 상태 관리
  • json-server를 이용한 REST API 통신
  • JWT 토큰을 이용한 인증/인가 기능 구현
  • REST API 서버에 파일 업로드 요청 방법

[필수 구현 사항]

  • 홈 화면 UI 구현 (CREATE, READ)
    • 팬 레터 추가 폼에서 닉네임 입력 값이 아닌 회원 가입 시 적용한 닉네임 표시
  • 상세 화면 UI 구현 (READ, UPDATE, DELETE)
    • 본인이 작성한 팬레터에서만 수정, 삭제 가능
  • 로그인 / 회원 가입 UI 구현
    • 로그인 해야만 팬레터 화면으로 진입 가능
  • 프로필 관리 UI 구현
    • 프로필 이미지, 닉네임 변경 기능 구현

과제가 처음 주어졌을 때는 늘 그랬듯이.. 막막하고.. 이걸 기한 안에 할 수 있을까 하지만 결국은 완성해서 제출하게 된다. 외않되? 하면서 좌절하다가.. 원하는 대로 동작할 때는 짜릿했다가..를 반복한다.

로그인 화면

// Router.js
const Router = () => {
  const { isLoggedIn } = useSelector((state) => state.authSlice);

  return (
    <BrowserRouter>
      <Routes>
        {isLoggedIn ? (
          <Route element={<Layout />}>
            <Route path="/" element={<Home />} />
            <Route path="/detail/:id" element={<Detail />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="*" element={<Navigate replace to="/" />} />
          </Route>
        ) : (
          <>
            <Route path="/" element={<Navigate replace to="/login" />} />
            <Route
              path="/detail/:id"
              element={<Navigate replace to="/login" />}
            />
            <Route path="/profile" element={<Navigate replace to="/login" />} />
            <Route path="/login" element={<Login />} />
          </>
        )}
      </Routes>
    </BrowserRouter>
  );
};

처음 / 페이지에 접속했을 때 로그인이 되어있지 않다면, /login 페이지로 이동한다. 이를 위해 전역으로 관리하고 있는 isLoggedIn 을 통해 삼항 연산자로, 로그인 되어있을 때와 로그인 되어있지 않을 때 해당 경로에 접속했을 때 다르게 동작하도록 구현했다. (로그인이 되어있지 않다면 Navigate 컴포넌트를 활용해서 /login 페이지로 이동시킨다.)
로그인과 회원가입 둘다 Login.jsx 컴포넌트에 구현하였으며 isLoginPage 라는 state에 boolen 값을 통해 switch 되도록 하였다.
Layout.js에 Outlet 컴포넌트를 적용하여 헤더와 푸터가 로그인 되어있을 때만 적용되도록 하였다.
"비밀번호가 일치하지 않습니다" 등의 내용을 react-toastify 라는 라이브러리를 통해 window 기본 alert 창 대신 사용하였다.

홈 화면

홈 화면의 UI는 기존 과 크게 다른 점이 없지만, 팬레터를 생성할 때 nickname을 직접 입력하는 것이 아닌, 가입 시 설정한 nickname 이 보여지도록 수정하였다. 그리고 헤더에 logout 버튼과 my profile 버튼을 만들었다. my profile 버튼을 클릭하면 /profile 페이지로 이동한다.

상세 화면

기존에 수정, 삭제 버튼이 전부 보이던 것에서 로그인 한 유저와 팬레터를 작성한 유저의 아이디가 일치할 때만 수정, 삭제 버튼이 보이도록 수정하였다.

프로필 화면

이미지를 클릭하면 파일 선택 창이 열리고, 이미지 파일 선택 시 이미지 프리뷰가 가능하도록 구현하였다. 그리고 JWT 인증 서버 API 문서를 참고하여 프로필 변경을 구현하였다.

프로필 변경까지는 큰 무리 없이 구현하였는데, 프로필 변경을 완료했을 때 json-server에 내가 이전에 작성한 팬레터들의 nickname과 avatar를 모두 수정하는 부분에서 조금 애를 먹었다.

json-server에 patch 요청을 보낼 때는

await jsonApi.patch(`/letters/${id}`, {
          nickname: payload.nickname,
          avatar: payload.avatar,
        });

이렇게 보내게 되는데, 여기서 id는 letter의 아이디 이고,
userId 가 작성한 letters 만 뽑고, 그 letters의 id만 뽑아서 patch 요청을 보내는게 관건이었다.

그래서 우선 해당 user가 직성한 letters만 get 요청을 통해 가져온다. (thunk 함수)

// commentSlice.js
export const __getUserLetters = createAsyncThunk(
  "GET_USER_LETTERS",
  async (payload, thunkAPI) => {
    try {
      const response = await jsonApi.get(`/letters?userId=${payload}`);
      console.log(response.data);
      return thunkAPI.fulfillWithValue(response.data);
    } catch (error) {
      console.log("error", error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);
// Profile.jsx
useEffect(() => {
    dispatch(__getUserLetters(userId));
  }, []);

그리고 get 해온 userLetters 에서 letter의 id만 뽑아서 targetIds 라는 배열로 저장하였다.

 const { userLetters } = useSelector((state) => state.commentSlice);
 const targetIds = userLetters.map((item) => item.id)

그리고 수정 완료 버튼을 클릭했을 때 targetIds와, nickname, avatar를 payload로 보내서 dispatch 했다.

dispatch(
  __updateUser({
    targetIds,
    nickname: edittedNickname || nickname,
    avatar: edittedAvatar || avatar,
  })
);
// 변경된 닉네임 값 또는 변경된 아바타 값이 없으면 기존 닉네임, 기존 아바타 보냄

그리고 thunk 함수를 통해 다음과 같이 json-server에 patch 요청을 보냈다.

export const __updateUser = createAsyncThunk(
  "UPDATE_USER",
  async (payload, thunkAPI) => {
    try {
      const updatePromises = payload.targetIds.map(async (id) => {
        await jsonApi.patch(`/letters/${id}`, {
          nickname: payload.nickname,
          avatar: payload.avatar,
        });
      });
      await Promise.all(updatePromises);
      console.log("일괄 업데이트 완료!");
      return thunkAPI.fulfillWithValue(payload);
    } catch (error) {
      console.log("error", error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);

map 을 돌면서 targetIds에 있는 배열 요소에 하나씩 patch 요청을 보낸다. 그리고 Promise.all 을 통해 일괄적으로 업데이트 한다.

하.. 이전에 작성한 팬레터의 닉네임, 아바타가 일괄 수정 되었을 때의 그 짜릿함이란....흑
더 최선의 방법이 있을 수도 있겠지만, 나로서는 이게 최선이었다.

이번 심화주차 개인과제도 결국은 완성을 했다.
vercel로 프로젝트를 배포 하고, glich 로 json-server를 배포하는 과정에서 또 약간 애를 먹기도 했지만.. 늘 그랬듯이 어쨌든 완성은 해 내는 내가 대견하기도 하다. 😭 또 높은 산이 기다리고 있겠지만.. 또 올라가야지 머..

https://fan-letter-ver2.vercel.app/

profile
느리더라도 조금씩, 꾸준히

0개의 댓글