1차 프로젝트(Wecode 1차 프로젝트 후기_Beats_Frontend) 때는 React class component 와 Sass를 사용했는데 2차 프로젝트 때는 함수형 component 와 style component 를 사용했고 리액트 Hooks 와 redux 를 사용하였다 백앤드 API 를 확인할 때는 Postman 을 사용하였다
핀터레스트 자체가 페이지 UI 가 많지 않아서 모든 페이지를 다 만들수 있었고 최대한 기능에 집중하기 좋았다
다른 프론트 팀원 두명 다 1차 프로젝트때 로그인과 회원가입을 해봐서 1차때 로그인, 회원가입을 안 해본 내가 로그인과 회원가입을 맡기로 했다. 하지만 소셜 로그인으로 구글 로그인을 연동하게 되서 만들 UI도 거의 없고 코드도 간단해서 생각보다 일찍 끝나게 되었다 그래서 추가로 1주차에 네비게이션 바도 맡아서 만들었다
실제로 핀터레스트가 구글 로그인으로 연동 할 수 있어서 우리는 구글 로그인으로 소셜 로그인을 사용하기로 했다 덕분에 제작할 UI 가 줄어 기능에 집중 할 수 있었다
아래 사진은 처음 핀터레스트를 켰을 때 화면이다 뒤에 이미지 콘텐츠들은 mock data 로 만들어 놓은 데이터를 뿌려놓은 모습이다 로그인/회원가입은 모달로 만들었고 뒷부분 스크롤은 막아놓았다 실제 핀터레스트 로그인 모달과는 조금 다른데 우리는 구글 로그인만 가능하게 하려고 디자인을 조금 수정해 보았다
디자인을 수정한 로그인 모달(맨 오른쪽 위에는 우리팀원의 강아지인 복실씨가 있다)
아래는 실제 페이지의 로그인 모달이다
구글 로그인을 누르면 뜨는 구글 로그인 창
구글 소셜 로그인은 프론트에서 구글로부터 받은 access token 을 백앤드로 넘겨주고 백에서는 그 access token을 다시 구글로 넘겨 승인을 받아 다시 프론트로 Authorization token 을 넘겨주는 방식이다
처음에는 밑에 보이는 accessToken 을 넘겨주었는데 너무 불안정했다 5번 시도하면 3번은 undefined 가 떴다 그래서 결국 중간 발표때도 로그인에 실패했다🤯 그러다 우리팀 백앤드님이 다른 팀 프론트분과 wc 안에 access_token 을 발견해(둘 다 값은 같았다) 그 token 으로 시도하니 허무하게도 너무 잘됐다... 이렇게 소셜 로그인 문제는 wc 만 바꿈으로 해결...
// 구글 로그인 성공했을 때 실행할 함수 const onLogin = (result) => { fetch(`${url}/account/google`, { method: "POST", headers: { // 서버로 access token 보내기 Authorization: result.wc.access_token, }, }) .then((res) => res.json()) .then((res) => { // 들어온 데이터 확인 // console.log("res", res); // authorization token 받아서 local storage 에 저장 localStorage.setItem("Authorization", res.access_token); // 구글 로그인한 사용자가 이미 회원일 경우 모달 창 닫기 if (res.message === "ALREADY SIGNED-UP") { window.location.reload(); // 회원이 아닐경우 다음단계로 넘어가는 함수 goNext 실행 } else { goNext(); } }); }; //구글 로그인 버튼 <GoogleLogin clientId="클라이언트Id" render={(renderProps) => ( // 버튼 커스터마이징 한 부분 <button onClick={renderProps.onClick} disabled={renderProps.disabled} Google로 계속하기 </button> )} // 로그인 성공 시 실행할 함수 result 안에 구글에서 보내준 데이터가 들어있다 onSuccess={ // result 를 인자로 받아 onLogin 함수 실행 (result) => onLogin(result) // 들어온 데이터 확인 뭐든 확실하게 하려면 console.log 로 찍어보는게 좋다는 것을 다시 한번 느꼈다 // (result) => console.log(result.accessToken) } onFailure={(result) => console.log(result)} cookiePolicy={"single_host_origin"} />
// 각 관심사 선택 시 state 에 추가하거나 있다면 뺀다 const checkBox = (id) => { // 선택한 관심사가 리스트에 없을 때 if (!check.includes(id)) { setCheck(check.concat(id)); // 이미 선택한 것을 또 눌렀을 때 } else { // 필터를 이용해서 id 와 일치하는 것을 빼고 새로 리스트를 만든다 const filtered = check.filter((el) => el !== id); setCheck(filtered); } };
스타일컴포넌트에서 직접 props 를 받아와서 사용하는 부분이 편해서 기억에 남는다
const Check = styled.button` ${(props) => props.active >= 5 && css` background-color: #e60023; color: #fff; `} `
//버튼 카운트 부분 <Button active={check.length} onClick={() => is(check)}> // 최소 5개를 선택해야 완료 버튼이 활성화 되게 length 로 카운트함 {check.length >= 5 ? `완료` : `${5 - check.length}개 더 선택`} </Button>
검색 부분은 1차 프로젝트 때부터 해보고 싶었다 물론 완벽하게 핀터레스트의 검색 기능을 구현한 것은 아니지만 그래도 실제로 검색이 되는 모습을 보니 신기했다
input 창에 입력한 내용을 state 에 담아 서버로 보내면 그 해당 주제에 대한 핀을 보내준다
const onChange = (e) => { // input 에 들어오는 값 state에 저장 setSearchValue(e.target.value); // 엔터 쳤을 때 함수 실행 if (e.keyCode === 13) { // 타겟의 value 값을 넣어서 search 함수 실행 search(e.target.value); e.target.blur(); // search 드롭 닫기 setActive(false); } }; // 검색한 값 서버로 보내서 해당 검색어에 해당하는 핀 받아오기 const searchHandler = (text) => { setSearch(true); fetch(`${url}/search/?search=${text}`, { headers: { Authorization: localStorage.getItem("Authorization"), }, }) .then((res) => res.json()) .then((res) => setContentsList(res.search_term)); };
// actions export const addPin = (pin) => { return { type: "ADD_PIN", payload: pin, }; }; // reducers export const cartList = (state = [], action) => { switch (action.type) { case "ADD_PIN": // 선택한 핀을 state 에 추가 return [...state, action.payload]; default: return state; } };