From 5b65bd49b1902ee3824f0a7029ae1eeca5fb1b38 Mon Sep 17 00:00:00 2001 From: luli Date: Mon, 25 Aug 2025 06:18:33 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[#79]=20=EC=9D=B4=EA=B1=B4=20=EA=B7=B8?= =?UTF-8?q?=EB=83=A5=20=EC=BD=94=EB=93=9C=20=EC=88=9C=EC=84=9C=EB=A7=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(404=EA=B4=80=EB=A0=A8=20=EC=95=84?= =?UTF-8?q?=EB=8B=98...)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이건 그냥 코드 순서만 변경 --- src/hooks/use-image-loader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/use-image-loader.jsx b/src/hooks/use-image-loader.jsx index edfde97..8d09205 100644 --- a/src/hooks/use-image-loader.jsx +++ b/src/hooks/use-image-loader.jsx @@ -48,13 +48,13 @@ function useImageListLodeChecker(imageList = []) { } const img = new Image(); - img.src = backgroundImageURL; img.onload = () => { setImageLoadStates((p) => ({ ...p, [id]: true })); }; img.onerror = () => { setImageLoadStates((p) => ({ ...p, [id]: false })); }; + img.src = backgroundImageURL; return prev; }); From 2fc60b456d5d0a6252eb8b921a86be799b81b9d5 Mon Sep 17 00:00:00 2001 From: luli Date: Mon, 25 Aug 2025 06:37:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[#109]=20image=20hook=20=EC=A1=B0=EA=B8=88?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 리랜더링 방지. --- src/hooks/use-image-loader.jsx | 35 ++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/hooks/use-image-loader.jsx b/src/hooks/use-image-loader.jsx index 8d09205..f4dc4db 100644 --- a/src/hooks/use-image-loader.jsx +++ b/src/hooks/use-image-loader.jsx @@ -29,36 +29,51 @@ function useImageListLodeChecker(imageList = []) { useEffect(() => { if (!imageList.length) return; + let cancelled = false; + setImageLoadStates((prev) => { const nextStates = { ...prev }; imageList.forEach(({ id }) => { - if (nextStates[id] === undefined) { - nextStates[id] = false; - } + if (nextStates[id] === undefined) nextStates[id] = false; }); return nextStates; }); + const imageMap = {}; + imageList.forEach(({ id, backgroundImageURL }) => { setImageLoadStates((prev) => { - if (prev[id]) return prev; + if (prev[id] === true) return prev; - if (!backgroundImageURL) { - return { ...prev, [id]: false }; - } + if (!backgroundImageURL) return { ...prev, [id]: false }; const img = new Image(); + imageMap[id] = img; + img.onload = () => { - setImageLoadStates((p) => ({ ...p, [id]: true })); + if (!cancelled) { + setImageLoadStates((p) => ({ ...p, [id]: true })); + } }; + img.onerror = () => { - setImageLoadStates((p) => ({ ...p, [id]: false })); + if (!cancelled) { + setImageLoadStates((p) => ({ ...p, [id]: false })); + } }; - img.src = backgroundImageURL; + img.src = backgroundImageURL; return prev; }); }); + + return () => { + cancelled = true; + Object.values(imageMap).forEach((img) => { + img.onload = null; + img.onerror = null; + }); + }; }, [imageList]); return imageLoadStates; From cb83cb2d1243e2293314c9cc46f8a1cffb9fe2e7 Mon Sep 17 00:00:00 2001 From: luli Date: Mon, 25 Aug 2025 20:55:08 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[#123]=20404=20=ED=95=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=EB=A1=9C=20=EC=88=98=EC=A0=95,=20/post/id=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=EA=B2=BD=EB=A1=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 404 한글 문구로 수정, /post/id 없는경로 예외처리 --- .../components/rolling-paper-list.jsx | 7 ++++++ src/pages/404-page.jsx | 24 ++++++++----------- src/pages/messages-page.jsx | 3 ++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/features/rolling-paper/components/rolling-paper-list.jsx b/src/features/rolling-paper/components/rolling-paper-list.jsx index e71e048..7f91ba3 100644 --- a/src/features/rolling-paper/components/rolling-paper-list.jsx +++ b/src/features/rolling-paper/components/rolling-paper-list.jsx @@ -248,8 +248,15 @@ function RollingPaperList({ cardData, totalPages, currentPage, onTurnCards }) { const navigate = useNavigate(); const handleCardClick = (cardId) => { + if(!cardId) { + navigate("*"); + return; + } + navigate(`/post/${cardId}`); }; + + const profileImages = useMemo( () => cardData.flatMap((card) => diff --git a/src/pages/404-page.jsx b/src/pages/404-page.jsx index e0354cd..d88d9c9 100644 --- a/src/pages/404-page.jsx +++ b/src/pages/404-page.jsx @@ -45,20 +45,20 @@ const ErrorNumber = styled.h1` font-weight: 900; color: var(--color-purple-700); margin: 0; - font-size: 200px; + font-size: 150px; ${media.mobile} { - font-size: 130px; + font-size: 110px; } `; const ErrorComment = styled.span` - font-size: 80px; + font-size: 70px; color: #6e6293; font-weight: 700; em { - font-size: 100px; + font-size: 90px; font-weight: 800; font-style: normal; color: #240079; @@ -72,24 +72,20 @@ const ErrorComment = styled.span` } ${media.mobile} { - font-size: 50px; + font-size: 40px; em { - font-size: 60px; + font-size: 50px; } } `; const ErrorCommentSofter = styled.span` - font-size: 50px; + font-size: 25px; color: #6e6293; font-weight: 400; - ${media.tablet} { - font-size: 40px; - } - ${media.mobile} { - font-size: 30px; + font-size: 18px; } `; @@ -133,10 +129,10 @@ const Error404Page = () => { 404 - Oops! Page Not Found... + 앗 이런! 페이지를 찾을 수 없습니다. - Don't worry, let's get you back on track. + 페이지의 주소가 올바르지 않거나, 삭제 또는 다른 페이지로 변경되었습니다. From dd341f1916dd711b95b91d16d2af3cd4279059f1 Mon Sep 17 00:00:00 2001 From: luli Date: Tue, 26 Aug 2025 03:06:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[#107]=20api=ED=98=B8=EC=B6=9C=20error=20to?= =?UTF-8?q?ast=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit api호출 error toast처리 --- src/pages/create-post-page.jsx | 21 ++++++++++++- src/pages/messages-page.jsx | 38 +++++++++++++++++++--- src/pages/rolling-paper-list-page.jsx | 45 +++++++++++++++++++++------ src/pages/send-message-page.jsx | 20 ++++++++++++ 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/src/pages/create-post-page.jsx b/src/pages/create-post-page.jsx index cba1a1b..b4edc9d 100644 --- a/src/pages/create-post-page.jsx +++ b/src/pages/create-post-page.jsx @@ -11,6 +11,8 @@ import BackgroundSelect from "../components/option/background-select"; import TextField from "../components/text-field/text-field"; import TEXT_FIELD_TYPE from "../components/text-field/text-field-type"; import { media } from "../utils/media"; +import Toast from "../components/toast/toast"; +import { useToast } from "../hooks/use-toast"; const PostContainer = styled.div` display: flex; @@ -77,8 +79,15 @@ function CreatePostPage() { const [selected, setSelected] = useState(0); const [backgroundUrls, setBackgroundUrls] = useState([]); const [loading, setLoading] = useState(false); + const [toastMessage, setToastMessage] = useState(); const navigate = useNavigate(); + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); + + const handleToastCloseClick = () => setShowsToast(false); + const colorOptions = [ { label: "beige", color: BACKGROUND_COLOR.beige }, { label: "purple", color: BACKGROUND_COLOR.purple }, @@ -137,7 +146,8 @@ function CreatePostPage() { navigate(`/post/${newPostId}`); } catch (error) { console.error("게시물 생성 실패:", error); - alert("게시물 생성에 실패했습니다. 다시 시도해 주세요"); + setToastMessage("게시물 생성에 실패했습니다. 다시 시도해 주세요.") + setShowsToast(true); } finally { setLoading(false); } @@ -189,6 +199,15 @@ function CreatePostPage() { onClick={handleCreate} /> + + {showsToast&& + + } ); } diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx index cc5e220..a15245f 100644 --- a/src/pages/messages-page.jsx +++ b/src/pages/messages-page.jsx @@ -25,6 +25,8 @@ import { useMedia } from "../hooks/use-media"; import { useModalDialog } from "../hooks/use-modal-dialog"; import ContentLayout from "../layouts/content-layout"; import { media } from "../utils/media"; +import Toast from "../components/toast/toast"; +import { useToast } from "../hooks/use-toast"; const Content = styled.div` & > div { @@ -111,6 +113,8 @@ function MessagesPage() { onPrimaryAction, onDismissDialog, } = useModalDialog(); + const [toastMessage, setToastMessage] = useState(); + const [hasMoreMessages, setHasMoreMessages] = useState(true); const isEditing = useMemo( () => location.pathname.includes("edit"), @@ -121,6 +125,12 @@ function MessagesPage() { navigate("edit"); }; + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); + + const handleToastCloseClick = () => setShowsToast(false); + const handleRollingPaperDelete = () => { openDialog({ title: `${recipient.name} 님의 롤링 페이퍼를 삭제할까요?`, @@ -130,8 +140,9 @@ function MessagesPage() { await deleteRecipient({ id: recipient.id }); navigate(`/list`); } catch (error) { - // TODO: Error 처리 console.log(error); + setToastMessage(error); + setShowsToast(true); } }, }); @@ -152,8 +163,9 @@ function MessagesPage() { prev.filter((prevMessage) => prevMessage.id !== message.id) ); } catch (error) { - // TODO: Error 처리 console.log(error); + setToastMessage(error); + setShowsToast(true); } }, }); @@ -168,8 +180,11 @@ function MessagesPage() { }; const handleInfiniteScroll = async () => { - const messages = await getNextPageMessages(); - if (!messages) return; + if (!hasMoreMessages) return; + + try { + const messages = await getNextPageMessages(); + if (!messages) return; setMessages((prev) => { const newMessages = [...prev]; @@ -181,6 +196,12 @@ function MessagesPage() { return newMessages; }); + } catch(error) { + console.error(error); + setToastMessage(error); + setShowsToast(true); + setHasMoreMessages(false); + } }; useEffect(() => { @@ -271,6 +292,15 @@ function MessagesPage() { } /> + + {showsToast&& + + } ); } diff --git a/src/pages/rolling-paper-list-page.jsx b/src/pages/rolling-paper-list-page.jsx index 7e780dc..1f6f507 100644 --- a/src/pages/rolling-paper-list-page.jsx +++ b/src/pages/rolling-paper-list-page.jsx @@ -7,6 +7,8 @@ import BUTTON_SIZE from "../components/button/button-size"; import RollingPaperList from "../features/rolling-paper/components/rolling-paper-list"; import { useMedia } from "../hooks/use-media"; import { media } from "../utils/media"; +import Toast from "../components/toast/toast"; +import { useToast } from "../hooks/use-toast"; const TopContainer = styled.div` text-align: center; @@ -71,9 +73,8 @@ function getCachedImage(url) { return cache[url].src; } -function RollingPaperListPage() { - const navigate = useNavigate(); +function RollingPaperListPage() { const [recipientsData, setRecipientsData] = useState([]); const [popularDataList, setPopularDataList] = useState([]); const [recentDataList, setRecentDataList] = useState([]); @@ -81,6 +82,15 @@ function RollingPaperListPage() { const [recentCurrentPage, setRecentCurrentPage] = useState(0); const [cardCount, setCardCount] = useState(4); const { isDesktop } = useMedia(); + const [isCalledApi, setIsCalledApi] = useState(true); + const [toastMessage, setToastMessage] = useState(); + const navigate = useNavigate(); + + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); + + const handleToastCloseClick = () => setShowsToast(false); useEffect(() => { isDesktop ? setCardCount(4) : setCardCount(null); @@ -90,16 +100,24 @@ function RollingPaperListPage() { navigate("/post"); }; + useEffect(() => { - apiClient - .get("/recipients/") - .then((res) => { + if (!isCalledApi) return; + + const fetchData = async () => { + try { + const res = await apiClient.get("/recipients/"); setRecipientsData(res.data.results); - }) - .catch((err) => { + } catch (err) { console.error("오류:", err); - }); - }, []); + setToastMessage("메시지를 가져오는 중 오류가 발생했어요.") + setShowsToast(true); + } finally { + setIsCalledApi(false); + } + }; + fetchData(); + }, [isCalledApi, setShowsToast]); useEffect(() => { recipientsData.forEach((data) => { @@ -183,6 +201,15 @@ function RollingPaperListPage() { onClick={handleMakingButton} /> + + {showsToast&& + + } ); } diff --git a/src/pages/send-message-page.jsx b/src/pages/send-message-page.jsx index 79e134a..aa97c67 100644 --- a/src/pages/send-message-page.jsx +++ b/src/pages/send-message-page.jsx @@ -12,6 +12,8 @@ import TextEditor from "../components/text-editor/text-editor"; import TextField from "../components/text-field/text-field"; import TEXT_FIELD_TYPE from "../components/text-field/text-field-type"; import { media } from "../utils/media"; +import Toast from "../components/toast/toast"; +import { useToast } from "../hooks/use-toast"; const SendContainer = styled.div` display: flex; @@ -110,8 +112,15 @@ function SendMessagePage() { title: "Noto Sans", fontFamily: "Noto Sans", }); + const [toastMessage, setToastMessage] = useState(); const navigate = useNavigate(); + const { showsToast, isOpen, setShowsToast, onDismiss } = useToast({ + timeout: 5000, + }); + + const handleToastCloseClick = () => setShowsToast(false); + const handleChange = (e) => { const value = e.target.value; setName(value); @@ -169,6 +178,8 @@ function SendMessagePage() { navigate(`/post/${recipientId}`); } catch (error) { console.error("메시지 저장 실패:", error); + setToastMessage("메시지를 저장하는 중 오류가 발생했어요.") + setShowsToast(true); } }; @@ -264,6 +275,15 @@ function SendMessagePage() { onClick={handleCreate} /> + + {showsToast&& + + } ); }