-
Notifications
You must be signed in to change notification settings - Fork 1
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
[#67] 대회 생성 페이지 구현 #100
The head ref may contain hidden characters: "67-\uB300\uD68C-\uC0DD\uC131-\uD398\uC774\uC9C0-\uAD6C\uD604"
[#67] 대회 생성 페이지 구현 #100
Conversation
- 문제를 선택할 수 있도록 기능을 추가함 - 대회 생성시 detail 페이지로 이동하도록 기능 추가
- 지정된 서버만 참조하는 api 인스턴스를 util로 분리함 - Response 타입과 데이터 타입을 분리해 정리
- CompetitionCreatePage -> CreateCompetitionPage (영문법에 맞춰 수정함)
- 시작 시작이 옳게 설정되면 에러가 나도록 하고 있었음 - 종료 시간도 마찬가지
} | ||
|
||
export function Input({ id, label, children, ...props }: Props) { | ||
const child = Children.only(children); |
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이 1개 뿐임을 검증하는 로직입니다
|
||
Input.TextField = forwardRef( | ||
({ className, ...props }: TextFieldProps, ref: ForwardedRef<HTMLInputElement>) => { | ||
return <input className={cx(inputStyle, className)} type="text" ref={ref} {...props}></input>; |
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.
cx는 두 클래스 이름을 합성한다고 보시면 됩니다.
cx('a','b') // 'a b'
const [startsAt, setStartsAt] = useState<string>(initialForm.startsAt ?? currentDateStr); | ||
const [endsAt, setEndsAt] = useState<string>(initialForm.endsAt ?? currentDateStr); |
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.
기본적으로 현재 시간으로 form이 셋업되도록 합니다.
const $target = e.target as HTMLElement; | ||
if ($target.tagName !== 'BUTTON') return; | ||
|
||
const $li = $target.closest('li'); | ||
if (!$li) return; | ||
|
||
const problemId = Number($li.dataset['problemId']); |
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.
여긴 이벤트 델리게이션 로직입니다.
이벤트 델리게이션이란?
리액트도 자체 델리게이션 로직이 있는걸로 아는데 잘 몰라서 일단 구현 했어요
</span> | ||
{getSelectButton({ isPicked: pickedProblemIds.includes(id) })} | ||
</li> |
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.
아이고 여기 좀 변경했는데 깜빡하고 안올렸네요
message?: string; | ||
}; | ||
|
||
const FIVE_MIN_BY_MS = 5 * 60 * 1000; |
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.
여기서 300000 대신 5 * 60 * 1000 을 쓰신 이유가 있나요?
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.
어떤 값인지 구분하기 편하게 하려고요. 1000 = 1초
1000 = 1초
1000 * 60 = 1분
1000 * 60 * 5 = 5분
이렇게 가독성을 높이는 겁니다 그리고 이렇게 해두면 나중에 분을 변경할 때도 편해서요
const VALIDATION_MESSAGE = { | ||
needLongName: '이름은 1글자 이상이어야합니다', | ||
needMoreParticipants: '최대 참여 인원은 1명 이상이어야 합니다', | ||
tooEarlyStartTime: '대회 시작 시간은 현재보다 5분 늦은 시간부터 가능합니다', | ||
tooEarlyEndTime: '대회 종료 시간은 대회 시작시간보다 늦게 끝나야합니다', | ||
needMoreProblems: '대회 문제는 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.
이렇게 묶는 것도 정말 좋은 것 같네요, 덕분에 배워갑니다!
frontend/src/apis/problems/index.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.
아직 api배포가 되진 않았지만, 대회 전체 조회api응답을 받도록 작성할 예정인데, 그것도 이런 식으로 분리할 수 있겠다는 생각이 드네요!
기조님은 대회 전체 조회 도 이런 식으로 분리하는 게 좋다고 생각하시나요?
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.
네 그렇게 생각합니다. 외부 데이터를 받아오는 영역은 분리해서 관리해주는게 좋아요. Model로직과 ViewModel 로직이 섞이지 않게 분리하는거죠
…web12-algo-with-me into 67-대회-생성-페이지-구현
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🧸
const api = axios.create({ | ||
baseURL: import.meta.env.VITE_API_URL, | ||
}); |
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 ONE_SEC_BY_MS = 1_000; | ||
const ONE_MIN_BY_MS = 60 * ONE_SEC_BY_MS; |
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 const isNil = (type: unknown): type is Nil => { | ||
if (Object.is(type, null)) return true; | ||
if (Object.is(type, undefined)) return 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.
type이 unknown은 알겠는데, return이 boolean이 아니라 type is Nil로 타이핑하신 이유가 있나요? ts playground에선 오류가 없어 보이는데 궁금합니다.
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.
저렇게 하면 type으로 들어온 값이 Nil이 거나 Nil이 아님을 보장할 수 있습니다.
const value:number|undefined = ...; // undefined이거나 number임
if(isNil(value)) {
// 여기서 value는 Nil이 된다.
}
else {
// 여기서 value는 number가 된다.
}
|
||
import type { CompetitionForm, CompetitionInfo, CreateCompetitionResponse } from './types'; | ||
|
||
export * from './types'; |
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 하시나요?
다른 곳에서 타입이 필요하다면, api/competitions/types에서 직접 가져오는 게 더 명확해 보이는데, 궁금합니다
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.
아 아닙니다!! 제가 착각했습니다.
export type CompetitionInfo = { | ||
id: CompetitionId; | ||
name: string; | ||
detail: string; | ||
maxParticipants: number; | ||
startsAt: string; | ||
endsAt: string; | ||
createdAt: string; | ||
updatedAt: string; | ||
}; | ||
|
||
export type CreateCompetitionResponse = CompetitionInfo; |
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.
타입 구조가 완전 같아도 네이밍을 정확히 해주는게 정말 좋아보입니다.
title: string; | ||
}; | ||
|
||
export type FetchProblemListResponse = ProblemInfo[]; |
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.
서버 응답 데이터 타입에는 항상 Response를 붙이시네요👍🏻
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.
네 좀 구분하는 편이에요. Response로 들어오는 데이터는 지금은 그냥 데이터이지만 규칙을 정하기에 따라 message나 statusCode를 같이 반환하는 경우도 생기는데 그 때 개념을 분리하기 위함입니다.
interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> { | ||
error?: boolean; | ||
} |
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.
ㅋㅋㅋㅋㅋㅋ
Omit = 특정 타입을 제외
InputHTMLAttributes = Input 태그가 가질 수 있는 컴포넌트 attributes 타입입니다.
즉 type을 제외한 모든 Input에 넣을 수 있는 속성을 가리키는 인터페이스에요.
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.
주말에 천천히 읽어보겠습니다. 좋은 코드 너무 감사합니다. 아직은 이해하기 어렵네요.
|
||
function togglePickedProblem(problemId: ProblemId) { | ||
if (problems.includes(problemId)) { | ||
setProblems((ids) => ids.filter((id) => id !== problemId).sort()); |
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.
좋은 코드 감사합니다. LGTM 😎
한 일
검증 로직
스크린 샷
/contest/create 페이지에서 확인할 수 있습니다.
form 검증 로직,
마지막에 contest/detail/id 화면으로 이동하도록 해두었는데, 아직 해당 페이지가 없어서 그렇게 나옴