Skip to content

d0ngwooK1m/contap

Repository files navigation

Contap FrontEnd

팀원의 장점

한우석

  • 이승준
    • 어떤 부탁을 드려도 정말 척척 해결 해주셨습니다. 덕분에 부담없이 필요한 기능들을 요청할 수 있어서 너무 좋았습니다. 서버 처리 속도도 되게 꼼꼼하게 신경 써주셔서 저희 사이트가 훨씬 더 쾌적해 졌다고 생각합니다. 매우 뛰어난 실력을 가지셨음에도 겸손하시고 항상 꼼꼼하게 코드 리뷰도 해주시면서 백엔드의 기둥같은 역할을 잘 해주셔서 너무 감사합니다.
  • 오준석
    • 미니때도 같이 프로젝트를 진행 했었는데 그때 보다 훨씬 많은 성장을 하셨다고 생각합니다. 저와 같이 항상 마지막 까지 게더에 남아서 다른 짜잘한 기능들을 같이 진행 했었는데 그렇게 필요하지 않은 기능인데도 불구하고 막히면 끝까지 해결 하려고 하시는 모습이 인상적이였습니다. 프로젝트 내,외적으로 문제가 생기면 가장 먼저 뛰어가고 팀을 위해주셔서 우리팀이 웃으면서 여기까지 올 수 있었습니다. 그리고 솔직히 소셜로그인, HTTPS모두 준석님 아니였으면 못했을 것 같아요!
  • 김혜림
    • 항상 밝은모습으로 맡은 일은 끝까지 해내고 마는 모습이 좋았습니다. 똑 부러지게 설명도 잘 해주시고 힘든 내색도 잘 하지 않으셨어요. 매번 회의를 진행 할 때도 정말 열심히 참여해주셔서 이렇게 좋은 결과물이 나왔다고 생각합니다. 저희 왕언니에요!
  • 김동우
    • 담당하신 부분에 대해서 어떻게든 찾아 문제를 해결 하시는 모습이 너무 든든 했습니다. 그래서 더 제 파트에 집중할 수 있었던 것 같아요! 의지가 많이 되었습니다. 좋은 아이디어를 많이 내주셔서 감사합니다.
  • 이아영
    • 거의 마이페이지에 감금당해 있으시던 우리 아영님.. 그래도 결국 이기셔서 다행입니다 ㅜㅜ 정말 끝까지 포기하지 않고 늦은 시간까지 너무 고생 많으셨어요! 저한테 물어보실때 계속 너무 죄송해 하셨는데 그러지 않아도 괜찮아요 덕분에 저도 많이 배울 수 있어서 너무 의미있었던 시간이였습니다. 덕분에 마이페이지 진짜 이쁘게 나왔어요 너무 감사하고 고생 많으셨습니다~
  • 김민지
    • 이번 실전프로젝트 모든 디자이너를 통틀어서 가장 열심히 참여 해주셨다고 확신합니다. UX/UI, 라이팅 등 뭐 하나 이유없이 작업하시지 않고 꼼꼼하게 신경 쓰시는 모습에 너무 많은 것을 배웠습니다. 제 의견을 정말 잘 들어주시고 피드백도 꼼꼼하게 해주셔서 저희 사이트 디자인이 그렇게 칭찬을 받았다고 생각해요! 하루도 빠지지 않고 게더에 계속 들어와 계시고 혼자여서 많이 부담 되었을텐데도 불구하고 정말 디자인을 잘 뽑아 주셨습니다. 전 저희 사이트 디자인이 세상에서 제일 이뻐요 채구채구

오준석

  • 이승준
    • 코드 이해력이 너무 좋으셨습니다. 남이 짠 코드도 쉽게 이해한 뒤에 알려주셨고, 전반적으로 코드를 어떻게 생각하며 짜면 좋을지 생각하는 방법을 많이 배웠습니다.어떤 질문이던 마다하지 않고 친절하게 알려주셨습니다.언제나 밝은 모습으로 팀원들을 항상 반겨주는 모습에 많은것을 배운 시간이었습니다.많은걸 알려주셔서 너무 감사합니다.
  • 김혜림
    • 항상 긍정적이시고 어떤 도움도 거절하지 않고 항상 밝은모습으로 도와주셨습니다. 워낙 경력도 있으신 분이었어서 코드를 작성하는데 있어서도 배울점이 많았습니다.그리고 좋은 분위기를 형성해주시려고 항상 노력해주셨고 어떤말이던 귀담아 들어주셔서 너무너무 감사했습니다. 많은걸 배울수 있게 해줘서 감사합니다.
  • 김동우
    • 팀장으로서 팀원들의 의견을 귀 귀울이려고 노력하시는 모습이 멋졌습니다.팀원의 생각이라면 절대 놓치지 않으려고 하시는 모습이 너무 인상깊었습니다. 팀장일하는게 만만치 않은 일 일텐데 개의치않아하시고 항상 팀원을 먼저 생각하려 하시는 모습에서 많은것을 배웠습니다.팀원들을 잘 이끌어주셔서 감사합니다.
  • 한우석
    • 미니프로젝트때도 한팀이었기에 너무나도 서로를 잘 아는 상태였습니다. 역시나 이번 프로젝트때도 서로의 케미가 빛을 내주었고,항상 밝은 모습과 책임감 있는 모습은 여전히 최고였습니다. 새벽까지 저와함께 짜잘한 버그들을 같이 수정하고 안되는것도 같이 헤딩하면서 많이 성장하는 시간이 되었던것 같습니다.함께 늦게까지 노력해줘서 감사합니다.
  • 이아영
    • 언제나 긍정적이시고 막히시면 언제나 질문 하시며 더 실력을 늘리고 싶어하는 열정이 너무나도 많이 보였었습니다. 본인 맡은 바를 어떻게해서든 끝내려는 모습도 인상깊었습니다. 본인이 항상 부족하다고 생각하시면서 겸손한 자세로 공부하는 모습을 볼때마다 나도 저렇게 해야겠다 라는 생각을 정말 많이 했던것 같습니다. 언제나 밝은모습 보여주셔서 감사합니다.
  • 김민지
    • 개발자가 아닌 디자이너이시기에 제가 감히 실력을 논할순 없지만 제가 느끼기엔 실력적으로도 너무 출중하셨고, 어떤 디자인의 요구가 들어오더라도 마다하지않고 다 반영하려고 노력해주셨던 모습이 본받아야 겠다라고 생각했습니다.학교를 다니시는 와중에도 디자이너일 외적으로도 팀원들과 자주 소통하고 정말 팀의 일부가 되어 함께하는 모습이 너무 인상 깊었습니다.언제나 긍정적으로 임해주셔서 감사합니다.

