-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[7주차] TIG 미션 제출합니다. #6
base: master
Are you sure you want to change the base?
Conversation
1. 과제 구현 기능 및 세부 사항은 리드미에 작성 2. 기존의 과제 리드미는 Assignment.md 파일로 이동 3. 대부분 지난 번 netflix 과제와 설정은 동일함
640px를 breakpoint로 잡아 그전까진 100%, 이후엔 640px 고정
Header 컴포넌트 구현 및 기본 테마, width 설정 merged by seungwan
-absolute 부여
CEOS 클릭시 랜딩페이지로 이동
- 높이 작을때 발생하는 overflow 방지
Feat/vote result merged by seungwan
로그인 페이지 및 회원 가입 페이지 구현
1. 클라이언트 코드 단에서 곧바로 진행하면 CORS 문제가 발생
1. 투표 상태와 연결 완료. 처음 렌더링 시에 사용자의 정보를 백엔드 api로 받아와서 상태로 연결해주면 됨 2. signupform에서 팀별 순서 재설정 -> 백엔드 로직이 원인
1. 로그인 되면, authorization헤더에 있는 jwt와, body에 있는 username, part 를 읽어들인다. 2. 읽어들인 정보를 각각 localStorage에 있는 jwtToken, username, part라는 key 값에 넣어둔다. 로그인 되면 홈 화면으로 보냄 3. 헤더 컴포넌트는 useEffect() 훅을 통해 dom에 mount 될 때 로컬 스토리지에 접근하여 part와 username을 보여준다. 로그아웃 & 로그인 버튼도 조건부 상태로 렌더링
Feat/eunsu merged by seungwan
- Parallel, Intercepting routes 사용
Modal feat made by eunsu, merged by seungwan
1. next.config.mjs 파일에 rewrites로 source와 destination 설정 2. 기존의 api 콜을 source에 해당하는 것으로 수정
1. 로그인된 사용자에게도 로그인 안된 사용자는 투표 못한다고 뜨는 문제 2. router.push에 {scroll: false} 속성 부여 3. 아예 replace() 메서드를 쓰는 게 나을 수도 있음
Feat/eunsu
Feat/eunsu
1. useEffect 내의 else 문에서도 상태를 변경시킴
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
마지막 과제도 고생 많으셨어요!! ㅠㅠ😄 항상 사소한 부분까지 꼼꼼하게 구현하시는 것 같아요 ! 세오스 활동하면서 정말 많이 배웠습니다..ㅎㅎ 남은 프로젝트도 파이팅이에요 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
경고창을 모달을 만들어주셨네요! 예쁘고 섬세해요 👍
Modal 컴포넌트를 하나만 만들어서 message를 prop으로 받아서 모달 재사용하는 방법도 좋을 것 같아요!!
|
||
try { | ||
const tmpResponse = await fetch( | ||
'http://43.202.139.24:8080/api/user/signup', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 api 주소 위에 정의해놓으신거로 바꿔주면 좋을 것 같아요!
'http://43.202.139.24:8080/api/user/signup', | |
'/api/user/signup ', |
|
||
try { | ||
const tmpResponse = await fetch( | ||
'http://43.202.139.24:8080/api/user/login', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도요!!
} catch (error) { | ||
console.log(error); | ||
return new Response('Login Failed!', { | ||
status: 400, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클라이언트에서 직접 상태를 지정할수도 있지만 서버에서 받은 상태 코드를 그대로 사용하는 것도 좋은 방법일 것 같아요!
loginmodal: React.ReactNode; | ||
}) { | ||
return ( | ||
<div className="w-full sm:w-[640px] bg-backgroundColor h-dvh flex flex-col overflow-y-scroll"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반응형ㅎㅎ
import { useRouter } from 'next/navigation'; | ||
import { useState, useEffect } from 'react'; | ||
|
||
const TeamName = ['AZITO', 'BEATBUDDY', 'TIG', 'BULDOG', 'COUPLELOG']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 이번에 데이터는 따로 data.ts에서 모아서 관리했는데 더 편하고 깔끔했어요! 따로 data들을 모아놓는 파일을 만드는 것도 좋은 것 같아요
body: JSON.stringify(sendingDataObject), | ||
}); | ||
if (response.ok) { | ||
alert('팀 투표가 완료되었습니다.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alert를 쓰니까 확실히 사용성이 좋아지네요... 사실 저는 윈도우 알람 모달을 별로 안좋아해서 하나도 안썼는데 있는게 훨씬 나은 것 같아요👍
|
||
// 백엔드로부터 해당 유저의 상태를 쿠키나 로컬 스토리지에 있는 jwt를 이용하여 받아오고, 이를 상태와 연결하는 side effect | ||
useEffect(() => { | ||
const localStorageToken = localStorage.getItem('jwtToken'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 useEffect 내부에 정의하신 이유가 따로 있을까요..!?localStorage.getItem('jwtToken')
가 반복되고 있는데, 함수 상단에 const jwtToken = localStorage.getItem('jwtToken');
이렇게 변수에 정의해두고 사용하면 더 좋을 것 같아요!
const tmpData: teamProp[] = data.sort((a, b) => { | ||
if (a.voteCount === b.voteCount) { | ||
// voteCount가 같을 경우, teamName으로 알파벳 순 정렬 | ||
return a.teamName.localeCompare(b.teamName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 ㅎㅎ 동점자까지 고려하신거 대박이에요 LGTM 👍 👍 (이러케쓰는건가 ㅋㅋ)
localStorage.removeItem('username'); | ||
setPart(null); | ||
setUsername(null); | ||
router.push('/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 onClick 핸들러를 따로 함수로 분리해서 사용하면 더 깔끔하게 바꿀 수 있을 것 같아요!
const handleLogout = () => {
localStorage.removeItem('jwtToken');
localStorage.removeItem('part');
localStorage.removeItem('username');
setPart(null);
setUsername(null);
router.push('/');
};
``` 이런식으로요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
마지막 과제까지...! 너무 수고하셨어요 > < 그동안 많이 배웠습니다ㅏ TIG 플젝도 화이팅하세요 !!
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
Authorization: `Bearer ${localStorage.getItem('jwtToken')}`, | ||
}, | ||
body: JSON.stringify(sendingDataObject), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API 요청시마다 매번 헤더 적는것 보다는 공통으로 빼면 더 좋을 것 같아요!
credentials: 'include', | ||
}); | ||
|
||
const data = await response.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getTeamData 함수에서 API 요청 후 바로 response.json()을 호출하기 전에 response.ok를 확인하면 서버 오류를 더 잘 처리할 수 있을 것 같아요 !!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const data = await response.json(); | |
if (!response.ok) { | |
throw new Error('데이터를 가져오는데 실패했습니다.'); | |
} | |
const data = await response.json(); |
onClick={() => { | ||
localStorage.removeItem('jwtToken'); | ||
localStorage.removeItem('part'); | ||
localStorage.removeItem('username'); | ||
setPart(null); | ||
setUsername(null); | ||
router.push('/'); | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그인과 로그아웃 함수 분리하면 가독성이 더 좋을 것 같아요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onClick={() => { | |
localStorage.removeItem('jwtToken'); | |
localStorage.removeItem('part'); | |
localStorage.removeItem('username'); | |
setPart(null); | |
setUsername(null); | |
router.push('/'); | |
}} | |
const handleLogout = () => { | |
localStorage.removeItem('jwtToken'); | |
localStorage.removeItem('part'); | |
localStorage.removeItem('username'); | |
setPart(''); | |
setUsername(''); | |
router.push('/'); | |
}; | |
const handleLogin = () => { | |
router.push('/login'); | |
}; | |
onClick={handleLogout} | |
onClick={handleLogin} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIG팀 안녕하세요! 벌써 스터디 마지막 코드리뷰네욥 ㅜㅜ
항상 깔끔한 코드 보고 많이 배워갑니다! 친절한 모달창까지..
프로젝트도 화이팅해요!!
} | ||
// voteCount 내림차순 정렬 | ||
return b.voteCount - a.voteCount; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VoteCount가 같은 경우도 꼼꼼하게 처리해주셨네요. 저도 공동 순위 고려해서 코드 수정해야겠어욥,.
} catch (error) { | ||
console.log(error); | ||
return new Response('Sign up Failed!', { | ||
status: 400, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 서버에서 상세하게 실패 경우에 따라 메세지를 작성해뒀다면 에러 객체에서 메세지도 출력해주면 좋을 것 같아요!
ㅎㅎ 그래서 회원 가입 실패 이유도 alert 창으로 띄워서 알려주면 좋을 것 같아용
return ( | ||
<div className="w-full h-full flex flex-col justify-center items-center gap-[40px]"> | ||
<div className="font-semibold text-[28px]">투표는 한 번씩만 가능합니다.</div> | ||
<button |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 모달창 친절해용
export interface teamProp { | ||
teamName: string; | ||
voteCount: number; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 .. 제 생각은,, 중복을 줄이기 위해, 공통된 프로퍼티를 가지는 인터페이스는 하나로 통합해서 사용하면 더 좋을 것 같아요!
TEAM TIG vote 과제 frontend by 은수, 승완
배포링크
배포 링크
기술 스택
디자인
피그마 링크
페이지 별 기능 ⚒️
주요 포인트 📌
인증
로그인 기능이 필요한 웹 페이지에는 인증을 어떻게 할 것인가?의 논쟁이 있다고 생각합니다. 제가 생각하는 인증의 구현 방식은 크게 2가지입니다
JWT를 어디에 저장할 것인가?
localStorage.getItem('jwtToken')
과 같은 방식JWT를 저장하는 추천되는 방법
참고 링크
CORS
CORS 정책은 브라우저 자체 단에서 출처(프로토콜 + 도메인 주소 + 포트번호)가 다를 경우 요청을 차단하는 개념입니다.
Access-Control-Allow-Origin
헤더에 프론트엔드의 주소가 포함되어 있어야 합니다.Access-Control-Expose-Headers
헤더에 해당 정보를 보내는 헤더명을 명시해주면 프론트엔드에서 Javascript로 접근할 수 있습니다.tailwindCSS와 nextJS
nextJS는 서버 사이드 렌더링을 지원하는 reactJS 기반의 프레임워크입니다. 기존의
styled-components
같은 라이브러리를 이용하여 스타일링을 진행하면, 내부적으로 reactJS의 상태 개념을 이용하여 만들어낸 hash 값을 클래스명으로 이용하기 때문에 로직이 클라이언트 단에서 이루어집니다. 만약 Javascript 실행 속도가 느린 환경이라면 사용자에게는 스타일링이 적용되지 않은, 단순 html 즉 layout만 보이는 순간이 존재할 수도 있습니다. 따라서 전처리기, 후처리기의 원리를 차용한tailwindCSS
를 이용하면 좀 더 편하지 않을까 싶습니다.nextJS의 장점
mixed content
오류를 해결하는 방법론으로 nextJS 서버를 이용할 수 있다. 이를 프록시 서버로서 활용하는 것이다. 하지만 reactJS는 s3로 배포하는 데에 반해 nextJS는 EC2를 통하기 때문에 더 복잡하다.loading.tsx
컴포넌트를 통해 자체적으로 Suspense 컴포넌트를 적용해줄 수 있다.느낀점 🧐
은수
그동안 백엔드 없이 구현했기 때문에 겪을 수 없던 CORS, 토큰관련 문제들을 경험해볼 수 있어서 좋았습니다. 모달을 만들 때 리액트에서 처럼 createPortal이 아닌 parallel & intercepting routes를 사용해 만들어 모달이 라우팅되는 것처럼 구현했는데 익숙치 않아서 그런지 더 번거로운 느낌이 들어 빨리 익숙해지고 싶다는 생각이 들었습니다. 쿠키를 사용하는 것, 중복된 코드들에 대한 리팩토링을 하지 못한 아쉬움이 남습니다. 본격적으로 프로젝트를 시작하게 되면 처음부터 재사용성을 고려한 코드를 작성해야겠다는 다짐을 했습니다.
승완
지난 과제를 진행할 때에는 어떤 컴포넌트는 서버 컴포넌트로 만들 것인지를 많이 고민하면서 했는데, 이번에는 백엔드와의 cors 이슈를 해결하느라 이에 대해 다소 소홀했던 것 같습니다. 또한 사실 jwt를 쿠키에 저장해두고, 매번 자동으로 브라우저의 동작 원리에 따라 jwt를 쿠키에 담아 백엔드 단으로 전송하는 로직이 좀 더 바람직한데, 이를 하지 못한 것 같아 다소 아쉽습니다. accessToken과 refreshToken을 각각 클라이언트에서 어떻게 저장할지 고려해보면 더 좋을 것 같습니다.