CREAM 프로젝트 회고

seul_velog·2022년 6월 7일
2

프로젝트 모티브 사이트

📌 Kream

  • 프로젝트 목적
    소셜로그인 통신 원리이해 & JS와 React, React 라이브러리 스킬 능력 향상을 위한 프로젝트 진행

프로젝트 기간 및 인원

  • 프로젝트 기간
    22.06.07 ~ 22.06.17

  • 개발 인원
    Front-end 4명, Back-end 1명


구현 항목

  • 필수구현
    Nav Bar & Footer ✔
    소셜 로그인 & 회원가입 ✔
    상품 페이지
    상품 상세
    매매입찰

  • 📌 focus
    2차 프로젝트는 아래 내용들을 목표로 진행되었다.
    - 1차 프로젝트시 각자 해보지 못한 기능들을 구현
    - 스프린트 일정에 맞는 작업 진행과 속도 개선
    - 프론트엔드와 백엔드 간의 커뮤니케이션 개선
    - 라이브러리 연습


기술 스택

  • Front-end
    : HTML/CSS, JavaScript, React.js, React-Router, Styled-Component, AWS(EC2)

  • Back-end
    : Django, Python, MySQL, AWS(RDS, EC2), Bcrypt, JWT, django-cors



협업도구

Git, Slack, Trello




1. 작업 페이지 UI

nav, footer & login page

✍️ 1차때 로그인 페이지를 작성하는 동기를 보고 나도 백엔드와 협업을 통해서 로그인 페이지를 만들어보고 싶다고 생각했었다.
2차에는 소셜로그인 기능을 사용해야 했는데, 직접 해보고 싶다고 의견을 냈고 팀원들의 배려로 해당 페이지를 맡을 수 있게되었다. 😀



1) input & htmlFor

  • React와 JSX는 class와 for를 제외하면 표준 HTML 속성을 모두 사용할 수 있다.

  • class와 for 대신 각각 classNamehtmlFor를 사용한다.

  • 인풋 값에 placeholder 적용 및 포커스 했을 때 placeholder 안 보이도록 설정하기

    const Input = styled.input`
      border: none;
      border-bottom: 1px solid #ebebeb;
      padding: 10px 0;
      font-size: 14px;
    
      &:focus {
        outline: none;
        ::placeholder {
          opacity: 0;
        }
      }
    
      ::placeholder {
        color: #bbb;
        opacity: 1;
      }
    `;

    ✍️ formlabelinput 부분에서 labelforhtmlFor로 작성해야하는 것을 알았다.


2) 스타일드 컴포넌트

팀마다 선호하는 방식이 다르다고 한다.

const LoginBtn = styled.button`
  background: #ebebeb;
  width: 100%;
  margin: ${props => props.theme.fontSizes.xl};
  padding: ${props => props.theme.paddings.large};
  border: none;
  border-radius: 0.625rem;
  font-size: ${props => props.theme.fontSizes.small};
  color: ${props => props.theme.colors.white};
  cursor: pointer;
`;

위 코드처럼 props를 일일이 받아올 수 있고

 ${({ theme }) => `
    font-size: ${theme.fontSizes.small}
    color: ${theme.colors.white};
  `}

구조분해 할당으로 모아서 쓸 수있다. 이 경우 함수 밖과 안에서 선언된 스타일의 순서 컨벤션은 각각 맞춰주면 된다.😀


3) react-icons

❓리액트 아이콘즈에서 받아온 아이콘 컴포넌트는 어떻게 스타일을 줄 수 있을까🤔

 <FooterIcons>
    {/* 각각의 아이콘(컴포넌트)는? */}
      <FaRegEnvelope />
      <FaInstagram />
      <FaBold />
  </FooterIcons>
