Skip to content

Conversation

@Insung-Jo
Copy link

@Insung-Jo Insung-Jo commented Jun 18, 2025

📌 변경 사항 개요

  • 프로필 수정 컴포넌트 구현

✨ 요약

  • 인스턴스 관련

    • axios 인스턴스 명 수정
    • 토큰 안 넘어오던 문제 수정
  • 프로필 수정 컴포넌트 구현

    • 이미지 업로드 컴포넌트
    • 프로필 수정 폼
    • 프로필 수정 관련 API 작성
    • 프로필 수정 관련 쿼리 작성
  • 여러 SVG 아이콘 컴포넌트 추가 (Plus, pen, x 등)

📝 상세 내용

  • 내용이 어느 정도 있기 때문에 핵심 로직 부분에는 주석으로도 작성이 되어 있습니다.
  • 시간이 그리 많지 않아 일부 누락 사항이 있을 수 있습니다.

인스턴스 관련

  • 기존 인스턴스명 api 에서 authHttpClient로 변경되었습니다. (적용하고 있던 부분도 반영 완료)
  • 일부 조건으로 인하여 토큰이 안 넘어가던 문제가 있어서 해당 조건문을 수정하였습니다. (예외처리로 사용한 부분은 리다이렉트 처리로 변경 예정)
// 변경 전
const publicPaths = [AUTH_ENDPOINT.LOGIN, AUTH_ENDPOINT.SIGNUP]
const isPulicPath = publicPaths.some((path) => config.url?.includes(path)) // 일부 url에서 false가 되버림

    if (!isPulicPath && token) {
      config.headers.Authorization = `Bearer ${token}`
    }

// 변경 
if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }

컴포넌트 관련

  • SVG로 작성한 컴포넌트가 추가 되었습니다. 적용과 관련된 부분은 해당 코드를 참고해주세요!
  • ProfileEditForm, ProfileImageUpload 컴포넌트가 추가 되었습니다.

ProfileEditForm

  • useUserQuery + reset 를 통해 데이터를 최신화 (SSR 도입 시 변경될 수도 있습니다.)
  • reset을 사용하여 유저가 로딩된 시점에서 해당 값을 초기화 하여 폼에 반영했습니다.
이름 주요 역할
reset 폼의 필드 값을 초기화하거나 외부 데이터로 재설정하며, 에러 등 상태도 초기화
  // 유저 정보가 비동기적으로 넘어오기 때문에 유저가 로딩된 시점에서 RHF을 초기화 하기 위함 (SSR 도입 시 변경 예정)
  useEffect(() => {
    if (user) { // 유저의 정보가 넘어올 경우
      reset({ // react-hook-form 값 재설정
        profileImageUrl: user.profileImageUrl ?? null,
        nickname: user.nickname ?? '',
        email: user.email ?? '',
      })
    }
  }, [user, reset])
  • 내부적으로 ref 사용하고 있어 ...register로 제어가 어렵기 때문에 Controller 사용
요소 역할
<Controller ... /> RHF의 커스텀 입력 컴포넌트 바인딩을 위한 래퍼 컴포넌트
name="profileImageUrl" RHF에서 관리할 필드의 이름 (form 상태 내 키 이름)
control={control} useForm 훅에서 생성한 control 객체로, RHF 내부 상태 제어에 필요
render={({ field }) => ...} RHF의 내부 상태와 연결되는 컴포넌트를 직접 그리는 방식
field.valuevalue={value} RHF가 현재 관리 중인 값 (이미지 URL)을 커스텀 컴포넌트에 전달 (미리보기용)
field.onChangeonChange={...} RHF의 상태를 업데이트하는 함수, 컴포넌트 내 변화 감지 시 호출됨
onFileChange={(file) => ...} RHF 외부 상태로 서버에 보낼 실제 파일(File)을 따로 저장
<Controller
        name="profileImageUrl"
        control={control}
        render={({ field: { value, onChange } }) => (
          <ProfileImageUpload
            value={value} // 미리보기용 이미지 URL
            onChange={onChange} // form 상태(profileImageUrl) 업데이트
            onFileChange={(file) => setProfileImageFile(file)} // 서버 전송용 파일 저장
          />
        )}
      />

