[React] 은행의 집

오늘의집 사이트를 모티브로 은행의집 사이트를 제작했다. 자취하면서 인테리어에 관심갖게 되고 자주 들락날락 했었던 사이트. 우리집 인테리어에 많은 기여를 해줬던 사이트.
1차 프로젝트의 경험으로 2차 업무분장은 금방 할 수 있었다. 이번에는 styled-component를 사용했는데 common.css 와 variable.css 등등을 적용해서 초기세팅하는 부분은 처음에 좀 혼란스러웠다. 헷갈렸던 부분들을 정리해두었다. 👉🏼 styled-components에 대해

프론트엔드 깃헙 주소
백엔드 깃헙 주소
전체 영상 보기

프로젝트 개요

프로젝트명

BANK HOUSE (은행의 도움을 받아 집을 매매하는 현실을 반영..)

요약

네브에 탭들이 많아서 선택과 집중을 해야 했다. 이커머스 기능은 1차 때 해봤으므로 패스..! 커뮤니티 부분에 집중을 했다. 사용자가 본인 집 인테리어를 직접 올려서 공유하고 소통하는 기능 위주로 구현했다. 메인페이지 구성을 조금 바꿨다. (기존 메인페이지의 배너 부분 + 사진 카테고리페이지)로 구성했다.

기술스택

프론트 : HTML, Styled-component, JavaScript, React(hooks)
백엔드 : Python, Django, Doker

사용한 툴

Trello, Gighub, Slack

진행기간

2주 (2021.05.24. ~ 06.04.)

팀 구성 및 주요 구현사항

프론트엔드 4명, 백엔드 2명

(내가 기여한 기능 볼드체 표시)

  • 로그인(일반로그인 및 소셜로그인), 회원가입, 로그아웃 기능
  • Nav 및 Footer
  • 메인페이지 슬라이더
  • 카테고리페이지 레이아웃
  • 카테고리페이지 페이지네이션
  • 카테고리페이지 주거형태, 평수, 스타일 등 필터 기능
  • 동적라우팅을 이용한 상품 클릭시 상세페이지로 이동 기능
  • 상세페이지 레이아웃
  • 상세페이지에서 해당 인테리어 삭제 기능
  • 상세페이지 댓글 달기 기능
  • 글쓰기(사진올리기 및 프리뷰) 기능

결과 화면

1. 메인페이지

슬라이더 부분은 react-slick 라이브러리를 사용!

2. 카테고리페이지

필터가 6개가 있다. 필터기능구현보다 해당 필터 선택시 색이 변하는 기능과 초기화 기능

3. 로그인 및 회원가입 페이지

소셜로그인과 일반로그인 기능이 있다.

4. 상세페이지

(gif업데이트)

  • 댓글을 달 때 1분전, 2분전 등 시간이 뜨는 기능이 재밌었다. 처음에는 현재시간과 댓글단시간의 year, month, day, hour, minutes 를 쪼개서 각각 비교했었다. year 차이가 0이면 month 비교. month도 차이가 0이면 day 비교.. 이런식으로 쭉 내려왔다. 그리고 minutes 차이까지 내려온다면 그 차를 return 했다.
  • 이 로직은 굉장한 문제가 있었다. 만약에 2020년 12월 31일 23시 59분에 댓글을 달았는데 현재시간이 2021년 1월 1일 00시 05분이라면 ‘1년전’이 뜨는 굉장한 기능..! 그래서 getTime() 으로 밀리세컨즈를 비교하는 식으로 변경했다.
const timeFromNow = postedTime => {
  const today = new Date()
  const postedDate = new Date(postedTime)

  const minutesDiff = Math.floor(
    (today.getTime() - postedDate.getTime()) / 1000 / 60
  )
  const hourDiff = Math.floor(minutesDiff / 60)
  const dayDiff = Math.floor(minutesDiff / 60 / 24)
  const monthDiff = Math.floor(minutesDiff / 60 / 24 / 30)

  if (minutesDiff < 1) return '방금전'
  if (minutesDiff < 60) {
    return `${minutesDiff}분 전`
  }
  if (hourDiff < 24) {
    return `${hourDiff}시간 전`
  }
  if (dayDiff < 31) {
    return `${dayDiff}일 전`
  }
  if (monthDiff < 12) {
    return `${monthDiff}달 전`
  }
  return `${Math.floor(dayDiff / 365)}년 전`
}