김민지

  • 이승준
    • 백엔드는 잘 모르지만 서버 최적화에 많은 힘을 써주신 것 같습니다. 덕분에 안정성 있는 홈페이지를 만들 수 있었던 것 같습니다. 또한 같은 팀 외에도 다른 팀 백엔드 개발자도 성심성의껏 도와주시는 점이 인상적이었습니다. 무뚝뚝한 말투에 그렇지 못한 따뜻한 심성에 같이 일하면서 재밌고 즐거웠습니다. 감사했습니다!
  • 오준석
    • 팀의 문제 해결사이자 분위기 메이커를 담당하셨습니다. 덕분에 트러블 없이 즐겁게 프로젝트를 진행할 수 있었고 의지할 수 있었습니다. 적극적으로 유저 테스트를 진행하거나, 팀원들의 개발 일정 체크하는 등 팀의 전반적인 부분에 기여하셨기 때문에 백엔드 개발자 외에도 PM과 같은 역량을 볼 수 있었습니다. 준석님이 있어서 모두가 즐거웠습니다. 감사합니다.
  • 김혜림
    • 백엔드 개발자지만 나은 서비스를 위해 기획면에서도 고민할 줄 아셨습니다. 기획 중에 흩어졌던 대화들을 정리하고 다음 방향을 제시하는데 탁월하셨습니다. 개발에서도 문제가 있으면 꼭 해결하려는 끈기와, 그 누구보다 성실하신 혜림님을 보며 같이 열심히 해야겠다고 생각했습니다. 고생 많으셨습니다.
  • 김동우
    • 팀장으로서 팀을 이끌어가는데 부담이 크셨을 거라 생각합니다. 하지만 그런 내색 없이 항상 팀원들의 이야기에 귀 기울여주셨고 책임감 있게 팀을 이끌어 주셨습니다. 또한 중요한 디자인이 있으면 어렵더라도 반영해 주시려고 노력해 주신 점이 감사했습니다.
  • 한우석
    • '좋은 프런트엔드 개발자'의 자질을 모두 갖추신 것 같습니다. 실력, 디자인 안목, 꼼꼼함, UX 이해도, 좋은 결과물을 위해 디자인을 최대한 구현하려는 노력까지 배우고 싶은 점이 많았습니다. 특히나 디자이너와의 커뮤니케이션 능력이 협업에서 빛을 발해 즐겁게 일할 수 있었습니다. 기억에 남는 개발자가 될 것 같습니다.
  • 이아영
    • 개발 중에 담당 파트에서 디자인이 많이 수정되었지만 UI의 개선을 위해 잔수정도 마다하지 않으셨습니다. 항상 따뜻하고 웃는 얼굴로 팀원들을 대해주셨고 덕분에 팀 분위기는 물론 피드백의 과정마저 즐거웠습니다. 또한 개발 중간중간 디자이너와 커뮤니케이션을 위해 노력하시는 점이 인상 깊었습니다. 너무 감사했습니다 아영님.

이승준

  • 오준석
    • 덕분에 6주동안 웃으면서 지낼수 있었습니다. 광대가 아니라 항상 좋은분위기를 만들어주셔서 재밌는 환경에 개발할수있어서 감사하게 생각하고 있습니다. 문서정리도 잘해주시고 , sentry,https,이메일 인증 등 새로운 기술을 적용해주셔서 서비스의 완성도를 높일수 있었던것 같습니다.
  • 김혜림
    • 어려운 문제도 끈기를 갖고 해결하려는 모습이 보기좋았습니다. 개발 과정중에서 문제가 발생했을때 혼자 공부하시면서 처리해주시고, 그내용까지 상세하게 정리해서 설명해주셔서 감사하게 생각하고있습니다.그리고 여러가지 자잘한 부분도 섬세하게 신경써주셔서 놓치고 갈수있던 부분들도 많이 잡아주셨습니다.!
  • 김동우
    • 팀장역할을 잘 해주셔서 감사하게 생각하고있습니다. 항해 1주차때 같은조로 만났었는데 열심히 하는 모습이 보기좋았었는데 그때 그모습 그대로 변하지않고 초심 그대로 열심히 하시는 모습을 보면서 저도 동기부여를 많이 받은것 같습니다. 변하지 않고 열심히 하길 바랄게요...!!
  • 한우석
    • 프론트 개발자 분들중에 제일 고생을 많이 하신것 같습니다. 여러가지 기술적인 측면도 해결해주시고 자잘한 부분까지 꼼꼼하게 신경써주셔서 좋은결과물을 얻어낼수 있었던것 같습니다.재밌는 성격 덕분에 6주동안 재밌게 지낼수있어서 좋았어요.. 정말 열심히 하시고 잘하시는 분이라서 성큼성큼 성장할것 같네요!
  • 이아영
    • 항상 긍정적인 분입니다. 어떤 상황에서도 밝게대해 주실것같아서 어떤 얘기던 편하게 얘기할수있을것같다는 생각이 드는 분입니다. 또한 일이 해결될때까지 자리를 지키는 모습도 정말 책임감있고 멋있으신것같습니다. 프로젝트 막바지에는 항상 해뜰때까지 하셧던것 같네요. 덕분에 클라이언트 부분이 더완성도가 높아진것같습니다. 정말 고생하셧어요.
  • 김민지
    • 수업까지들으시고 다른 사이드 프로젝트까지 진행하시면서 디자인을 만들어 주셨습니다.디자인에대한 안목은 없지만 정말 누가봐도 이쁜 디자인을 만들어주셔서 감사하게 생각하고 있어요, 바쁘신 와중에도 항상 게더에 접속해서 밤늦게까지 같이 열심히 일 해 주시고, 의사소통도 적극적으로 해주신것 같아요 다시한번 감사드립니다.

이아영

  • 이승준
    • 프론트에서 필요한 요청이 있는지 항상 신경써주셔서 정말 감사했으며, 프론트 코드도 공부하시면서 어려운 부분 같이 보면서 도와주셔서 감사합니다. 묵묵히 맡으신 부분 책임감있게 마무리해주셔서 멋지십니다!
  • 오준석
    • 개발하면서 힘들어할때 좋은 말씀으로 응원해주셔서 정말 감사했으며, https 연결 성공하시고 다른조원 분들에게도 알려주셨던 모습이 멋지십니다! 팀 프로젝트가 좋은 방향으로 갈수있게 많은 아이디어 말씀해주셔서 덕분에 좋은 결과물을 얻어가게 되어 감사합니다.
  • 김혜림
    • 마이페이지 추가 요청사항나 변경사항이 초반에 자주있었는데 바로바로 수정해주시고, 마이페이지 편하게 작업할 수 있어서 정말 감사했습니다. 매일 알고리즘 공부하시는 열정적인 모습이 멋지십니다!
  • 김동우
    • 팀장님으로서 6주동안 팀을 이끄시느라 고생많으셨습니다! 항상 팀원들 말씀에 귀기울여 주시고 팀장역할과 개발 두가지를 동시에 하시느라 힘들고 부담도 되셨을텐데 잘해주셔서 정말 멋지십니다! 마이페이지 검색기능 맡아서 마무리 해주시고, 자잘한 질문들 같이 봐주셔서 정말 감사합니다.
  • 한우석
    • 프론트엔드 부분 전체적인 피드백 꼼꼼하게 챙겨주셔서 감사합니다. 잠을 포기하시면서 맡은 부분 책임감있게 끝내시고 프론트엔드 해결사 역할 해주셔서 감사했고 정말 멋지십니다! 6주동안 제가 질문 많이 했는데 매번 자세히 설명해주셔서 개발 꿀팁들도 많이 알아가고 배웠습니다ㅠㅠ 덕분에 프로젝트 무사히 끝낼 수 있었습니다. 정말 감사합니다 우석님!
  • 김민지
    • 이번 프로젝트 기획짤때 아이디어가 넘치는 모습이 정말 멋지셨습니다. UX적인 부분을 고려해서 디자인하시고 라이팅하시는 모습을 통해 웹페이지들이 그냥 나오는게 아니구나를 알게되고 덕분에 UX적인 부분을 많이 배워서 유익했습니다. 매번 회의에 참여해주시고, 열심히 작업해주시는 모습에 감동이였으며 감사했습니다. 민지님의 밝음이 그리울거 같습니다. 저희팀과 즐겁게 작업해주시고 멋진 결과물 만들어주셔서 정말 감사합니다 민지님!

