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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"JIWOO",
"josa",
"KAZUHA",
"Laneun",
"LEESEO",
"mbti",
"MINJI",
Expand Down
47 changes: 17 additions & 30 deletions src/pages/DonationDetail/components/DonationDetailInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down Expand Up @@ -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 = () => {
Expand All @@ -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 (
Expand Down Expand Up @@ -185,11 +173,10 @@ export default function DonationDetailInfo({ donation, loading }) {
<Button
disabled={credit === 0 || isError || isSubmitting}
fullWidth
type="button"
type="submit"
variant="primary"
onClick={handleDonate}
>
{isSubmitting ? "μΆ©μ „ 쀑..." : "μΆ©μ „ν•˜κΈ°"}
{isSubmitting ? "후원 처리 쀑..." : "ν›„μ›ν•˜κΈ°"}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ™€μš° λ””ν…ŒμΌ ...!

</Button>
</>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/DonationDetail/components/DonationDetailText.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, "을λ₯Ό")
: "";
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function DonationDetail({ donation, loading }) {
<dl>
<dt>⚑{idol.name}의 1λ…„ = μš°λ¦¬κ°€ λ§Œλ“  기적 ⚑</dt>
<dd>
λ‹¨μˆœν•œ μ‹œκ°„μ΄ μ•„λ‹ˆλΌ <br />🌟{idol.name}λΌλŠ” 이름 μ•„λž˜, μš°λ¦¬κ°€
λ‹¨μˆœν•œ μ‹œκ°„μ΄ μ•„λ‹ˆλΌ <br />🌟{idolWithLaneun} 이름 μ•„λž˜, μš°λ¦¬κ°€
ν•¨κ»˜ν•œ μ„œμ‚¬μ§‘πŸŒŸ
<br />
<br />이 μ•„λ¦„λ‹€μš΄ μ„œν¬νŠΈβ€¦ ν•¨κ»˜ν•΄μ£Όμ‹€ κ±°μ£ ? <br />
Expand Down
57 changes: 26 additions & 31 deletions src/pages/DonationDetail/index.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 =
Expand All @@ -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 (
<div className="mainGrid" css={DonationDetailStyle}>
{loading ? (
{isLoading ? (
<DonationDetailSkeleton />
) : error ? (
) : hasError ? (
<LoadingError />
) : (
) : donation ? (
<>
<div css={DonationDetailTop}>
<h2>
Expand All @@ -83,13 +76,15 @@ export default function DonationDetail() {
alt={donation.idol.name}
/>
</div>
<DonationDetailText donation={donation} loading={loading} />
<DonationDetailText donation={donation} />
</div>
<div css={DonationDetailInfoArea}>
<DonationDetailInfo donation={donation} loading={loading} />
<DonationDetailInfo donation={donation} />
</div>
</div>
</>
) : (
<LoadingError message="ν•΄λ‹Ή 후원 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€." />
)}
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions src/pages/List/Charge/components/CreditCharge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,28 @@ 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 (
<>
<div css={CreditChargeStyle}>
<div>
<p>λ‚΄ ν¬λ ˆλ”§</p>
<div className="credit">
<img src="/icons/icon_credit.svg" alt="credit" />
<span ref={creditRef}>{credit.toLocaleString()}</span>
<span ref={creditRef}>0</span>
</div>
</div>
<button type="button" onClick={openModal}>
Expand Down
5 changes: 3 additions & 2 deletions src/pages/List/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ const BlurStyle = css`
position: fixed;
top: 0;
left: 0;
width: 800px;
height: 800px;
width: 500px;
height: 500px;
transform: translate(-50%, -50%);
z-index: 100;
img {
width: 100%;
height: 100%;
Expand Down
1 change: 1 addition & 0 deletions src/utils/postPosition.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default function withPostPosition(word, josa) {
이가: hasBatchim ? "이" : "κ°€",
을λ₯Ό: hasBatchim ? "을" : "λ₯Ό",
으둜: hasBatchim ? "으둜" : "둜",
λΌλŠ”μ΄λΌλŠ”: hasBatchim ? "μ΄λΌλŠ”" : "λΌλŠ”",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옷! hasλ°›μ·Έ!

};

return word + (josaMap[josa] || "");
Expand Down
Loading