ProfileImageUpload

  • 프로필을 설정할 때 업로드 과정을 거쳐야 하기 때문에 상위 컴포넌트로 해당 URL을 넘겼습니다.
// mypageApi.ts
export async function uploadProfileImage(
  image: File,
): Promise<UploadProfileImageResponse> {
  const formData = new FormData() // multipart/form-data 형식이 필요하기 때문에 사용
  formData.append('image', image) // image 라는 키로 해당 파일 추가
  const response = await authHttpClient.post<UploadProfileImageResponse>(
    MYPAGE_ENDPOINT.IMAGE,
    formData,
  )
  return response.data
}

// ProfileImageUpload.tsx 파일 변경 로직

  // 파일이 선택되었을 때 처리
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    if (!file) return

    const url = URL.createObjectURL(file) // 로컬 미리보기 URL 생성
    setPreview(url)
    onChange(url) // RHF 상태 업데이트

    // 서버 업로드용 파일을 상위로 전달
    if (onFileChange) onFileChange(file)

    // 같은 파일 다시 선택할 수 있도록 초기화
    if (inputRef.current) inputRef.current.value = ''
  }

  // 이미지 삭제 처리
  const handleDelete = () => {
    if (preview) URL.revokeObjectURL(preview) // 메모리 누수 방지
    setPreview(null)
    onChange(null) // RHF 상태 초기화
    if (inputRef.current) inputRef.current.value = ''
  }

🔗 관련 이슈

#74

🖼️ 스크린샷

2025-06-19.04-15-39.mp4

호출 관련

  • 제출 시 /me,/image API 호출 후 새로 고침
  • /me가 많이 보이는 이유는 GET 요청의 /mePUT 요청의 /me가 존재합니다.

image

✅ 체크리스트

  • 브랜치 네이밍 컨벤션을 준수했습니다
  • 커밋 컨벤션을 준수했습니다
  • 코드가 프로젝트의 스타일 가이드라인을 준수합니다

💡 참고 사항

  • 마감 기한이 다가 오고 있기 때문에 일부 로직을 설명하지 못했습니다. (자세한 건 내부 코드를 확인해주세요!)

Summary by CodeRabbit

  • 신규 기능

    • 마이페이지에서 프로필 이미지 업로드 및 미리보기, 닉네임/이메일 수정이 가능한 프로필 편집 폼이 추가되었습니다.
    • 프로필 이미지 업로드, 프로필 정보 수정, 유저 정보 조회를 위한 API 및 커스텀 훅이 도입되었습니다.
    • 닉네임 입력값에 대한 유효성 검사(한글/영문, 최대 10자)가 적용되었습니다.
    • 새로운 아이콘(플러스, 펜, 닫기)이 추가되었습니다.
  • 버그 수정

    • 로그인 및 회원가입 에러 처리에서 오타가 수정되어 서버 메시지가 올바르게 표시됩니다.
  • 스타일

    • 읽기 전용 입력창에 회색 텍스트와 기본 커서 스타일이 적용되었습니다.
    • 텍스트 색상을 지정하는 .Text-blue 클래스가 추가되었습니다.
  • 리팩터

    • 인증 및 대시보드 관련 API 요청에 사용하는 HTTP 클라이언트가 명확하게 분리 및 명명되었습니다.
  • 기타

    • 마이페이지의 기존 프로필/비밀번호 변경 UI가 새로운 프로필 편집 폼으로 대체되었습니다.

@Insung-Jo Insung-Jo added this to the 2차 구현 기간 milestone Jun 18, 2025
@Insung-Jo Insung-Jo self-assigned this Jun 18, 2025
@Insung-Jo Insung-Jo added ✨Feat 기능 개발 🐛Fix 버그 수정 🎨Style UI, 스타일 관련 수정 labels Jun 18, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 18, 2025