김동우

  • 이승준
    • 첫 프로젝트 때 기획이 막판에 바뀌어 쉽지 않았을텐데도 완벽하게 해내는 모습이 멋져 이번에도 같이 프로젝트를 하게 되었습니다. 6주 동안에 긴 여정에도 검색, 무한스크롤 등 저와 함께하는 작업을 너무나 잘 만들어 주셨습니다. 앞으로 더 어렵고 복잡한 기능을 쉽게 만느는 능력자가 되실거라 믿어 의심치 않습니다!
  • 오준석
    • 6주동안의 긴 여정에서 제가 팀장으로서 부족한 부분이 있을 때 같이 분위기를 살려주고 이끌어 주셔서 무사히 잘 마친 것 같습니다. 기능적인 부분에서도 로그인, 회원가입, 탈퇴, 비밀번호 변경 등을 제가 어렵지 않게 작업할 수 있도록 도와주셨습니다. 프로젝트 내외적으로 너무 든든했습니다!
  • 김혜림
    • 저와 같은 아침형 인간이신 혜림님ㅋㅋㅋ 저와는 알람 기능을 작업했는데 처음 들어 잘 이해가 안가는 비트연산자 개념을 친절하게 설명해주셔서 어렵지 않게 기능을 만들 수 있었던 것 같습니다. 그리고 최종 발표자료에 디테일한 부분을 잡아주셔서 좀 더 발표를 편하게 할 수 있었던 것 같습니다! 다음에도 멋진 동료 개발자로서 멋진 프로젝트 같이 하고 싶습니다!
  • 한우석
    • 이번에 가장 중요한 채팅이란 생소할 수 있는 기능을 맡았지만 멋지게 해내주셔서 감사합니다. 덕분에 제 기능에만 집중을 할 수 있었습니다. 그리고 잠을 줄여가며 저희 사이트의 전체적인 디테일을 잡아주셔서 감사합니다. 메인페이지 이펙트를 멋지게 해주신 걸 봤을 때 감동은 잊을 수 없습니다ㅎㅎ 어떤 어려운 기능이라도 해내실 수 있는 개발자가 될 것 같습니다!
  • 이아영
    • 승준님과 마찬가지로 첫 프로젝트 때 스타일링이 어려웠었는데 너무 멋지게 바꿔주셔서 같이 프로젝트를 하게 됐습니다. 이번에 카드 앞면, 마이페이지의 상세하고 디테일한 기능을 위해 여러 번 수정을 하며 작업 한 결과 지금의 멋있는 결과물이 나올 수 있었습니다. 디자인 감각도 뛰어난 멋진 프론트엔드 개발자가 될거라 믿어 의심치 않습니다!
  • 김민지
    • 디자인으로 프로젝트의 시작을 열고, 프로젝트 썸네일로 마무리를 지어주셨습니다. 물론 그 사이에도 수많은 디자인 수정과 마케팅도 맡아서 해주셨습니다. 디자이너분과 첫 협업인데 너무 잘 마무리 되어 처음 해보는 협업에 대한 두려움도 없앨 수 있었던 것 같습니다. 무엇보다 멋진 디자인으로 다른 페이지보다 더욱 고급스러워 보이게 만들어주신 민지님 채구

김혜림

  • 이승준
    • 아이디어가 뛰어나며, 집중력 있게 만들어내었습니다. 개발적으로 많이 배울 수 있었습니다. 늦게까지 많은 일을 맡아 프로젝트 진행에 힘써주었습니다.
  • 오준석
    • 타인에게 배려가 많습니다. 배우고자 하는 마음이 커서, 3개월 전보다 지금 실력이 많이 늘었습니다. 또한 늦게까지 남아 일을 하시며 프로젝트에 많은 부분을 맡아주셨습니다.
  • 김동우
    • 맡은 바에 욕심이 있어 끝까지 책임감 있게 준비하는 모습을 볼 수 있었습니다. 또한 팀장으로서 팀의 분위기를 잡는 것에 최선을 다 하신 것 같습니다.
  • 한우석
    • 완성도 높은 프론트엔드 개발을 위해 노력하는 모습을 볼 수 있었습니다. 책임감 있게 프로젝트 진행에 도움 주셨습니다.
  • 이아영
    • 팀이 원활하게 돌아가도록 긍정적으로 임해주셨습니다. 언제나 질문하시며 더 실력을 키우시려는 모습과 기능 개발에 최선을 다하시는 모습이 멋졌습니다.
  • 김민지
    • 전체 팀 중 가장 열심히 하시는 디자이너로 뽑을 수 있었을 것 같습니다. 대학생임에도 불구하고 매일 게더에 오래 계시고 회의에 꼭 참여하시는, 디자인에 적극 참여하시는 모습이 좋았습니다.

FrontEnd(Language,Library,Framework)

  • Javascript
  • React
  • Redux (redux-actions, immer)
    • 데이터 전역 관리를 위한 리덕스 관리
  • connected-react-router, history
    • 라우팅 및 페이지 이동을 위한 패키지
  • axios
    • 서버 통신을 위한 패키지
  • swiper
    • 슬라이더 구현 패키지
  • sockjs-client, stompjs
    • 실시간 웹 소켓 통신을 위한 패키지
  • styled-components
    • 컴포넌트 스타일 설정 패키지

Project Introduce

디자이너와 개발자를 위한 커뮤니티 플랫폼 'Contap'

프로젝트로 나를 소개하고
함께 일하고 싶은 디자이너와 개발자를 만날 수 있는 곳!

프로젝트를 한곳에 모아 아카이빙 할 수 있어요.

Project Intention

백엔드와 프론트엔드의 프로젝트나 협업은 생각보다 쉽지 않습니다. 디자이너와의 협업은 더더욱 쉽지 않구요.

그러기 힘든 이유로는 이제 막 개발을 배우기 시작한 주니어 개발자, 디자이너들은 아직 협업 경험도 별로 없기도 하고 서로의 정보들이 한 곳에 모여있지 않아서, 그 정보를 보고 이야기를 할 수 있는 공간 또한 많지 않기 때문 이라고 생각했습니다.

그래서 저희는 이 문제를 해결하고자 개발자와 디자이너가 서로의 프로젝트를 공유하고 더 나아가서는 프로젝트를 진행 할 사람을 만날 수 있는 사이트인 Contap을 만들게 되었습니다.

Target

  • Developer (FrontEnd/BackEnd)
  • Designer

Team Introduce

  • FrontEnd React : 김동우,이아영,한우석
  • BackEnd Spring Boot : 이승준,오준석,김혜림
  • Designer UX/UI : 김민지

