Disney+ 사이트 클론 - 8

movieSlice와 동일하게 user폴더에 userSlice를 만들고

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  name: "",
  email: "",
  photo: "",
};

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setuserLogin: (state, action) => {
      state.name = action.payload.name;
      state.email = action.payload.email;
      state.photo = action.payload.photo;
    },
    setSignOut: (state) => {
      state.name = null;
      state.email = null;
      state.photo = null;
    },
  },
});

export const { setUserLogin, setSignOut } = userSlice.actions;

export const selectUserName = (state) => state.user.name;
export const selectUserEmail = (state) => state.user.email;
export const selectUserPhoto = (state) => state.user.photo;

export default userSlice.reducer;

초기설정을 해준다. 이 코드는 유저가 로그인하면 로그인 한 값으로 설정해주고 로그아웃 하면 그 값들을 다시 null로 바꿔준다.
store.js에서 동일하게 import해주고 reducer에 추가해준다.

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "../features/user/userSlice"
import movieReducer from "../features/movie/movieSlice";

export const store = configureStore({
  reducer: {
    movie: movieReducer,
    user: userReducer
  },
});

이제 로그인 한 상태가 아니면 Header의 메뉴들 대신 Login버튼만 보여줄 것이기 때문에 Header.js로 가서

import { selectUserName, selectUserPhoto } from "../features/user/userSlice.js";
import { useSelector } from "react-redux";

function Header() {
  const userName = useSelector(selectUserName);
  const userPhoto = useSelector(selectUserPhoto);

만들어준것들과 셀렉터를 import하고 불러와준다.
그리고 Nav메뉴는 유저가 로그인 한 상태에만 보여줄 것이기 때문에 !userName으로 userName이 존재하지 않으면 Login만 아니면 NavMenu를 보여주도록 한다.

 return (
    <Nav>
      <Logo src="/images/logo.svg" />
      {!userName ? (
        <Login>Login</Login>
      ) : (
        <>
          <NavMenu>
            <a>
              <img src="/images/home-icon.svg" />
              <span>HOME</span>
            </a>
            <a>
              <img src="/images/search-icon.svg" />
              <span>SEARCH</span>
            </a>
            <a>
              <img src="/images/watchlist-icon.svg" />
              <span>WATCHLIST</span>
            </a>
            <a>
              <img src="/images/original-icon.svg" />
              <span>ORIGINALS</span>
            </a>
            <a>
              <img src="/images/movie-icon.svg" />
              <span>MOVIES</span>
            </a>
            <a>
              <img src="/images/series-icon.svg" />
              <span>SERIES</span>
            </a>
          </NavMenu>
          <UserImg src="https://pbs.twimg.com/profile_images/601738800310583296/mBwRWaKY_400x400.png" />
        </>
      )}
    </Nav>
  );


스타일을 지정해줘서 위치와 모양을 바꾸면

...
        <LoginContainer>
          <Login>Login</Login>
        </LoginContainer>
...

const Login = styled.div`
  border: 1px solid #f9f9f9;
  border-radius: 4px;
  padding: 8px 16px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  background-color: rgba(0, 0, 0, 0.6);
  transition: all 0.2s ease 0s;
  cursor: pointer;

  &:hover {
    background-color: #f9f9f9;
    color: #000;
    border-color: transparent;
  }
`;

const LoginContainer = styled.div`
  flex: 1;
  display: flex;
  justify-content: flex-end;
`;


이제 firebase.js에 설정한 GoogleAuthProvider를 적용하자

function Header() {
  ...

  const signIn = () => {
    auth.signInWithPopup(provider).then((result) => {
      console.log(result);
    });
  };
  ...
  return (
    <Nav>
      <Logo src="/images/logo.svg" />
      {!userName ? (
        <LoginContainer>
          <Login onClick={signIn}>Login</Login>
        </LoginContainer>

signIn함수를 만들고 Login태그에 onClick으로 지정해주면

Login을 클릭했을 때 아래와 같은 구글 로그인 창이 나타난다.
로그인해서 clg로 찍어본 정보를 보면

우리가 필요한 displayName, email, photoURL의 정보를 볼 수 있다.
이 데이터들을 받아오기 위해 setUserLogin과 useDispatch를 import하고 dispatch를 활용해 데이터를 받아보자

import {
  selectUserName,
  selectUserPhoto,
  setUserLogin,
} from "../features/user/userSlice.js";
import { useDispatch, useSelector } from "react-redux";

function Header() {
  const dispatch = useDispatch();
  const userName = useSelector(selectUserName);
  const userPhoto = useSelector(selectUserPhoto);

  const signIn = () => {
    auth.signInWithPopup(provider).then((result) => {
      let user = result.user;
      dispatch(
        setUserLogin({
          name: user.displayName,
          email: user.email,
          photo: user.photoURL,
        })
      );
      console.log(result);
    });
  };


우리가 필요한 데이터는 user안에 있으므로 provieder로부터 result를 받아오고 result.user를 변수로 지정한 뒤 이를 dispatch하여 필요한 데이터를 setUserLogin에 할당해준다.
이렇게 되면 로그인을 했을 때 setUserLogin의 GlobalState가 업데이트 되어 userName도 유효한 값을 가지게 되어 Login버튼 대신 NavMenu들이 보이게 된다.

이제 프로필 아이콘을 클릭하면 로그아웃 하는 기능을 만들자 로그인과 동일하게

  const signOut = () => {
    auth.signOut().then(() => {
      dispatch(setSignOut());
      navigate("/login");
    });
  };
  ...
              </a>
          </NavMenu>
          <UserImg
            onClick={signOut}
            src={userPhoto}
          />

setSignOut을 실행함과 동시에 로그인 페이지로 이동시켜주는 함수를 만들어주고 UserImg에 onClick이벤트로 등록해준 뒤 이미지 경로를 userPhoto로 바꿔준다.

프로필이미지를 클릭하면 로그아웃 됨과 동시에 로그인 페이지로 이동한다.

추가로 로그인 했을 때에도 홈페이지로 이동하도록 signIn함수 안에 navigate를 추가하자

          name: user.displayName,
          email: user.email,
          photo: user.photoURL,
        })
      );
      navigate("/");
      console.log(result);
    });
  };

그런데 로그인 한 상태에서 새로고침을 하면 로그인 상태가 풀리는 현상이 있다.

  useEffect(() => {
    auth.onAuthStateChanged(async (user) => {
      if (user) {
        dispatch(
          setUserLogin({
            name: user.displayName,
            email: user.email,
            photo: user.photoURL,
          })
        );
        navigate("/");
      }
    });
  }, []);

useEffect를 추가해서 새로고침해서 화면을 다시 렌더링해도 로그인상태로 진입하도록 해주자

좋은 웹페이지 즐겨찾기