Skip to content
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

Merged
merged 4 commits into from
Nov 23, 2024

Conversation

wjsdncl
Copy link
Owner

@wjsdncl wjsdncl commented Nov 22, 2024

Summary by CodeRabbit

  • New Features

    • 프로젝트 삭제 및 편집을 위한 모달 인터페이스 추가.
    • 프로젝트 제출을 위한 새로운 ProjectForm 컴포넌트 도입.
    • 텍스트 요약 기능을 위한 Cohere AI API 통합.
    • ProjectCard 컴포넌트에 프로젝트 식별을 위한 id 속성 추가.
    • Form 컴포넌트에서 기본값 관리 및 수정된 필드 추적 기능 향상.
  • Bug Fixes

    • 프로젝트 카드에서 성공 및 오류 알림 표시 개선.

@wjsdncl wjsdncl self-assigned this Nov 22, 2024
Copy link

vercel bot commented Nov 22, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
next-blog ⬜️ Ignored (Inspect) Visit Preview Nov 22, 2024 9:09am

Copy link

coderabbitai bot commented Nov 22, 2024

Walkthrough

이 변경 사항은 package.json 파일에 새로운 종속성인 "cohere-ai": "^7.14.0"를 추가하는 것을 포함합니다. 또한, 프로젝트 카드 컴포넌트의 삭제 기능과 모달 상호작용을 관리하는 기능이 ProjectCard.tsx에 도입되었습니다. ProjectList.tsx는 각 프로젝트 카드를 고유하게 식별하기 위해 id 속성을 추가했습니다. 새 파일 ProjectForm.tsx는 프로젝트 제출을 관리하는 컴포넌트를 도입하며, PortfolioWritePage는 비동기 함수로 재구성되어 사용자 접근 제어를 강화했습니다. 마지막으로, TextSummarizer.ts 파일은 Cohere AI API를 사용하여 텍스트 요약 기능을 구현했습니다.

Changes

파일 경로 변경 요약
package.json 새로운 종속성 "cohere-ai": "^7.14.0" 추가.
src/app/portfolio/_components/ProjectCard.tsx ProjectCardProps 인터페이스에 id 속성 추가, 프로젝트 삭제 및 모달 관리 기능 도입.
src/app/portfolio/_components/ProjectList.tsx ProjectCard 호출에 idisPersonal 속성 추가.
src/app/portfolio/write/_components/ProjectForm.tsx 프로젝트 제출을 위한 새로운 React 컴포넌트 추가, 생성 및 업데이트 모드 지원.
src/app/portfolio/write/page.tsx PortfolioWritePage를 비동기 함수로 재구성, 사용자 접근 제어 로직 업데이트.
src/components/Form.tsx defaultValues 속성 추가, 양식 제출 처리 방식 개선.
src/services/Project.api.ts TextSummarizer 함수 제거, 새로운 TextSummarizer 모듈로 요약 기능 위임.
src/services/TextSummarizer.ts Cohere AI API를 사용하여 텍스트 요약 기능 추가, 에러 처리 포함.

Possibly related PRs

🐰 새로운 기능이 생겼네,
프로젝트 카드가 더 똑똑해졌어!
삭제하고 편집, 모달도 관리,
요약도 멋지게, AI가 도와줘!
이제 더 쉽게, 포트폴리오를 써,
모두 함께 즐겨보자, 이 멋진 변화! 🌟


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 사용 시 에러 처리 및 타입 안전성 개선이 필요합니다.

다음 사항들을 개선하는 것이 좋겠습니다:

  1. TextSummarizer 호출 시 에러 처리가 없습니다
  2. summary가 undefined일 때의 처리가 불명확합니다
  3. 타입 안전성이 부족합니다

다음과 같이 개선하는 것을 제안드립니다:

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5403680 and 0f7ba29.

