-
Notifications
You must be signed in to change notification settings - Fork 12
[7주차] Team 이어드림 김영서 & 이주희 미션 제출합니다. #4
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
base: master
Are you sure you want to change the base?
Conversation
chore: 기본 세팅
chore: 디자인 시스템 세팅
Feature/sign
Feature/votes 투표 페이지 만듬
Feature/home 홈페이지
Feature/responsive 홈, 로그인, 회원가입 페이지 반응형
chore: 반응형 적용
Feature/api 로그인, 회원가입 api 연결
docs: readme 작성
chore: 배포 1.0.1
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.
전반적으로 상수를 활용해서 안정성을 높인 게 인상적인 코드였습니다! 과제 하느라 고생하셨고, 본 프로젝트도 파이팅입니다~~
src/app/auth/demovote/page.tsx
Outdated
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.
auth와 직접적인 관계가 없는 페이지도 auth의 하위경로에 들어가는 이유가 있나요? 공통 접근 제어 또는 단순 공통 레이아웃 적용 등의 이유라면 라우트 그룹을 사용하면 어땠을까 싶습니다!
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.
데모데이와 파트장 투표/결과 페이지를 /vote/[type]
, /result/[type]
또는 /[type]/vote
, /[type]/result
와 같이 동적 라우팅을 이용할 수도 있었을 것 같은데 별도의 페이지로 분리한 이유도 궁금합니다.
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.
auth 밑에 둔 건 실수라고 하네용...ㅎㅎ 2차 때 바꿀 것 같습니다!
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.
InputField 컴포넌트와 withFeedback 컴포넌트를 분리한 이유가 궁금합니다 이렇게 되면 input field ui가 바뀔 경우 두 컴포넌트를 모두 수정해야 하는 번거로움이 생기지 않을까요?
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.
원래 회원가입 페이지의 id랑 password는 error 메시지를 출력하지 않으려고 InputField와 withFeedback을 분리했는데...
생각해보니까 아이디는 영문소문자 + 숫자조합만 허용하고, 비밀번호도 모든 특수문자를 허용할 필요가 없다는 생각이 들어서 결국 withFeedback만 사용하게 되었습니다... InputField는 삭제해야 됩니다...
const Header = ({ children }: { children: React.ReactNode }) => { | ||
return ( | ||
<div className="p-2"> | ||
<header className="font-headline-1 w-full p-4">{children}</header> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Header; |
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.
이렇게 children을 받는 방식 좋네요! 저는 자꾸 text를 props로 넘겨주는 방식을 사용하게 되어서 가능한 경우 이런 방식을 사용해야겠다고 다시 한 번 다짐하게 됩니다...
src/app/auth/demovote/page.tsx
Outdated
<form className="flex flex-1 flex-col justify-between"> | ||
<div className="grid grid-cols-1 gap-4 p-4"> | ||
{candidates.map((name) => { | ||
const isActive = selected === name; | ||
return ( | ||
<button | ||
type="button" | ||
key={name} | ||
onClick={() => handleSelect(name)} | ||
className={`mx-auto h-[56px] max-w-[350px] min-w-[350px] rounded-[6px] p-4 text-center text-sm font-semibold shadow-sm transition duration-200 ease-in-out ${ | ||
isActive | ||
? "bg-[#00AF8F] text-white shadow-md" | ||
: "bg-white text-black hover:bg-[#00AF8F] hover:text-white hover:shadow-md" | ||
}`} | ||
> | ||
{name} | ||
</button> | ||
); | ||
})} | ||
</div> | ||
|
||
<SubmitButton isActive={selected !== null}>투표하기</SubmitButton> | ||
</form> |
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.
저는 투표 페이지를 그냥 button click으로만 처리했는데, 접근성 측면에서 서버에 데이터를 제출(전송)하는 구조에서는 form을 쓰는 것이 좋다고 하네요!! 다음주 과제에서는 단순 button 대신 form + submit으로 변경하는 리팩토링을 진행해 봐야 할 것 같습니다
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 처리 필요가 없어서 편한 것 같아요~!
src/constants/part.enum.ts
Outdated
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.
enum을 사용하신 이유가 궁금합니다. ts의 enum은 번들 최적화, js 호환성, api 응답 검사의 복잡성 등의 이유로 사용을 지양하고 있는 것으로 알고 있어서요. 변수 자체로도 사용하면서 타입으로 사용하기 위한 수단이라면 const Value = { ... } as const
방식은 어떠신가요?
enum과 as const 방식에 대해 비교해 보고 결정하시면 좋을 것 같습니다.
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.
백엔드의 타입 명세를 보고 그대로 복붙했습니다 ㅎㅎ...
사용을 지양하고 있는지 몰랐네요...ㅎㅎ as const
사용으로 바꿔야겠네요..! 배워갑니다.
src/constants/team.label.ts
Outdated
import { Part } from "./part.enum"; | ||
import { Team } from "./team.enum"; | ||
|
||
export const TeamLabel: Record<Team, string> = { | ||
[Team.DEARDREAM]: "이어드림", | ||
[Team.INFLUEE]: "인플루이", | ||
[Team.POPUPCYCLE]: "팝업사이클", | ||
[Team.PROMETHA]: "프로메타", | ||
[Team.HONEYHOME]: "허니홈", | ||
}; | ||
|
||
export const PartLabel: Record<Part, string> = { | ||
[Part.FRONTEND]: "프론트", | ||
[Part.BACKEND]: "백엔드", | ||
[Part.NONE]: "기타", | ||
}; |
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.
스네이크로 바꿔야겠어요!!
@import "tailwindcss"; | ||
@layer theme { | ||
:root { | ||
--color-main: #00af8f; | ||
--color-pink: #eb4d9a; | ||
--color-gray0: #f8f9fb; | ||
--color-gray1: #bab9c6; | ||
|
||
--color-gray50: #fcfcfe; | ||
--color-gray100: #ebebf0; | ||
--color-gray200: #e4e4eb; | ||
--color-gray500: #a4a3ae; | ||
--color-gray600: #72717d; | ||
--color-gray900: #121214; | ||
|
||
--color-bg-gray: #f6f8fa; | ||
} | ||
} | ||
|
||
@layer utilities { | ||
.text-main { | ||
color: var(--color-main); | ||
} | ||
.text-pink { | ||
color: var(--color-pink); | ||
} | ||
.text-gray50 { | ||
color: var(--color-gray50); | ||
} | ||
.text-gray500 { |
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.
theme 레이어로 css 변수를 선언하고 utilities 레이어에 유틸리티 클래스를 선언하는 방식으로 작성하셨는데요, 애초부터 @theme
지시어를 사용해서 tailwind에서 제공하는 prefix를 사용하는 theme 변수를 선언하면 자동으로 유틸리티 클래스가 생성됩니다!
utilities 레이어로 클래스를 선언 -> 테일윈드에서 제공하는 variants와 호환 x
theme variable 사용 -> 호환 o (ex. sm:text-main
)
공식문서 첨부하니 참고하시면 좋을 것 같습니당
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.
헉 감사합니다;; 이래서 hover가 안 먹었나 싶네요.. 관련 유틸리티 클래스들을 삭제해봐야겠네요.
src/utils/validators.ts
Outdated
const VALID_REGEX = { | ||
loginId: /^[a-zA-Z0-9]*$/, | ||
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, | ||
password: /^[A-Za-z0-9~!@#$%^&*_]*$/, | ||
}; | ||
|
||
type ValidatorType = [boolean, string]; | ||
|
||
export const validateLoginId = (loginId: string): ValidatorType => { | ||
const isValid = VALID_REGEX.loginId.test(loginId); | ||
return isValid | ||
? [true, " "] | ||
: [false, "영문 소문자, 숫자의 조합만 가능합니다."]; | ||
}; |
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.
우와 직접 정규식 객체를 만들어서 유효성 검사를 구현하셨군요! 최근에 zod를 사용할 기회가 있었는데, 유효성 검사가 굉장히 편리해지더라고요! key questions에도 등장하는 만큼 zod를 사용해 리팩토링해 봐도 좋을 것 같습니다
@import "tailwindcss"; | ||
|
||
@layer utilities { | ||
.font-headline-1 { | ||
font-size: 24px; | ||
font-style: normal; | ||
font-weight: 600; | ||
line-height: 36px; /* 150% */ | ||
} | ||
.font-headline-2 { | ||
font-size: 20px; | ||
font-style: normal; | ||
font-weight: 600; | ||
line-height: 30px; /* 150% */ | ||
letter-spacing: -0.3px; | ||
} | ||
.font-headline-3 { | ||
font-size: 18px; | ||
font-style: normal; | ||
font-weight: 600; | ||
line-height: 27px; /* 150% */ | ||
letter-spacing: -0.45px; | ||
} | ||
|
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.
타이포 시스템도 @theme 지시어를 사용해 설정할 수 있습니다! 프로메사 팀 디자인시스템 세팅 참고해 보시면 좋을 것 같아요!
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.
인증이라는 어려운 기능을 넣어서 과제 진행하시느라 정말 고생 많으셨어요! 그리고 디자인도 정말 너무 이쁜 것 같아요.
실제 ceos 프로젝트 하실 때에는 mixed-content 관련 이슈와 토큰을 어떻게 보관하실지 고민 많이 해보시면 좋겠습니다!
고생하셨습니다.
@@ -0,0 +1 @@ | |||
npm run lint |
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.
linting을 돌려서 코드의 품질을 체크한 뒤, 커밋을 할 수 있도록 husky를 쓰는 태도 정말 훌륭합니다
src/styles/globals.css
Outdated
@media (prefers-color-scheme: dark) { | ||
:root { | ||
--background: #0a0a0a; | ||
--foreground: #ededed; |
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.
지금 현재 배포된 사이트와 개발 환경에서 돌려보면 이 코드와 7 line의 코드가 충돌해서 피그마 디자인처럼 폰트 컬러가 적용되지는 않는 것 같아요
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.
영향 없을 줄 알고 삭제 안 했었는데 삭제해야겠어요!!
src/hooks/useSignup.ts
Outdated
return useMutation({ | ||
mutationFn: signup, | ||
onSuccess: (data) => { | ||
console.log("회원가입 성공 ✅"); | ||
console.log("응답 메시지:", data.message); | ||
}, | ||
onError: (error: unknown) => { | ||
if (error instanceof Error) { | ||
console.error("회원가입 실패 ❌", error.message); | ||
} else { | ||
console.error("회원가입 실패 ❌ 알 수 없는 에러 발생"); | ||
} | ||
}, | ||
}); |
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.
음... 솔직히 영서님 주희님 모두 느끼셨을 것 같기는 한데, 성공 시와 실패 시에 그냥 콘솔 문을 찍어버리는 것은 좋은 태도는 아니에요.
그리고 개인적으로 회원 가입이 성공이 되었다면 useRouter()
훅을 이용해서 replace()나 push() 메서드를 써서 로그인 페이지로 넘겨주는 것이 맞지 않을까요? 그냥 사용자에게 alert를 띄워준 것도 아니고 아무 반응이 없는 것은 좀 아쉽네요.
그리고 실패 시에는 지금 과제는 실제 프로덕션에서 쓸 것이 아니라서 괜찮지만 관련 오류를 백엔드 단으로 보내거나 아니면 팀의 slack 이나 discord 채널로 보내서 동료 개발자들이 확인할 수 있게 하면 더 좋겠죠
} from "@/utils/validators"; | ||
|
||
const Register = () => { | ||
const { mutate: signup, isPending } = useSignup(); |
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.
여기에서 onSucess를 정의해줘도 되고, 실제 signup 함수를 이용하실 때 첫번째 인자에는 fetch 용 데이터를 담으면 두번째 인자에 옵션 객체가 optional하게 넣을 수 있거든요. 이때 onSuccess를 컨텍스트에 맞춰서 적용하셔도 됩니다.
alert("모든 필드를 입력해주세요."); | ||
return; | ||
} | ||
|
||
if (user.password !== confirmPassword) { | ||
alert("비밀번호가 일치하지 않습니다."); | ||
return; | ||
} | ||
|
||
if (!isValidEmail) { | ||
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.
이 동작들은 nextJS의 패러랠과 인터셉트 라우트를 이용하셔서 모달로 띄워주셔도 되고, 아예 ReactDOM의 createPortal() 을 이용하는 방식으로 구현하셔도 좋을 것 같아요.
src/app/auth/register/page.tsx
Outdated
return; | ||
} | ||
|
||
signup(user as User); |
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.
지금 이 동작이 제 맥북에 받아서 로컬로 돌려볼 때에는 잘 되는데, 배포하신 링크에서는 오류가 납니다. 이를 mixed-contents
에러라고 해서 https 서버와 http 서버가 서로 통신하는 것을 막는 상황을 말해요.
nextJS 개발 환경을 https로 맞추시고 백엔드 분들께 https 배포를 요청드리거나, 아니면 백엔드 배포를 http로 하는 것을 요청 드리면 될 것 같네요.
그래도 프로덕션에서는 당연히 https로 하셔야겠죠??
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.
넵! 1차 과제 기간에는 백엔드 분들께서 https 배포는 아직 안된다고 하셔서 http 로컬 기준으로 했어요!
2차 과제 기간에는 https로 배포해주신다고 하셨습니다!
<InputFieldWithFeedback | ||
label="아이디" | ||
name="loginId" | ||
placeholder="아이디를 입력해주세요" | ||
value={user.loginId} | ||
onChange={handleChange} | ||
isValid={isValidId} | ||
message={user.loginId ? idMsg : " "} | ||
/> |
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.
지금 해당 page가 user 상태를 선언한 제어 컴포넌트이고, 이를 form의 input 요소에 양방향 바인딩을 해주고 계시군요.
react-hook-form 같은 라이브러리를 이용하셔서 개선하면 사용자가 잘못된 데이터를 입력했을 경우의 처리 등을 편리하게 처리할 수 있으니 한번 살펴보셔도 좋을 것 같네요
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.
감사합니다!! react-hook-form
라이브러리도 살펴봐야겠어요!!
src/hooks/useLogin.ts
Outdated
setCookie("accessToken", data.result.accessToken, { | ||
path: "/", | ||
maxAge: 60 * 60, // 1시간 | ||
}); |
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.
현재 응답을 보면, accessToken과 refreshToken이 왔는데 왜 accessToken만 사용하시는 것인가요? 그리고 set-cookie헤더를 백엔드 팀에게 요청하세요. 프론트엔드가 body로 받아서 직접 Javascript로 쿠키에 넣어주는 것은 보안상 좋지 않습니다. xss 같은 공격을 막으려면 httpOnly 속성을 쿠키에 넣어줘야하는데 이는 우리 프론트엔드에서도 js로 쿠키에 접근을 못하는 것이라 백엔드가 해줘야 하는 것으로 이해하시면 됩니다.
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.
그리고 저는 개인적으로 at는 프론트엔드가 전역 상태로 메모리 단에서 관리하고 rt는 httpOnly 속성을 적용해서 관리하셔야 한다고 생각합니다. 백엔드에 요청을 보낼 때 at를 함께 보내시고 없다면 rt로 at를 다시 발급 받으셔서 원래 보내려고 했던 요청을 계속 진행하는 interceptor 로직까지 구현해보시면 좋겠습니다.
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.
refreshToken은 백엔드에서 httpOnly 쿠키에 저장하도록 설정해서 보내줘야 한다고 해서
백엔드에서 설정해주기 전까진 그냥 버리는 방식으로 해뒀습니다.. 아마 2차 과제 기간엔 백엔드에서 설정될 거 같습니다!!
액세스 토큰은 메모리, 리프레쉬 토큰은 httpOnly로 설정된 상태로 받는 것으로 바꾸고 interceptor 로직 구현해봐야겠어요. 감사합니다!
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.
인증이라는 어려운 기능을 넣어서 과제 진행하시느라 정말 고생 많으셨어요! 그리고 디자인도 정말 너무 이쁜 것 같아요.
실제 ceos 프로젝트 하실 때에는 mixed-content 관련 이슈와 토큰을 어떻게 보관하실지 고민 많이 해보시면 좋겠습니다!
고생하셨습니다.
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.
투표 페이지까지 만드시다니 손이 빠르시네요... 저희도 백에서 https 배포가 안돼서 mixed-content 에러가 나는데 같은 상황이라 공감이 됩니다...ㅎㅎ 다른 분들이 리뷰 너무 잘 달아주셔서 리뷰할게 별로 없네여,, 과제하시느라 수고하셨습니다!👍
.github/ISSUE_TEMPLATE/feature.md
Outdated
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.
헉 저희팀 fork한 레포지토리에서 issue를 못 파는 줄 알고 이슈 못 쓰고 있었는데 이슈 템플릿 있으셔서 찾아보니까 포크 뜬 레포지토리에서도 이슈를 생성할 수 있네요🫢
<div className="p-2"> | ||
<header className="font-headline-1 w-full p-4">{children}</header> | ||
</div> |
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.
왜 header를 div로 감싸셨는지 궁금합니다! padding 주는 것 빼고는 용도가 없어 보여서요,,,
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.
패딩 주는 용도로 감싼 게 맞아요... 저번에 margin을 사용하면 레이아웃이 예상치 못하게 동작할 수 있다고 피드백 받아서 margin 사용을 최대한 자제하려고 하다보니 이렇게 됐네요.
placeholder={placeholder} | ||
value={value} | ||
onChange={onChange} | ||
className="font-headline-2 border-b-gray200 outline-0" |
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.
input에서 반복해서 outline-0을 쓰시는 것 같은데 globals.css 쪽에서 outline: none을 주는 건 어떨까요?!
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.
앗 그래야겠어요... reset 관련된 건 globals.css에 적어야되는데 계속 까먹네요...
src/app/auth/demovote/page.tsx
Outdated
const candidates = [ | ||
"이어드림", | ||
"인플루이", | ||
"팝업사이클", | ||
"프로메사", | ||
"하니홈", | ||
]; |
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.
상수라서 대문자로 써주면 좋을 것 같습니다!
src/app/auth/demovote/page.tsx
Outdated
import Header from "../_components/Header"; | ||
import SubmitButton from "@/components/SubmitButton"; |
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.
_components와 components 폴더를 무슨 기준으로 나누신건지 궁금합니다!
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.
@/components/
는 공통 컴포넌트
@/app/페이지 폴더/_components
는 이 페이지에서만 사용하는 컴포넌트로 나누었습니다.
src/app/auth/demovote/page.tsx
Outdated
const handleSelect = (name: string) => { | ||
setSelected(name); | ||
}; |
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 [errorMessage, setErrorMessage] = useState(""); | ||
const [errorState, setErrorState] = useState({ | ||
isUserIdValid: true, | ||
isPasswordValid: true, | ||
}); |
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.
errorState 따로 관리할 필요없이 !!errorMessage로 대신해서 사용하는 것도 좋아보입니다
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.
이게 백에서 보내주는 errorMessage가 [아이디가 존재하지 않음, 아이디는 존재하지 않는데 비번이 안 맞음] 이렇게 두 종류가 있어서 이렇게 구현했었어요. 근데 이 메시지를 제가 원하는 걸로 바꿔 달라고 요청하고 한 곳에서 띄우면 코드가 더 간결해질 거 같긴 한데... 시간 여유가 된다면 시도해볼 것 같아요.
Feat: API 연결
* chore: 변수명 스네이크 케이스 변경, 주석 삭제 * fix: 팀명 오류 수정 * feat: 에러 메시지 로직 추가 및 변경 * style: 로고 헤더 수정 * style: 투표 이동 UI 변경 * style: auth 레이아웃 변경 * feat: 1등 동점자 처리 로직 추가 * fix: voteId 2로 변경 * feat: 투표 여부 반영
* chore: 변수명 스네이크 케이스 변경, 주석 삭제 * fix: 팀명 오류 수정 * feat: 에러 메시지 로직 추가 및 변경 * style: 로고 헤더 수정 * style: 투표 이동 UI 변경 * style: auth 레이아웃 변경 * feat: 1등 동점자 처리 로직 추가 * fix: voteId 2로 변경 * feat: 투표 여부 반영 * fix: 로컬 스토리지 에러 수정
* chore: 변수명 스네이크 케이스 변경, 주석 삭제 * fix: 팀명 오류 수정 * feat: 에러 메시지 로직 추가 및 변경 * style: 로고 헤더 수정 * style: 투표 이동 UI 변경 * style: auth 레이아웃 변경 * feat: 1등 동점자 처리 로직 추가 * fix: voteId 2로 변경 * feat: 투표 여부 반영 * fix: 로컬 스토리지 에러 수정 * chore: readme 작성 * style: 글자 깨짐 수정
Chore: main update
배포 링크 🗳️투표드림
피그마

/
home/auth/login
,/auth/register
/auth/vote
,/auth/vote/result
/auth/demovote
,/auth/demovote/result
투표드림 팀
Key Question
1. Zod 스키마가 무엇인지, 어떻게 활용할 수 있는지 알아봅시다.
1. Zod 스키마
Zod는 스키마 선언 및 유효성 검사 라이브러리이다. 스키마란 데이터의 형태 및 구조로 Zod를 통해 스키마를 선언하고 런타임에서 구조나 타입에 대한 유효성 검사를 실행할 수 있다.
2. 어떻게 활용할 수 있는가
2. 이번 프로젝트에서 토큰 관리를 어떻게 할 예정인지, 그리고 왜 그런 방법을 선택했는지에 대해 설명해 주세요.
1. 토큰 관리 계획 => 쿠키에 보관
Access Token
은 보통 쿠키에 저장해서 API 요청 시 사용Refresh Token
은 httpOnly Cookie에 저장해서 자동 갱신 처리2. 왜 이런 방법을 선택했는가
accessToken
: 가볍고 빠르게 인증refreshToken
: 자동 로그인/세션 유지