"""

Walkthrough

마이페이지(프로필) 기능을 위한 API, 타입, 훅, 컴포넌트, 검증 스키마 등이 새롭게 추가되었습니다. 기존 인증 및 대시보드 관련 API 호출 코드들은 공통 axios 인스턴스(authHttpClient)로 일원화되었으며, 마이페이지 UI는 ProfileEditForm 컴포넌트로 리팩터링되었습니다.

Changes

파일/그룹 변경 요약
src/app/features/auth/api/authApi.ts, src/app/shared/components/common/sidebar/modal/CreateDashboardModal.tsx, src/app/shared/hooks/useDashboard.ts api 인스턴스 → authHttpClient로 axios 인스턴스명 변경 및 적용
src/app/shared/lib/axios.ts axios 인스턴스명을 authHttpClient로 변경, 인증 헤더 조건 단순화 및 export명 변경
src/app/features/mypage/api/mypageApi.ts 마이페이지용 loadUser, updateMyProfile, uploadProfileImage API 함수 신규 추가
src/app/features/mypage/api/mypageEndPoint.ts 환경변수 기반 마이페이지 엔드포인트 상수 추가
src/app/features/mypage/components/ProfileEditForm.tsx 프로필 수정 폼 컴포넌트 신규 구현(프로필 이미지, 닉네임, 이메일 등)
src/app/features/mypage/components/ProfileImageUpload.tsx 프로필 이미지 업로드 및 미리보기 컴포넌트 신규 구현
src/app/features/mypage/hook/useUpdateMyProfileMutation.ts 프로필 수정 API용 커스텀 mutation 훅 추가
src/app/features/mypage/hook/useUploadProfileImageMutation.ts 프로필 이미지 업로드용 커스텀 mutation 훅 추가
src/app/features/mypage/hook/useUserQurey.ts 사용자 정보 조회용 커스텀 query 훅 추가
src/app/features/mypage/schemas/mypageValidation.ts 닉네임 유효성 검사 스키마 추가(한글/영문 1~10자)
src/app/features/mypage/types/mypage.type.ts 프로필 수정/이미지 업로드용 타입 정의 추가
src/app/globals.css .Text-blue 유틸리티 클래스 추가(라이트/다크모드 색상)
src/app/mypage/page.tsx 기존 마이페이지 폼 삭제 및 ProfileEditForm 컴포넌트로 교체, 비밀번호 변경 폼 주석처리
src/app/shared/components/Input.tsx readOnly 시 텍스트 색상/커서 스타일 추가
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx 닫기(원형 X) SVG 아이콘 컴포넌트 추가
src/app/shared/components/common/PlusIcon/PlusIcon.tsx 플러스(+) SVG 아이콘 컴포넌트 추가
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx 흰색 펜 SVG 아이콘 컴포넌트 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ProfileEditForm
    participant useUserQuery
    participant useUploadProfileImageMutation
    participant useUpdateMyProfileMutation
    participant mypageApi

    User->>ProfileEditForm: 페이지 진입
    ProfileEditForm->>useUserQuery: 사용자 정보 조회
    useUserQuery->>mypageApi: loadUser()
    mypageApi-->>useUserQuery: 사용자 데이터 반환
    useUserQuery-->>ProfileEditForm: 데이터 제공

    User->>ProfileEditForm: 프로필 이미지/닉네임 수정, 저장 클릭
    alt 이미지 변경
        ProfileEditForm->>useUploadProfileImageMutation: 이미지 업로드
        useUploadProfileImageMutation->>mypageApi: uploadProfileImage(file)
        mypageApi-->>useUploadProfileImageMutation: profileImageUrl 반환
        useUploadProfileImageMutation-->>ProfileEditForm: profileImageUrl 전달
    end
    ProfileEditForm->>useUpdateMyProfileMutation: 닉네임/이미지 URL로 프로필 수정
    useUpdateMyProfileMutation->>mypageApi: updateMyProfile(data)
    mypageApi-->>useUpdateMyProfileMutation: 수정된 사용자 데이터 반환
    useUpdateMyProfileMutation-->>ProfileEditForm: 성공/실패 알림
Loading

Possibly related PRs

  • CoPlay-FE/coplan#71: 마이페이지 기본 UI 및 폼을 최초 도입한 PR로, 이번 PR의 리팩터링 및 컴포넌트화의 기반이 되는 코드와 직접적으로 연결됨.
  • CoPlay-FE/coplan#29: 인증 API(authApi.ts)에서 기존 api 인스턴스를 사용하던 부분을 이번 PR에서 authHttpClient로 변경하였으므로, 동일 함수 및 파일을 다루는 직접적 연관이 있음.

Suggested labels

♻️Refactor, 🔧Chore

Suggested reviewers

  • dkslel1225
  • LeeCh0129

Poem

🐇
마이페이지 새단장, 폼이 뿅!
프로필 사진도 척, 닉네임도 쏙
새 API와 훅, 타입도 든든
파란 글씨 반짝, 아이콘이 춤춘다
코드 속 토끼는 오늘도 행복!
🖊️✨
"""

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-18T19_25_46_283Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 296bae0 and 76fadb9.

