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.toLocaleString()}
+
0