-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Feat : 포트폴리오 수정, 삭제 기능 추가 및 요약 AI 수정 #28
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Skipped Deployment
|
Walkthrough이 변경 사항은 Changes
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 19
🧹 Outside diff range comments (1)
src/services/Project.api.ts (1)
Line range hint
41-67
: TextSummarizer 사용 시 에러 처리 및 타입 안전성 개선이 필요합니다.다음 사항들을 개선하는 것이 좋겠습니다:
- TextSummarizer 호출 시 에러 처리가 없습니다
- summary가 undefined일 때의 처리가 불명확합니다
- 타입 안전성이 부족합니다
다음과 같이 개선하는 것을 제안드립니다:
export const createProject = async ({ projectData, userId }: { projectData: ProjectRequest; userId: string }) => { - const summaries = await TextSummarizer(projectData.content); + let summaries: string[] | undefined; + try { + summaries = await TextSummarizer(projectData.content); + if (!summaries?.length) { + throw new Error('요약 생성 실패'); + } + } catch (error) { + console.error('텍스트 요약 중 오류 발생:', error); + throw new Error('프로젝트 생성 중 요약 생성에 실패했습니다'); + } const response = await instance.post<Project>("/projects", { ...projectData, userId, summary: summaries }); return response.data; }; export const updateProject = async ({ id, projectData, userId, }: { id: number; projectData: ProjectRequest; userId: string; }) => { - let summaries = undefined; + let summaries: string[] | undefined; if (projectData.content) { - summaries = await TextSummarizer(projectData.content); + try { + summaries = await TextSummarizer(projectData.content); + if (!summaries?.length) { + console.warn('요약이 생성되지 않았습니다'); + } + } catch (error) { + console.error('텍스트 요약 중 오류 발생:', error); + throw new Error('프로젝트 수정 중 요약 생성에 실패했습니다'); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
⛔ Files ignored due to path filters (1)
package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (8)
package.json
(1 hunks)src/app/portfolio/_components/ProjectCard.tsx
(5 hunks)src/app/portfolio/_components/ProjectList.tsx
(1 hunks)src/app/portfolio/write/_components/ProjectForm.tsx
(1 hunks)src/app/portfolio/write/page.tsx
(1 hunks)src/components/Form.tsx
(6 hunks)src/services/Project.api.ts
(1 hunks)src/services/TextSummarizer.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/portfolio/_components/ProjectCard.tsx
[error] 94-102: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 103-112: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 227-228: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🔇 Additional comments (8)
src/services/Project.api.ts (2)
2-2
: TextSummarizer 임포트가 적절히 추가되었습니다.
새로운 TextSummarizer 모듈이 올바르게 임포트되어 있습니다.
2-2
: TextSummarizer 구현 검증이 필요합니다.
TextSummarizer 모듈의 구현을 확인하여 일관된 동작을 보장해야 합니다.
✅ Verification successful
Let me gather more information about the TextSummarizer implementation.
Let me try one more verification with a different approach.
TextSummarizer 구현이 적절하게 되어 있습니다.
TextSummarizer는 Cohere API를 활용하여 텍스트를 3개의 한국어 문장으로 요약하는 기능을 올바르게 구현하고 있으며, Project.api.ts에서도 적절하게 사용되고 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# TextSummarizer 구현 확인
rg -A 10 "export.*TextSummarizer" "./src/services/"
Length of output: 115
Script:
#!/bin/bash
# TextSummarizer 클래스 구현 상세 확인
ast-grep --pattern 'class TextSummarizer {
$$$
}'
# TextSummarizer 관련 파일 내용 확인
rg -l "class.*TextSummarizer" "./src/services/" | xargs cat
Length of output: 113
Script:
#!/bin/bash
# TextSummarizer.ts 파일의 전체 내용 확인
cat ./src/services/TextSummarizer.ts
# TextSummarizer 사용 패턴 검색
rg "TextSummarizer" -A 3 -B 3
Length of output: 3049
package.json (1)
18-18
: 🧹 Nitpick (assertive)
cohere-ai 의존성 추가에 대한 고려사항
cohere-ai 라이브러리 추가는 텍스트 요약 기능 구현을 위한 것으로 보입니다. 다음 사항들을 고려해주시기 바랍니다:
- 버전 관리: 현재 ^7.14.0 으로 설정되어 있어 마이너 버전 업데이트가 자동으로 적용될 수 있습니다. 안정성을 위해 정확한 버전으로 고정하는 것을 고려해보세요.
- 보안: Cohere AI API를 사용할 때 민감한 정보가 노출되지 않도록 주의가 필요합니다.
- 비용: API 사용량에 따른 비용이 발생할 수 있으므로, 적절한 사용량 제한과 모니터링이 필요합니다.
✅ Verification successful
cohere-ai 패키지의 보안 및 버전 관리 확인 완료
패키지 검토 결과 다음과 같은 사항들을 확인했습니다:
- 현재 7.14.0이 최신 버전이며, 보안 취약점은 발견되지 않았습니다.
- 패키지가 안정적인 메이저 버전(7.x)을 유지하고 있으며, 정기적인 업데이트가 이루어지고 있습니다.
따라서 원래 리뷰 의견을 다음과 같이 수정합니다:
- 버전 관리: ^7.14.0 설정은 적절합니다. 메이저 버전이 안정적이므로 마이너 업데이트 허용은 문제없습니다.
- 보안: npm audit 결과 현재 알려진 취약점이 없습니다.
- 비용 관련 고려사항은 여전히 유효하므로, API 사용량 모니터링 설정을 권장드립니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for security advisories and latest versions
# Check npm for latest versions and security advisories
npm view cohere-ai versions --json
npm audit
Length of output: 993
src/app/portfolio/_components/ProjectList.tsx (1)
86-86
: 프로젝트 카드의 고유 식별자 추가가 적절히 구현되었습니다.
프로젝트 삭제 기능을 위해 필요한 id
prop이 올바르게 추가되었습니다.
ProjectCard 컴포넌트에서 id prop이 올바르게 사용되는지 확인하겠습니다:
✅ Verification successful
프로젝트 카드의 id prop이 적절하게 구현되어 있음을 확인했습니다.
id prop이 다음과 같이 올바르게 활용되고 있습니다:
- 프로젝트 삭제 기능에서 DeleteProjectMutation을 통해 사용
- 프로젝트 수정 시 라우팅 파라미터로 활용
- ProjectCardProps 인터페이스에서 필수 속성으로 정의
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# ProjectCard 컴포넌트에서 id prop 타입 정의와 사용 확인
ast-grep --pattern 'interface ProjectCardProps {
$$$
id: $_
$$$
}'
# id를 사용하는 삭제 관련 함수 확인
rg -A 5 'id.*delete' src/app/portfolio/_components/ProjectCard.tsx
Length of output: 1036
Script:
#!/bin/bash
# ProjectCard 컴포넌트에서 id prop의 사용 확인
ast-grep --pattern 'function ProjectCard({ id, $$ }: ProjectCardProps) {
$$$
}'
# 삭제 관련 함수나 핸들러 검색
rg "delete.*project.*id" src/app/portfolio/_components/ProjectCard.tsx
rg "handleDelete.*id" src/app/portfolio/_components/ProjectCard.tsx
# 삭제 API 호출이나 mutation 검색
rg "mutation.*delete.*project" src/app/portfolio/_components/ProjectCard.tsx
Length of output: 305
Script:
#!/bin/bash
# ProjectCard 컴포넌트의 전체 내용 확인
cat src/app/portfolio/_components/ProjectCard.tsx
# 삭제 관련 API나 mutation이 정의된 파일 검색
rg -l "deleteProject" src/
Length of output: 10019
src/app/portfolio/write/_components/ProjectForm.tsx (1)
1-13
: 임포트 구문이 잘 구성되어 있습니다.
필요한 모든 의존성이 적절히 임포트되어 있으며, "use client" 지시문이 올바르게 배치되어 있습니다.
src/components/Form.tsx (2)
21-21
: 폼 초기값 설정 기능이 잘 구현되었습니다!
defaultValues
속성의 추가로 폼의 재사용성이 향상되었으며, 타입 안정성도 잘 보장되어 있습니다.
Also applies to: 65-65, 70-70
154-156
: 날짜 입력 처리 시 시간대 고려 필요
날짜 처리 로직이 잘 구현되어 있으나, 시간대 처리에 대해 확인이 필요합니다.
✅ Verification successful
날짜 처리가 적절히 구현되어 있음을 확인했습니다
FormatDate.ts 유틸리티에서 이미 시간대를 고려한 처리가 구현되어 있으며, Form 컴포넌트의 date 타입 입력 처리도 적절합니다:
formatKoreanDate
함수에서timeZone: "Asia/Seoul"
설정으로 한국 시간대 처리- Form 컴포넌트의 date 입력은 브라우저 기본 date picker를 사용하여 시간대 문제 없음
- ISO 문자열 변환 시 날짜 부분만 사용하여 시간대 영향 제거
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: 프로젝트 내의 날짜 처리 일관성 확인
# 날짜 관련 유틸리티 함수 검색
rg -l "new Date|toISOString|Date\." --type ts --type tsx
# 날짜 입력 필드 사용 검색
ast-grep --pattern 'type="date"'
Length of output: 121
Script:
#!/bin/bash
# Description: 날짜 처리 관련 코드 검색 (수정된 버전)
# TypeScript 파일에서 날짜 관련 패턴 검색
rg "new Date|toISOString|Date\." -t ts
# tsx 확장자 파일에서 날짜 관련 패턴 검색
rg "new Date|toISOString|Date\." --glob "*.tsx"
# date 타입 입력 필드 검색 (수정된 패턴)
ast-grep --pattern 'type = "date"'
Length of output: 1641
Script:
#!/bin/bash
# FormatDate.ts 유틸리티의 구현 확인
cat src/utils/FormatDate.ts
# 날짜 입력 필드가 있는 Form 컴포넌트의 전체 구현 확인
cat src/components/Form.tsx
Length of output: 9683
src/app/portfolio/write/page.tsx (1)
35-37
: ProjectForm
에 전달되는 id
의 유효성 검증 필요
<ProjectForm id={searchParams.id} />
에서 searchParams.id
가 없을 경우 undefined
가 전달될 수 있습니다. ProjectForm
컴포넌트에서 id
가 필수적인 경우, id
의 존재 여부를 확인하고 없을 경우에 대한 처리가 필요합니다.
ProjectForm
컴포넌트 내에서 id
가 undefined
일 때를 대비한 예외 처리가 되어있는지 확인해주세요.
src/services/TextSummarizer.ts
Outdated
const cohere = new CohereClientV2({ | ||
token: process.env.NEXT_PUBLIC_COHERE_API_KEY, // 환경 변수에 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.
API 키 보안 문제 해결 필요
NEXT_PUBLIC_
접두사를 사용하면 API 키가 클라이언트 측에 노출되어 보안상 위험합니다. 다음과 같이 수정이 필요합니다:
NEXT_PUBLIC_
접두사를 제거하고 서버 측 환경 변수로 변경- API 키가 없는 경우에 대한 유효성 검사 추가
- token: process.env.NEXT_PUBLIC_COHERE_API_KEY,
+ token: process.env.COHERE_API_KEY ?? (() => {
+ throw new Error('COHERE_API_KEY가 설정되지 않았습니다.');
+ })(),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const cohere = new CohereClientV2({ | |
token: process.env.NEXT_PUBLIC_COHERE_API_KEY, // 환경 변수에 API 키 저장 | |
}); | |
const cohere = new CohereClientV2({ | |
token: process.env.COHERE_API_KEY ?? (() => { | |
throw new Error('COHERE_API_KEY가 설정되지 않았습니다.'); | |
})(), | |
}); |
} catch (error) { | ||
// eslint-disable-next-line no-console | ||
console.error("Error summarizing text with chat API:", error); | ||
return undefined; | ||
} |
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.
🛠️ Refactor suggestion
오류 처리 방식 개선 필요
현재 오류 처리 방식은 너무 단순합니다. 다음과 같은 개선이 필요합니다:
- 오류 유형 구분
- 의미 있는 오류 메시지 전달
+class SummarizerError extends Error {
+ constructor(message: string, public cause?: unknown) {
+ super(message);
+ this.name = 'SummarizerError';
+ }
+}
+
} catch (error) {
- // eslint-disable-next-line no-console
- console.error("Error summarizing text with chat API:", error);
- return undefined;
+ throw new SummarizerError(
+ '텍스트 요약 중 오류가 발생했습니다.',
+ error
+ );
}
Committable suggestion skipped: line range outside the PR's diff.
if (response.message) { | ||
const summaryLines = response.message.content?.map((line) => line.text).join(". "); | ||
const cleanedSummary = summaryLines?.split("\n")[0]?.split(". ").slice(0, 3); | ||
return cleanedSummary; | ||
} |
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.
🧹 Nitpick (assertive)
요약 처리 로직 개선 필요
현재 구현은 불완전한 요약을 반환할 수 있습니다. 다음과 같은 개선이 필요합니다:
- 요약 문장 수 검증
- 빈 요약 처리
if (response.message) {
const summaryLines = response.message.content?.map((line) => line.text).join(". ");
const cleanedSummary = summaryLines?.split("\n")[0]?.split(". ").slice(0, 3);
+ if (!cleanedSummary || cleanedSummary.length < 3) {
+ throw new Error('요약을 생성할 수 없습니다.');
+ }
return cleanedSummary;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (response.message) { | |
const summaryLines = response.message.content?.map((line) => line.text).join(". "); | |
const cleanedSummary = summaryLines?.split("\n")[0]?.split(". ").slice(0, 3); | |
return cleanedSummary; | |
} | |
if (response.message) { | |
const summaryLines = response.message.content?.map((line) => line.text).join(". "); | |
const cleanedSummary = summaryLines?.split("\n")[0]?.split(". ").slice(0, 3); | |
if (!cleanedSummary || cleanedSummary.length < 3) { | |
throw new Error('요약을 생성할 수 없습니다.'); | |
} | |
return cleanedSummary; | |
} |
const TextSummarizer = async (description: string): Promise<string[] | undefined> => { | ||
try { | ||
const response = await cohere.chat({ | ||
model: "command-r", | ||
messages: [ | ||
{ | ||
role: "user", | ||
content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`, | ||
}, | ||
], | ||
returnPrompt: 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.
🛠️ Refactor suggestion
입력 유효성 검사 및 타입 안전성 개선 필요
입력 매개변수와 API 응답에 대한 보호 장치가 부족합니다. 다음 개선사항을 제안합니다:
- description 매개변수 유효성 검사
- API 응답 타입 정의
+interface ChatResponse {
+ message?: {
+ content: Array<{ text: string }>;
+ };
+}
+
const TextSummarizer = async (description: string): Promise<string[] | undefined> => {
+ if (!description?.trim()) {
+ throw new Error('설명 텍스트가 비어있습니다.');
+ }
+
try {
- const response = await cohere.chat({
+ const response: ChatResponse = await cohere.chat({
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const TextSummarizer = async (description: string): Promise<string[] | undefined> => { | |
try { | |
const response = await cohere.chat({ | |
model: "command-r", | |
messages: [ | |
{ | |
role: "user", | |
content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`, | |
}, | |
], | |
returnPrompt: false, | |
}); | |
interface ChatResponse { | |
message?: { | |
content: Array<{ text: string }>; | |
}; | |
} | |
const TextSummarizer = async (description: string): Promise<string[] | undefined> => { | |
if (!description?.trim()) { | |
throw new Error('설명 텍스트가 비어있습니다.'); | |
} | |
try { | |
const response: ChatResponse = await cohere.chat({ | |
model: "command-r", | |
messages: [ | |
{ | |
role: "user", | |
content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`, | |
}, | |
], | |
returnPrompt: false, | |
}); |
<div className="mx-96 flex items-center justify-end"> | ||
<Form.Submit text="포트폴리오 작성하기" /> | ||
</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.
🧹 Nitpick (assertive)
제출 버튼 스타일링 개선이 필요합니다.
고정된 마진(mx-96
)은 반응형 디자인에 적합하지 않습니다.
-<div className="mx-96 flex items-center justify-end">
+<div className="flex items-center justify-end max-w-3xl mx-auto w-full">
<Form.Submit text="포트폴리오 작성하기" />
</div>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<div className="mx-96 flex items-center justify-end"> | |
<Form.Submit text="포트폴리오 작성하기" /> | |
</div> | |
<div className="flex items-center justify-end max-w-3xl mx-auto w-full"> | |
<Form.Submit text="포트폴리오 작성하기" /> | |
</div> |
import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||
import { useRouter } from "next/navigation"; | ||
import { useState, useRef, useEffect } from "react"; | ||
import { createPortal } from "react-dom"; | ||
import ReactMarkdown from "react-markdown"; | ||
import rehypeSlug from "rehype-slug"; | ||
import remarkBreaks from "remark-breaks"; | ||
import remarkGfm from "remark-gfm"; | ||
import removeMarkdown from "remove-markdown"; | ||
import { useShallow } from "zustand/shallow"; | ||
import components from "@/components/MarkdownComponents"; | ||
import GitHub from "@/Icons/Github"; | ||
import LinkIcon from "@/Icons/Link"; | ||
import { deleteProject } from "@/services/Project.api"; | ||
import useModalStore from "@/stores/ModalStore"; | ||
import toast from "@/utils/Toast"; | ||
|
||
interface ProjectCardProps { | ||
id: 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.
🧹 Nitpick (assertive)
import 구문 정리 및 Props 타입 개선이 필요합니다.
import 구문들이 잘 구성되어 있지만, 더 나은 구조화를 위해 다음과 같이 개선하면 좋을 것 같습니다:
- React 관련 import
- Next.js 관련 import
- 외부 라이브러리 import
- 내부 컴포넌트/유틸리티 import
또한, ProjectCardProps 인터페이스에 각 필드에 대한 JSDoc 문서화를 추가하면 좋을 것 같습니다.
const handleEdit = () => { | ||
router.push(`/portfolio/write?id=${id}`); | ||
}; |
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.
🛠️ Refactor suggestion
편집 기능의 에러 처리가 필요합니다.
handleEdit 함수에 다음 사항들을 추가하면 좋을 것 같습니다:
- try-catch 블록을 사용한 에러 처리
- 라우팅 실패 시 사용자 피드백
- 편집 페이지 이동 전 로딩 상태 표시
const handleEdit = () => {
+ try {
+ // 편집 페이지로 이동 전 로딩 상태 설정
+ setIsLoading(true);
router.push(`/portfolio/write?id=${id}`);
+ } catch (error) {
+ toast.error("편집 페이지로 이동하는데 실패했습니다.");
+ } finally {
+ setIsLoading(false);
+ }
};
Committable suggestion skipped: line range outside the PR's diff.
const user = queryClient.getQueryData<User>(["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.
user
데이터의 존재 여부 확인 필요
queryClient.getQueryData<User>(["user"])
를 통해 user
데이터를 가져오고 있는데, user
데이터가 존재하지 않을 경우 예기치 않은 동작이 발생할 수 있습니다.
user
데이터가 없을 경우를 대비하여 예외 처리를 추가하는 것을 제안합니다.
const user = queryClient.getQueryData<User>(["user"]);
+ if (!user) {
+ redirect("/#");
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const user = queryClient.getQueryData<User>(["user"]); | |
const user = queryClient.getQueryData<User>(["user"]); | |
if (!user) { | |
redirect("/#"); | |
} |
if (!user?.isAdmin) { | ||
redirect("/#"); |
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.
🧹 Nitpick (assertive)
권한 확인 로직의 명확한 처리
현재 if (!user?.isAdmin)
으로 관리자 권한을 확인하고 있습니다. 그러나 user
가 undefined
인 경우에도 동일하게 처리되지만, 코드의 명확성을 위해 user
존재 여부와 관리자 권한을 분리하여 확인하는 것이 좋습니다.
user
가 존재하지 않거나 관리자가 아닐 경우를 명확하게 구분하여 처리하는 것을 고려해보시기 바랍니다.
- if (!user?.isAdmin) {
+ if (!user || !user.isAdmin) {
redirect("/#");
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (!user?.isAdmin) { | |
redirect("/#"); | |
if (!user || !user.isAdmin) { | |
redirect("/#"); |
export default async function PortfolioWritePage({ searchParams }: { searchParams: { id: number } }) { | ||
const queryClient = getQueryClient({ staleTime: 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.
searchParams
의 타입 정의 개선
현재 PortfolioWritePage
함수에서 searchParams
의 타입을 { id: number }
로 정의하고 있습니다. 그러나 Next.js에서 제공하는 searchParams
는 일반적으로 문자열 형태입니다. 따라서 searchParams.id
는 string
타입일 가능성이 높으며, 이를 number
로 변환하는 처리가 필요합니다.
searchParams.id
를 숫자로 변환하고, 변환 결과가 NaN인지 확인하여 예외 처리를 추가하는 것을 제안합니다.
export default async function PortfolioWritePage({ searchParams }: { searchParams: { id?: string } }) {
+ const id = Number(searchParams.id);
+ if (isNaN(id)) {
+ // id가 유효하지 않을 경우 에러 페이지로 리다이렉트 또는 예외 처리
+ redirect("/error");
+ }
const queryClient = getQueryClient({ staleTime: 60 * 1000 });
Committable suggestion skipped: line range outside the PR's diff.
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.
Actionable comments posted: 6
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
📒 Files selected for processing (2)
src/app/portfolio/write/_components/ProjectForm.tsx
(1 hunks)src/services/TextSummarizer.ts
(1 hunks)
🔇 Additional comments (5)
src/services/TextSummarizer.ts (5)
1-4
: 서버 지시문과 임포트가 적절히 구성되었습니다!
서버 사이드 렌더링을 위한 "use server" 지시문과 Cohere AI 클라이언트 임포트가 올바르게 설정되었습니다.
9-21
: 입력 매개변수 검증과 타입 안전성 강화가 필요합니다
이전 리뷰 의견이 여전히 유효합니다. 입력 매개변수 검증과 API 응답에 대한 타입 정의가 필요합니다.
22-26
: 요약 결과의 품질 검증이 필요합니다
이전 리뷰 의견이 여전히 유효합니다. 요약 결과의 완전성 검증이 필요합니다.
29-33
: 오류 처리 방식의 개선이 필요합니다
이전 리뷰 의견이 여전히 유효합니다. 더 구체적인 오류 처리와 사용자 친화적인 오류 메시지가 필요합니다.
36-36
: 모듈 내보내기가 올바르게 구현되었습니다!
기본 내보내기가 적절히 설정되었습니다.
const cohere = new CohereClientV2({ | ||
token: process.env.COHERE_API_KEY, // 환경 변수에 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.
🛠️ Refactor suggestion
API 키 오류 처리 검증이 필요합니다
환경 변수가 설정되지 않은 경우에 대한 처리가 필요합니다.
다음과 같이 수정을 제안합니다:
const cohere = new CohereClientV2({
- token: process.env.COHERE_API_KEY, // 환경 변수에 API 키 저장
+ token: process.env.COHERE_API_KEY ?? (() => {
+ throw new Error('COHERE_API_KEY 환경 변수가 설정되지 않았습니다.');
+ })(),
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const cohere = new CohereClientV2({ | |
token: process.env.COHERE_API_KEY, // 환경 변수에 API 키 저장 | |
}); | |
const cohere = new CohereClientV2({ | |
token: process.env.COHERE_API_KEY ?? (() => { | |
throw new Error('COHERE_API_KEY 환경 변수가 설정되지 않았습니다.'); | |
})(), | |
}); |
const response = await cohere.chat({ | ||
model: "command-r", | ||
messages: [ | ||
{ | ||
role: "user", | ||
content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`, | ||
}, | ||
], | ||
returnPrompt: 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.
🧹 Nitpick (assertive)
API 요청 구성의 개선이 필요합니다
프롬프트 구성과 모델 설정에 대한 추가적인 고려사항이 있습니다.
다음과 같은 개선을 제안합니다:
const response = await cohere.chat({
- model: "command-r",
+ model: "command-r",
+ temperature: 0.3, // 일관된 요약을 위해 낮은 temperature 설정
messages: [
{
role: "user",
- content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`,
+ content: `다음 텍스트를 정확히 3개의 문장으로 요약해주세요. 각 문장은 독립적이고 논리적으로 완성되어야 하며, 텍스트의 주요 포인트를 담고 있어야 합니다: ${description}`,
},
],
returnPrompt: false,
+ maxTokens: 200, // 요약 길이 제한
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const response = await cohere.chat({ | |
model: "command-r", | |
messages: [ | |
{ | |
role: "user", | |
content: `Please summarize the following text into exactly 3 sentences. Each sentence should be independent, logically complete, and convey distinct key points from the text. Please provide the summary in Korean: ${description}`, | |
}, | |
], | |
returnPrompt: false, | |
}); | |
const response = await cohere.chat({ | |
model: "command-r", | |
temperature: 0.3, // 일관된 요약을 위해 낮은 temperature 설정 | |
messages: [ | |
{ | |
role: "user", | |
content: `다음 텍스트를 정확히 3개의 문장으로 요약해주세요. 각 문장은 독립적이고 논리적으로 완성되어야 하며, 텍스트의 주요 포인트를 담고 있어야 합니다: ${description}`, | |
}, | |
], | |
returnPrompt: false, | |
maxTokens: 200, // 요약 길이 제한 | |
}); |
<Form.Checkbox label="isPersonal" isChecked={true} /> | ||
</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.
'isPersonal' 체크박스 상태가 폼 데이터와 연동되지 않습니다
현재 체크박스가 항상 선택된 상태로 표시됩니다. 기존 데이터나 사용자 입력과 연동되도록 체크박스의 상태를 폼 데이터와 바인딩해야 합니다.
다음과 같이 수정하는 것을 제안합니다:
-<Form.Checkbox label="isPersonal" isChecked={true} />
+<Form.Checkbox label="isPersonal" />
Committable suggestion skipped: line range outside the PR's diff.
type="date" | ||
label="endDate" | ||
validation={{ | ||
min: { value: new Date(), message: "시작일 이후의 날짜를 입력해주세요." }, | ||
}} | ||
/> | ||
</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.
종료일 유효성 검사를 시작일과 비교하도록 개선하세요
종료일이 시작일보다 이전일 수 있으므로, 종료일의 유효성 검사를 시작일과 비교하여 진행해야 합니다. 또한 new Date()
를 직접 사용하면 매 렌더링 시 새로운 객체가 생성되어 성능에 영향을 줄 수 있습니다.
다음과 같이 수정하는 것을 제안합니다:
<Form.Input
type="date"
label="endDate"
validation={{
- min: { value: new Date(), message: "시작일 이후의 날짜를 입력해주세요." },
+ validate: {
+ afterStartDate: (value) => {
+ const startDate = getValues('startDate');
+ return value >= startDate || "종료일은 시작일 이후의 날짜여야 합니다.";
+ },
+ },
}}
/>
Committable suggestion skipped: line range outside the PR's diff.
value: /^(https?:\/\/)?(www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[\w\.-]*)*\/?$/, | ||
message: "올바른 주소를 입력해주세요.", | ||
}, |
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.
🧹 Nitpick (assertive)
프로젝트 링크의 정규식을 개선하세요
직접 작성한 정규식은 모든 유효한 URL을 처리하지 못할 수 있습니다. validator
라이브러리의 isURL
함수를 사용하여 URL 유효성을 검사하는 것이 좋습니다.
다음과 같이 수정하는 것을 제안합니다:
import validator from 'validator';
...
<Form.Input
label="projectLink"
placeholder="프로젝트 링크를 입력해주세요."
validation={{
pattern: {
- value: /^(https?:\/\/)?(www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[\w\.-]*)*\/?$/,
+ value: (value) => validator.isURL(value) || "올바른 주소를 입력해주세요.",
message: "올바른 주소를 입력해주세요.",
},
}}
/>
Committable suggestion skipped: line range outside the PR's diff.
value: /^(https?:\/\/)?(www\.)?github\.com\/[\w-]+\/[\w.-]+$/, | ||
message: "올바른 깃허브 주소를 입력해주세요.", | ||
}, |
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.
🧹 Nitpick (assertive)
GitHub 링크의 정규식을 개선하세요
현재 정규식은 일부 유효한 GitHub URL을 인식하지 못할 수 있습니다. 정규식을 직접 작성하는 대신, validator
라이브러리의 isURL
함수를 사용하여 URL 유효성을 검사하는 것을 권장합니다.
다음과 같이 수정하는 것을 제안합니다:
import validator from 'validator';
...
<Form.Input
label="githubLink"
placeholder="Github 링크를 입력해주세요."
validation={{
pattern: {
- value: /^(https?:\/\/)?(www\.)?github\.com\/[\w-]+\/[\w.-]+$/,
+ value: (value) => validator.isURL(value) || "올바른 GitHub 주소를 입력해주세요.",
message: "올바른 GitHub 주소를 입력해주세요.",
},
}}
/>
Committable suggestion skipped: line range outside the PR's diff.
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.
Actionable comments posted: 1
<label className="text-lg font-medium" htmlFor="title"> | ||
프로젝트 이름 | ||
</label> | ||
<div className="h-10"> | ||
<Form.Input | ||
label="title" | ||
placeholder="프로젝트 이름을 입력해주세요." | ||
validation={{ required: "프로젝트 이름을 입력해주세요." }} | ||
/> | ||
</div> | ||
<Form.Error name="title" /> |
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.
🧹 Nitpick (assertive)
레이블의 htmlFor
와 입력 필드의 연결을 확인해주세요.
현재 레이블의 htmlFor
속성은 "title"
로 설정되어 있지만, 실제 입력 필드의 id
속성과 일치하지 않을 수 있습니다. 접근성을 향상시키기 위해 레이블의 htmlFor
속성과 해당 입력 필드의 id
속성을 동일하게 설정하는 것을 권장합니다.
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.
Actionable comments posted: 7
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
📒 Files selected for processing (5)
src/app/portfolio/_components/ProjectCard.tsx
(5 hunks)src/app/portfolio/_components/ProjectList.tsx
(1 hunks)src/app/portfolio/write/_components/ProjectForm.tsx
(1 hunks)src/components/Form.tsx
(7 hunks)src/services/Project.api.ts
(2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/portfolio/_components/ProjectCard.tsx
[error] 99-107: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 108-117: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 232-233: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🔇 Additional comments (7)
src/services/Project.api.ts (2)
2-2
: TextSummarizer 모듈 가져오기가 추가되었습니다.
기존 내부 구현이 별도의 모듈로 이동된 것은 좋은 리팩토링입니다. 단일 책임 원칙(SRP)을 잘 따르고 있습니다.
46-54
: userId 매개변수가 제거된 이유를 확인해주세요.
updateProject 함수에서 userId 매개변수가 제거되었습니다. 이로 인해 권한 검증이 누락될 수 있습니다.
다음 스크립트로 권한 검증 로직을 확인해보겠습니다:
src/app/portfolio/_components/ProjectList.tsx (1)
86-86
: ProjectCard 컴포넌트에 새로운 props가 올바르게 전달되었습니다.
프로젝트 삭제 기능과 개인 프로젝트 구분을 위한 필수 props가 잘 추가되었습니다.
ProjectCard 컴포넌트의 props 타입 정의를 확인하기 위해 다음 스크립트를 실행하겠습니다:
Also applies to: 88-88
✅ Verification successful
ProjectCard 컴포넌트의 props 타입이 올바르게 정의되어 있습니다.
ProjectCard 인터페이스에 id
와 isPersonal
props가 정확하게 타입 정의되어 있어 안전하게 사용할 수 있습니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: ProjectCard 컴포넌트의 props 타입 정의 확인
# ProjectCard 컴포넌트의 타입 정의 검색
ast-grep --pattern 'interface ProjectCardProps {
$$$
}'
# id와 isPersonal props 사용 검색
rg -A 2 'id: |isPersonal: ' 'src/app/portfolio/_components'
Length of output: 1205
src/components/Form.tsx (2)
21-21
: 기본값 설정 기능이 잘 구현되었습니다!
defaultValues
속성의 추가로 폼 초기화가 더욱 유연해졌습니다. 타입 안전성도 잘 보장되어 있습니다.
Also applies to: 64-64, 69-69
78-87
: 수정된 필드만 추출하는 로직이 개선되었습니다!
이전 리뷰의 제안사항이 잘 반영되어 있습니다:
Object.entries
를 사용하여 성능이 최적화되었습니다- 타입 안전성이 잘 보장되어 있습니다
- 수정된 데이터만 전송하는 로직이 깔끔합니다
src/app/portfolio/_components/ProjectCard.tsx (1)
117-119
: 🛠️ Refactor suggestion
handleEdit 함수의 에러 처리와 로딩 상태 관리가 필요합니다.
라우팅 실패에 대한 처리와 사용자 피드백이 필요합니다.
+ const [isLoading, setIsLoading] = useState(false);
const handleEdit = () => {
+ setIsLoading(true);
+ try {
router.push(`/portfolio/write?id=${id}`);
+ } catch (error) {
+ toast.error("편집 페이지로 이동하는데 실패했습니다.");
+ } finally {
+ setIsLoading(false);
+ }
};
Likely invalid or redundant comment.
src/app/portfolio/write/_components/ProjectForm.tsx (1)
57-57
:
isPending
대신 isLoading
을 사용해야 합니다.
React Query
의 뮤테이션 객체에는 isPending
속성이 없으며, 대신 isLoading
속성을 사용해야 합니다. 뮤테이션 상태를 확인하기 위해 isLoading
을 사용해야 합니다.
다음과 같이 수정할 것을 제안합니다:
- if (createProjectMutation.isPending || updateProjectMutation.isPending) return;
+ if (createProjectMutation.isLoading || updateProjectMutation.isLoading) return;
Likely invalid or redundant comment.
export const updateProject = async ({ id, projectData }: { id: number; projectData: ProjectRequest }) => { | ||
let summaries = undefined; | ||
if (projectData.content) { | ||
summaries = await TextSummarizer(projectData.content); | ||
} | ||
const response = await instance.patch(`/projects/${id}`, { | ||
...projectData, | ||
userId, | ||
...(summaries && { summary: summaries }), | ||
}); |
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.
🧹 Nitpick (assertive)
updateProject 함수의 개선이 필요합니다.
다음과 같은 개선사항들이 있습니다:
- summaries 변수가 undefined로 초기화되는 것은 불필요합니다.
- 조건부 로직이 복잡하여 가독성이 떨어집니다.
다음과 같이 개선하는 것을 제안드립니다:
export const updateProject = async ({ id, projectData }: { id: number; projectData: ProjectRequest }) => {
- let summaries = undefined;
- if (projectData.content) {
- summaries = await TextSummarizer(projectData.content);
- }
+ const summaries = projectData.content
+ ? await TextSummarizer(projectData.content)
+ : undefined;
+
const response = await instance.patch(`/projects/${id}`, {
...projectData,
...(summaries && { summary: summaries }),
});
return response.data;
};
또한, 에러 처리가 누락되어 있습니다.
다음과 같이 에러 처리를 추가하는 것을 제안드립니다:
export const updateProject = async ({ id, projectData }: { id: number; projectData: ProjectRequest }) => {
+ try {
const summaries = projectData.content
? await TextSummarizer(projectData.content)
: undefined;
const response = await instance.patch(`/projects/${id}`, {
...projectData,
...(summaries && { summary: summaries }),
});
return response.data;
+ } catch (error) {
+ console.error('프로젝트 업데이트 중 오류 발생:', error);
+ throw new Error('프로젝트를 업데이트하는 중 문제가 발생했습니다.');
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const updateProject = async ({ id, projectData }: { id: number; projectData: ProjectRequest }) => { | |
let summaries = undefined; | |
if (projectData.content) { | |
summaries = await TextSummarizer(projectData.content); | |
} | |
const response = await instance.patch(`/projects/${id}`, { | |
...projectData, | |
userId, | |
...(summaries && { summary: summaries }), | |
}); | |
export const updateProject = async ({ id, projectData }: { id: number; projectData: ProjectRequest }) => { | |
try { | |
const summaries = projectData.content | |
? await TextSummarizer(projectData.content) | |
: undefined; | |
const response = await instance.patch(`/projects/${id}`, { | |
...projectData, | |
...(summaries && { summary: summaries }), | |
}); | |
return response.data; | |
} catch (error) { | |
console.error('프로젝트 업데이트 중 오류 발생:', error); | |
throw new Error('프로젝트를 업데이트하는 중 문제가 발생했습니다.'); | |
} | |
}; |
value={ | ||
type === "date" && field.value ? new Date(field.value).toISOString().split("T")[0] : field.value || "" | ||
} |
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.
🧹 Nitpick (assertive)
날짜 처리 로직을 더 명확하게 개선할 수 있습니다.
현재 구현도 잘 작동하지만, 가독성을 높일 수 있습니다.
다음과 같은 개선을 제안드립니다:
- value={
- type === "date" && field.value ? new Date(field.value).toISOString().split("T")[0] : field.value || ""
- }
+ value={(() => {
+ if (type === "date" && field.value) {
+ return new Date(field.value).toISOString().split("T")[0];
+ }
+ return field.value || "";
+ })()}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
value={ | |
type === "date" && field.value ? new Date(field.value).toISOString().split("T")[0] : field.value || "" | |
} | |
value={(() => { | |
if (type === "date" && field.value) { | |
return new Date(field.value).toISOString().split("T")[0]; | |
} | |
return field.value || ""; | |
})()} |
import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||
|
||
import { useRouter } from "next/navigation"; | ||
import { useState, useRef, useEffect } from "react"; | ||
import { createPortal } from "react-dom"; | ||
|
||
import ReactMarkdown from "react-markdown"; | ||
import rehypeSlug from "rehype-slug"; | ||
import remarkBreaks from "remark-breaks"; | ||
import remarkGfm from "remark-gfm"; | ||
import removeMarkdown from "remove-markdown"; | ||
|
||
import { useShallow } from "zustand/shallow"; | ||
|
||
import components from "@/components/MarkdownComponents"; | ||
|
||
import GitHub from "@/Icons/Github"; | ||
import LinkIcon from "@/Icons/Link"; | ||
import { deleteProject } from "@/services/Project.api"; | ||
import useModalStore from "@/stores/ModalStore"; | ||
import toast from "@/utils/Toast"; |
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.
🧹 Nitpick (assertive)
import 구문의 체계적인 정리가 필요합니다.
import 구문을 다음과 같은 순서로 정리하면 코드의 가독성이 향상될 것 같습니다:
- React 관련 (
react
,react-dom
) - Next.js 관련
- 외부 라이브러리
- 내부 컴포넌트/유틸리티
<button onClick={handleEdit} className="p-1 font-semibold text-brand-tertiary"> | ||
수정 | ||
</button> | ||
<button onClick={handleDeleteModal} className="p-1 font-semibold text-brand-tertiary"> | ||
삭제 | ||
</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.
🛠️ Refactor suggestion
버튼 구현의 타입 안전성과 사용자 피드백 개선이 필요합니다.
다음 사항들을 개선하면 좋을 것 같습니다:
- 버튼 타입 명시
- 로딩 상태 표시
- 비활성화 상태 처리
<div className="flex items-center gap-1">
<button
+ type="button"
+ disabled={isLoading}
onClick={handleEdit}
className="p-1 font-semibold text-brand-tertiary"
>
- 수정
+ {isLoading ? "수정 중..." : "수정"}
</button>
<button
+ type="button"
+ disabled={DeleteProjectMutation.isPending}
onClick={handleDeleteModal}
className="p-1 font-semibold text-brand-tertiary"
>
- 삭제
+ {DeleteProjectMutation.isPending ? "삭제 중..." : "삭제"}
</button>
</div>
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Biome (1.9.4)
[error] 232-233: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
const handleDeleteModal = () => { | ||
const modalId = openModal( | ||
<div className="flex flex-col gap-4"> | ||
<p className="pb-8 pt-6 text-center text-2xl font-semibold">정말로 삭제하시겠습니까?</p> | ||
<div className="flex gap-4"> | ||
<button | ||
onClick={() => { | ||
DeleteProjectMutation.mutateAsync(id); | ||
modalId && closeModal(modalId); | ||
}} | ||
className="grow rounded bg-brand-primary px-4 py-2 text-text-primary hover:bg-brand-secondary dark:hover:bg-brand_dark-secondary" | ||
> | ||
삭제 | ||
</button> | ||
<button | ||
onClick={() => modalId && closeModal(modalId)} | ||
className="grow rounded bg-gray-300 px-4 py-2 text-text-primary hover:bg-gray-400" | ||
> | ||
취소 | ||
</button> | ||
</div> | ||
</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.
🛠️ Refactor suggestion
모달의 접근성과 타입 안전성 개선이 필요합니다.
다음 사항들을 개선하면 좋을 것 같습니다:
- ARIA 속성 추가
- 키보드 접근성 개선
- 버튼 타입 명시
const handleDeleteModal = () => {
const modalId = openModal(
- <div className="flex flex-col gap-4">
+ <div
+ role="dialog"
+ aria-labelledby="delete-modal-title"
+ aria-modal="true"
+ className="flex flex-col gap-4"
+ >
- <p className="pb-8 pt-6 text-center text-2xl font-semibold">
+ <p id="delete-modal-title" className="pb-8 pt-6 text-center text-2xl font-semibold">
정말로 삭제하시겠습니까?
</p>
<div className="flex gap-4">
<button
+ type="button"
onClick={() => {
DeleteProjectMutation.mutateAsync(id);
modalId && closeModal(modalId);
}}
className="grow rounded bg-brand-primary px-4 py-2 text-text-primary hover:bg-brand-secondary dark:hover:bg-brand_dark-secondary"
>
삭제
</button>
<button
+ type="button"
onClick={() => modalId && closeModal(modalId)}
className="grow rounded bg-gray-300 px-4 py-2 text-text-primary hover:bg-gray-400"
>
취소
</button>
</div>
</div>
);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleDeleteModal = () => { | |
const modalId = openModal( | |
<div className="flex flex-col gap-4"> | |
<p className="pb-8 pt-6 text-center text-2xl font-semibold">정말로 삭제하시겠습니까?</p> | |
<div className="flex gap-4"> | |
<button | |
onClick={() => { | |
DeleteProjectMutation.mutateAsync(id); | |
modalId && closeModal(modalId); | |
}} | |
className="grow rounded bg-brand-primary px-4 py-2 text-text-primary hover:bg-brand-secondary dark:hover:bg-brand_dark-secondary" | |
> | |
삭제 | |
</button> | |
<button | |
onClick={() => modalId && closeModal(modalId)} | |
className="grow rounded bg-gray-300 px-4 py-2 text-text-primary hover:bg-gray-400" | |
> | |
취소 | |
</button> | |
</div> | |
</div> | |
); | |
}; | |
const handleDeleteModal = () => { | |
const modalId = openModal( | |
<div | |
role="dialog" | |
aria-labelledby="delete-modal-title" | |
aria-modal="true" | |
className="flex flex-col gap-4" | |
> | |
<p id="delete-modal-title" className="pb-8 pt-6 text-center text-2xl font-semibold"> | |
정말로 삭제하시겠습니까? | |
</p> | |
<div className="flex gap-4"> | |
<button | |
type="button" | |
onClick={() => { | |
DeleteProjectMutation.mutateAsync(id); | |
modalId && closeModal(modalId); | |
}} | |
className="grow rounded bg-brand-primary px-4 py-2 text-text-primary hover:bg-brand-secondary dark:hover:bg-brand_dark-secondary" | |
> | |
삭제 | |
</button> | |
<button | |
type="button" | |
onClick={() => modalId && closeModal(modalId)} | |
className="grow rounded bg-gray-300 px-4 py-2 text-text-primary hover:bg-gray-400" | |
> | |
취소 | |
</button> | |
</div> | |
</div> | |
); | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 99-107: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form
element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
const DeleteProjectMutation = useMutation({ | ||
mutationFn: deleteProject, | ||
onSuccess: () => { | ||
toast.success("프로젝트가 삭제되었습니다."); | ||
queryClient.invalidateQueries({ queryKey: ["projects"] }); | ||
}, | ||
onError: () => { | ||
toast.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.
🛠️ Refactor suggestion
DeleteProjectMutation의 에러 처리와 로딩 상태 관리가 필요합니다.
다음과 같은 개선사항을 제안드립니다:
- 구체적인 에러 타입에 따른 처리
- 로딩 상태 표시
- 낙관적 업데이트(Optimistic Update) 구현 고려
const DeleteProjectMutation = useMutation({
mutationFn: deleteProject,
+ onMutate: () => {
+ // 낙관적 업데이트 로직
+ const previousProjects = queryClient.getQueryData(["projects"]);
+ queryClient.setQueryData(["projects"], (old) =>
+ old.filter(project => project.id !== id)
+ );
+ return { previousProjects };
+ },
onSuccess: () => {
toast.success("프로젝트가 삭제되었습니다.");
queryClient.invalidateQueries({ queryKey: ["projects"] });
},
- onError: () => {
+ onError: (error: Error, _, context) => {
+ if (context?.previousProjects) {
+ queryClient.setQueryData(["projects"], context.previousProjects);
+ }
toast.error("프로젝트 삭제에 실패하였습니다.");
},
});
Committable suggestion skipped: line range outside the PR's diff.
const createProjectMutation = useMutation({ | ||
mutationFn: createProject, | ||
onSuccess: () => { | ||
queryClient.invalidateQueries({ queryKey: ["projectList"] }); | ||
router.push("/portfolio"); | ||
}, | ||
}); |
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.
🧹 Nitpick (assertive)
뮤테이션 함수를 리팩토링하여 중복 코드를 제거하세요.
createProjectMutation
과 updateProjectMutation
은 유사한 로직을 가지고 있으므로, 함수 또는 공통 로직으로 추출하여 코드의 중복을 줄일 수 있습니다. 이는 코드의 유지보수성과 가독성을 향상시킵니다.
예를 들어, 공통의 뮤테이션 함수를 생성할 수 있습니다:
const projectMutation = useMutation({
mutationFn: (data) =>
id ? updateProject({ id: data.id, projectData: data.projectData }) : createProject(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["projectList"] });
router.push("/portfolio");
},
});
Also applies to: 46-53
Summary by CodeRabbit
New Features
ProjectForm
컴포넌트 도입.ProjectCard
컴포넌트에 프로젝트 식별을 위한id
속성 추가.Form
컴포넌트에서 기본값 관리 및 수정된 필드 추적 기능 향상.Bug Fixes