Service Introduce

  • 일반회원가입은 이메일 인증을 통해 가입할 수 있으며, 그 외에 카카오톡,깃허브로 로그인할 수 있습니다.
  • 메인페이지에서 작성된 카드들을 확인할 수 있습니다.
  • 마이페이지에서 카드의 앞면에 수정버튼을 누를 시 나오는 해쉬태그 목록에서 사용하는 기술 및 관심있는 항목을 선택할 수 있습니다.
  • 카드의 뒷면에는 내가 진행했던 프로젝트를 설명하는 내용을 작성할 수 있습니다.
  • 메인페이지에서 카드들을 확인한 뒤 마음에 드는 사람에게 간단한 쪽지내용과 함께 Tap요청을 보낼 수 있습니다.
  • Tap요청을 받은 사람은 보낸 사람의 카드내용을 확인한 뒤에 수락 및 거절을 할 수 있습니다.
  • Tap요청을 수락하면 나의 Grab항목에 추가되며 Grab된 사람과 실시간으로 1:1채팅을 할 수 있습니다.
  • 채팅이 종료되거나 채팅시 불쾌한 내용이 오갈 시 그랩을 끊을 수 있습니다.

ERD(Entity-Relationship Diagram)

ERD Image

Trouble Shooting

뷰데이터 관리 (이아영)
- 문제 발생 : 프로젝트 추가하기를 클릭하고 작성완료를 누르면 추가하기 창이 새로고침을 해야 사라짐

- 문제 발생 이유 : 클릭해서 추가하기 창이 나오고 작성 완료하면 없어지는 부분이 각각 다른 컴포넌트에 연결이 되있어서 처음에는 스테이트로 관리를 해서 부모 컴포넌트에 있는 데이터를 자식 컴포넌트에서 변경시키려고 했는데 계속 오류가 발생하고 데이터 전달이 잘 이루어지지 않았다.

- 문제 해결 : 자식이 부모의 데이터를 관리하는 방법을 피하기 위해 뷰 데이터를 리덕스로 관리하게 해서 1차 해결이 됐었는데, 뷰데이터를 리덕스에 저장하는 부분 재고해야 한다는 피드백을 받았다. 리덕스에서는 최대한 비즈니스 로직에 관련한 엔티티들, 데이터들, 모델들을 저장해서 활용하면 좋을 것 같다고 하셔서 자식이 부모데이터를 바꾸도록 접근하는 것이 아닌 자식이 부모의 데이터를 바꿔라라는 이벤트를 나타낼 수 있도록 다시 접근을 했다.
//부모 컴포넌트
const CardAdd = () => {
  const [click, setClick] = React.useState(false);

  const closeClick = () => {
    setClick(false);
  };

  return (
    <Grid width="100%" height="100%" padding="0px 0px 7% 0px;">
      <TextDiv>
        <TitleText>
          나의 카드 <Count>{cardCount.length}</Count>
        </TitleText>
        <TextBtn
          onClick={() => {
            setClick(true);
          }}
        >
          + 카드 추가하기
        </TextBtn>
      </TextDiv>
      <Grid margin="0px 0px 48px 0px">
        // closeClick 함수를 onHide에 담아서 자식 컴포넌트에서 사용
        <CardBackWrite onHide={closeClick} />
      </Grid>
      {cardList.backCardIdx.map((cardId) => {
        return (
          <Grid key={cardId}>
            <CardPortfolio cardId={cardId} />
          </Grid>
        );
      })}
    </Grid>
  );
};
//자식 컴포넌트
const CardBackWrite = ({ onHide }) => {
  const addCardBack = () => {
    //작성완료 버튼 누르면 작성화면 꺼지게 함. () 꼭 붙이기..!(함수 바로 실행한다는 의미)
    onHide();
    // dispatch(isSuccess(!handleClick));
  };

  return (
    <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
      <AddBtn onClick={addCardBack} disabled={disabled}>
        <Text
          bold20
          color={disabled ? ColorStyle.PrimaryPurple : ColorStyle.Gray300}
        >
          작성 완료
        </Text>
      </AddBtn>
      <div
        onClick={() => {
          // 작성 취소버튼을 누르면 작성화면 꺼지게 함
          onHide();
        }}
      >
        <CloseBtn cursor="pointer" />
      </div>
    </div>
  );
};
연관검색어, 무한스크롤 기능 작성 및 문제점 파악(김동우)

연관 검색어

  1. 스택 및 관심사 데이터를 한꺼번에 배열로 가져온다.
  2. input에 글자를 검색할 때마다 배열에 있는 글자와 일치하는지 비교 후 같다면 연관 검색어 배열에 넣는다.
  3. 연관 검색어 배열에 넣을 때 일치하는 문자의 순서를 비교하여 넣는다.
  4. 완료된 배열은 리덕스에 저장한다(렌더링이 발생하기 때문에)
  5. map을 이용하여 연관검색어 목록이 나오도록 한다.
  6. 연관 검색어를 클릭했을 때 검색 API를 전송한다.
// 0. 검색어 목록을 만든다
React.useEffect(async () => {
  try {
    const res = await axios.get(`${baseURL}/main/hashtag`);

    const { data } = res;

    const searchDataArr = [];
    data.forEach((val) => {
      searchDataArr.push(val.name);
    });
    dispatch(searchDataList(searchDataArr));
  } catch (error) {
    console.error(error);
  }
}, []);

//1. setState로 input에 들어가는 정보를 받아온다.
const [data, setData] = React.useState('');

//2. 연관검색어를 담는 searchArr과 이것을 렌더링 이후에도
// 가지고 있을 수 있게하는 searchList 설정
const searchArr = [];
const searchList = useSelector((state) => state.cards.searchArr);

//3. data가 바뀔 때마다 searchList가 갱신될 수 있도록 useEffect 사용
// data와 searchData를 filter로 비교 후 일치하는 value를 searchList로 채운다.

React.useEffect(() => {
  searchData.filter((val) => {
    if (data.toLocaleLowerCase() === '') {
      return null;
    }
    if (val.toLocaleLowerCase().indexOf(data.toLocaleLowerCase()) !== -1) {
      searchArr.push(val);
    }
    // console.log(val);
    console.log(searchArr);

    return searchArr;
  });
  if (searchArr !== []) {
    dispatch(searchArrList(searchArr));
  }
}, [data]);

//4. searchList를 해당 value로 검색할 수 있는 함수를 넣어 버튼으로 만든다.
const ArrayData = searchList.map((val) => {
  return (
    <ContentWrapper>
      <li>
        <ContentBtn
          type="button"
          onClick={async () => {
            setData(val);
            const searchInfo = {
              searchTags: [val],
              type: 0,
              page: 0,
              field: 3,
            };
            await dispatch(searchInfoDB(searchInfo));
            setTag(true);
            setClick(false);
          }}
        >
          <Text color="black" regular16>
            {val}
          </Text>
        </ContentBtn>
      </li>
    </ContentWrapper>
  );
});

무한스크롤

  1. 검색 시 API에서, 현재 페이지를 함께 전송한다. 처음 보낼 때는 0페이지 이다.
  2. scroll event로 스크롤 시 페이지가 마지막 페이지인지 확인한다.
  3. 스크롤이 끝에 닿았다면, 현재 검색어에서 페이지가 1 증가한 API를 보낸다.
  4. 다음 페이지에 해당하는 정보를 백엔드에서 전송한다.
  5. 현재 카드들 아래에 붙혀준다.
  6. 불려저오는 카드의 개수가 9개 이하라면 더 이상 무한 스크롤이 작동하지 않도록 한다.

