Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions apps/web/src/app/community/[boardCode]/PostCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,23 @@ export const PostCard = ({ post }: { post: ListPost }) => (
</div>
</div>

<div className="ml-4 mt-3 h-20 w-20 select-none">
{post.postThumbnailUrl ? (
<Image
className="rounded-md object-cover"
src={convertUploadedImageUrl(post.postThumbnailUrl)}
height={82}
width={82}
alt="게시글 사진"
fallbackSrc="/images/article-thumb.png"
/>
) : (
<div className="bg-gray-c-50 flex h-20 w-20 items-center justify-center rounded border border-k-100">
<IconSolidConnentionLogo />
</div>
)}
<div className="ml-4 mt-3 h-20 w-20 shrink-0 select-none">
<div className="bg-gray-c-50 relative h-full w-full overflow-hidden rounded border border-k-100">
{post.postThumbnailUrl ? (
<Image
className="object-cover"
src={convertUploadedImageUrl(post.postThumbnailUrl)}
fill
sizes="80px"
alt="게시글 사진"
fallbackSrc="/images/article-thumb.png"
/>
) : (
<div className="flex h-full w-full items-center justify-center">
<IconSolidConnentionLogo />
</div>
)}
</div>
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { type ChangeEvent, useEffect, useRef, useState } from "react";

import { useUpdatePost } from "@/apis/community";
import { toast } from "@/lib/zustand/useToastStore";
Expand Down Expand Up @@ -30,6 +30,8 @@ const PostModifyForm = ({
const textareaRef = useRef<HTMLTextAreaElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const imageUploadRef = useRef<HTMLInputElement>(null);
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [imagePreviewUrl, setImagePreviewUrl] = useState<string | null>(null);
const router = useRouter();

const updatePostMutation = useUpdatePost();
Expand Down Expand Up @@ -61,6 +63,32 @@ const PostModifyForm = ({
return () => {};
}, []);

useEffect(() => {
if (!selectedImage) {
setImagePreviewUrl(null);
return;
}

const objectUrl = URL.createObjectURL(selectedImage);
setImagePreviewUrl(objectUrl);

return () => {
URL.revokeObjectURL(objectUrl);
};
}, [selectedImage]);

const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0] ?? null;
setSelectedImage(file);
};

const removeSelectedImage = () => {
setSelectedImage(null);
if (imageUploadRef.current) {
imageUploadRef.current.value = "";
}
};

const submitPost = async () => {
if (!title.trim()) {
toast.error("제목을 입력해주세요.");
Expand All @@ -82,7 +110,7 @@ const PostModifyForm = ({
title,
content,
},
file: imageUploadRef.current?.files ? Array.from(imageUploadRef.current.files) : [],
file: selectedImage ? [selectedImage] : [],
},
},
{
Expand Down Expand Up @@ -137,7 +165,7 @@ const PostModifyForm = ({
>
<IconImage />
</button>
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" multiple />
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" onChange={handleImageChange} />
</div>
</div>
<div>
Expand All @@ -148,6 +176,22 @@ const PostModifyForm = ({
onChange={(e) => setContent(e.target.value)}
/>
</div>
{imagePreviewUrl ? (
<div className="px-5 pb-2">
<p className="mb-2 text-gray-250/87 typo-regular-4">첨부 이미지</p>
<div className="relative h-24 w-24 overflow-hidden rounded-md border border-gray-c-100">
<img src={imagePreviewUrl} alt="업로드 이미지 미리보기" className="h-full w-full object-cover" />
<button
type="button"
className="absolute right-1 top-1 rounded bg-black/60 px-1 py-0.5 text-xs text-white"
onClick={removeSelectedImage}
aria-label="이미지 제거"
>
삭제
</button>
</div>
</div>
) : null}
<div className="px-5 pt-2.5">
<p className="text-gray-250/87 typo-sb-9">{noticeTitle}</p>
<p className="mt-2 whitespace-pre-line text-gray-100 typo-regular-4">{noticeContent}</p>
Expand Down
50 changes: 47 additions & 3 deletions apps/web/src/app/community/[boardCode]/create/PostForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { type ChangeEvent, useEffect, useRef, useState } from "react";
import { useCreatePost } from "@/apis/community";
import TopDetailNavigation from "@/components/layout/TopDetailNavigation";
import { IconImage, IconPostCheckboxFilled, IconPostCheckboxOutlined } from "@/public/svgs";
Expand All @@ -17,6 +17,8 @@ const PostForm = ({ boardCode }: PostFormProps) => {
const imageUploadRef = useRef<HTMLInputElement>(null);
const router = useRouter();
const [isQuestion, setIsQuestion] = useState<boolean>(false);
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [imagePreviewUrl, setImagePreviewUrl] = useState<string | null>(null);

const createPostMutation = useCreatePost();

Expand All @@ -43,6 +45,32 @@ const PostForm = ({ boardCode }: PostFormProps) => {
return () => {};
}, []);

useEffect(() => {
if (!selectedImage) {
setImagePreviewUrl(null);
return;
}

const objectUrl = URL.createObjectURL(selectedImage);
setImagePreviewUrl(objectUrl);

return () => {
URL.revokeObjectURL(objectUrl);
};
}, [selectedImage]);

const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0] ?? null;
setSelectedImage(file);
Comment on lines +63 to +64

Choose a reason for hiding this comment

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

P1 Badge Preserve multi-image uploads in community post forms

This change silently removes multi-image upload support by taking only files?.[0] and no longer using a multi-file input, so users who select multiple images now upload just one without warning; the same regression appears in both create and modify forms (apps/web/src/app/community/[boardCode]/create/PostForm.tsx and .../[postId]/modify/PostModifyForm.tsx). That is a functional downgrade because post detail rendering still supports multiple images (PostImage branches on images.length and maps all images in apps/web/src/app/community/[boardCode]/[postId]/Content.tsx).

Useful? React with 👍 / 👎.

};

const removeSelectedImage = () => {
setSelectedImage(null);
if (imageUploadRef.current) {
imageUploadRef.current.value = "";
}
};

const submitPost = async () => {
createPostMutation.mutate(
{
Expand All @@ -53,7 +81,7 @@ const PostForm = ({ boardCode }: PostFormProps) => {
content,
isQuestion,
},
file: imageUploadRef.current?.files ? Array.from(imageUploadRef.current.files) : [],
file: selectedImage ? [selectedImage] : [],
},
{
onSuccess: (data) => {
Expand Down Expand Up @@ -116,7 +144,7 @@ const PostForm = ({ boardCode }: PostFormProps) => {
>
<IconImage />
</button>
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" multiple />
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" onChange={handleImageChange} />
</div>
</div>
<div>
Expand All @@ -127,6 +155,22 @@ const PostForm = ({ boardCode }: PostFormProps) => {
onChange={(e) => setContent(e.target.value)}
/>
</div>
{imagePreviewUrl ? (
<div className="px-5 pb-2">
<p className="mb-2 text-gray-250/87 typo-regular-4">첨부 이미지</p>
<div className="relative h-24 w-24 overflow-hidden rounded-md border border-gray-c-100">
<img src={imagePreviewUrl} alt="업로드 이미지 미리보기" className="h-full w-full object-cover" />
<button
type="button"
className="absolute right-1 top-1 rounded bg-black/60 px-1 py-0.5 text-xs text-white"
onClick={removeSelectedImage}
aria-label="이미지 제거"
>
삭제
</button>
</div>
</div>
) : null}
<div className="px-5 pt-2.5">
<p className="text-gray-250/87 typo-sb-9">{noticeTitle}</p>
<p className="mt-2 whitespace-pre-line text-gray-100 typo-regular-4">{noticeContent}</p>
Expand Down
Loading