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
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const ExecInfo = ({
</StInputLabel>

<StDescription>
사진은 1:1 비율로 올려주세요. 사진 용량은 00mb 아래로 첨부해주세요.
사진은 1:1 비율로 올려주세요. 사진 용량은 10mb 아래로 첨부해주세요.
</StDescription>
<MyDropzone
method={method}
Expand Down
9 changes: 5 additions & 4 deletions src/components/org/OrgAdmin/AboutSection/Schedule/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ const Schedule = ({ isEditable = true }: ScheduleProps) => {
<StScheduleHeader>
<StScheduleTitle>
<span>전체 일정</span>
<StScheduleInfoButton onClick={onInfoToggle} type="button">
{/* TODO: 이미지 제대로 안뜨는 이슈로 잠시 주석처리 */}
{/* <StScheduleInfoButton onClick={onInfoToggle} type="button">
<IconInfoCircle />
</StScheduleInfoButton>
</StScheduleInfoButton> */}
</StScheduleTitle>
<Button
size="md"
Expand Down Expand Up @@ -140,7 +141,7 @@ const Schedule = ({ isEditable = true }: ScheduleProps) => {
</StScheduleRowWrapper>
</StScheduleBody>

<StScheduleModalWrapper>
{/* <StScheduleModalWrapper>
<Modal
title="전체 일정"
description="소개 탭에 표시되는 이번 기수의 전체 일정이에요."
Expand All @@ -149,7 +150,7 @@ const Schedule = ({ isEditable = true }: ScheduleProps) => {
isInfoVisible={isInfoVisible}
onInfoToggle={onInfoToggle}
/>
</StScheduleModalWrapper>
</StScheduleModalWrapper> */}
</StScheduleWrapper>
</StWrapper>
);
Expand Down
9 changes: 9 additions & 0 deletions src/components/org/OrgAdmin/HomeSection/HomeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const EditNewsModal = ({
{
id: newsId,
file: image.file,
existingImageUrl: news?.image,
title,
link: link ?? '',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const reconcileWithServerData = <T extends { id: number }>(
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];
};
56 changes: 39 additions & 17 deletions src/components/org/OrgAdmin/HomeSection/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<typeof news> => Boolean(news));

const deployResponse = await postHomeTab({
generation: Number(ACTIVITY_GENERATION),
Expand All @@ -246,13 +245,11 @@ export const deployHomeTab = async ({
content: content ?? '',
authorInfo: authorInfo ?? '',
})),
news: newsDetails
.filter((news): news is NonNullable<typeof news> => 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) {
Expand All @@ -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();
};
20 changes: 14 additions & 6 deletions src/components/org/OrgAdmin/HomeSection/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
getNews,
getPresignedUrl,
getReviews,
patchNews,
patchNewsV2,
patchReview,
postNewsV2,
postReview,
Expand Down Expand Up @@ -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({
Expand Down
Loading