현재 이 기능의 가장 큰 문제는 API와 컴포넌트가 얽혀있는 것이라고 생각한다.
이렇게 얽힌 컴포넌트나 API는 다른 곳에 활용하기가 아주 힘들다는 것을 알 수 있었다. 앞으로는 기능 작성 시 각 기능을 분리해서 독립적으로 활용이 가능하게 해야겠다는 생각이 들었다.

실시간 알림 기능 구현 (한우석)
로직
  • 로그인 시 모든 유저를 공통된 room(PublicRoom)에 넣는다.
  • 채팅을 하는 1:1 room에(ChatRoom) 입장해서 메시지 입력 시, 상대방이 ChatRoom에 들어와 있지 않고 PublicRoom에 들어와 있을 때 해당하는 유저를 찾은 후 알림 보낸다.
  • 페이지 이동 시, 새로 고침 시에도 subscribe 상태를 유지해야 한다.+

해결 과정

  • 과정 1

    • 로그인 시 root Page인 CardList 컴포넌트에 소켓에 연결되는 로직을 추가하고 첫 랜더링 시 한번만 실행되도록 하였다
  • 결과

    • 모든 페이지에서 정상적으로 subscribe 상태 유지
  • 문제점

    • CardList 컴포넌트에 useEffect 안에 들어가 있기 때문에 다른 페이지에서 새로고침을 하면 소켓 연결이 끊어짐


  • 과정 2

    • Login 컴포넌트에서 로그인 버튼을 클릭 시 소켓에 연결 되는 로직 추가
  • 결과

    • 모든 페이지에서 정상적으로 subscribe 상태 유지
  • 문제점

    • Login 컴포넌트에 useEffect 안에 들어가 있기 때문에 다른 페이지에서 새로고침을 하면 소켓 연결이 끊어짐, 로직 작성 중간에 바로 다음 방법 시도


  • 과정 3

    • 어떤 페이지에서 사용자가 새로고침을 할 지 모르기 때문에 페이지를 이동 할 때마다 구독과 구독해제를 하는 로직 추가.
    • 전체적인 코드를 줄이기 위해서 소켓을 연결하는 로직을 커스텀훅으로 작성
  • 결과

    • 모든 페이지에서 정상적으로 subscribe 상태 유지, 새로고침해도 끊어지지 않고 다시 연결
  • 문제점

    • 기능은 정상적으로 동작하나 모든 페이지에서 소켓에 연결을 하는 로직을 추가해야 하기 때문에 코드가 쓸데없이 늘어난다는 느낌을 받음.


  • 과정 4

    • Header 는 사라지지 않기 때문에 Header 안에 소켓 연결하는 로직 추가
  • 결과

    • 모든 페이지에서 정상적으로 subscribe 상태 유지, 새로고침해도 끊어지지 않고 다시 연결
  • 문제점

    • 구독 해제가 되지 않고 원하는 대로 동작하지만 Header 안에 소켓을 넣는게 맞을까 라는 의문이 계속 들었다.
    • 기능적인 분리를 하지 못했다는 생각에 드는 찝찝함이라고 판단하여 다음 방법으로 넘어 갔다.


  • 과정 5

    • 소켓 연결을 위한 컴포넌트를 추가하여 다른 컴포넌트를 Children으로 받음
    // WsNotiRoom.js
    import React from 'react';
    import useSocketNotiRoom from '../hooks/useSocketNotiRoom';
    
    const WsNotiRoom = ({ children }) => {
      const [wsConnectSubscribe, token] = useSocketNotiRoom();
    
      React.useEffect(() => {
        if (!token) {
          return null;
        }
        wsConnectSubscribe();
        return null;
      }, []);
      return children;
    };
    
    export default WsNotiRoom;
    
    // App.js
    //WsNotiRoom 추가
    function App() {
      return (
        <WrapApp>
          <Wrap>
            <Reset />
            <PublicRoute restricted path="/login" component={Login} exact />
            <PublicRoute restricted path="/signup" component={Signup} exact />
            <>
              <WsNotiRoom>
                <Header />
                <Permit>
                  <PublicRoute path="/" component={CardList} exact />
                  <PrivatecRoute path="/settings" component={Settings} exact />
                  <PrivatecRoute path="/contap" component={Contap} exact />
                  <PrivatecRoute path="/mypage" component={Mypage} exact />
                  <PrivatecRoute path="/edit" component={CardEdit} exact />
                </Permit>
              </WsNotiRoom>
            </>
          </Wrap>
        </WrapApp>
      );
    }
  • 결과

    • 모든 페이지에서 정상적으로 subscribe 상태 유지, 새로고침해도 끊어지지 않고 다시 연결
    • 가장 깔끔하게 해결 되었다는 생각이 들어서 현재 이 방법을 선택 했습니다.

  • 상세 과정

새로고침 시 로그인이 필요한 페이지 에서는 /auth 로 get요청을 보내서 user정보를 받아 오는데 받아오기 전에 소켓이 먼저 연결 되어서 userEmail이 들어오지 않음.

해결법

그냥 단순하게 생각을 바꿔보니 wsConnectSubscribe 함수 안에서 /auth로 get요청을 하면 될 것 같아서 시도 해보니 정상적으로 동작 하였다.

const wsConnectSubscribe = React.useCallback(async () => {
  if (!token) {
    return null;
  }
  try {
    //커넥트 하기 전 유저 데이터 받아옴
    const { data } = await T.GET('/auth');
    console.log(data);
    ws.connect({}, () => {
      ws.subscribe(
        `/user/sub/user`,
        (data) => {
          // const newMessage = JSON.parse(data.body);
          console.log('알람');
          if (!isNoti) {
            dispatch(setNoti(true));
          }
        },
        { token, userEmail: data.email },
      );
    });
  } catch (error) {
    console.log(error);
  }
}, []);

로그인 페이지에서 로그인 버튼을 누를 때 소켓에 연결이 된다는 생각으로 로직을 작성했다.

// Login.js
const wsConnectSubscribe = (userEmail, token) => {
  console.log('토큰 있냐? ===> ', token);
  if (!token) {
    console.log('토큰 업쩡');
    return null;
  }

  try {
    ws.connect({}, () => {
      ws.subscribe(`/user/sub/user`, {}, { token, userEmail });
    });
  } catch (error) {
    console.log(error);
  }
};

// 로그인 버튼 클릭 시 실행
<form
  onSubmit={handleSubmit(async (loginInfo) => {
    console.log('로그인 인포 ===>', loginInfo);
    await dispatch(loginToServer(loginInfo));
    const token = getToken();
    console.log('커넥트 실행');
    wsConnectSubscribe(loginInfo.email, token);
    console.log('히스토리 푸시');
    // history.push('/');
  })}
>
  ...
</form>;

??????....

왜 소켓 연결이 안될까?

근데 로그인창에서 코드를 작성 하다가 든 생각인데 결국 유저가 메인페이지에서 새로고침을 하면 소켓에 재연결이 안될 것 같아 결국 메인페이지로 다시 돌아왔다..

어떤 페이지에서 사용자가 새로고침을 할 지 모르기 때문에 페이지를 이동 할 때마다 구독과 구독해제를 하도록 로직을 작성 했다.

