Skip to content

Commit e257171

Browse files
authored
fix(web): improve community image preview and thumbnail consistency (#475)
1 parent 527556c commit e257171

3 files changed

Lines changed: 111 additions & 21 deletions

File tree

apps/web/src/app/community/[boardCode]/PostCards.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,23 @@ export const PostCard = ({ post }: { post: ListPost }) => (
9494
</div>
9595
</div>
9696

97-
<div className="ml-4 mt-3 h-20 w-20 select-none">
98-
{post.postThumbnailUrl ? (
99-
<Image
100-
className="rounded-md object-cover"
101-
src={convertUploadedImageUrl(post.postThumbnailUrl)}
102-
height={82}
103-
width={82}
104-
alt="게시글 사진"
105-
fallbackSrc="/images/article-thumb.png"
106-
/>
107-
) : (
108-
<div className="bg-gray-c-50 flex h-20 w-20 items-center justify-center rounded border border-k-100">
109-
<IconSolidConnentionLogo />
110-
</div>
111-
)}
97+
<div className="ml-4 mt-3 h-20 w-20 shrink-0 select-none">
98+
<div className="bg-gray-c-50 relative h-full w-full overflow-hidden rounded border border-k-100">
99+
{post.postThumbnailUrl ? (
100+
<Image
101+
className="object-cover"
102+
src={convertUploadedImageUrl(post.postThumbnailUrl)}
103+
fill
104+
sizes="80px"
105+
alt="게시글 사진"
106+
fallbackSrc="/images/article-thumb.png"
107+
/>
108+
) : (
109+
<div className="flex h-full w-full items-center justify-center">
110+
<IconSolidConnentionLogo />
111+
</div>
112+
)}
113+
</div>
112114
</div>
113115
</div>
114116
);

apps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useRouter } from "next/navigation";
4-
import { useEffect, useRef, useState } from "react";
4+
import { type ChangeEvent, useEffect, useRef, useState } from "react";
55

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

3537
const updatePostMutation = useUpdatePost();
@@ -61,6 +63,32 @@ const PostModifyForm = ({
6163
return () => {};
6264
}, []);
6365

66+
useEffect(() => {
67+
if (!selectedImage) {
68+
setImagePreviewUrl(null);
69+
return;
70+
}
71+
72+
const objectUrl = URL.createObjectURL(selectedImage);
73+
setImagePreviewUrl(objectUrl);
74+
75+
return () => {
76+
URL.revokeObjectURL(objectUrl);
77+
};
78+
}, [selectedImage]);
79+
80+
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
81+
const file = event.target.files?.[0] ?? null;
82+
setSelectedImage(file);
83+
};
84+
85+
const removeSelectedImage = () => {
86+
setSelectedImage(null);
87+
if (imageUploadRef.current) {
88+
imageUploadRef.current.value = "";
89+
}
90+
};
91+
6492
const submitPost = async () => {
6593
if (!title.trim()) {
6694
toast.error("제목을 입력해주세요.");
@@ -82,7 +110,7 @@ const PostModifyForm = ({
82110
title,
83111
content,
84112
},
85-
file: imageUploadRef.current?.files ? Array.from(imageUploadRef.current.files) : [],
113+
file: selectedImage ? [selectedImage] : [],
86114
},
87115
},
88116
{
@@ -137,7 +165,7 @@ const PostModifyForm = ({
137165
>
138166
<IconImage />
139167
</button>
140-
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" multiple />
168+
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" onChange={handleImageChange} />
141169
</div>
142170
</div>
143171
<div>
@@ -148,6 +176,22 @@ const PostModifyForm = ({
148176
onChange={(e) => setContent(e.target.value)}
149177
/>
150178
</div>
179+
{imagePreviewUrl ? (
180+
<div className="px-5 pb-2">
181+
<p className="mb-2 text-gray-250/87 typo-regular-4">첨부 이미지</p>
182+
<div className="relative h-24 w-24 overflow-hidden rounded-md border border-gray-c-100">
183+
<img src={imagePreviewUrl} alt="업로드 이미지 미리보기" className="h-full w-full object-cover" />
184+
<button
185+
type="button"
186+
className="absolute right-1 top-1 rounded bg-black/60 px-1 py-0.5 text-xs text-white"
187+
onClick={removeSelectedImage}
188+
aria-label="이미지 제거"
189+
>
190+
삭제
191+
</button>
192+
</div>
193+
</div>
194+
) : null}
151195
<div className="px-5 pt-2.5">
152196
<p className="text-gray-250/87 typo-sb-9">{noticeTitle}</p>
153197
<p className="mt-2 whitespace-pre-line text-gray-100 typo-regular-4">{noticeContent}</p>

apps/web/src/app/community/[boardCode]/create/PostForm.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

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

2123
const createPostMutation = useCreatePost();
2224

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

48+
useEffect(() => {
49+
if (!selectedImage) {
50+
setImagePreviewUrl(null);
51+
return;
52+
}
53+
54+
const objectUrl = URL.createObjectURL(selectedImage);
55+
setImagePreviewUrl(objectUrl);
56+
57+
return () => {
58+
URL.revokeObjectURL(objectUrl);
59+
};
60+
}, [selectedImage]);
61+
62+
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
63+
const file = event.target.files?.[0] ?? null;
64+
setSelectedImage(file);
65+
};
66+
67+
const removeSelectedImage = () => {
68+
setSelectedImage(null);
69+
if (imageUploadRef.current) {
70+
imageUploadRef.current.value = "";
71+
}
72+
};
73+
4674
const submitPost = async () => {
4775
createPostMutation.mutate(
4876
{
@@ -53,7 +81,7 @@ const PostForm = ({ boardCode }: PostFormProps) => {
5381
content,
5482
isQuestion,
5583
},
56-
file: imageUploadRef.current?.files ? Array.from(imageUploadRef.current.files) : [],
84+
file: selectedImage ? [selectedImage] : [],
5785
},
5886
{
5987
onSuccess: (data) => {
@@ -116,7 +144,7 @@ const PostForm = ({ boardCode }: PostFormProps) => {
116144
>
117145
<IconImage />
118146
</button>
119-
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" multiple />
147+
<input className="hidden" ref={imageUploadRef} type="file" accept="image/*" onChange={handleImageChange} />
120148
</div>
121149
</div>
122150
<div>
@@ -127,6 +155,22 @@ const PostForm = ({ boardCode }: PostFormProps) => {
127155
onChange={(e) => setContent(e.target.value)}
128156
/>
129157
</div>
158+
{imagePreviewUrl ? (
159+
<div className="px-5 pb-2">
160+
<p className="mb-2 text-gray-250/87 typo-regular-4">첨부 이미지</p>
161+
<div className="relative h-24 w-24 overflow-hidden rounded-md border border-gray-c-100">
162+
<img src={imagePreviewUrl} alt="업로드 이미지 미리보기" className="h-full w-full object-cover" />
163+
<button
164+
type="button"
165+
className="absolute right-1 top-1 rounded bg-black/60 px-1 py-0.5 text-xs text-white"
166+
onClick={removeSelectedImage}
167+
aria-label="이미지 제거"
168+
>
169+
삭제
170+
</button>
171+
</div>
172+
</div>
173+
) : null}
130174
<div className="px-5 pt-2.5">
131175
<p className="text-gray-250/87 typo-sb-9">{noticeTitle}</p>
132176
<p className="mt-2 whitespace-pre-line text-gray-100 typo-regular-4">{noticeContent}</p>

0 commit comments

Comments
 (0)