//2021-06-01T01:08:38.348Z 값을
//"2021-05-23T23:19:20+0000" 형식으로 변경
const postedDate = date => {
  const timeSplit = date.slice(0, 19)
  const posted = new Date(`${timeSplit}+0000`)
  return timeFromNow(posted.getTime())
}
  • 프로필의 동그란 원과 URL 공유기능의 동그란 원의 모양이 같았다. styled-component의 as 기능을 사용하니까 해당 태그를 div로 혹은 img 등으로 자유자재로 변경이 가능했다. 신기했던 기능.

5. 글쓰기 페이지

(gif업데이트)

  • select option 들을 every로 비교해서 하나라도 선택이 되어있지 않다면 올리기 금지 및 해당 보더를 빨간색으로 칠했다. 처음에는 include 메서드를 이용해서 선택이 되어있는지 안되어있는지를 파악했는데 멘토님의 조언으로 every로 변경!
  • 올리기를 누를 때 체크되지 않은 태그의 보더색을 빨간색으로 변경해야 했다. 그래서 처음에는 isBtnOnceClicked state 를 false로 하고 올리기 버튼을 누를 때 true로 바꿨다.
  • 조건문이 두개가 걸려있는데 isBtnOnceClicked가 true && 아래의 selectedCategory가 비어져있다면 red 로 변경
const [selectedCategory, setSelectedCategory] = useState({
  description: '',
  livingtype: '',
  space: '',
  size: '',
  style: '',
})
 <SelectWrapper
      isSelected={!!selectedCategory?.[menuName.menuNameValue]}
      isBtnOnceClicked={isBtnOnceClicked}
    >
      <Option
	 	 // 생략
      />
      ))}
 </SelectWrapper>
  );
}