import StompJs from 'stompjs';
import SockJS from 'sockjs-client';
import { useDispatch, useSelector } from 'react-redux';
import { getToken } from '../utils/auth';
import T from '../api/tokenInstance';
import { setNoti } from '../features/notice/actions';

// 변수 및 함수 선언, useEffect
const isNoti = useSelector((state) => state.notice.isGlobalNoti);
const sock = new SockJS(`${baseURL}/ws-stomp`);
const ws = StompJs.over(sock);
const token = getToken();

const wsConnectSubscribe = React.useCallback(async () => {
  if (!token) {
    return null;
  }
  try {
    const { data } = await T.GET('/auth');
    console.log(data);
    ws.connect({}, () => {
      ws.subscribe(
        `/user/sub/user`,
        () => {
          if (!isNoti) {
            dispatch(setNoti(true));
          }
        },
        { token, userEmail: data.email },
      );
    });
  } catch (error) {
    console.log(error);
  }
  return null;
}, []);

const wsDisConnectUnsubscribe = React.useCallback(() => {
  try {
    ws.disconnect(
      () => {
        ws.unsubscribe('sub-0');
      },
      // { token }
    );
  } catch (error) {
    console.log(error);
  }
}, []);

React.useEffect(() => {
  if (!token) {
    return null;
  }
  wsConnectSubscribe();

  return () => {
    wsDisConnectUnsubscribe();
  };
}, []);

소켓에 연결하는 위의 로직을 모든 페이지에 추가하다가 문득 이런 상황에 커스텀 훅을 써야하지 않을까 싶어서 항상 생각만 하던 커스텀 훅을 직접 만들어 보았다.

// useSocketNotiRoom.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import StompJs from 'stompjs';
import SockJS from 'sockjs-client';
import { getToken } from '../utils/auth';
import T from '../api/tokenInstance';
import { setNoti } from '../features/notice/actions';

const baseURL = process.env.REACT_APP_SERVER_URI;

export default function useSocketNotiRoom() {
  const dispatch = useDispatch();
  const isNoti = useSelector((state) => state.notice.isGlobalNoti);

  const sock = new SockJS(`${baseURL}/ws-stomp`);
  const ws = StompJs.over(sock);
  const token = getToken();

  const wsConnectSubscribe = React.useCallback(async () => {
    if (!token) {
      return null;
    }
    try {
      const { data } = await T.GET('/auth');
      console.log(data);
      ws.connect({}, () => {
        ws.subscribe(
          `/user/sub/user`,
          () => {
            if (!isNoti) {
              dispatch(setNoti(true));
            }
          },
          { token, userEmail: data.email },
        );
      });
    } catch (error) {
      console.log(error);
    }
    return null;
  }, []);

  const wsDisConnectUnsubscribe = React.useCallback(() => {
    try {
      ws.disconnect(() => {
        ws.unsubscribe('sub-0');
      });
    } catch (error) {
      console.log(error);
    }
  }, []);

  return [wsConnectSubscribe, wsDisConnectUnsubscribe, token];
}

아래와 같이 쓰기만 하면 끝나서 정말 많은 코드가 줄어들었다 !

import useSocketNotiRoom from '../hooks/useSocketNotiRoom';

// 커스텀 훅 호출, useEffect
const [wsConnectSubscribe, wsDisConnectUnsubscribe, token] =
  useSocketNotiRoom();

React.useEffect(() => {
  if (!token) {
    return null;
  }
  wsConnectSubscribe();

  return () => {
    wsDisConnectUnsubscribe();
  };
}, []);

헤더에 소켓을 넣은게 계속 마음에 걸렸는데 왜 마음에 걸렸는지 알 것 같다.

헤더에 소켓을 넣는다는 것은 결국 모든 페이지에서 사용하기 위해 넣은 거지만 정작 헤더 컴포넌트의 기능과는 관련이 없는 상황이라 기능적인 분리를 하지 못한 것에 대한 찝찝함인 것 같다.

그래서 WsNotiRoom 컴포넌트를 분리해서 거기서 소켓을 쓴 다음에 App.js에서 다른 모든 컴포넌트를 감싸 주었다. 그렇기에 그 컴포넌트는 새로고침 전까지 절대 렌더링 되지 않는다

// WsNotiRoom.js
import React from 'react';
import useSocketNotiRoom from '../hooks/useSocketNotiRoom';

const WsNotiRoom = ({ children }) => {
  const [wsConnectSubscribe, token] = useSocketNotiRoom();

  React.useEffect(() => {
    if (!token) {
      return null;
    }
    wsConnectSubscribe();
    return null;
  }, []);
  return children;
};

export default WsNotiRoom;

// App.js
//WsNotiRoom 추가
function App() {
  return (
    <WrapApp>
      <Wrap>
        <Reset />
        <PublicRoute restricted path="/login" component={Login} exact />
        <PublicRoute restricted path="/signup" component={Signup} exact />
        <>
          <WsNotiRoom>
            <Header />
            <Permit>
              <PublicRoute path="/" component={CardList} exact />
              <PrivatecRoute path="/settings" component={Settings} exact />
              <PrivatecRoute path="/contap" component={Contap} exact />
              <PrivatecRoute path="/mypage" component={Mypage} exact />
              <PrivatecRoute path="/edit" component={CardEdit} exact />
            </Permit>
          </WsNotiRoom>
        </>
      </Wrap>
    </WrapApp>
  );
}

이렇게 하나로 쓰려니 커스텀훅을 굳이 쓸 필요가 없다고 생각 되었지만 그래도 처음 만들어본 훅이니까 그냥 쓰기로 했다..!

Render (한우석)
사이드바의 카테고리를 클릭 할 때 마다 랜더링이 여러번 되는 현상이 있었다.

useCallback과 useMemo를 사용하기 위해 찾아 보았다.

CardList 컴포넌트에서 카드 클릭 시 전체 카드 렌더링 됨.

이유가 뭔지 모르겠다.

CardList에서 CardFront를 불러오는데 모달을 CardFront의 State로 관리 하니 하나를 클릭 할 때 하나의 CardFront가 리렌더링 될 것이라고 생각 했지만 모든 CardFront가 랜더링이 되어서 useCallback이나 useMemo를 사용하려고 했지만 유의미한 효과를 얻지 못하였고 다른 방법을 더 찾아 보았다.

Props가 바뀌기 전까지 랜더링을 하지 않는 React.memo를 적용시켜 보았는데 일단 결과는 성공적이지만 아직 정확히 왜 모든 카드가 랜더링이 되었는지는 잘 모르겠다...

React.memo란?

UI 성능을 증가시키기 위해, React는 고차 컴퍼넌트(Higher Order Component, HOC) React.memo()를 제공한다. 렌더링 결과를 메모이징(Memoizing)함으로써, 불필요한 리렌더링을 건너뛴다.

// 변경 전
export default CardFront;
// React.memo() 사용
export const MemoizedCardFront = React.memo(CardFront);
  • 메모 사용 전
  • 클릭 시 9개 카드 전부 렌더링

  • 메모 사용 후
  • 클릭 시 1개 카드만 렌더링