const Footer = () => {
  return (
    <FooterContainer>
      <FooterIcons>
        <MessageIcon />
        <InstaIcon />
        <FaBold />
      </FooterIcons>
      <Contents>이용안내</Contents>
      <Contents>고객지원</Contents>
      <Contents>고객센터 1588-7813</Contents>
      <ContentDetails>
...

const MessageIcon = styled(FaRegEnvelope)`
  margin-right: 10px;
`;

const InstaIcon = styled(FaInstagram)`
  margin-right: 10px;
`;
  • 이런식으로 기존 아이콘의 스타일을 상속 받음과 동시에 추가적으로 css 속성을 추가하거나 할 수있다.

✍️ 새로 알게된 점
→ 이경우 리액트아이콘에서 이 기능을 지원해 줄 경우에만 가능하다고 한다.


리팩토링 (네스팅을 받아서 내부에서 사용하기)

const FooterIcons = styled.div`
  svg {
    margin-right: ${props => props.theme.margins.large};
    margin-bottom: ${props => props.theme.margins.xxl};
    font-size: ${props => props.theme.fontSizes.xxl};

    &:last-child {
      margin-right: 0;
    }

    &:hover {
      cursor: pointer;
    }
  }

✍️ 새로 알게된 점

1) import 로 {css} 로 가져오면
스타일을 줄때 내가 정의한 css 속성으로 mixin 하듯이 가져다 쓸 수 있다. ( 일종의 스타일드 컴포넌트에서 제공되는 기능중 하나인 것 같다. )
→ 쓰는 이유중 하나는 theme에서 저장을 미리 못했을때나, 내 컴포넌트에서 자주 쓰일 경우 형식을 지정해두고 가져다 쓰기 좋기 때문이라고 한다 :)!

2) 스타일드 컴포넌트로 인해서 랜덤 클래스명을 확인하기 힘들때 import 부분에 styled-components/macro 를 적어주면 해당 컴포넌트의 클래스네임은 기존 값으로 볼 수 있는 듯 하다.🧐






2. KAKAO LOGIN API 기능구현

1. 기능소개

  • 카카오 로그인은 카카오계정으로 다양한 서비스에 로그인할 수 있도록 하는 OAuth 2.0 기반의 소셜 로그인 서비스이다.

2. API 및 기능: 로그인

  • 카카오계정과 애플리케이션을 연결하고 토큰을 발급받아 카카오 API를 사용할 수 있도록 하는 기능이다.
  • 카카오 로그인은 인가 코드 받기와 토큰 받기의 두 단계로 이뤄진다.
    1) 인가 코드 받기: 카카오 로그인 동의 화면을 호출하고, 사용자 동의를 거쳐 인가 코드 발급을 요청한다.
    2) 토큰 받기: 인가 코드로 액세스 토큰과 리프레시 토큰 발급을 요청한다.

✍️ API 구현해보기

애플리케이션 추가

  • 앱 키를 발급받는다.


동의항목 - 개인정보 동의 파트

  • 이 부분은 백엔드 팀원과 상의 후 맞춰서 설정했다.


카카오 로그인

1) 활성화 설정을 켜면 카카오 로그인을 사용할 수 있다.

2) Redirect URI

Redirect URI 등록

  • Redirect URI는 Oauth 2.0을 기반으로 동작하는 카카오 로그인의 핵심 요소이다.
  • 카카오 로그인은 서비스와 카카오 서버가 서로 정보를 주고받는 방식으로 진행된다.
  • 카카오 서버는 Redirect URI로 서비스에서 필요한 로그인 인증 정보를 보내고, 서비스는 Redirect URI로 받은 로그인 인증 정보를 처리해 다음 단계 요청을 보낸다.
  • 따라서 이 정보가 앱 설정에 등록되어 있지 않으면 카카오 로그인 시 에러가 발생한다.
  • Redirect URI 등록은 [내 애플리케이션] > [카카오 로그인] > [Redirect URI]

3) 동의창 미리보기로 실제 사용자가 보게 될 카카오 로그인 동의 화면을 확인할 수 있다.

  • 내애플리케이션-앱설정-플랫폼-web & Redirect URI 둘다 http://localhost:3000/ 로 설정했다.

✨ 이렇게 기본 셋팅 완료! 이제 통신과정을 이해하고 백엔드 팀원과 함께 통신해보자!





통신과정 이해하기

나는 어떻게 구현할까?

📌 방법 1)

  • 1~5 프론트엔드에서 카카오로부터 액세스 토큰을 받아오는 루트 (받은 액세스 토큰 로컬스토리지에 저장)까지 처리한다.
  • 6 백엔드에서 처리한다.
  • 7 받아서 로컬스토리지에 사이트전용 토큰저장한다.
const Redirect = () => {
  const url = new URL(window.location.href);
  const code = url.searchParams.get('code');

  const getToken = async () => {
    await fetch(`${API.KAKAO_TOKEN}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
      },
      body: `grant_type=authorization_code&client_id=${REST_API_KEY}&code=${code}&redirect_uri=${REDIRECT_URI}`,
    }).then(res => {
      console.log(res.json());
    });
  };

  useEffect(() => {
    code && getToken();
  }, [code]);

❗️ 작성하니 아래와같은 에러가 발생했다.
→ 인가가 중복적으로 사용되고 있어서 그렇다. (처음 발급시에는 200, 새로고침하면 에러)

✍️ 해결과정
→ 먼저 에러의 이유를 알아보기 위해 res.json()을 콘솔에 찍어서 error_code 를 확인(이전에는 console.log(res)로 찍어서 그 값만 확인하느라 에러코드를 보지 못했다.🥲)
→ 에러 코드 KOE 30 확인 후 카카오 API명세서에 기재된 에러 사항을 확인
→ 인가 코드 중복 사용과 관련된 문제임을 확인한 후 해결할 수 있었다.