const SelectWrapper = styled.select`
  border: 1px solid
    ${({ theme, isSelected, isBtnOnceClicked }) =>
      !isSelected && isBtnOnceClicked ? 'red' : theme.inputGray};
  • styled-component의 props 기능의 편리함을 깨닫게 되어서 다행이다. scss만 사랑할 뻔 했다.
  • 사진 올리기를 라이브러리를 쓸까 하다가 쓰지 않았다. css를 만져보니 심상치 않다는 것을 느꼈기 때문.. 이번 프로젝트에서 라이브러리를 한번도 못써봤는데 다른 팀에서 달력 라이브러리를 사용했다 해서 부러웠다. 나도 머리 싸매면서 라이브러리 분석해보고 싶다. 수료하고 나서 1차 2차 프로젝트 코드들 다듬을 때 써봐야겠다.

사진을 올릴 때

;<UploadInput
  type="file"
  name="photo"
  id="photo"
  accept="image/*"
  onChange={onFileInput}
  required
/>

const onFileInput = e => {
  e.preventDefault()
  const reader = new FileReader()
  const file = e.target.files[0]
  reader.readAsDataURL(file)

  reader.onload = () => {
    setPreviewURL(reader.result)
    setFile(file)
  }
}

사진 올리기 기능과 preview!

  • FileReader 객체를 생성한다.
  • file 변수에 선택한 파일을 담는다.
  • FileReader 객체에 readAsDataURL을 이용해서 파일을 읽는다.
  • onload: 읽기동작이 성공적으로 수행되면 previewURL에 넣어서 preview를 띄우고 file state 에 넣는다.(아래쪽에서 설명)

올리기 버튼을 누를 때

const onPost = () => {
  const selectedInfo = JSON.stringify(selectedCategory)
  // 스타일, 평수 등의 data는 JSON문자열로 변환한다.
  const categoryData = new FormData()
  // FormData 객체를 만든다.
  categoryData.append('info', selectedInfo)
  categoryData.append('image', file)
  // 스타일, 평수 등의 data는 변환한 값을, file data는 변환하지 않은 값을 넣어준다. 'info', 'image'는 백과 값을 맞춰준다.

  fetch('API', {
    method: 'POST',
    headers: {
      Authorization: localStorage.getItem('access_token'),
    },
    body: categoryData,
  })
    .then(res => res.json())
    .then(data => console.log(`data`, data))
  history.push(`/`)
}

댓글달기

<form onSubmit={onSubmitComment}>
  {' '}
  // 댓글달고 submit을 누르면
  <CommentInput
    type="text"
    name="comment"
    placeholder="칭찬과 격려의 댓글은 작성자에게 큰 힘이 됩니다 :)"
    value={inputValue}
    onChange={onChangeCommentInput}
  />
  <Register alert={opacity}>등록</Register>
</form>

headers에 토큰을 보내 로그인상태인 사람만 댓글추가 가능.
성공적으로 POST 됐다면 setCommentList를 해서 댓글state 변경

const onSubmitComment = e => {
  e.preventDefault()
  fetch(`${API}/posts/comments`, {
    method: 'POST',
    headers: {
      Authorization: localStorage.getItem('access_token'),
    },
    body: JSON.stringify({
      post_id: path,
      content: inputValue,
    }),
  })
    .then(res => res.json())
    .then(res => {
      if (res.message === 'SUCCESS') {
        fetch(`${API}/posts/comments/${path}`)
          .then(res => res.json())
          .then(res => setCommentList(res.data))
      }
    })
}

후기

아쉬웠던 점

  • 1차 때 너무 기능구현 부분을 신경쓴 나머지 내가 쓴 코드가 몇몇 버그가 발생했었다. 2차 팀원분들 모두 이번에는 하나라도 기능을 제대로 완성하자라는 모토에 동의하셔서 안되는 부분을 슬쩍 빼고 넘어가는 일이 없도록 했다.
  • 1차의 조급함과 2차의 완전함의 추구 모두 귀중한 경험이었다.
  • 로직이 장황해질 만큼 큰 기능이 없었다. 그래도 1차 때보다 신경써야 했던 건 component 별로 state 관리하는 부분. useReducer랑 context API를 꼭 사용해보고 싶었는데 이들을 쓸만큼 component가 복잡하지는 않았..지만 신경써야 할 부분들이 있었다.
  • 사진을 불러오는게 너무너무 느렸다. 최적화를 하면 나아지는 것인가. useCallback이나 useMemo같은 기능들을 안써보아서 아쉬웠다. 기능 최적화를 해서 얼마나 빨리 불러와지는지 실험해보고싶다.

마치며

  • 오늘의집에 굉장히 많은 기능들이 있다. 위코드 3개월차 기업협업을 마치면 추가기능들도 구현해봐야겠다. 백엔드 분들도 동의해주셔서 API를 맘껏 요구해야겠다.. !!??
  • 1차때도 물론이었지만 이번에도 팀원분들이랑 합이 너무 잘 맞았다. 이번에도 너무 즐겁게 코딩할 수 있었고 팀 분위기가 역시 중요하군!! 깨달았다. 잠깐 힘들 때에도 옆에서 기능 하나하나에 공들이는 분들을 보며 정신차리고 다시 집중할 수 있었다.
  • 매일 아침 모여서 스크럼 방식의 데일리 미팅을 했다. 어제는 뭘 했고 오늘은 뭘 할거고 어떤 부분에서 막히고 있는지 위주로 진행했다. 백엔드와 진도가 착착 맞아야하기 때문에 중요한 하루일과 중 하나였다. 그런데 이렇게 스크럼 방식이 아니면 과연 어떻게 진행하는건지 궁금해졌다.

Written by@Jiyon Lee
뜨거운 코드를 가르며

GitHub