PathName 가져오기 (한우석)
- 다른 사람이 채팅을 보냈을 때 채팅 미리보기 창의 메시지를 바꾸기 위해 알람을 감지 했을 때 `loadTalkRoomListToAxios()` 를 dispatch 했다.
// chat 보냈을 때 채팅방에 둘다 있을 때 타입 0
// chat 보냈을 때 채팅방에 한명만 있고 상대방은 로그인 했을 때 타입 1
// chat 보냈을 때 상대방이 로그아웃 타입 2
// tap 요청 받았을 때 타입 3
// tap 요청 거절한게 타입 4
// tap 요청 수락한게 타입 5
if (newNoti.type === 1) {
  console.log('채팅알람!');
  console.log('디패 로드 톡룸');
  await dispatch(loadTalkRoomListToAxios());
  dispatch(setChatNoti(true));
}
  • user A 가 메시지를 보내면

  • user B 의 채팅방 목록에 미리보기로 표기 된다.

user B가 /grabtalk 페이지에 있을 때만 dispatch를 하면 될 것 같아서 코드를 수정 했다.

const pageCheck = window.location.pathname;

if (newNoti.type === 1) {
  if (pageCheck === '/grabtalk') {
    console.log('디패 로드 톡룸');
    await dispatch(loadTalkRoomListToAxios());
  }
  dispatch(setChatNoti(true));
}
  • consol에 찍힌 값

정상적으로 동작 하길래 이것저것 더 테스트를 해보던 중 메인페이지로 돌아갔다가 채팅페이지로 오게 되면 pathname이 날아가서 미리보기가 갱신 되지 않는 문제점을 발견 했다.

그래서 url을 가지고 오는 몇가지 방법을 더 시도해 보았다.

import { useHistory, useLocation } from 'react-router-dom';

const history = useHistory();
const location = useLocation();

const pageCheck = window.location.href.split('/');
const nowPage = pageCheck[pageCheck.length - 1];
const nowPageE = window.location.pathname;

if (newNoti.type === 1) {
  console.log('nowPageE = window.location.pathname ===>', nowPageE);
  console.log('location ====>', location);
  console.log('history ====>', history);
  console.log('nowPage ====>', nowPage);
  console.log('pageCheck = window.location.href.split("/") ====>', pageCheck);
  if (nowPage === '/grabtalk') {
    console.log('디패 로드 톡룸');
    await dispatch(loadTalkRoomListToAxios());
  }
  dispatch(setChatNoti(true));
}
  • 이렇게 해서 콘솔을 확인 해보니 history.location.pathname 빼고는 전부 root 경로로 바뀐 것을 확인 할 수 있었다.
  • 명확한 이유를 아직 알지 못했다.... 차차 찾아봐야지...

  • 이제 해결이 된 줄 알고 다시 콘솔을 찍어 보았는데 동일한 증상이 발생 하였다..
const nowPage = history.location.pathname;

if (newNoti.type === 1) {
  console.log('history.location.pathname ====>', nowPage);
  console.log('history ====>', history);
  if (nowPage === '/grabtalk') {
    console.log('디패 로드 톡룸');
    await dispatch(loadTalkRoomListToAxios());
  }
  dispatch(setChatNoti(true));
}
  • 분명 history 안에는 들어있는데 nowPage라는 변수에 담은 history.location.pathname은 root경로를 출력했다.

  • 결국 최종적으로 해결 한 방법은 따로 변수에 담지 않고 바로 history를 가져오니 해결 되기 했는데 너무 찝찝하다.. 정확한 원인이 무었인지 어떻게 찾아야 할지 감이 오질 않는다....
  • 그래도 일단 해결은 되어서 다행이다 ㅜㅜ 진짜 이거때문에 몇시간을 삽질 했는지.. 오늘은 진짜 다섯시에는 자려고 했는데 결국 7시가 다 되어버렸다.
if (newNoti.type === 1) {
  if (history.location.pathname === '/grabtalk') {
    console.log('디패 로드 톡룸');
    await dispatch(loadTalkRoomListToAxios());
  }
  dispatch(setChatNoti(true));
}

채팅 말풍선 디테일 (한우석)
- 같은 사람이 보낸 말풍선의 위,아래 마진은 16px - 보낸 사람이 다르면 말풍선의 위,아래 마진은 32px - 한사람이 여러개의 말풍선을 보냈을 때 한 세트처럼 보여질 수 있도록 구현

  • 기존 코드
return (
      <ChatMessageBox ref={scrollRef}>
        {messageList?.map((msg, i) => {
          return (
            <Speechbubble key={i} isMe={msg.writer === userInfo}>
                <Text regular16>{msg.message}</Text>
              </Speechbubble>
            )
        })}
      </ChatMessageBox>
  );
};

// css margin
// 본인의 말풍선인지 아닌지만 체크
  margin: ${({ isMe }) =>
  isMe ? '24px 0px 24px auto' : '24px auto 24px 48px'};

해당 사항들을 변경 하려고 하니 딱 떠오르는 로직이 없었다....

해결 과정

  • 이전 메시지를 감지해야 하나 싶어서 아래의 코드를 추가 했다.
const speechCheck = (idx) => {
	console.log(messageList[idx - 1])
  console.log(messageList[idx])
  if (messageList[idx - 1].writer === messageList[idx].writer) {
    return true
  } return false
}

return (
      <ChatMessageBox ref={scrollRef}>
        {messageList?.map((msg, i) => {
          return (
            <Speechbubble key={i} isMe={msg.writer === userInfo} speechCheck={speechCheck(i)}>
                <Text regular16>{msg.message}</Text>
              </Speechbubble>
            )
        })}
      </ChatMessageBox>
  );
};
  • 사실 바로 될 줄 몰랐는데 일단 이렇게 하니 연속 된 메시지의 정보를 확인 할 수 있었다.

  • 다음으로 내가 필요한 조건
    • 메시지를 내가 보냈는가?
    • 같은 사람이 연달아서 보낸 메시지가 있는가?
  • 메시지를 써보니 두개씩 뜬다.. 뭔가 단단히 잘못 되었다..

일단 다시 코드를 원상태로 돌린 다음에 하나하나 해결을 먼저 해보기로 했다.

말풍선을 한 세트로 묶는거 보다 일단 글자 사이 간격 먼저!

return (
	  <ChatMessageBox ref={scrollRef}>
	    {messageList?.map((msg, i) => {
	      return msg.writer === userInfo ? (
	        <MySpeechbubble key={i}>
	          <Text regular16>{msg.message}</Text>
	        </MySpeechbubble>
	      ) : (
	        <Speechbubble key={i}>
	          <Text regular16>{msg.message}</Text>
	        </Speechbubble>
	      );
	    })}
	  </ChatMessageBox>
  );
};

isMe={msg.writer === userInfo} 를 기준으로 나누었었는데 그냥 div 자체를 따로 주는게 작업하기 편할 것 같아서 일단은 둘이 나누어 보았다.

근데 나누고 보니 결국 똑같다는 생각이 든다..

나누고 어쩌고 해봐야 결국 둘을 구분할 수 없을 것 같아 다시 처음부터 생각을 해보았다.

  • 아래 보이는 정보가 반복되어 div로 들어간다.
  • 이 안에서 내가 말풍선을 구분할 수 있는 키값이 있나?

  • 결국 map을 돌린다는 건 이런식으로 된다는 건데 여기서 어떻게 할까?
  • 내가 너무 map 안에서만 해결을 하려고 하나?