//LoginData.js
export const REST_API_KEY = `...`;
export const REDIRECT_URI = `http://localhost:3000/users/signin/kakao/callback`;
export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}`;
// Redirect.js
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { API } from '../../config';
import { REST_API_KEY, REDIRECT_URI } from './login/LoginData.js';


  const getToken = async () => {

  await fetch(`${API.KAKAO_TOKEN}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
    },
    body: `grant_type=authorization_code&client_id=${REST_API_KEY}&code=${code}&redirect_uri=${REDIRECT_URI}`,
  })
    .then(res => res.json())
    .then(data => localStorage.setItem('kakao_token', data.access_token));
  sendToken();
  };

  const sendToken = async () => {
    await fetch(`${BASE_URL}/users/login/kakao`, {
      method: 'GET',
      headers: { Authorization: localStorage.getItem('kakao_token') },
    })
      .then(res => res.json())
      .then(data => {
        localStorage.setItem('cream_token', data.cream_token);
        navigate('/');
      });
  };

  useEffect(() => {
    getToken();
  }, []);

  return <div>redirect.js 컴포넌트 화면</div>;
};

export default Redirect;




❗️ 방법1의 경우
→ 이렇게 되면 로컬스토리지에 두 개의 토큰이 저장되는 문제가있고, 카카오서버로부터 받은 액세스 토큰에서 어떤 문제가 발생될지 모르므로 보완이 필요하다는 조언을 받았다.

✍️ → 추후 질문을 통해서 알게된 점
카카오 서버로부터 받아온 액세스 토큰을 굳이 로컬스토리지에 저장하지 않고 바로 fetch 함수 로직 내에서 바로 서버로 넘겨준다. → 로컬스토리지에 저장되는 토큰은 마지막 절차에서 백엔드로부터 받는 우리사이트 토큰 하나로, 이전과 같이 잘 작동 된다는 것을 알게되었다.😀 )



📌 방법 2)

  • 인가 코드를 받아서 백엔드 서버에 넘겨주고 백엔드쪽에서 액세스 토큰을 받은 후 서버 전용 토큰 발행, 그 토큰을 프론트에게 넘겨주는 로직으로 수정했다.

  • 이럴경우 최종 사이트 토큰만 로컬스토리지에 저장하면 된다. (위 사진 단계와 동일한 흐름으로 전개된다.)

const Redirect = () => {
  const url = new URL(window.location.href);
  const code = url.searchParams.get('code');
  const navigate = useNavigate();

  const getToken = async () => {
    await fetch(`${API.KAKAO_LOGIN}?code=${code}`, {
      method: 'GET',
    })
      .then(res => res.json())
      .then(data => {
        localStorage.setItem('cream_token', data.cream_token);
        navigate('/');
      });
  };

  useEffect(() => {
    getToken();
  }, []);

  return <div>redirect.js 컴포넌트 화면</div>;
};

사이트 전용 토큰을 가져와서 스토리지에 저장을 해주려는데 키는 잘 떴지만, value가 undefined로 계속 처리되는 것을 확인했다. 서버쪽엔 200으로 잘 뜨는데 ..😧?
넘겨주는 값에 문제가 있는 것 같아서 이것저것 확인해 보다가 json() 처리를 안하고 res 그대로 넘겨주고 있는 것을 확인, .then(res => res.json()) 을 하고 데이터를 받아와서 로컬스토리지에 저장했더니 잘 나오게 되었다.👍

이과정에서 json() 를 하지 않은 값과 처리를 한 값 모두 콘솔에 출력해 보았다.

(1) res만 출력

(2) res.json() 출력




+) 로딩스피너 추가구현 ✔️

  • 서버가 느린 사용자는 redirect uri 컴포넌트 페이지에 오래 머물러 있을지도 모른다.
    유저에게 보여질 화면을 컨트롤 하는 것이 좋을 것 같아서 로딩스피너를 구현했다 :)



+) Nav 마이페이지로 넘어가기 추가구현 ✔️

 const [isLogin, setIsLogin] = useState(false);
  const navigate = useNavigate();
  const creamToken = localStorage.getItem('cream_token');

  useEffect(() => {
    creamToken && setIsLogin(true);
  }, [creamToken]);

  const goToMypage = () => {
    creamToken
      ? fetch(`${API.USERS}`, {
          method: 'GET',
          headers: {
            Authorization: creamToken,
          },
        })
          .then(res => {
            if (res.ok) {
              return res.json();
            }
          })
          .then(() => {
            navigate('/마이페이지');
          })
      : navigate(`/login`);
  };