⛔ 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 라이브러리 추가는 텍스트 요약 기능 구현을 위한 것으로 보입니다. 다음 사항들을 고려해주시기 바랍니다:

  1. 버전 관리: 현재 ^7.14.0 으로 설정되어 있어 마이너 버전 업데이트가 자동으로 적용될 수 있습니다. 안정성을 위해 정확한 버전으로 고정하는 것을 고려해보세요.
  2. 보안: Cohere AI API를 사용할 때 민감한 정보가 노출되지 않도록 주의가 필요합니다.
  3. 비용: API 사용량에 따른 비용이 발생할 수 있으므로, 적절한 사용량 제한과 모니터링이 필요합니다.
✅ Verification successful

cohere-ai 패키지의 보안 및 버전 관리 확인 완료

패키지 검토 결과 다음과 같은 사항들을 확인했습니다:

  1. 현재 7.14.0이 최신 버전이며, 보안 취약점은 발견되지 않았습니다.
  2. 패키지가 안정적인 메이저 버전(7.x)을 유지하고 있으며, 정기적인 업데이트가 이루어지고 있습니다.

따라서 원래 리뷰 의견을 다음과 같이 수정합니다:

  1. 버전 관리: ^7.14.0 설정은 적절합니다. 메이저 버전이 안정적이므로 마이너 업데이트 허용은 문제없습니다.
  2. 보안: npm audit 결과 현재 알려진 취약점이 없습니다.
  3. 비용 관련 고려사항은 여전히 유효하므로, 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 컴포넌트 내에서 idundefined일 때를 대비한 예외 처리가 되어있는지 확인해주세요.

Comment on lines 3 to 5
const cohere = new CohereClientV2({
token: process.env.NEXT_PUBLIC_COHERE_API_KEY, // 환경 변수에 API 키 저장
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

API 키 보안 문제 해결 필요

NEXT_PUBLIC_ 접두사를 사용하면 API 키가 클라이언트 측에 노출되어 보안상 위험합니다. 다음과 같이 수정이 필요합니다:

  1. NEXT_PUBLIC_ 접두사를 제거하고 서버 측 환경 변수로 변경
  2. 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.

Suggested change
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가 설정되지 않았습니다.');
})(),
});

Comment on lines +27 to +31
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error summarizing text with chat API:", error);
return undefined;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

오류 처리 방식 개선 필요

현재 오류 처리 방식은 너무 단순합니다. 다음과 같은 개선이 필요합니다:

  1. 오류 유형 구분
  2. 의미 있는 오류 메시지 전달
+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.

Comment on lines +20 to +24
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

요약 처리 로직 개선 필요

현재 구현은 불완전한 요약을 반환할 수 있습니다. 다음과 같은 개선이 필요합니다:

  1. 요약 문장 수 검증
  2. 빈 요약 처리
     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.

Suggested change
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;
}

Comment on lines +7 to +19
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,
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

입력 유효성 검사 및 타입 안전성 개선 필요

입력 매개변수와 API 응답에 대한 보호 장치가 부족합니다. 다음 개선사항을 제안합니다:

  1. description 매개변수 유효성 검사
  2. 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.

Suggested change
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,
});

Comment on lines +229 to +231
<div className="mx-96 flex items-center justify-end">
<Form.Submit text="포트폴리오 작성하기" />
</div>
Copy link

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.

Suggested change
<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>

Comment on lines 3 to 21
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;
Copy link

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 구문들이 잘 구성되어 있지만, 더 나은 구조화를 위해 다음과 같이 개선하면 좋을 것 같습니다:

  1. React 관련 import
  2. Next.js 관련 import
  3. 외부 라이브러리 import
  4. 내부 컴포넌트/유틸리티 import

또한, ProjectCardProps 인터페이스에 각 필드에 대한 JSDoc 문서화를 추가하면 좋을 것 같습니다.

