의존성 픽스#55
Conversation
Summary of ChangesHello @Jieunsse, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 사용자가 팀을 생성하거나 기존 팀에 참여할 수 있도록 하는 '팀 추가' 기능을 구현합니다. 이 기능은 새로운 UI 컴포넌트, React Query를 활용한 효율적인 데이터 관리, 그리고 API 통신을 위한 견고한 백엔드 통합을 포함합니다. 또한, 환경 변수 처리 로직을 개선하여 애플리케이션의 설정 관리와 개발 편의성을 높였습니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
이 PR은 의존성 추가 외에 새로운 팀 생성 및 참여 기능을 구현하고 API 호출 로직을 개선하는 등 많은 변경 사항을 포함하고 있습니다. PR 제목을 "feat: 팀 생성 및 참여 기능 추가"와 같이 변경하면 변경 내용을 더 잘 나타낼 수 있을 것 같습니다.
전반적으로 코드 품질이 우수합니다. 특히 fetchApi와 config.ts의 리팩토링을 통해 API 연동 코드의 안정성과 개발 편의성이 크게 향상되었습니다. 또한 react-query를 도입하고 Providers, 쿼리 키 관리, 커스텀 뮤테이션 훅 등을 체계적으로 구성한 점이 인상적입니다.
다만, .env.example 파일이 삭제되어 새로운 개발자가 프로젝트를 설정할 때 어떤 환경 변수가 필요한지 파악하기 어려울 수 있습니다. 이 파일을 다시 추가하거나, README 등 다른 문서에 필요한 환경 변수 목록을 안내하는 것을 고려해보시는 것이 좋겠습니다.
몇 가지 개선점에 대한 리뷰 의견을 남겼으니 확인 부탁드립니다.
| 'use client'; | ||
|
|
||
| import { BaseButton } from '@/components/Button/base'; | ||
| import { Input } from '@/components/input'; | ||
| import joinCardStyles from './JoinTeamCard.module.css'; | ||
| import clsx from 'clsx'; | ||
| import commonStyles from '../styles/common.module.css'; | ||
|
|
||
| interface JoinTeamCardProps { | ||
| teamLink: string; | ||
| onTeamLinkChange: (value: string) => void; | ||
| } | ||
|
|
||
| export default function JoinTeamCard({ teamLink, onTeamLinkChange }: JoinTeamCardProps) { | ||
| const helperTextId = 'join-team-helper-text'; | ||
|
|
||
| return ( | ||
| <div className={clsx(commonStyles.flexColCenter, joinCardStyles.joinCard)}> | ||
| <h2 className={joinCardStyles.title}>팀 참여하기</h2> | ||
|
|
||
| <div className={clsx(commonStyles.flexCol, joinCardStyles.joinInputSection)}> | ||
| <label htmlFor="team-link" className={joinCardStyles.joinLabel}> | ||
| 팀 링크 | ||
| </label> | ||
| <Input | ||
| id="team-link" | ||
| value={teamLink} | ||
| onChange={(event) => onTeamLinkChange(event.target.value)} | ||
| aria-describedby={helperTextId} | ||
| placeholder="팀 링크를 입력해주세요." | ||
| className={joinCardStyles.teamLinkInput} | ||
| /> | ||
| </div> | ||
|
|
||
| <BaseButton className={joinCardStyles.joinSubmitButton}>참여하기</BaseButton> | ||
|
|
||
| <p id={helperTextId} className={joinCardStyles.joinHelperText}> | ||
| 공유받은 팀 링크를 입력해 참여할 수 있어요. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
현재 JoinTeamCard 컴포넌트는 div로 구현되어 있고 '참여하기' 버튼에 onClick 핸들러나 type='submit' 속성이 없어 아무런 동작을 하지 않습니다. 사용자가 팀 링크를 입력하고 버튼을 클릭해도 아무 일도 일어나지 않아 기능이 동작하지 않는 상태입니다.
아래와 같이 컴포넌트를 <form>으로 감싸고 onSubmit 핸들러를 추가하여 이 문제를 해결할 수 있습니다. JoinTeamCardProps에도 onSubmit을 추가해야 하며, 부모 컴포넌트인 JoinTeamPage에서 실제 팀 참여 API를 호출하는 로직을 구현하여 전달해야 합니다.
'use client';
import { type FormEvent } from 'react';
import { BaseButton } from '@/components/Button/base';
import { Input } from '@/components/input';
import joinCardStyles from './JoinTeamCard.module.css';
import clsx from 'clsx';
import commonStyles from '../styles/common.module.css';
interface JoinTeamCardProps {
teamLink: string;
onTeamLinkChange: (value: string) => void;
onSubmit: (e: FormEvent<HTMLFormElement>) => void;
}
export default function JoinTeamCard({ teamLink, onTeamLinkChange, onSubmit }: JoinTeamCardProps) {
const helperTextId = 'join-team-helper-text';
return (
<form className={clsx(commonStyles.flexColCenter, joinCardStyles.joinCard)} onSubmit={onSubmit}>
<h2 className={joinCardStyles.title}>팀 참여하기</h2>
<div className={clsx(commonStyles.flexCol, joinCardStyles.joinInputSection)}>
<label htmlFor="team-link" className={joinCardStyles.joinLabel}>
팀 링크
</label>
<Input
id="team-link"
value={teamLink}
onChange={(event) => onTeamLinkChange(event.target.value)}
aria-describedby={helperTextId}
placeholder="팀 링크를 입력해주세요."
className={joinCardStyles.teamLinkInput}
/>
</div>
<BaseButton type="submit" className={joinCardStyles.joinSubmitButton}>
참여하기
</BaseButton>
<p id={helperTextId} className={joinCardStyles.joinHelperText}>
공유받은 팀 링크를 입력해 참여할 수 있어요.
</p>
</form>
);
}
| export function getCreateTeamFailureMessage(error: unknown) { | ||
| if (!(error instanceof Error)) { | ||
| return CREATE_TEAM_MESSAGES.defaultFailure; | ||
| } | ||
|
|
||
| const { message } = error; | ||
|
|
||
| if (message === CREATE_TEAM_MESSAGES.emptyTeamNameError) { | ||
| return CREATE_TEAM_MESSAGES.emptyTeamNameError; | ||
| } | ||
|
|
||
| if ( | ||
| message === CREATE_TEAM_MESSAGES.duplicatedTeamNameError || | ||
| hasStatusMessage(message, DUPLICATED_STATUS) | ||
| ) { | ||
| return CREATE_TEAM_MESSAGES.duplicatedTeamNameFailure; | ||
| } | ||
|
|
||
| if (hasStatusMessage(message, INVALID_REQUEST_STATUS)) { | ||
| return CREATE_TEAM_MESSAGES.invalidRequestFailure; | ||
| } | ||
|
|
||
| if (UNAUTHORIZED_STATUSES.some((status) => hasStatusMessage(message, status))) { | ||
| return CREATE_TEAM_MESSAGES.unauthorizedFailure; | ||
| } | ||
|
|
||
| return CREATE_TEAM_MESSAGES.defaultFailure; | ||
| } |
There was a problem hiding this comment.
API 에러를 처리할 때 error.message 문자열에 특정 상태 코드 문자열('status: 409' 등)이 포함되어 있는지 확인하는 방식은 불안정하며 유지보수가 어렵습니다. http.ts에서 에러 메시지 형식이 변경되면 이 로직이 예기치 않게 실패할 수 있습니다.
더 안정적인 방법은 http.ts에서 HTTP 상태 코드와 같은 구조화된 정보를 포함하는 커스텀 에러 클래스(예: ApiError)를 정의하고 throw하는 것입니다. 그러면 이 함수에서 instanceof로 에러 타입을 확인하고, error.status와 같이 타입-세이프하게 상태 코드에 접근할 수 있어 코드의 안정성과 가독성이 향상됩니다.
예시:
// In http.ts
export class ApiError extends Error {
constructor(message: string, public readonly status: number) {
super(message);
}
}
// In getCreateTeamFailureMessage.ts
if (error instanceof ApiError) {
if (error.status === 409) {
return CREATE_TEAM_MESSAGES.duplicatedTeamNameFailure;
}
// ... other status code checks
}
Summary
Issue
Scope
포함
특이사항