return (
  ...
  {isLogin ? (
          <Button
            onClick={() => {
              goToMypage();
            }}
          >
            MYPAGE
          </Button>
        ) : (
          <Button login>LOGIN</Button>
        )}



+) 분기처리 완료 ✔️ , axios 라이브러리 사용 ✔️

  • 코드가독성을 높인다.
  • Axios를 사용하면 응답 데이터를 기본적으로 JSON 타입으로 사용할 수 있다.
    응답 데이터는 언제나 응답 객체의 data 프로퍼티에서 사용할 수 있다.
  const sendCodeToBack = () => {
    const option = {
      url: `${API.KAKAO_LOGIN}?code=${accessCode}`,
      method: 'GET',
    };
    axios(option).then(res => {
      if (res.status === 200) {
        localStorage.setItem(USER_TOKEN, res.data.token);
        navigate('/');
      } else {
        navigate('/login');
        alert('로그인이 실패하였습니다.');
      }
    });
  };



+) 로그아웃 구현 ✔️

 const [isLogin, setIsLogin] = useState(false);
  const creamToken = localStorage.getItem('access_token');
  const navigate = useNavigate();

  useEffect(() => {
    creamToken && setIsLogin(true);
  }, [creamToken, isLogin]);

  const logout = () => {
    localStorage.removeItem('access_token');
    alert('로그아웃 되었습니다');
    setIsLogin(false);
  };

...
  • 처음에는 localStorage.clear(); 로 로컬스토리지를 비웠으나 localStorage.removeItem('access_token') 로 키에 해당하는 값만 삭제하는 것으로 수정했다.



+) env 처리 ✔️

  • .env 파일 생성후 API 키를 넣어서 gitignore에서 관리하고, API키는 외부에서 따로 참조할 수 있도록 했다.
  • 이 부분을 구현하고 다른 팀원들의 .env 작업도 도와주게 되었는데, 다른 팀 코드에서도 작은 에러상황을 많이 마주하게 되었다.😂
    → API키를 설정하면서 불필요한 스페이스나 ; 가 있는지, ' or " 처리가 잘 되었는지 확인하기.
    → 분명 맞게 작성했는데 되지 않을 때엔 코드를 복사 하고 그대로 붙여넣었는지 다시 확인하기, 에디터를 재실행 해보기.
    → 이렇게 안되는 부분까지 콘솔창을 확인하며 차근차근 단계별로 체크하다보면 결국 다 된다! 😎 내코드가 작동해도 다른 사람의 코드가 작동되지 않을 때 마주할 수 있는 여러 에러상황에 대해 의심하고 접근하는 건 언제나 재미있다.. 의미없는 삽질은 없다. 모든 건 배우는 과정일 뿐✨✨✨




프로젝트를 마치며...

  • 아쉬웠던 점
    : 카카오 API 공식문서를 더 끈질기게 정독 했으면 어떨까 하는 아쉬움과 조금 더 욕심내서 다른 소셜 로그인도 구현 해 보았으면 좋았을 것 같았다.

  • 프론트단에서 카카오서버로부터 액세스토큰까지 받는과정과 인가코드까지만 받고 백엔드로 넘겨주는 과정 모두 경험해 볼 수 있어서 더 기억에 남았다.

  • 로그인 통신 구현을 통해서 프론트와 백엔드 사이에서의 역할과 협업에 대해 배울 수 있었다. 소통을 하면서 서로 자신의 코드를 설명하고, 각자 맡은 파트에서 아는 지식을 공유하면서, 몰랐던 백엔드의 지식을 얻을 수 있을 때가 정말 너무너무 재밌었다.🥺👍

  • .env 파일을 다루는게 쉽지 않았지만 모르던 개념도 많이 알게 되었고, 성공적으로 통신이 되어서 뿌듯하다. :)!

  • 1차 때처럼 학원을 오가던 길에서 틈틈히 Oauth 2.0에 대해 공부했는데 신기하고 재미있는 지식을 많이 접할 수 있어서 좋았다. 이 부분은 다시 공부해서 정리하고 싶다!

  • 2차에서는 백엔드 팀원이 API명세서를 초반에 잘 작성해 주셔서 이부분을 미리 참고하며 작업할 수 있었던 점이 좋았던 것 같다. 😀

  • 소셜로그인 파트를 해보길 너무 잘했다!✨
    마지막 날에는 다른 파트를 담당해서 아직 해보지 못했던 우리 팀원과 몇몇 동기분들에게 이 정보를 공유해 드리고 싶어서 우리팀 백엔드분과 함께 내가 이해한 로직을 설명해 드리는 스터디 모임도 가졌다. 😆👍

profile
기억보단 기록을 ✨

2개의 댓글

comment-user-thumbnail
2022년 7월 22일

슬비님 블로그 알았으면 프로젝트 끝나고 바로 댓글 달았을텐데 늦었지만 남기고 가요 👍👍👍

1개의 답글