Comment on lines +112 to +114
const handleEdit = () => {
router.push(`/portfolio/write?id=${id}`);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

편집 기능의 에러 처리가 필요합니다.

handleEdit 함수에 다음 사항들을 추가하면 좋을 것 같습니다:

  1. try-catch 블록을 사용한 에러 처리
  2. 라우팅 실패 시 사용자 피드백
  3. 편집 페이지 이동 전 로딩 상태 표시
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.

Comment on lines +23 to +24
const user = queryClient.getQueryData<User>(["user"]);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
const user = queryClient.getQueryData<User>(["user"]);
const user = queryClient.getQueryData<User>(["user"]);
if (!user) {
redirect("/#");
}

Comment on lines +26 to +27
if (!user?.isAdmin) {
redirect("/#");
Copy link

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)으로 관리자 권한을 확인하고 있습니다. 그러나 userundefined인 경우에도 동일하게 처리되지만, 코드의 명확성을 위해 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.

Suggested change
if (!user?.isAdmin) {
redirect("/#");
if (!user || !user.isAdmin) {
redirect("/#");

Comment on lines +9 to +11
export default async function PortfolioWritePage({ searchParams }: { searchParams: { id: number } }) {
const queryClient = getQueryClient({ staleTime: 60 * 1000 });

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

searchParams의 타입 정의 개선

현재 PortfolioWritePage 함수에서 searchParams의 타입을 { id: number }로 정의하고 있습니다. 그러나 Next.js에서 제공하는 searchParams는 일반적으로 문자열 형태입니다. 따라서 searchParams.idstring 타입일 가능성이 높으며, 이를 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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0f7ba29 and 5f25c36.

📒 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: 모듈 내보내기가 올바르게 구현되었습니다!

기본 내보내기가 적절히 설정되었습니다.

Comment on lines +5 to +7
const cohere = new CohereClientV2({
token: process.env.COHERE_API_KEY, // 환경 변수에 API 키 저장
});
Copy link

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.

Suggested change
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 환경 변수가 설정되지 않았습니다.');
})(),
});

Comment on lines +11 to +20
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,
});
Copy link

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.

Suggested change
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, // 요약 길이 제한
});

Comment on lines 99 to 100
<Form.Checkbox label="isPersonal" isChecked={true} />
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

'isPersonal' 체크박스 상태가 폼 데이터와 연동되지 않습니다

현재 체크박스가 항상 선택된 상태로 표시됩니다. 기존 데이터나 사용자 입력과 연동되도록 체크박스의 상태를 폼 데이터와 바인딩해야 합니다.

다음과 같이 수정하는 것을 제안합니다:

-<Form.Checkbox label="isPersonal" isChecked={true} />
+<Form.Checkbox label="isPersonal" />

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 125 to 131
type="date"
label="endDate"
validation={{
min: { value: new Date(), message: "시작일 이후의 날짜를 입력해주세요." },
}}
/>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

종료일 유효성 검사를 시작일과 비교하도록 개선하세요

종료일이 시작일보다 이전일 수 있으므로, 종료일의 유효성 검사를 시작일과 비교하여 진행해야 합니다. 또한 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.

Comment on lines +216 to +218
value: /^(https?:\/\/)?(www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[\w\.-]*)*\/?$/,
message: "올바른 주소를 입력해주세요.",
},
Copy link

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.

Comment on lines +197 to +199
value: /^(https?:\/\/)?(www\.)?github\.com\/[\w-]+\/[\w.-]+$/,
message: "올바른 깃허브 주소를 입력해주세요.",
},
Copy link

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.

Copy link

@coderabbitai coderabbitai bot left a 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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Reviewing files that changed from the base of the PR and between 5f25c36 and a894a89.

📒 Files selected for processing (1)
  • src/app/portfolio/write/_components/ProjectForm.tsx (1 hunks)

Comment on lines +81 to +91
<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" />
Copy link

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 속성을 동일하게 설정하는 것을 권장합니다.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between a894a89 and a7a7b13.

📒 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 인터페이스에 idisPersonal 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: ⚠️ Potential issue

isPending 대신 isLoading을 사용해야 합니다.

React Query의 뮤테이션 객체에는 isPending 속성이 없으며, 대신 isLoading 속성을 사용해야 합니다. 뮤테이션 상태를 확인하기 위해 isLoading을 사용해야 합니다.

다음과 같이 수정할 것을 제안합니다:

- if (createProjectMutation.isPending || updateProjectMutation.isPending) return;
+ if (createProjectMutation.isLoading || updateProjectMutation.isLoading) return;

Likely invalid or redundant comment.

Comment on lines +46 to 54
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 }),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

⚠️ Potential issue

updateProject 함수의 개선이 필요합니다.

다음과 같은 개선사항들이 있습니다:

  1. summaries 변수가 undefined로 초기화되는 것은 불필요합니다.
  2. 조건부 로직이 복잡하여 가독성이 떨어집니다.

다음과 같이 개선하는 것을 제안드립니다:

 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.

Suggested change
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('프로젝트를 업데이트하는 중 문제가 발생했습니다.');
}
};

Comment on lines +150 to +152
value={
type === "date" && field.value ? new Date(field.value).toISOString().split("T")[0] : field.value || ""
}
Copy link

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.

Suggested change
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 || "";
})()}

Comment on lines +3 to +23
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";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

import 구문의 체계적인 정리가 필요합니다.

import 구문을 다음과 같은 순서로 정리하면 코드의 가독성이 향상될 것 같습니다:

  1. React 관련 (react, react-dom)
  2. Next.js 관련
  3. 외부 라이브러리
  4. 내부 컴포넌트/유틸리티

Comment on lines +229 to +234
<button onClick={handleEdit} className="p-1 font-semibold text-brand-tertiary">
수정
</button>
<button onClick={handleDeleteModal} className="p-1 font-semibold text-brand-tertiary">
삭제
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

버튼 구현의 타입 안전성과 사용자 피드백 개선이 필요합니다.

다음 사항들을 개선하면 좋을 것 같습니다:

  1. 버튼 타입 명시
  2. 로딩 상태 표시
  3. 비활성화 상태 처리
<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)

Comment on lines +92 to +115
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>
);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

모달의 접근성과 타입 안전성 개선이 필요합니다.

다음 사항들을 개선하면 좋을 것 같습니다:

  1. ARIA 속성 추가
  2. 키보드 접근성 개선
  3. 버튼 타입 명시
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.

Suggested change
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)

Comment on lines +69 to +78
const DeleteProjectMutation = useMutation({
mutationFn: deleteProject,
onSuccess: () => {
toast.success("프로젝트가 삭제되었습니다.");
queryClient.invalidateQueries({ queryKey: ["projects"] });
},
onError: () => {
toast.error("프로젝트 삭제에 실패하였습니다.");
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

DeleteProjectMutation의 에러 처리와 로딩 상태 관리가 필요합니다.

다음과 같은 개선사항을 제안드립니다:

  1. 구체적인 에러 타입에 따른 처리
  2. 로딩 상태 표시
  3. 낙관적 업데이트(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.

Comment on lines +37 to +43
const createProjectMutation = useMutation({
mutationFn: createProject,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["projectList"] });
router.push("/portfolio");
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

뮤테이션 함수를 리팩토링하여 중복 코드를 제거하세요.

createProjectMutationupdateProjectMutation은 유사한 로직을 가지고 있으므로, 함수 또는 공통 로직으로 추출하여 코드의 중복을 줄일 수 있습니다. 이는 코드의 유지보수성과 가독성을 향상시킵니다.

예를 들어, 공통의 뮤테이션 함수를 생성할 수 있습니다:

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

@wjsdncl wjsdncl merged commit bdf9703 into develop Nov 23, 2024
3 checks passed
@wjsdncl wjsdncl deleted the feat/portfolio-add-edit-delete branch November 23, 2024 03:03
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant