Skip to content

feat: 커뮤니티 작성/수정 이미지 드래그 업로드 지원#477

Merged
manNomi merged 4 commits intomainfrom
codex/community-dnd-image-upload
Mar 14, 2026
Merged

feat: 커뮤니티 작성/수정 이미지 드래그 업로드 지원#477
manNomi merged 4 commits intomainfrom
codex/community-dnd-image-upload

Conversation

@manNomi
Copy link
Contributor

@manNomi manNomi commented Mar 14, 2026

변경 사항

  • 커뮤니티 글 작성 페이지에서 이미지 드래그 앤 드롭 업로드 지원
  • 커뮤니티 글 수정 페이지에서 이미지 드래그 앤 드롭 업로드 지원
  • 드롭 시 이미지 파일만 허용하고, 비이미지 파일은 에러 토스트 표시
  • 드래그 중 전체 영역 오버레이 안내("이미지를 놓아 업로드하세요") 표시

구현 상세

  • PostForm.tsx
    • drag enter/over/leave/drop 핸들러 추가
    • selectImageFile 유틸로 파일 검증 로직 통합
  • PostModifyForm.tsx
    • 동일한 drag/drop 및 파일 검증 로직 추가

검증

  • pnpm --filter @solid-connect/web run ci:check 통과
  • pre-push 훅의 ci:check + build 통과

@vercel
Copy link

vercel bot commented Mar 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connection-web Ready Ready Preview, Comment Mar 14, 2026 11:42am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connect-web-admin Skipped Skipped Mar 14, 2026 11:42am

@github-actions github-actions bot added the web label Mar 14, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

Warning

Rate limit exceeded

@manNomi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 352c0c63-5f27-4c19-912a-46de9eafdd2c

📥 Commits

Reviewing files that changed from the base of the PR and between 380f5d8 and eb65153.

📒 Files selected for processing (6)
  • apps/web/src/apis/community/api.ts
  • apps/web/src/app/community/[boardCode]/[postId]/Content.tsx
  • apps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx
  • apps/web/src/app/community/[boardCode]/create/PostForm.tsx
  • apps/web/src/app/community/_hooks/useCommunityImageUpload.ts
  • apps/web/src/constants/community.ts

Walkthrough

두 파일에 동일한 패턴으로 드래그 앤 드롭 이미지 업로드 기능이 추가되었습니다. 각 파일에서는 DragEvent 처리와 깊이 추적 참조(dragDepthRef)를 통해 드래그 상태를 관리하고, 드래그 오버레이 UI를 표시합니다. 새로운 selectImageFile 헬퍼 함수로 이미지 MIME 타입을 검증하며, 유효하지 않은 파일 선택 시 토스트로 사용자에게 피드백합니다.

Estimated code review effort

PostModifyForm.tsx
🎯 2 (Simple) | ⏱️ ~10 minutes

PostForm.tsx
🎯 2 (Simple) | ⏱️ ~10 minutes

두 파일 모두 동일한 구조의 변경이 반복되므로, 첫 파일을 검토한 후 두 번째 파일은 일관성 확인만 필요합니다.

Suggested reviewers

  • wibaek
  • enunsnv
  • khwww
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 핵심 변경사항인 커뮤니티 이미지 드래그 업로드 지원을 명확하고 간결하게 요약하고 있습니다.
Description check ✅ Passed PR 설명은 변경 사항, 구현 상세, 검증 내용을 포함하여 대부분 완전합니다. 다만 템플릿의 '관련 이슈' 섹션이 누락되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/community-dnd-image-upload
📝 Coding Plan
  • Generate coding plan for human review comments

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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 380f5d82a4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +84 to +86
const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

Choose a reason for hiding this comment

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

P2 Badge Restrict drag handlers to file drags only

The new drag handlers unconditionally call preventDefault/stopPropagation, so any drag operation inside this form (including dragging selected text or dropping text/links into the post textarea) is now intercepted as an image-drop flow and native drop behavior is blocked. This is a user-facing regression introduced with the image DnD support; gate the handlers to dataTransfer.types containing files before suppressing default behavior (the same pattern is also present in the modify form).

Useful? React with 👍 / 👎.

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/community/`[boardCode]/[postId]/modify/PostModifyForm.tsx:
- Around line 101-137: Add a small helper (e.g., isFileDrag(event:
DragEvent<HTMLDivElement>): boolean) that returns true only when
event.dataTransfer?.types includes "Files" (and for drop, that
event.dataTransfer?.files?.length > 0); then update handleImageDragEnter,
handleImageDragOver, handleImageDragLeave, and handleImageDrop to call this
helper first and early-return if false so preventDefault()/stopPropagation() and
the overlay logic only run for actual file drags (for handleImageDrop also read
the files after the helper confirms a file drag).

In `@apps/web/src/app/community/`[boardCode]/create/PostForm.tsx:
- Around line 84-120: Add a helper isFileDrag that checks
event.dataTransfer?.types includes "Files" and update the drag handlers
(handleImageDragEnter, handleImageDragOver, handleImageDragLeave,
handleImageDrop) to early-return when !isFileDrag(event) so you only call
event.preventDefault() and event.stopPropagation() for actual file drags; keep
existing dragDepthRef and setIsDraggingImage logic inside the file-drag branch
and only read files from event.dataTransfer in handleImageDrop when isFileDrag
is true, then call selectImageFile as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d06af50d-c80f-4ce4-8240-13cebd14c3e8

📥 Commits

Reviewing files that changed from the base of the PR and between bf041a2 and 380f5d8.

📒 Files selected for processing (2)
  • apps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx
  • apps/web/src/app/community/[boardCode]/create/PostForm.tsx

Comment on lines 101 to 137
const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current += 1;
setIsDraggingImage(true);
};

const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = "copy";
};

const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current -= 1;
if (dragDepthRef.current <= 0) {
dragDepthRef.current = 0;
setIsDraggingImage(false);
}
};

const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current = 0;
setIsDraggingImage(false);

const file = event.dataTransfer.files?.[0] ?? null;
if (!file) return;

selectImageFile(file);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "PostModifyForm.tsx" | head -20

Repository: solid-connection/solid-connect-web

Length of output: 153


🏁 Script executed:

fd "PostModifyForm.tsx" -x cat -n {} | head -200

Repository: solid-connection/solid-connect-web

Length of output: 6738


🏁 Script executed:

rg "handleImageDrag" -A 3 -B 1

Repository: solid-connection/solid-connect-web

Length of output: 4340


🏁 Script executed:

sed -n '190,210p' ./apps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx

Repository: solid-connection/solid-connect-web

Length of output: 1049


🏁 Script executed:

rg "textarea" ./apps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx -B 2 -A 2

Repository: solid-connection/solid-connect-web

Length of output: 1683


🏁 Script executed:

rg "dataTransfer\.types" . -t tsx -t ts

Repository: solid-connection/solid-connect-web

Length of output: 105


파일 드래그만 감지하도록 조건을 추가해주세요.

현재 구현에서는 모든 드래그 이벤트에 preventDefault()를 호출하고 있어서, 제목과 내용 textarea의 텍스트 드롭 동작도 막히고 있습니다. 결과적으로 이미지 업로드와 무관한 드래그 액션에도 오버레이가 표시되고 사용자 편의성이 떨어집니다.

다음과 같이 파일 드래그 여부를 먼저 확인한 후에만 기본 동작을 중단하도록 수정해주세요:

  1. 파일 드래그 감지 헬퍼 함수 추가

    • event.dataTransfer.types에 "Files"가 포함되어 있는지 확인
    • 파일 드래그가 아니면 조기 반환
  2. 각 드래그 핸들러에 조건문 적용

    • handleImageDragEnter, handleImageDragOver, handleImageDragLeave, handleImageDrop 모두에 파일 확인 로직 추가
    • preventDefault() 호출 전에 파일 드래그 여부 검사
🔧 수정 예시
+const isFileDrag = (event: DragEvent<HTMLDivElement>) =>
+  Array.from(event.dataTransfer.types).includes("Files");
+
 const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current += 1;
   setIsDraggingImage(true);
 };
 
 const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
   event.dataTransfer.dropEffect = "copy";
 };
 
 const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current -= 1;
   if (dragDepthRef.current <= 0) {
@@
 };
 
 const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current = 0;
   setIsDraggingImage(false);
📝 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 handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
dragDepthRef.current += 1;
setIsDraggingImage(true);
};
const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = "copy";
};
const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
dragDepthRef.current -= 1;
if (dragDepthRef.current <= 0) {
dragDepthRef.current = 0;
setIsDraggingImage(false);
}
};
const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
dragDepthRef.current = 0;
setIsDraggingImage(false);
const file = event.dataTransfer.files?.[0] ?? null;
if (!file) return;
selectImageFile(file);
};
const isFileDrag = (event: DragEvent<HTMLDivElement>) =>
Array.from(event.dataTransfer.types).includes("Files");
const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
if (!isFileDrag(event)) return;
event.preventDefault();
event.stopPropagation();
dragDepthRef.current += 1;
setIsDraggingImage(true);
};
const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
if (!isFileDrag(event)) return;
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = "copy";
};
const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
if (!isFileDrag(event)) return;
event.preventDefault();
event.stopPropagation();
dragDepthRef.current -= 1;
if (dragDepthRef.current <= 0) {
dragDepthRef.current = 0;
setIsDraggingImage(false);
}
};
const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
if (!isFileDrag(event)) return;
event.preventDefault();
event.stopPropagation();
dragDepthRef.current = 0;
setIsDraggingImage(false);
const file = event.dataTransfer.files?.[0] ?? null;
if (!file) return;
selectImageFile(file);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/community/`[boardCode]/[postId]/modify/PostModifyForm.tsx
around lines 101 - 137, Add a small helper (e.g., isFileDrag(event:
DragEvent<HTMLDivElement>): boolean) that returns true only when
event.dataTransfer?.types includes "Files" (and for drop, that
event.dataTransfer?.files?.length > 0); then update handleImageDragEnter,
handleImageDragOver, handleImageDragLeave, and handleImageDrop to call this
helper first and early-return if false so preventDefault()/stopPropagation() and
the overlay logic only run for actual file drags (for handleImageDrop also read
the files after the helper confirms a file drag).

Comment on lines 84 to 120
const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current += 1;
setIsDraggingImage(true);
};

const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = "copy";
};

const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current -= 1;
if (dragDepthRef.current <= 0) {
dragDepthRef.current = 0;
setIsDraggingImage(false);
}
};

const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();

dragDepthRef.current = 0;
setIsDraggingImage(false);

const file = event.dataTransfer.files?.[0] ?? null;
if (!file) return;

selectImageFile(file);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and get its complete context
fd "PostForm.tsx" --type f

Repository: solid-connection/solid-connect-web

Length of output: 136


🏁 Script executed:

# Once we find the file, let's read the relevant section
cat -n apps/web/src/app/community/[boardCode]/create/PostForm.tsx | sed -n '80,125p'

Repository: solid-connection/solid-connect-web

Length of output: 1569


🏁 Script executed:

# Let's also check the full file structure to understand the context better
wc -l apps/web/src/app/community/[boardCode]/create/PostForm.tsx

Repository: solid-connection/solid-connect-web

Length of output: 140


🏁 Script executed:

# Search for drag event handlers in the file
rg "handle.*Drag|dragDepthRef" apps/web/src/app/community/[boardCode]/create/PostForm.tsx -A 3 -B 1

Repository: solid-connection/solid-connect-web

Length of output: 1361


파일 드래그가 아닐 때는 기본 드롭 동작을 살려 주세요.

현재 모든 drag 이벤트에서 preventDefault()를 호출하고 있어서, 폼 안의 textarea로 텍스트를 끌어다 놓는 기본 동작까지 차단됩니다. 그 결과 이미지와 무관한 텍스트/링크 드래그에도 오버레이가 표시되고, 텍스트 편집 UX가 회귀합니다.

다음과 같이 수정해주세요:

  1. 드래그 타입 검사 함수 추가

    • event.dataTransfer.types에서 "Files"가 포함되어 있는지 확인하는 isFileDrag() 헬퍼 함수를 추가합니다.
  2. 각 드래그 핸들러에 파일 검증 로직 추가

    • handleImageDragEnter, handleImageDragOver, handleImageDragLeave, handleImageDrop 각 함수에서 파일 드래그가 아니면 조기에 반환합니다.
    • preventDefault()stopPropagation()은 파일 드래그일 때만 호출되도록 변경합니다.

이렇게 하면 이미지 드롭은 정상적으로 처리되면서도, 텍스트나 링크 드래그는 브라우저의 기본 동작을 유지할 수 있습니다.

🔧 최소 수정 예시
+const isFileDrag = (event: DragEvent<HTMLDivElement>) =>
+  Array.from(event.dataTransfer.types).includes("Files");
+
 const handleImageDragEnter = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current += 1;
   setIsDraggingImage(true);
 };
 
 const handleImageDragOver = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
   event.dataTransfer.dropEffect = "copy";
 };
 
 const handleImageDragLeave = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current -= 1;
   if (dragDepthRef.current <= 0) {
@@
 };
 
 const handleImageDrop = (event: DragEvent<HTMLDivElement>) => {
+  if (!isFileDrag(event)) return;
   event.preventDefault();
   event.stopPropagation();
 
   dragDepthRef.current = 0;
   setIsDraggingImage(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/community/`[boardCode]/create/PostForm.tsx around lines 84 -
120, Add a helper isFileDrag that checks event.dataTransfer?.types includes
"Files" and update the drag handlers (handleImageDragEnter, handleImageDragOver,
handleImageDragLeave, handleImageDrop) to early-return when !isFileDrag(event)
so you only call event.preventDefault() and event.stopPropagation() for actual
file drags; keep existing dragDepthRef and setIsDraggingImage logic inside the
file-drag branch and only read files from event.dataTransfer in handleImageDrop
when isFileDrag is true, then call selectImageFile as before.

@vercel vercel bot temporarily deployed to Preview – solid-connect-web-admin March 14, 2026 11:39 Inactive
@vercel vercel bot temporarily deployed to Preview – solid-connect-web-admin March 14, 2026 11:40 Inactive
@manNomi manNomi merged commit e81a7c3 into main Mar 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant