Skip to content

[FEATURE] 에디터 이미지 추가#161

Merged
sunhwaaRj merged 14 commits intodevelopfrom
feature/#160-editor-image
Mar 29, 2026
Merged

[FEATURE] 에디터 이미지 추가#161
sunhwaaRj merged 14 commits intodevelopfrom
feature/#160-editor-image

Conversation

@sunhwaaRj
Copy link
Copy Markdown
Contributor

@sunhwaaRj sunhwaaRj commented Mar 29, 2026

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호


✅ Key Changes

  • 이미지 추가 및 수정 기능 완료
  • 팀피셜록 pc 에서 바텀시트 올라오는 현상 제거

📸 스크린샷 or 실행영상

image

🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 프로젝트 상세에 이미지 갤러리 추가 및 에디터/모집폼에서 최대 2장 이미지 업로드·미리보기·삭제 지원
    • 이미지 업로드 시 서버용 presigned URL을 통해 다중 업로드 처리
  • UI/UX Improvements

    • 카드 그리드: 태블릿 2열 · 데스크톱 3열로 조정
    • 하단 시트·오버레이의 표시 및 전환 방식 개선
    • 데스크톱의 일부 체크박스 표시 제거
  • Chores

    • 불필요한 콘솔 로그 제거 및 폼·스키마에 imageKeys 필드 추가

@sunhwaaRj sunhwaaRj self-assigned this Mar 29, 2026
@sunhwaaRj sunhwaaRj added the FEATURE 기능 구현 label Mar 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa98450a-1096-4a81-a800-3923f8e96a90

📥 Commits

Reviewing files that changed from the base of the PR and between 04b8a37 and e9b5814.

📒 Files selected for processing (3)
  • src/app/(main)/project/_components/ProjectList.tsx
  • src/components/recruit/useRecruitForm.ts
  • src/libs/api/image.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/(main)/project/_components/ProjectList.tsx
  • src/libs/api/image.ts
  • src/components/recruit/useRecruitForm.ts

Walkthrough

텍스트 에디터에 이미지 첨부 기능을 추가하고, 프로젝트 상세에 이미지를 렌더링하는 새로운 컴포넌트를 통합했습니다. 이미지 업로드 API 및 폼 스키마/타입이 확장되었고, 일부 UI 레이아웃과 바텀시트/오버레이 표시 로직이 조정되었습니다.

Changes

Cohort / File(s) Summary
프로젝트 이미지 표시
src/app/(main)/project/[id]/_components/ProjectImage.tsx, src/app/(main)/project/[id]/_components/ProjectInfo.tsx
ProjectImage 컴포넌트를 추가하고 ProjectInfo에 통합해 images 배열을 조건부로 그리드로 렌더링합니다.
프로젝트 목록 레이아웃
src/app/(main)/project/_components/ProjectList.tsx
태블릿/데스크톱 그리드 컬럼 구성을 tablet:grid-cols-2, desktop:grid-cols-3로 조정하고 빈 상태의 컬럼스팬을 반응형으로 수정했습니다.
에디터 이미지 업로드 UI
src/components/recruit/editor/PostImage.tsx, src/components/recruit/editor/TextContent.tsx
최대 2개 이미지 업로드/미리보기/삭제를 처리하는 PostImage 컴포넌트 추가 및 TextContent에 initialImages prop 전달 통합.
리크루트 폼 및 훅 변경
src/components/recruit/RecruitForm.tsx, src/components/recruit/useRecruitForm.ts
폼 반환형을 { formMethods, onSubmit, onError }로 변경, FormProvider로 래핑하도록 수정, imageKeys 필드 초기화/전달 로직 추가.
이미지 업로드 API
src/libs/api/image.ts
다중 presigned URL 획득 함수 getPostImagePresignedUrls 및 다중 파일 업로드 uploadPostImages 추가(업로드 순서 보장, 실패 시 예외).
스키마 및 타입 업데이트
src/libs/schemas/projectSchema.ts, src/types/project.ts
리크루트 폼 스키마에 imageKeys: string[] 추가. ProjectImage 타입 추가 및 Project 타입에 imageKeys?/images? 필드 추가.
디버그 로그 제거
src/hooks/queries/useRecruitingPosts.ts, src/libs/api/recruitingPosts.ts
개발용 console.log 문 제거(동작 유효성 변동 없음).
UI 컴포넌트 조정
src/app/(main)/teampsylog/_components/BottomComment.tsx, src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx
바텀시트/오버레이의 가시성 제어를 className 기반으로 변경하고(visibility 스타일 삭제), 데스크톱의 체크박스 라벨 블록을 주석 제거하여 비표시 처리.

Sequence Diagram

sequenceDiagram
    actor User
    participant PostImage as PostImage(Component)
    participant FormContext as FormProvider/useFormContext
    participant API as Image API
    participant S3 as S3 Storage

    User->>PostImage: 파일 선택 (input[file])
    PostImage->>API: uploadPostImages(files)
    API->>API: getPostImagePresignedUrls(fileNames[])
    API-->>PostImage: [{ preSignedUrl, objectKey }, ...]
    par 병렬 업로드
        PostImage->>S3: PUT file[0] -> preSignedUrl (x-amz-acl: public-read)
        PostImage->>S3: PUT file[1] -> preSignedUrl (x-amz-acl: public-read)
        S3-->>PostImage: 200 OK
    end
    PostImage->>PostImage: preview URL 생성 (createObjectURL)
    PostImage->>FormContext: setValue('imageKeys', [objectKey,...])
    FormContext-->>PostImage: 값 갱신 확인
    User->>PostImage: 이미지 삭제 클릭
    PostImage->>PostImage: revokeObjectURL, state 업데이트
    PostImage->>FormContext: setValue('imageKeys', updated[])
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #159: 바텀시트/오버레이 렌더링/가시성 처리 관련 변경(동일 파일 BottomComment.tsx 수정).
  • PR #30: 프로젝트 상세 페이지 컴포넌트 구조(예: ProjectInfo/ProjectTitle) 변경과 연관됨 — ProjectImage 통합과 연계 가능성 높음.
  • PR #37: 리크루트 폼/스키마와 이미지 필드(이미지 키/업로드) 관련 변경사항과 직접적으로 관련됨.

Suggested reviewers

  • woneeeee
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive 대부분의 변경사항이 #160 이슈의 범위 내에 있으나, ProjectList의 그리드 레이아웃 조정과 KeywordGuideOverlay의 데스크톱 체크박스 제거는 명시된 요구사항과 직접적인 관련이 없어 보입니다. ProjectList 레이아웃 변경과 KeywordGuideOverlay 체크박스 제거가 #160 이슈 범위에 포함되는지 명확히 하거나, 필요시 별도 이슈로 분리하기를 권장합니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항인 '에디터 이미지 추가' 기능을 명확하게 요약하고 있으며, 변경사항의 핵심을 정확히 나타냅니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며, PR 유형(새로운 기능, 버그 수정)을 명시하고, 관련 이슈번호(#160)를 포함하고, 주요 변경사항을 나열하고, 스크린샷도 첨부되어 있습니다.
Linked Issues check ✅ Passed PR이 #160 이슈의 주요 요구사항인 '에디터 이미지 첨부 기능 추가'와 'PC 버전 바텀시트 제거'를 모두 구현했습니다. PostImage 컴포넌트 추가, 이미지 업로드 API 구현, BottomComment에서 바텀시트 가시성 제어 변경이 확인됩니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#160-editor-image

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx (1)

66-81: ⚠️ Potential issue | 🟡 Minor

데스크톱에서 "다시 보지 않기" 기능이 비활성화되어 매번 오버레이가 재노출됩니다

라인 66-81의 체크박스 코드가 주석 처리되어 데스크톱 사용자는 handleClick을 실행할 수 없습니다. 현재 데스크톱의 "튜토리얼 마치기" 버튼은 onClose만 호출하므로 localStorage.setItem('hideKeywordGuide', 'true')이 실행되지 않고, 오버레이가 매번 다시 노출됩니다. 모바일은 체크박스가 활성화되어 있어 정상 작동합니다.

주석 처리된 체크박스를 활성화하거나, 버튼 클릭 시 localStorage 저장 로직을 추가하여 데스크톱에서도 "다시 보지 않기" 기능을 동작하도록 수정해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(main)/teampsylog/_components/KeywordGuideOverlay.tsx around lines
66 - 81, The desktop overlay never persists "do not show again" because the
checkbox block in KeywordGuideOverlay is commented out so handleClick is never
reachable; either restore that checkbox UI (the commented label/input block) so
users can toggle checked and trigger handleClick, or ensure the tutorial finish
flow also writes localStorage.setItem('hideKeywordGuide','true') before calling
onClose; update the component's finish button handler (the function that calls
onClose) or onClose itself to call
localStorage.setItem('hideKeywordGuide','true') when checked/when user
explicitly chooses to hide, and keep handleClick, checked, and hideKeywordGuide
key names consistent.
🧹 Nitpick comments (1)
src/app/(main)/project/[id]/_components/ProjectImage.tsx (1)

1-4: 이 컴포넌트는 images만 받도록 좁히는 편이 좋겠습니다.

Line 4처럼 ResponseProject 전체를 props로 받으면 실제로 쓰지 않는 필드까지 public API가 됩니다. images만 받도록 좁혀 두면 상세 응답 타입 변화가 여기까지 전파되지 않습니다.

수정 예시
-import { ResponseProject } from '@/types/project';
+import { ProjectImage as ProjectImageItem } from '@/types/project';
 import Image from 'next/image';

-const ProjectImage = ({ images }: ResponseProject) => {
+type ProjectImageProps = {
+  images?: ProjectImageItem[];
+};
+
+const ProjectImage = ({ images }: ProjectImageProps) => {
-        <ProjectImage {...data} />
+        <ProjectImage images={data.images} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(main)/project/[id]/_components/ProjectImage.tsx around lines 1 - 4,
The ProjectImage component currently types its props as ResponseProject which
exposes the whole response; narrow the prop type to only images by updating the
ProjectImage signature to accept a single prop named images typed as
ResponseProject['images'] (or the concrete images type) and remove the unused
ResponseProject usage in props, ensuring references inside ProjectImage still
use the images variable; this prevents unrelated ResponseProject shape changes
from affecting this component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(main)/project/_components/ProjectList.tsx:
- Line 110: The empty-state span still uses a hardcoded col-span-3 which breaks
tablet layout; locate the empty-state element in ProjectList (the span/div that
currently has "col-span-3") and make its grid span responsive to match the
container by replacing or augmenting the class with tablet:col-span-2 (e.g.
change "col-span-3" to "desktop:col-span-3 tablet:col-span-2" or add
"tablet:col-span-2" alongside the existing class) so the empty message occupies
two columns on tablet and three on desktop.

In `@src/app/`(main)/teampsylog/_components/BottomComment.tsx:
- Around line 30-35: The close animation is cut off because BottomComment.tsx
toggles display with 'block/hidden' (and a similar 'flex/hidden' later) causing
the element to be removed immediately when isOpen flips; instead keep the
element mounted and control visibility via opacity/transform and pointer-events.
Update BottomComment.tsx to use a stable display (no 'hidden' toggle) and
replace the conditional in className with a pointer-events class (e.g.,
pointer-events-none when shouldShow is false); update the inline style to
animate opacity and transform (slide) based on isDragging, dragCurrentY, isOpen
and isClosing (use the existing calculations used for opacity, extend to
transform translateY) and ensure transition is applied when not dragging. Apply
the same change to the other block (lines ~39-50) that toggles 'flex/hidden'.
Verify interactions tied to handleClose only fire when pointer-events are
enabled. Also ensure coordination with useBottomSheetDrag.tsx's shouldShow and
LogNote.tsx's isOpen paths so the element remains mounted during the closing
animation.

In `@src/components/recruit/RecruitForm.tsx`:
- Around line 86-95: The deadline validation logic in ProjectDate is inverted:
the UI currently shows the error when deadline > startDate and otherwise only
shows errors.deadline, letting invalid dates pass and lacking resolver-level
checks; update the validation by moving this rule into the form schema (use
Zod.refine) to assert deadline > startDate (or use a shared isValid-like
predicate), remove the inverted check around isEndDateInvalid in the RecruitForm
ProjectDate props (deadline, startDate, control, errors), and ensure the
resolver returns errors.deadline when the refine fails so the component displays
the validation message consistently from the schema.

In `@src/components/recruit/useRecruitForm.ts`:
- Around line 67-68: The form initializes imageKeys only from
initialData.images, losing values when the server returns initialData.imageKeys;
update the initialization in useRecruitForm so imageKeys uses
initialData.imageKeys when present (falling back to mapping initialData.images
to img.objectKey or to an empty array), i.e. change the imageKeys expression to
prefer initialData?.imageKeys and otherwise derive from initialData?.images to
preserve existing attachments when opening the editor.

In `@src/libs/api/image.ts`:
- Around line 80-87: The code currently assumes presignedList and files are in
the same order which can break mapping; first validate presignedList.length ===
files.length and throw a clear error if not, then iterate over the files array
(not presignedList) and for each file locate the corresponding presigned entry
(e.g., find entry in presignedList where objectKey or preSignedUrl maps to
file.name, e.g., objectKey.endsWith(file.name) or other project-specific match),
call uploadToS3 with that entry.preSignedUrl and the file, and push the matched
entry.objectKey; if no match is found for a file, throw an error so uploads can
fail-fast. Ensure you update the code paths using presignedList,
getPostImagePresignedUrls, uploadToS3, files, and objectKey accordingly.
- Line 11: The uploadToS3 function currently hardcodes the 'x-amz-acl':
'public-read' header causing all uploads (including profile images) to be
public; change uploadToS3 to accept an optional acl parameter (or options
object) instead of a fixed ACL, remove the hardcoded 'x-amz-acl' entry and only
set that header when acl is provided, then update callers (e.g.,
useUploadProfileImage.ts) to pass a suitable ACL (e.g., 'private' for profile
images, 'public-read' for public post images) so access control is handled per
upload.

---

Outside diff comments:
In `@src/app/`(main)/teampsylog/_components/KeywordGuideOverlay.tsx:
- Around line 66-81: The desktop overlay never persists "do not show again"
because the checkbox block in KeywordGuideOverlay is commented out so
handleClick is never reachable; either restore that checkbox UI (the commented
label/input block) so users can toggle checked and trigger handleClick, or
ensure the tutorial finish flow also writes
localStorage.setItem('hideKeywordGuide','true') before calling onClose; update
the component's finish button handler (the function that calls onClose) or
onClose itself to call localStorage.setItem('hideKeywordGuide','true') when
checked/when user explicitly chooses to hide, and keep handleClick, checked, and
hideKeywordGuide key names consistent.

---

Nitpick comments:
In `@src/app/`(main)/project/[id]/_components/ProjectImage.tsx:
- Around line 1-4: The ProjectImage component currently types its props as
ResponseProject which exposes the whole response; narrow the prop type to only
images by updating the ProjectImage signature to accept a single prop named
images typed as ResponseProject['images'] (or the concrete images type) and
remove the unused ResponseProject usage in props, ensuring references inside
ProjectImage still use the images variable; this prevents unrelated
ResponseProject shape changes from affecting this component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1507cc76-3ed2-4315-9292-c2677f4186df

📥 Commits

Reviewing files that changed from the base of the PR and between d16dd5f and 04b8a37.

📒 Files selected for processing (14)
  • src/app/(main)/project/[id]/_components/ProjectImage.tsx
  • src/app/(main)/project/[id]/_components/ProjectInfo.tsx
  • src/app/(main)/project/_components/ProjectList.tsx
  • src/app/(main)/teampsylog/_components/BottomComment.tsx
  • src/app/(main)/teampsylog/_components/KeywordGuideOverlay.tsx
  • src/components/recruit/RecruitForm.tsx
  • src/components/recruit/editor/PostImage.tsx
  • src/components/recruit/editor/TextContent.tsx
  • src/components/recruit/useRecruitForm.ts
  • src/hooks/queries/useRecruitingPosts.ts
  • src/libs/api/image.ts
  • src/libs/api/recruitingPosts.ts
  • src/libs/schemas/projectSchema.ts
  • src/types/project.ts
💤 Files with no reviewable changes (2)
  • src/hooks/queries/useRecruitingPosts.ts
  • src/libs/api/recruitingPosts.ts

Comment thread src/app/(main)/project/_components/ProjectList.tsx
Comment thread src/app/(main)/teampsylog/_components/BottomComment.tsx
Comment thread src/components/recruit/RecruitForm.tsx
Comment thread src/components/recruit/useRecruitForm.ts
Comment thread src/libs/api/image.ts Outdated
Comment thread src/libs/api/image.ts
@sunhwaaRj sunhwaaRj merged commit 300074e into develop Mar 29, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 29, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEATURE 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 에디터 이미지 추가

1 participant