return (
	  <ChatMessageBox ref={scrollRef}>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
        <Speechbubble isMe={msg.writer === userInfo}>
          <Text regular16>{msg.message}</Text>
        </Speechbubble>
	  </ChatMessageBox>
  );
};
  • 백엔드에게 요청해서 해결 할 수 있는 방법은?

    • 메시지를 보내는 사람이 바뀌었을 때 체크를 할 수 있는 값을 받을 수 있다면 그 부분이 체크 되었을 떈 마진 높게?
    • 그런식으로 해서 된다면 지금도 체크할 수 있는 값만 만들면 되나?
    • 그래서 스테이트로 관리를 할 수 있을까 싶어 작성 했다가 길이 보이지 않아서 일단 다시 돌아왔다 ㅎ..
    // 마지막으로 받은 메시지의 writer를 가져온다.
      const [writer, changeWriter] = React.useState(messageList[0].writer)
    
    return (
    	  <ChatMessageBox ref={scrollRef}>
    	    {messageList?.map((msg, i) => {
              // writer이 메시지를 보낸 유저와 다를 때
              if (writer !== msg.writer) {
                // writer을 메시지를 보낸 유저로 바꾼다.
                changeWriter(msg.writer)
              }
              return (
                <Speechbubble key={i} isMe={msg.writer === userInfo}>
                    <Text regular16>{msg.message}</Text>
                  </Speechbubble>
                )
            })}
    	  </ChatMessageBox>
      );
    };
  • 다음으로 했던 방법은 다시 함수를 하나 만들었다

const test = () => {
  //state를 copy
  const copy = messageList.slice();
  //messageList가 하나일 땐 굳이 나눌 필요가 없으니 return
  if (messageList.length === 1) {
    return copy;
  }

  // 다음 메시지와 writer를 비교해서 isMargin 이라는 key,value를 주었다.
  for (let i = 0; i < messageList.length - 1; i++) {
    const previousMessage = copy[i];
    const nextMessage = copy[i + 1];
    if (!nextMessage) {
      break;
    }

    if (previousMessage.writer !== nextMessage.writer) {
      copy[i].isMargin = true;
    } else {
      copy[i + 1].isMargin = false;
    }
    copy[i].isMargin = false;
  }
  return copy;
};

const etest = test();

console.log(etest);
  • 실행 결과
  • 객체를 확장할 수 없다는 에러메시지가 나왔고 처음부터 반신반의 하며 만들었던 코드라서 굳이 더 찾아보지 않고 다른 방법을 찾아보았다.

  • 다음으로는 map함수를 조금 더 찾아 보았다.
  • callback의 인수가 더 있을 것 같아서 찾아 보았는데 역시 array 라는 파라미터가 있었다.

이 array를 이용해서 다시 이전 메시지를 체크할 수 있는 조건문을 작성 했다.

return (
	  <ChatMessageBox ref={scrollRef}>
	    {messageList?.map((msg, i, arr) => {
	      const prevMessage = arr[i];
	      const nextMessage = arr[i + 1];
	      const isMargin =
					// isMargin은 message가 하나거나, 첫번째 message거나, 작성자가 같다면 false
					// 아니면 true
	        arr.length === 1 ||
	        i === arr.length - 1 ||
	        prevMessage.writer === nextMessage.writer
	          ? false
	          : true;
	      return (
	        <SpeechBubble key={i} isMe={msg.writer === userInfo} isMargin={isMargin}>
	          <Text regular16>{msg.message}</Text>
	        </SpeechBubble>
	      );
	    })}
	  </ChatMessageBox>
  );
};
  • 조금 더 방법을 다듬어 볼 수 있을 것 같은데 일단 동작은 정상적으로 되었다.


  • 다음은 이 부분을 해결 해야한다...
  • 한사람이 여러개의 말풍선을 보냈을 때 한 세트처럼 보여질 수 있도록 구현

위에 마진과 비슷하게 구현을 하긴 했는데... 이게 맞나 싶은데... 어쩔 수 없이 일단은 그대로 두기로 했다.

return (
	  <ChatMessageBox ref={scrollRef}>
	    {messageList?.map((msg, i, arr) => {
	      const isMargin =
				  arr.length === 1 ||
				  i === arr.length - 1 ||
				  arr[i].writer === arr[i + 1].writer
				    ? false
				    : true;

				const orderCheck = () => {
				  if (arr.length === 1) {
				    return;
				  }
				  if (arr[i].writer !== arr[i - 1]?.writer) {
				    if (isMe) {
				      return 'meFirst';
				    }
				    return 'first';
				  }
				  if (arr[i].writer !== arr[i + 1]?.writer) {
				    if (isMe) {
				      return 'meLast';
				    }
				    return 'last';
				  }
				  if (isMe) {
				    return 'meMiddle';
				  }
				  return 'middle';
				};

	      return (
	        <SpeechBubble key={i} isMe={msg.writer === userInfo} isMargin={isMargin}>
	          <Text regular16>{msg.message}</Text>
	        </SpeechBubble>
	      );
	    })}
	  </ChatMessageBox>
  );
};

// css border-radius
// ...... 목으로 라도 돌려야지...
border-radius: ${({ orderCheck }) =>
  orderCheck === 'meFirst'
    ? '30px 30px 5px 30px'
    : orderCheck === 'meLast'
    ? '30px 5px 30px 30px'
    : orderCheck === 'first'
    ? '30px 30px 30px 5px'
    : orderCheck === 'last'
    ? '5px 30px 30px 30px'
    : orderCheck === 'middle'
    ? '5px 30px 30px 5px'
    : '30px 5px 5px 30px'};

구현 완료

User FeedBack

  • FeedBack 통계

  • FeedBack - 카드작성시 뭘 해야할지 잘 모르겠습니다, 카드형식으로 프로젝트를 보여주기 때문에 제약이 많았습니다.

  • Solution - 기획단계부터 Closed Community형식으로 가기위해 뒷면카드를 작성하지 않으면 다른사람의 카드를 열람하시 못하게 하였으나 그렇게하니 카드 작성을 어떤식으로 해야할지 모르겠다는 피드백이 많았는데 이때 떠오른 해결방법은 두가지 였습니다.
    1. ClosedCommunity를 유지하고 온보딩 형식으로 카드작성 가이드를 보여준다.
    2. 뒷면카드 열람권한을 로그인만 하면 가능하게 하여 사용자가 직접 카드를 탐색하여 작성할 수 있게 한다.

      전자의 방법은 너무 강제성이 강할 것 같았고 아무래도 하나하나 설명하는 사이트가 좋은 사이트처럼 보이지는 않아서 PlaceHolder로 최대한 상세하게 알려주는 것을 전제로 해결방안으로 후자를 선택하게 되었습니다.

  • FeedBack - 백엔드,프론트엔드의 색이 구분이 되었으면 좋겠다.

  • Solution - 핵심 기능이 개발자와 디자이너의 매칭이기 때문에 개발자 끼리의 색 구분은 크게 의미가 없다고 생각 했었으나 생각보다 많은 피드백 요청이 와서 많은 고민을 했었습니다.색을 추가만하면되는 간단한 작업이었지만 이 부분 또한 사이트의 메인컬러라고 생각할 수 있기 때문에 정말 신중하게 색상을 선택하여 적용했습니다.

Marketing

About

contap frontend repository

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages