diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cf5fac..1b4af39 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,6 +40,7 @@ "JIWOO", "josa", "KAZUHA", + "Laneun", "LEESEO", "mbti", "MINJI", diff --git a/src/pages/DonationDetail/components/DonationDetailInfo.jsx b/src/pages/DonationDetail/components/DonationDetailInfo.jsx index 3823a7d..4f53a53 100644 --- a/src/pages/DonationDetail/components/DonationDetailInfo.jsx +++ b/src/pages/DonationDetail/components/DonationDetailInfo.jsx @@ -20,25 +20,33 @@ export default function DonationDetailInfo({ donation, loading }) { const [isScrollDown, setIsScrollDown] = useState(false); const [isError, setIsError] = useState(false); const [donatedAmount, setDonatedAmount] = useState(receivedDonations); - const { isSubmitting, safeSubmit } = useSafeSubmit(); + const { isSubmitting, safeSubmit } = useSafeSubmit(async () => { + if (credit === 0 || isError) return; + + try { + await donationsAPI.contribute(donation.id, credit); + myCredit.deductCredit(credit); + setDonatedAmount((prev) => prev + credit); + toast.success("후원에 성공하셨습니다!"); + setCredit(0); + } catch (error) { + console.error("후원 처리 중 오류 발생:", error); + alert(error.message || "후원 처리 중 오류가 발생했습니다."); + } + }); const checkIsLimitOver = (newCredit) => { - // 보유 크레딧보다 많은지 확인 const isOverLimit = newCredit > (myCredit.credit || 0); - // 에러 상태 업데이트 setIsError(isOverLimit); - // 크레딧 값 업데이트 setCredit(newCredit); if (isOverLimit) { - // 에러 메시지 표시 또는 최대값으로 제한 - // toast.error 호출 시 toastId 옵션 추가 - 연속 입력 시 중복 표시 방지 toast.error("보유한 크레딧을 초과할 수 없습니다.", { toastId: "credit-error", }); - setCredit(myCredit.credit || 0); // 최대값으로 제한 + setCredit(myCredit.credit || 0); } else { toast.dismiss("credit-error"); } @@ -83,25 +91,6 @@ export default function DonationDetailInfo({ donation, loading }) { setIsModalOpen(false); }; - const handleDonate = async () => { - if (credit === 0 || isError) return; - - try { - // 후원 요청 - await donationsAPI.contribute(donation.id, credit); - // 후원 금액 반영 - myCredit.deductCredit(credit); - // 후원 금액 반영 - setDonatedAmount((prev) => prev + credit); // 상태 업데이트 - toast.success("후원에 성공하셨습니다!"); - setCredit(0); //크레딧 초기화 - } catch (error) { - alert(error.message); - } - }; - - // * window의 스크롤 위치 구하기, - // * 스크롤 위치가 400px 이상이면 isScrollDown을 true로 설정 useEffect(() => { if (window.innerWidth <= 768) return; const handleWindowScroll = () => { @@ -122,7 +111,6 @@ export default function DonationDetailInfo({ donation, loading }) { const value = e.target.value.replace(/[^0-9]/g, ""); const newValue = value === "" ? 0 : Number(value); - // 입력값이 보유 크레딧보다 크면 에러 상태로 설정 checkIsLimitOver(newValue); }; return ( @@ -185,11 +173,10 @@ export default function DonationDetailInfo({ donation, loading }) { )} diff --git a/src/pages/DonationDetail/components/DonationDetailText.jsx b/src/pages/DonationDetail/components/DonationDetailText.jsx index 08f04fa..c5c0760 100644 --- a/src/pages/DonationDetail/components/DonationDetailText.jsx +++ b/src/pages/DonationDetail/components/DonationDetailText.jsx @@ -7,6 +7,7 @@ export default function DonationDetail({ donation, loading }) { const idolWithGa = idol ? withPostPosition(idol.name, "이가") : ""; const idolWithEun = idol ? withPostPosition(idol.name, "은는") : ""; + const idolWithLaneun = idol ? withPostPosition(idol.name, "라는이라는") : ""; const donationWithEul = donation ? withPostPosition(donation.subtitle, "을를") : ""; @@ -72,7 +73,7 @@ export default function DonationDetail({ donation, loading }) {
⚡{idol.name}의 1년 = 우리가 만든 기적 ⚡
- 단순한 시간이 아니라
🌟{idol.name}라는 이름 아래, 우리가 + 단순한 시간이 아니라
🌟{idolWithLaneun} 이름 아래, 우리가 함께한 서사집🌟

이 아름다운 서포트… 함께해주실 거죠?
diff --git a/src/pages/DonationDetail/index.jsx b/src/pages/DonationDetail/index.jsx index c5260c7..f0db2db 100644 --- a/src/pages/DonationDetail/index.jsx +++ b/src/pages/DonationDetail/index.jsx @@ -1,7 +1,7 @@ -import { donationsAPI } from "@/apis/donationsAPI"; import LoadingError from "@/components/Error"; +import { useDonations } from "@/hooks/useDonation"; import { css } from "@emotion/react"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import DonationDetailInfo from "./components/DonationDetailInfo"; import DonationDetailSkeleton from "./components/DonationDetailSkeleton"; @@ -10,35 +10,23 @@ import DonationDetailText from "./components/DonationDetailText"; export default function DonationDetail() { const { id } = useParams(); + const { + donations, + loading: donationsLoading, + error: donationsError, + } = useDonations(); const [donation, setDonation] = useState(null); - const [loading, setLoading] = useState(true); const [randomEmoji, setRandomEmoji] = useState(""); - const [error, setError] = useState(false); - - const getDonation = useCallback(async () => { - try { - const response = await donationsAPI.getDonations(); - if (response) { - const foundDonation = response.list.find( - (item) => item.id === Number.parseInt(id) || item.id === id, - ); - setDonation(foundDonation || null); - } - } catch (error) { - // 로딩 UI 잠시 유지 - setTimeout(() => { - setError(true); - }, 600); // 600초 정도 기다렸다가 에러 표시 - } finally { - setLoading(false); - } - }, [id]); useEffect(() => { - getDonation(); - }, [getDonation]); + if (!donationsLoading && donations.length > 0) { + const foundDonation = donations.find( + (item) => item.id === Number.parseInt(id) || item.id === id, + ); + setDonation(foundDonation || null); + } + }, [donations, donationsLoading, id]); - // 랜덤 이모지 - 초기 렌더링에서만 이모지를 선택 useEffect(() => { const emojiKeys = Object.keys(emojis); const selectedEmoji = @@ -57,13 +45,18 @@ export default function DonationDetail() { ribbon: "🎀", }; + const isLoading = + donationsLoading || (!donationsError && !donation && donations.length > 0); + const hasError = + donationsError || (!donationsLoading && !donation && donations.length > 0); + return (
- {loading ? ( + {isLoading ? ( - ) : error ? ( + ) : hasError ? ( - ) : ( + ) : donation ? ( <>

@@ -83,13 +76,15 @@ export default function DonationDetail() { alt={donation.idol.name} />

- +
- +
+ ) : ( + )} ); diff --git a/src/pages/List/Charge/components/CreditCharge.jsx b/src/pages/List/Charge/components/CreditCharge.jsx index 1898860..0f77240 100644 --- a/src/pages/List/Charge/components/CreditCharge.jsx +++ b/src/pages/List/Charge/components/CreditCharge.jsx @@ -52,20 +52,20 @@ export default function CreditCharge() { }, []); useEffect(() => { - // credit이 0이 아닌 정수일 때 동작하게 처리 + // credit이 변했을 때만(충전시에만) 애니메이션 호출되고, + // 초기 렌더링 시에는 0부터 카운트업 if (typeof credit === "number") { if (isInitialMount.current) { - // 새로고침 혹은 처음 마운트 시 countCredit(0, credit); previousCreditRef.current = credit; isInitialMount.current = false; - } else { - // 그 이후 credit 변경 시 + } else if (previousCreditRef.current !== credit) { countCredit(previousCreditRef.current, credit); previousCreditRef.current = credit; } } }, [countCredit, credit]); + return ( <>
@@ -73,7 +73,7 @@ export default function CreditCharge() {

내 크레딧

credit - {credit.toLocaleString()} + 0