Skip to content

Commit 2d324bf

Browse files
committed
Refactor: PostUpload 리팩토링
1 parent c15e7f1 commit 2d324bf

2 files changed

Lines changed: 98 additions & 98 deletions

File tree

src/pages/Post/PostUpload/SearchBottomSheetContent/index.tsx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,41 @@ const SearchBottomSheetContent: React.FC<SearchBottomSheetProps> = ({ onClose, o
1919
const observerRef = useRef<IntersectionObserver | null>(null);
2020
const loadMoreRef = useRef<HTMLDivElement | null>(null);
2121

22+
const handleCloseSheet = () => {
23+
onClose();
24+
};
25+
2226
const handleInputChange = (query: string) => {
2327
setSearchQuery(query);
2428
};
2529

26-
const fetchSearchResult = async (searchQuery: string, start: number) => {
30+
const handleClothingInfoAdd = (item: any) => {
31+
onSelectClothingInfo({
32+
imageUrl: item.image,
33+
brandName: item.brand,
34+
modelName: removeBrandFromTitle(item.title, item.brand), //검색 결과에서 <b></b> 태그 제거하고 텍스트만 표시
35+
modelNumber: '1',
36+
url: item.link,
37+
});
38+
onClose();
39+
};
40+
41+
const removeBrandFromTitle = (title: string, brand: string) => {
42+
// 브랜드 이름에서 특수 문자를 이스케이프 처리하여 정규 표현식에서 사용할 수 있도록 변환
43+
const escapedBrand = brand.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
44+
// 브랜드 이름을 감싸고 있는 [ ]를 제거
45+
const bracketRegex = new RegExp(`[\\[\\]()<>]*${escapedBrand}[\\[\\]()<>]*`, 'gi'); //gi: 대소문자 구분 없이(g) 모든 위치에서(i)
46+
// 변환된 브랜드 이름을 제거
47+
const brandRegex = new RegExp(escapedBrand, 'gi');
48+
// 제목에서 브랜드 이름과 <b></b> 태그를 제거하고 양쪽 끝의 공백을 제거
49+
return title
50+
.replace(/<[^>]+>/g, '')
51+
.replace(bracketRegex, '')
52+
.replace(brandRegex, '')
53+
.trim();
54+
};
55+
56+
const getSearchResult = async (searchQuery: string, start: number) => {
2757
try {
2858
//네이버 쇼핑 api 프록시 서버 사용
2959
const response = await axios.get(
@@ -56,7 +86,7 @@ const SearchBottomSheetContent: React.FC<SearchBottomSheetProps> = ({ onClose, o
5686
setReachedEnd(false);
5787

5888
if (searchQuery) {
59-
fetchSearchResult(searchQuery, 1).then((data) => {
89+
getSearchResult(searchQuery, 1).then((data) => {
6090
if (data) {
6191
setSearchResult(data.items);
6292
} else {
@@ -77,7 +107,7 @@ const SearchBottomSheetContent: React.FC<SearchBottomSheetProps> = ({ onClose, o
77107
if (!reachedEnd && searchQuery) {
78108
setIsLoading(true);
79109

80-
const data = await fetchSearchResult(searchQuery, start);
110+
const data = await getSearchResult(searchQuery, start);
81111

82112
if (data) {
83113
setSearchResult((prevResult) => [...prevResult, ...data.items]);
@@ -118,36 +148,6 @@ const SearchBottomSheetContent: React.FC<SearchBottomSheetProps> = ({ onClose, o
118148
};
119149
}, [reachedEnd, searchQuery, isLoading, searchResult]);
120150

121-
const handleAddClothingInfo = (item: any) => {
122-
onSelectClothingInfo({
123-
imageUrl: item.image,
124-
brandName: item.brand,
125-
modelName: removeBrandFromTitle(item.title, item.brand), //검색 결과에서 <b></b> 태그 제거하고 텍스트만 표시
126-
modelNumber: '1',
127-
url: item.link,
128-
});
129-
onClose();
130-
};
131-
132-
const removeBrandFromTitle = (title: string, brand: string) => {
133-
// 브랜드 이름에서 특수 문자를 이스케이프 처리하여 정규 표현식에서 사용할 수 있도록 변환
134-
const escapedBrand = brand.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
135-
// 브랜드 이름을 감싸고 있는 [ ]를 제거
136-
const bracketRegex = new RegExp(`[\\[\\]()<>]*${escapedBrand}[\\[\\]()<>]*`, 'gi'); //gi: 대소문자 구분 없이(g) 모든 위치에서(i)
137-
// 변환된 브랜드 이름을 제거
138-
const brandRegex = new RegExp(escapedBrand, 'gi');
139-
// 제목에서 브랜드 이름과 <b></b> 태그를 제거하고 양쪽 끝의 공백을 제거
140-
return title
141-
.replace(/<[^>]+>/g, '')
142-
.replace(bracketRegex, '')
143-
.replace(brandRegex, '')
144-
.trim();
145-
};
146-
147-
const handleCloseSheet = () => {
148-
onClose();
149-
};
150-
151151
return (
152152
<Content>
153153
<div className="input_container">
@@ -164,7 +164,7 @@ const SearchBottomSheetContent: React.FC<SearchBottomSheetProps> = ({ onClose, o
164164
{searchQuery && searchResult.length > 0 ? (
165165
<SearchResultList>
166166
{searchResult.map((searchResultItem, index) => (
167-
<SearchResultItem key={index} onClick={() => handleAddClothingInfo(searchResultItem)}>
167+
<SearchResultItem key={index} onClick={() => handleClothingInfoAdd(searchResultItem)}>
168168
<img src={searchResultItem.image} alt={searchResultItem.title.replace(/<[^>]+>/g, '')} />
169169
<div className="infoContainer">
170170
<StyledText className="detail" $textTheme={{ style: 'body2-bold', lineHeight: 1.2 }}>

src/pages/Post/PostUpload/index.tsx

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,6 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
8484
'luxury',
8585
];
8686

87-
// 게시물 업로드인지 수정인지 모드 확인
88-
useEffect(() => {
89-
const handleMode = async () => {
90-
const state = location.state as { mode?: string; postId?: number };
91-
if (state?.mode) {
92-
setMode(state.mode); // 모드 상태를 설정
93-
}
94-
if (state.mode === 'edit' && state?.postId && selectedImages.length === 0) {
95-
await getPost(state.postId);
96-
}
97-
};
98-
99-
handleMode();
100-
}, []);
101-
10287
const handlePrev = () => {
10388
const state = location.state as { mode?: string; postId?: number };
10489
if (mode === 'edit') {
@@ -107,37 +92,19 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
10792
navigate('/post/upload/photo/select', { state: { mode: mode, postId: state.postId } });
10893
};
10994

110-
const getPost = async (postId: number) => {
111-
setIsLoading(true);
112-
113-
try {
114-
const response = await getPostDetailApi(postId);
115-
116-
const { postImages, content, postStyletags, postClothings, isRepresentative } = response.data;
117-
118-
setSelectedImages(postImages);
119-
setContent(content);
120-
setClothingInfos(postClothings ?? []);
121-
setSelectedStyletag(postStyletags);
122-
setIsRepresentative(isRepresentative);
123-
} catch (error) {
124-
const errorMessage = handleError(error, 'post');
125-
setModalContent(errorMessage);
126-
setIsStatusModalOpen(true);
127-
} finally {
128-
setIsLoading(false);
129-
}
130-
};
131-
132-
const handleToggleSearchSheet = () => {
95+
const handleSearchSheetToggle = () => {
13396
setIsSearchBottomSheetOpen((open) => !open);
13497
};
13598

136-
const handleToggleStyleTagList = () => {
99+
const handleStyleTagListToggle = () => {
137100
setIsStyletagListOpen((open) => !open);
138101
};
139102

140-
const handleAddClothingInfo = (newClothingInfo: ClothingInfo) => {
103+
const handleIsRepresentativeToggle = () => {
104+
setIsRepresentative((isRepresentative) => !isRepresentative);
105+
};
106+
107+
const handleClothingInfoAdd = (newClothingInfo: ClothingInfo) => {
141108
setClothingInfos((clothingInfos) => {
142109
// 중복 확인 (새로운 의류 정보가 이미 존재하지 않을 경우 추가)
143110
const isDuplicate = clothingInfos.some(
@@ -151,25 +118,12 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
151118
});
152119
};
153120

154-
const handleDeleteClothingInfo = (deleteClothingInfo: ClothingInfo) => {
121+
const handleClothingInfoDelete = (deleteClothingInfo: ClothingInfo) => {
155122
const deletedClothingInfo = clothingInfos.filter((clothing) => clothing !== deleteClothingInfo);
156123
setClothingInfos(deletedClothingInfo);
157124
};
158125

159-
const bottomSheetProps: BottomSheetProps = {
160-
isOpenBottomSheet: isSearchBottomSheetOpen,
161-
isHandlerVisible: false,
162-
Component: SearchBottomSheetContent,
163-
onCloseBottomSheet: () => {
164-
setIsSearchBottomSheetOpen(false);
165-
},
166-
componentProps: {
167-
onClose: () => setIsSearchBottomSheetOpen(false),
168-
onSelectClothingInfo: handleAddClothingInfo,
169-
},
170-
};
171-
172-
const handleSelectStyletag = (tag: string) => {
126+
const handleStyletagSelect = (tag: string) => {
173127
setSelectedStyletag((prev) => {
174128
// 선택된 태그가 이미 존재하면 제거
175129
if (prev.includes(tag)) {
@@ -181,10 +135,6 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
181135
setIsStyletagListOpen(false);
182136
};
183137

184-
const handleToggleIsRepresentative = () => {
185-
setIsRepresentative(!isRepresentative);
186-
};
187-
188138
const cropImage = (imageUrl: string): Promise<Blob> => {
189139
return new Promise((resolve) => {
190140
const img = new Image();
@@ -300,6 +250,56 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
300250
}
301251
};
302252

253+
const getPost = async (postId: number) => {
254+
setIsLoading(true);
255+
256+
try {
257+
const response = await getPostDetailApi(postId);
258+
259+
const { postImages, content, postStyletags, postClothings, isRepresentative } = response.data;
260+
261+
setSelectedImages(postImages);
262+
setContent(content);
263+
setClothingInfos(postClothings ?? []);
264+
setSelectedStyletag(postStyletags);
265+
setIsRepresentative(isRepresentative);
266+
} catch (error) {
267+
const errorMessage = handleError(error, 'post');
268+
setModalContent(errorMessage);
269+
setIsStatusModalOpen(true);
270+
} finally {
271+
setIsLoading(false);
272+
}
273+
};
274+
275+
// 게시물 업로드인지 수정인지 모드 확인
276+
useEffect(() => {
277+
const handleMode = async () => {
278+
const state = location.state as { mode?: string; postId?: number };
279+
if (state?.mode) {
280+
setMode(state.mode); // 모드 상태를 설정
281+
}
282+
if (state.mode === 'edit' && state?.postId && selectedImages.length === 0) {
283+
await getPost(state.postId);
284+
}
285+
};
286+
287+
handleMode();
288+
}, []);
289+
290+
const bottomSheetProps: BottomSheetProps = {
291+
isOpenBottomSheet: isSearchBottomSheetOpen,
292+
isHandlerVisible: false,
293+
Component: SearchBottomSheetContent,
294+
onCloseBottomSheet: () => {
295+
setIsSearchBottomSheetOpen(false);
296+
},
297+
componentProps: {
298+
onClose: () => setIsSearchBottomSheetOpen(false),
299+
onSelectClothingInfo: handleClothingInfoAdd,
300+
},
301+
};
302+
303303
// api 처리 상태 모달 (성공/실패)
304304
const statusModalProps: ModalProps = {
305305
content: modalContent,
@@ -320,7 +320,7 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
320320
placeholder="문구를 작성하세요..."
321321
/>
322322
<TagContainer className="clothingTag">
323-
<div onClick={handleToggleSearchSheet}>
323+
<div onClick={handleSearchSheetToggle}>
324324
<img src={ClothingTag} />
325325
<StyledText className="label" $textTheme={{ style: 'headline2-bold' }}>
326326
옷 정보 태그
@@ -335,13 +335,13 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
335335
{clothingInfos.length > 0 && (
336336
<ClothingInfoList>
337337
{clothingInfos.map((clothingObj, index) => (
338-
<ClothingInfoItem key={index} clothingObj={clothingObj} onDelete={handleDeleteClothingInfo} />
338+
<ClothingInfoItem key={index} clothingObj={clothingObj} onDelete={handleClothingInfoDelete} />
339339
))}
340340
</ClothingInfoList>
341341
)}
342342
</TagContainer>
343343
<TagContainer>
344-
<div onClick={handleToggleStyleTagList}>
344+
<div onClick={handleStyleTagListToggle}>
345345
<img src={StyleTag} />
346346
<StyledText className="label" $textTheme={{ style: 'headline2-bold', lineHeight: 1 }}>
347347
스타일 태그
@@ -368,7 +368,7 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
368368
{styletags.map((tag) => (
369369
<StyletagItem
370370
key={tag}
371-
onClick={() => handleSelectStyletag(tag)}
371+
onClick={() => handleStyletagSelect(tag)}
372372
selected={selectedStyletag[0] === tag}
373373
>
374374
<StyledText className="tag" $textTheme={{ style: 'body2-light', lineHeight: 1 }}>
@@ -383,7 +383,7 @@ const PostUpload: React.FC<PostUploadModalProps> = () => {
383383
<img src={Pin} />
384384
<StyledText $textTheme={{ style: 'headline2-bold', lineHeight: 1 }}>대표 OOTD 지정</StyledText>
385385
<div>
386-
<ToggleSwitch checked={isRepresentative} onChange={handleToggleIsRepresentative} />
386+
<ToggleSwitch checked={isRepresentative} onChange={handleIsRepresentativeToggle} />
387387
</div>
388388
</PinnedPostToggleContainer>
389389
</Content>

0 commit comments

Comments
 (0)