📒 Files selected for processing (4)
  • src/app/features/auth/hooks/useLoginMutation.ts (1 hunks)
  • src/app/features/auth/hooks/useSignupMutation.ts (1 hunks)
  • src/app/features/mypage/components/ProfileEditForm.tsx (1 hunks)
  • src/app/features/mypage/components/ProfileImageUpload.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/app/features/auth/hooks/useLoginMutation.ts
  • src/app/features/auth/hooks/useSignupMutation.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/features/mypage/components/ProfileEditForm.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/app/features/mypage/components/ProfileImageUpload.tsx (3)
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1)
  • WhitePenIcon (5-20)
src/app/shared/components/common/PlusIcon/PlusIcon.tsx (1)
  • PlusIcon (6-38)
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)
  • CloseCircleIcon (5-40)
🪛 Biome (1.9.4)
src/app/features/mypage/components/ProfileImageUpload.tsx

[error] 55-56: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (4)
src/app/features/mypage/components/ProfileImageUpload.tsx (4)

1-13: 인터페이스와 임포트가 잘 정의되어 있습니다.

Props 인터페이스가 명확하게 정의되어 있고, 한국어 주석으로 각 프로퍼티의 역할이 잘 설명되어 있습니다.


15-26: 컴포넌트 초기화 로직이 올바릅니다.

useEffect를 통해 외부 value와 내부 preview 상태를 동기화하는 로직이 적절합니다.


28-42: 파일 선택 처리 로직이 잘 구현되어 있습니다.

파일 선택 시 미리보기 URL 생성, 상태 업데이트, 그리고 동일 파일 재선택을 위한 input 초기화까지 모든 로직이 적절히 처리되었습니다.


54-102: UI 렌더링 로직이 잘 구현되어 있습니다.

조건부 렌더링을 통한 미리보기/플러스 아이콘 표시, 호버 효과, 삭제 버튼 등이 적절히 구현되었습니다. 정적 분석 도구의 옵셔널 체이닝 힌트는 해당 라인에서 거짓 양성으로 보입니다.

정적 분석 도구가 55-56번 라인에서 옵셔널 체이닝을 제안했지만, 해당 코드를 검토한 결과 옵셔널 체이닝이 필요한 부분을 찾을 수 없습니다. 혹시 다른 위치의 이슈가 잘못 보고된 것일 수 있습니다.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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: 3

🔭 Outside diff range comments (1)
src/app/features/mypage/hook/useUserQurey.ts (1)

1-11: 파일명에 오타가 있습니다.

파일명이 useUserQurey.ts로 되어 있는데, useUserQuery.ts로 수정해야 합니다. "Query"가 올바른 철자입니다.

그 외에는 React Query를 사용한 커스텀 훅 구현이 적절합니다.

🧹 Nitpick comments (6)
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1)

1-20: SVG 접근성 개선 제안: aria-hidden="true" 추가 고려
현재 SVG는 스크린 리더에서 decorative 아이콘으로 처리되지 않을 수 있습니다.

<svg
  width={size}
  height={size}
  viewBox="0 0 24 24"
  fill="none"
+ role="img"
+ aria-hidden="true"
  {...props}
>
src/app/shared/components/common/PlusIcon/PlusIcon.tsx (1)

1-38: SVG 접근성 개선 제안: aria-hidden="true" 추가 고려
PlusIcon도 decorative 아이콘인 경우 스크린 리더 무시를 위해 aria-hidden="true"를 추가하면 좋습니다.

<svg
  className={className}
  width={size}
  height={size}
  viewBox="0 0 18 18"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
+ role="img"
+ aria-hidden="true"
  {...props}
>
src/app/shared/components/Input.tsx (1)

48-49: 중복된 readOnly 조건부 클래스 분리 제안
password/기본 input 분기에서 동일한 props.readOnly && 'Text-gray cursor-default' 로직이 중복됩니다.
readOnly를 구조 분해로 추출하고, 공통 readOnlyClass 변수를 정의해 중복을 줄이면 가독성과 유지보수성이 향상됩니다.

Also applies to: 76-77

src/app/mypage/page.tsx (1)

35-37: 버튼 구현이 개선되었습니다.

type="button" 명시와 멀티라인 포맷팅이 좋습니다. 접근성을 위해 aria-label을 추가하는 것을 고려해보세요.

-              <button type="button" className="text-xm self-start">
+              <button type="button" className="text-xm self-start" aria-label="이전 페이지로 돌아가기">
                 돌아가기
               </button>
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)

10-11: 불필요한 Fragment를 제거하세요.

SVG 요소 하나만 반환하므로 Fragment가 필요하지 않습니다.

-    <>
       <svg
         width={size}
         height={size}
         viewBox="0 0 24 24"
         fill="none"
         {...props}
       >
         {/* ... */}
       </svg>
-    </>

Also applies to: 38-39

src/app/features/mypage/components/ProfileImageUpload.tsx (1)

28-42: 파일 검증 로직 추가를 고려해 주세요.

현재 구현은 기본적인 파일 처리를 잘 하고 있지만, 사용자 경험 향상을 위해 파일 검증을 추가하는 것을 제안합니다.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0]
  if (!file) return

  // 파일 크기 검증 (예: 5MB 제한)
  if (file.size > 5 * 1024 * 1024) {
    showError('파일 크기는 5MB 이하로 업로드해 주세요.')
    return
  }

  // 파일 타입 검증
  if (!file.type.startsWith('image/')) {
    showError('이미지 파일만 업로드할 수 있습니다.')
    return
  }

  const url = URL.createObjectURL(file)
  // ... 기존 로직
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0156e8 and 296bae0.

📒 Files selected for processing (19)
  • src/app/features/auth/api/authApi.ts (1 hunks)
  • src/app/features/mypage/api/mypageApi.ts (1 hunks)
  • src/app/features/mypage/api/mypageEndPoint.ts (1 hunks)
  • src/app/features/mypage/components/ProfileEditForm.tsx (1 hunks)
  • src/app/features/mypage/components/ProfileImageUpload.tsx (1 hunks)
  • src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (1 hunks)
  • src/app/features/mypage/hook/useUploadProfileImageMutation.ts (1 hunks)
  • src/app/features/mypage/hook/useUserQurey.ts (1 hunks)
  • src/app/features/mypage/schemas/mypageValidation.ts (1 hunks)
  • src/app/features/mypage/types/mypage.type.ts (1 hunks)
  • src/app/globals.css (1 hunks)
  • src/app/mypage/page.tsx (3 hunks)
  • src/app/shared/components/Input.tsx (2 hunks)
  • src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1 hunks)
  • src/app/shared/components/common/PlusIcon/PlusIcon.tsx (1 hunks)
  • src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1 hunks)
  • src/app/shared/components/common/sidebar/modal/CreateDashboardModal.tsx (2 hunks)
  • src/app/shared/hooks/useDashboard.ts (2 hunks)
  • src/app/shared/lib/axios.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/app/shared/hooks/useDashboard.ts (1)
src/app/shared/types/dashboard.ts (1)
  • DashboardListResponse (13-17)
src/app/features/auth/api/authApi.ts (2)
src/app/features/auth/types/auth.type.ts (3)
  • LoginRequest (3-6)
  • LoginResponse (8-11)
  • SignupRequest (13-17)
src/app/features/auth/api/authEndpoint.ts (1)
  • AUTH_ENDPOINT (1-4)
src/app/shared/lib/axios.ts (1)
src/app/features/auth/store/useAuthStore.ts (1)
  • useAuthStore (5-20)
src/app/features/mypage/hook/useUserQurey.ts (1)
src/app/features/mypage/api/mypageApi.ts (1)
  • loadUser (11-14)
src/app/features/mypage/hook/useUploadProfileImageMutation.ts (2)
src/app/features/mypage/types/mypage.type.ts (1)
  • UploadProfileImageResponse (6-8)
src/app/features/mypage/api/mypageApi.ts (1)
  • uploadProfileImage (26-36)
src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (2)
src/app/features/mypage/types/mypage.type.ts (1)
  • UpdateProfileRequest (1-4)
src/app/features/mypage/api/mypageApi.ts (1)
  • updateMyProfile (16-24)
src/app/features/mypage/api/mypageApi.ts (2)
src/app/features/mypage/api/mypageEndPoint.ts (1)
  • MYPAGE_ENDPOINT (1-4)
src/app/features/mypage/types/mypage.type.ts (2)
  • UpdateProfileRequest (1-4)
  • UploadProfileImageResponse (6-8)
src/app/features/mypage/components/ProfileImageUpload.tsx (3)
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1)
  • WhitePenIcon (5-20)
src/app/shared/components/common/PlusIcon/PlusIcon.tsx (1)
  • PlusIcon (6-38)
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)
  • CloseCircleIcon (5-40)
🔇 Additional comments (23)
src/app/globals.css (1)

81-83: 새로운 유틸리티 클래스 추가가 적절합니다

기존 패턴과 일치하며 라이트/다크 모드를 모두 고려한 색상 정의가 잘 되어 있습니다.

src/app/shared/lib/axios.ts (2)

5-7: axios 인스턴스 이름 변경이 적절합니다

authHttpClient라는 명시적인 이름으로 변경하여 인증이 필요한 HTTP 클라이언트임을 명확히 표현했습니다.


9-15: authHttpClient 사용처와 로그인/회원가입 훅이 어떤 axios 인스턴스를 쓰는지 확인이 필요합니다. 아래 스크립트를 실행해 주세요.

#!/bin/bash
echo "=== authHttpClient 사용 검색 ==="
rg -n "authHttpClient"

echo "=== shared/lib/axios import 검색 ==="
rg -n "@\/app\/shared\/lib\/axios"

echo "=== useSignupMutation.ts 내용 확인 ==="
sed -n '1,200p' src/app/features/auth/hooks/useSignupMutation.ts || echo "useSignupMutation.ts: 파일을 찾을 수 없습니다."

echo "=== useLoginMutation.ts 내용 확인 ==="
sed -n '1,200p' src/app/features/auth/hooks/useLoginMutation.ts || echo "useLoginMutation.ts: 파일을 찾을 수 없습니다."
src/app/shared/hooks/useDashboard.ts (2)

5-5: HTTP 클라이언트 import 업데이트가 적절합니다

authHttpClient로의 변경이 일관되게 적용되었습니다.


24-26: API 호출 업데이트가 올바릅니다

대시보드 데이터는 인증이 필요한 리소스이므로 authHttpClient 사용이 적절합니다.

src/app/shared/components/common/sidebar/modal/CreateDashboardModal.tsx (2)

7-7: HTTP 클라이언트 import 업데이트가 적절합니다

authHttpClient로의 변경이 일관되게 적용되었습니다.


49-52: API 호출 업데이트가 올바릅니다

대시보드 생성은 인증이 필요한 작업이므로 authHttpClient 사용이 적절합니다.

src/app/features/auth/api/authApi.ts (3)

1-1: HTTP 클라이언트 import 업데이트가 적절합니다

authHttpClient로의 변경이 일관되게 적용되었습니다.


8-11: 로그인 API 호출 업데이트 확인

authHttpClient 사용으로 변경되었습니다. 로그인 요청 시에는 토큰이 없으므로 인터셉터에서 토큰이 첨부되지 않을 것으로 예상되지만, 동작을 확인해 주세요.


16-19: 회원가입 API 호출 업데이트 확인

authHttpClient 사용으로 변경되었습니다. 회원가입 요청 시에도 토큰이 없으므로 문제없을 것으로 예상되지만, 로그인과 마찬가지로 동작을 확인해 주세요.

src/app/features/mypage/types/mypage.type.ts (1)

1-8: 타입 정의 적절
UpdateProfileRequestUploadProfileImageResponse 인터페이스가 명확하며, 프로필 이미지 null 허용도 일관성 있습니다.

src/app/features/mypage/api/mypageEndPoint.ts (1)

1-4: 환경 변수 존재 여부 확인 필요
process.env.NEXT_PUBLIC_TEAM_ID가 정의되지 않으면 URL이 /undefined/...가 됩니다.
빌드/런타임 시 환경 변수가 항상 설정되어 있는지 검증하거나 기본값을 고려하세요.

src/app/features/mypage/schemas/mypageValidation.ts (1)

1-8: 검증 스키마가 적절하게 구현되었습니다.

정규표현식이 한글과 영문을 올바르게 매칭하고, 길이 제한도 적절합니다. 에러 메시지도 명확합니다.

src/app/mypage/page.tsx (1)

6-6: 컴포넌트 분리가 잘 이루어졌습니다.

ProfileEditForm 컴포넌트로 분리하여 관심사를 잘 분리했습니다.

src/app/features/mypage/hook/useUploadProfileImageMutation.ts (1)

7-11: React Query 뮤테이션 훅이 올바르게 구현되었습니다.

타입 정의가 명확하고, 에러 처리도 적절합니다. AxiosError와 일반 Error를 모두 포함한 것이 좋습니다.

src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (1)

9-17: 깔끔한 뮤테이션 훅 구현입니다.

React Query의 useMutation을 적절히 래핑하여 타입 안전성을 보장하고 있습니다. 제네릭 타입 정의가 정확하며, 구현이 간결합니다.

src/app/features/mypage/api/mypageApi.ts (2)

11-24: API 함수들이 잘 구현되어 있습니다.

사용자 데이터 로드와 프로필 업데이트 함수가 일관된 패턴으로 구현되어 있고, 타입 안전성이 보장되고 있습니다.


26-36: 이미지 업로드 구현이 적절합니다.

FormData를 사용한 파일 업로드 구현이 올바르게 되어 있습니다. 다만 FormData의 키 이름('image')이 백엔드 API 스펙과 일치하는지 확인하시기 바랍니다.

백엔드 API 스펙에서 이미지 업로드 시 사용하는 필드명이 'image'인지 확인해 주세요.

src/app/features/mypage/components/ProfileEditForm.tsx (3)

22-53: 폼 초기화 로직이 잘 구현되어 있습니다.

비동기 사용자 데이터 로딩에 대응하여 useEffectreset을 활용한 폼 초기화가 적절합니다. SSR 도입을 고려한 주석도 좋습니다.


55-77: 폼 제출 로직이 체계적으로 구현되어 있습니다.

이미지 업로드와 프로필 업데이트를 순차적으로 처리하는 로직이 명확하고, 주석을 통해 각 단계가 잘 설명되어 있습니다.


92-133: 폼 UI 구성이 깔끔하게 되어 있습니다.

Controller를 사용한 커스텀 컴포넌트 연동과 반응형 레이아웃이 잘 구현되어 있습니다.

src/app/features/mypage/components/ProfileImageUpload.tsx (2)

20-42: 파일 처리 로직이 잘 구현되어 있습니다.

URL.createObjectURL을 사용한 미리보기 생성과 파일 전달 로직이 깔끔하게 구현되어 있습니다. 같은 파일 재선택을 위한 input 초기화도 좋은 접근입니다.


52-99: UI 구성이 사용자 친화적으로 잘 구현되어 있습니다.

접근성을 고려한 label/input 연결, hover 상태의 시각적 피드백, 그리고 삭제 버튼 배치가 매우 좋습니다. Image 컴포넌트 사용도 적절합니다.

Comment on lines 44 to 50
// 이미지 삭제 처리
const handleDelete = () => {
if (preview) URL.revokeObjectURL(preview) // 메모리 누수 방지
setPreview(null)
onChange(null) // RHF 상태 초기화
if (inputRef.current) inputRef.current.value = ''
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메모리 누수 방지 로직을 개선해 주세요.

메모리 누수 방지를 위한 URL.revokeObjectURL 호출이 좋습니다만, 더 안전한 구현을 위해 개선을 제안합니다.

  const handleDelete = () => {
-    if (preview) URL.revokeObjectURL(preview)
+    if (preview && preview.startsWith('blob:')) {
+      URL.revokeObjectURL(preview)
+    }
    setPreview(null)
    onChange(null)
    if (inputRef.current) inputRef.current.value = ''
  }

또한 컴포넌트 언마운트 시에도 메모리 정리를 위해 useEffect 클린업을 추가하는 것을 고려해 보세요.

📝 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 handleDelete = () => {
if (preview) URL.revokeObjectURL(preview) // 메모리 누수 방지
setPreview(null)
onChange(null) // RHF 상태 초기화
if (inputRef.current) inputRef.current.value = ''
}
// 이미지 삭제 처리
const handleDelete = () => {
if (preview && preview.startsWith('blob:')) {
URL.revokeObjectURL(preview)
}
setPreview(null)
onChange(null)
if (inputRef.current) inputRef.current.value = ''
}
🤖 Prompt for AI Agents
In src/app/features/mypage/components/ProfileImageUpload.tsx around lines 44 to
50, improve the memory leak prevention by ensuring URL.revokeObjectURL is called
safely only if preview exists and is a valid object URL. Additionally, add a
useEffect hook with a cleanup function that revokes the object URL when the
component unmounts to prevent memory leaks from lingering URLs.

@LeeCh0129 LeeCh0129 self-requested a review June 19, 2025 06:09
Copy link

@LeeCh0129 LeeCh0129 left a comment

Choose a reason for hiding this comment

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

프로필 수정 고생많으셨습니다~👍 남은 기한동안도 화이팅입니다 :)

}

const response = await api.post(`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards`, formData)
const response = await authHttpClient.post(

Choose a reason for hiding this comment

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

수정하시면서 해당 부분도 함께 수정해주셨군요. 감사합니다~

Copy link
Contributor

@dkslel1225 dkslel1225 left a comment

Choose a reason for hiding this comment

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

프로필 수정 컴포넌트 구현 수고하셨습니다!

@Insung-Jo Insung-Jo merged commit 8860060 into feature/mypage Jun 19, 2025
1 check passed
Copy link
Contributor

@yuj2n yuj2n left a comment

Choose a reason for hiding this comment

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

인성님 프로필 수정 컴포넌트 구현 수고많으셨습니다~
수정 내용 반영하여 작업하도록 하겠습니당!!

showSuccess('프로필 변경이 완료되었습니다.')
router.refresh()
} catch (error) {
if (isAxiosError(error)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

axiosError 처리 사용해주셨군용!

Comment on lines +29 to +52
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return

const url = URL.createObjectURL(file) // 로컬 미리보기 URL 생성
setPreview(url)
onChange(url) // RHF 상태 업데이트

// 서버 업로드용 파일을 상위로 전달
if (onFileChange) onFileChange(file)

// 같은 파일 다시 선택할 수 있도록 초기화
if (inputRef.current) inputRef.current.value = ''
}

// 이미지 삭제 처리
const handleDelete = () => {
if (preview && preview.startsWith('blob:')) {
URL.revokeObjectURL(preview) // 메모리 누수 방지
}
setPreview(null)
onChange(null) // RHF 상태 초기화
if (inputRef.current) inputRef.current.value = ''
}
Copy link
Contributor

Choose a reason for hiding this comment

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

function으로 함수명 통합하기로 하였던 것으로 기억하는데 const로 선언해주신 이유가 있으실까요?

import { useAuthStore } from '@/app/features/auth/store/useAuthStore'

const api = axios.create({
const authHttpClient = axios.create({
Copy link
Contributor

Choose a reason for hiding this comment

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

api가 authHttpClient로 수정되었군요! 참고해서 수정하도록 하겠습니다~

const isPulicPath = publicPaths.some((path) => config.url?.includes(path))

if (!isPulicPath && token) {
if (token) {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분이 토큰이 안 넘겨지고 있었던 이유였군용~!😮

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 기능 개발 🐛Fix 버그 수정 🎨Style UI, 스타일 관련 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants