diff --git a/src/components/org/OrgAdmin/AboutSection/Executives/ExecInfo.tsx b/src/components/org/OrgAdmin/AboutSection/Executives/ExecInfo.tsx
index c2b75e45..fe0c79d6 100644
--- a/src/components/org/OrgAdmin/AboutSection/Executives/ExecInfo.tsx
+++ b/src/components/org/OrgAdmin/AboutSection/Executives/ExecInfo.tsx
@@ -46,7 +46,7 @@ const ExecInfo = ({
- 사진은 1:1 비율로 올려주세요. 사진 용량은 00mb 아래로 첨부해주세요.
+ 사진은 1:1 비율로 올려주세요. 사진 용량은 10mb 아래로 첨부해주세요.
{
전체 일정
-
+ {/* TODO: 이미지 제대로 안뜨는 이슈로 잠시 주석처리 */}
+ {/*
-
+ */}
{
-
+ {/*
{
isInfoVisible={isInfoVisible}
onInfoToggle={onInfoToggle}
/>
-
+ */}
);
diff --git a/src/components/org/OrgAdmin/HomeSection/HomeSection.tsx b/src/components/org/OrgAdmin/HomeSection/HomeSection.tsx
index c2655891..9d204873 100644
--- a/src/components/org/OrgAdmin/HomeSection/HomeSection.tsx
+++ b/src/components/org/OrgAdmin/HomeSection/HomeSection.tsx
@@ -14,6 +14,7 @@ import NewsSection from '@/components/org/OrgAdmin/HomeSection/_components/News/
import ReviewSection from '@/components/org/OrgAdmin/HomeSection/_components/Review/ReviewSection';
import type { Review } from '@/components/org/OrgAdmin/HomeSection/_types/types';
import { isSameOrder } from '@/components/org/OrgAdmin/HomeSection/_utils/isSameOrder';
+import { reconcileWithServerData } from '@/components/org/OrgAdmin/HomeSection/_utils/reconcileWithServerData';
import { extractFileNameFromUrl } from '@/components/org/OrgAdmin/HomeSection/api';
import {
useAdminInfoQuery,
@@ -64,6 +65,14 @@ const HomeSectionContent = ({ onEditModeChange }: HomeSectionProps) => {
useEffect(() => {
if (isEditMode) {
+ // 편집 중엔 로컬 재정렬(드래그 순서)을 그대로 유지하되, 그 사이 추가/삭제된
+ // 항목만 반영한다. 서버 데이터로 통째로 덮어쓰면 로컬 순서가 날아가고
+ // (리뷰 수정 시 순서 초기화 버그), 반대로 아예 무시하면 편집 중 새로
+ // 추가한 항목이 draft에 안 실려 배포 시 누락된다.
+ setDraft((prev) => ({
+ reviews: reconcileWithServerData(prev.reviews, initialReviews),
+ news: reconcileWithServerData(prev.news, latestNews),
+ }));
return;
}
diff --git a/src/components/org/OrgAdmin/HomeSection/_components/Modal/EditNewsModal.tsx b/src/components/org/OrgAdmin/HomeSection/_components/Modal/EditNewsModal.tsx
index 5ba44cd1..fcee6f49 100644
--- a/src/components/org/OrgAdmin/HomeSection/_components/Modal/EditNewsModal.tsx
+++ b/src/components/org/OrgAdmin/HomeSection/_components/Modal/EditNewsModal.tsx
@@ -72,6 +72,7 @@ export const EditNewsModal = ({
{
id: newsId,
file: image.file,
+ existingImageUrl: news?.image,
title,
link: link ?? '',
},
diff --git a/src/components/org/OrgAdmin/HomeSection/_utils/reconcileWithServerData.ts b/src/components/org/OrgAdmin/HomeSection/_utils/reconcileWithServerData.ts
new file mode 100644
index 00000000..36f2307d
--- /dev/null
+++ b/src/components/org/OrgAdmin/HomeSection/_utils/reconcileWithServerData.ts
@@ -0,0 +1,15 @@
+export const reconcileWithServerData = (
+ draftItems: T[],
+ serverItems: T[],
+): T[] => {
+ const serverById = new Map(serverItems.map((item) => [item.id, item]));
+
+ const reconciledExisting = draftItems
+ .filter((item) => serverById.has(item.id))
+ .map((item) => serverById.get(item.id) as T);
+
+ const draftIds = new Set(draftItems.map((item) => item.id));
+ const newItems = serverItems.filter((item) => !draftIds.has(item.id));
+
+ return [...reconciledExisting, ...newItems];
+};
diff --git a/src/components/org/OrgAdmin/HomeSection/api.ts b/src/components/org/OrgAdmin/HomeSection/api.ts
index 3e1b44cf..89d335fe 100644
--- a/src/components/org/OrgAdmin/HomeSection/api.ts
+++ b/src/components/org/OrgAdmin/HomeSection/api.ts
@@ -110,18 +110,17 @@ export const getNews = async (id: number) => {
return data;
};
-export const patchNews = async (id: number, formData: FormData) => {
- const res = await fetcher.PATCH('/admin/news/{id}', {
+export const patchNewsV2 = async (
+ id: number,
+ data: { imageUrl: string; title: string; link: string },
+) => {
+ const res = await fetcher.PATCH('/admin/news/{id}/v2', {
params: {
path: {
id,
},
},
- body: {
- image: formData.get('image') as string,
- title: formData.get('title') as string,
- link: formData.get('link') as string,
- },
+ body: data,
});
return res;
@@ -234,9 +233,9 @@ export const deployHomeTab = async ({
reviewItems,
newsItems,
}: DeployHomeInput) => {
- const newsDetails = await Promise.all(
- newsItems.map((item) => getNews(item.id)),
- );
+ const newsDetails = (
+ await Promise.all(newsItems.map((item) => getNews(item.id)))
+ ).filter((news): news is NonNullable => Boolean(news));
const deployResponse = await postHomeTab({
generation: Number(ACTIVITY_GENERATION),
@@ -246,13 +245,11 @@ export const deployHomeTab = async ({
content: content ?? '',
authorInfo: authorInfo ?? '',
})),
- news: newsDetails
- .filter((news): news is NonNullable => Boolean(news))
- .map((news) => ({
- imageFileName: extractFileNameFromUrl(news.image),
- title: news.title,
- link: news.link,
- })),
+ news: newsDetails.map((news) => ({
+ imageFileName: extractFileNameFromUrl(news.image),
+ title: news.title,
+ link: news.link,
+ })),
});
if (homeHeaderImageFile && deployResponse.homeHeaderImage) {
@@ -266,5 +263,30 @@ export const deployHomeTab = async ({
}
}
+ // /admin/home은 뉴스마다 새 presigned URL을 발급한다(기존 이미지를 그대로
+ // 재사용하지 않음). 이 URL에 실제로 업로드하지 않으면 confirm 단계에서
+ // 이미지가 비어있는 채로 반영되어 최신소식 이미지가 깨지거나 누락된다.
+ await Promise.all(
+ newsDetails.map(async (news, index) => {
+ const presignedUrl = deployResponse.news?.[index]?.imagePresignedUrl;
+
+ if (!presignedUrl) return;
+
+ const imageResponse = await fetch(news.image);
+ const imageBlob = await imageResponse.blob();
+
+ const uploadResponse = await fetch(presignedUrl, {
+ method: 'PUT',
+ body: imageBlob,
+ });
+
+ if (!uploadResponse.ok) {
+ throw new Error(
+ `최신소식(${news.title}) 이미지 업로드에 실패했습니다.`,
+ );
+ }
+ }),
+ );
+
return postHomeTabConfirm();
};
diff --git a/src/components/org/OrgAdmin/HomeSection/queries.ts b/src/components/org/OrgAdmin/HomeSection/queries.ts
index 64d9d82a..a234b928 100644
--- a/src/components/org/OrgAdmin/HomeSection/queries.ts
+++ b/src/components/org/OrgAdmin/HomeSection/queries.ts
@@ -14,7 +14,7 @@ import {
getNews,
getPresignedUrl,
getReviews,
- patchNews,
+ patchNewsV2,
patchReview,
postNewsV2,
postReview,
@@ -113,19 +113,27 @@ export const useEditNewsMutation = () => {
mutationFn: async (data: {
id: number;
file?: File;
+ existingImageUrl?: string;
title: string;
link: string;
}) => {
- const formData = new FormData();
+ let imageUrl = data.existingImageUrl;
if (data.file) {
- formData.append('image', data.file);
+ const presignedData = await getPresignedUrl(data.file);
+ await uploadToS3(presignedData.presignedUrl, data.file);
+ imageUrl = presignedData.fileUrl;
}
- formData.append('title', data.title);
- formData.append('link', data.link);
+ if (!imageUrl) {
+ throw new Error('이미지가 필요합니다.');
+ }
- return await patchNews(data.id, formData);
+ return await patchNewsV2(data.id, {
+ imageUrl,
+ title: data.title,
+ link: data.link,
+ });
},
onSuccess: (_, data) => {
queryClient.invalidateQueries({