diff --git a/service/package.json b/service/package.json index 72e4bb0..a9fd602 100644 --- a/service/package.json +++ b/service/package.json @@ -21,6 +21,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.5.1", "framer-motion": "^11.3.28", + "js-confetti": "^0.12.0", "json-server": "^1.0.0-beta.1", "micro-slider": "^1.1.0", "postcss": "^8.4.39", diff --git a/service/src/App.jsx b/service/src/App.jsx index 247f0f0..01fed5e 100644 --- a/service/src/App.jsx +++ b/service/src/App.jsx @@ -16,6 +16,7 @@ function App() { /^\/event\/noQuiz$/, /^\/event\/reward$/, /^\/event\/comments\/commentId\/\d+$/, + /^\/event\/\w+$/, ]; const hideHeader = hideHeaderPattern.some(pattern => @@ -30,6 +31,7 @@ function App() { /^\/event\/noQuiz$/, /^\/event\/reward$/, /^\/event\/comments\/commentId\/\d+$/, + /^\/event\/\w+$/, ]; const hideFooter = hideFooterPattern.some(pattern => diff --git a/service/src/assets/icons/Loading.svg b/service/src/assets/icons/Loading.svg new file mode 100644 index 0000000..b04b312 --- /dev/null +++ b/service/src/assets/icons/Loading.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/service/src/components/modal/EventResultModal.jsx b/service/src/components/modal/EventResultModal.jsx index 678b07a..ff1f367 100644 --- a/service/src/components/modal/EventResultModal.jsx +++ b/service/src/components/modal/EventResultModal.jsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; - import ModalFrame from './ModalFrame'; import BlueButton from '@/components/buttons/BlueButton'; import BlueCheckIcon from '@/assets/icons/blueCheckIcon.svg'; @@ -26,7 +25,11 @@ function EventResultModal({ closeResultModal, data, handleSendPrize }) { > {data.isDrawWin ? (
- successImage + successImage { - try { - const response = await postReward(); - if (response && response.image) { - const updatedUserInfo = { ...userInfo, toolBoxCnt: toolBoxCnt - 1 }; - setUserInfo(updatedUserInfo); - navigate(`/event/reward`, { state: response }); - } else { - console.log('유효하지 않은 응답입니다: ', response); - } - } catch (error) { - console.error(error); - } + navigate(`/event/reward`); }; return ( diff --git a/service/src/pages/joinEvent/Reward.jsx b/service/src/pages/joinEvent/Reward.jsx index f4b6c9d..0853991 100644 --- a/service/src/pages/joinEvent/Reward.jsx +++ b/service/src/pages/joinEvent/Reward.jsx @@ -1,15 +1,63 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import EventResultModal from '@/components/modal/EventResultModal'; import PrizeLinkSentModal from '@/components/modal/PrizeLinkSentModal'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { postSendPrize } from '@/api/rapple/index'; +import { useNavigate } from 'react-router-dom'; +import { postReward, postSendPrize } from '@/api/rapple/index'; +import { AuthContext } from '@/context/authContext'; +import Loading from '@/assets/icons/Loading.svg'; +import BlueButton from '@/components/buttons/BlueButton'; +import JSConfetti from 'js-confetti'; function Reward() { const navigate = useNavigate(); - const [openResultModal, setOpenResultModal] = useState(true); + const [openResultModal, setOpenResultModal] = useState(false); const [openMessageModal, setOpenMessageModal] = useState(false); const [resultImage, setResultImage] = useState(''); - const data = useLocation().state; + const { userInfo, setUserInfo } = useContext(AuthContext); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const [data, setData] = useState({}); + const { toolBoxCnt } = userInfo; + + useEffect(() => { + const confetti = new JSConfetti(); + + const timer = setTimeout(() => { + handleReward(confetti); + }, 1500); + + return () => clearTimeout(timer); + }, []); + + const handleReward = async confetti => { + if (userInfo.toolBoxCnt > 0) { + try { + const response = await postReward(); + console.log(response); + if (response && response.image) { + setData(response); + const updatedUserInfo = { ...userInfo, toolBoxCnt: toolBoxCnt - 1 }; + setUserInfo(updatedUserInfo); + setLoading(false); + setOpenResultModal(true); + if (response.isDrawWin && confetti) { + confetti.addConfetti({ + confettiColors: ['#CAB0FF'], + confettiNumber: 500, + }); + } + } else { + console.log('유효하지 않은 응답입니다: ', response); + setError(true); + } + } catch (error) { + console.error(error); + setError(true); + } + } else { + setError(true); + } + }; const closeResultModal = () => { setOpenResultModal(false); @@ -26,8 +74,44 @@ function Reward() { } }; + const goToEventPage = () => { + navigate('/event'); + }; + + if (error) { + return ( +
+

+ 예상하지 못한 에러가 발생했습니다..! +

+ +
+ ); + } + return (
+ {loading && !error && ( +
+
+ Loading +

+ 경품을 응모중입니다. +

+

+ 잠시만 기다려주세요. +

+
+
+ )} {openResultModal && ( { setOpenCommentModal(false); @@ -69,6 +71,7 @@ function CommentDetail() { setComment(response); setIsLiked(response.isLiked); setLikeCount(response.likeCount); + setLoading(false); } catch (error) { console.error('Failed to fetch comment detail:', error); } @@ -82,8 +85,13 @@ function CommentDetail() { fetchComment(); }, [userInfo]); - if (!comment) { - return
Loading...
; + if (loading) { + console.log('ddd'); + return ( +
+ Loading +
+ ); } return ( diff --git a/service/src/pages/joinEvent/commentList/Redirect.jsx b/service/src/pages/joinEvent/commentList/Redirect.jsx index 0555c08..454551e 100644 --- a/service/src/pages/joinEvent/commentList/Redirect.jsx +++ b/service/src/pages/joinEvent/commentList/Redirect.jsx @@ -1,6 +1,7 @@ -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { getRedirectLink } from '@/api/comment/index'; import { useParams, useNavigate } from 'react-router-dom'; +import Loading from '@/assets/icons/Loading.svg'; function Redirect() { const navigate = useNavigate(); @@ -13,6 +14,12 @@ function Redirect() { useEffect(() => { getLink(); }, []); + + return ( +
+ Loading +
+ ); } export default Redirect; diff --git a/service/src/styles/global.css b/service/src/styles/global.css index 1081974..3e0b369 100644 --- a/service/src/styles/global.css +++ b/service/src/styles/global.css @@ -27,3 +27,16 @@ transition: transform 1s; transform: rotate(180deg); } + +.rotate-360 { + animation: spin 2s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/service/yarn.lock b/service/yarn.lock index 20705d1..f4603d3 100644 --- a/service/yarn.lock +++ b/service/yarn.lock @@ -4032,6 +4032,11 @@ jiti@^1.21.0: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-confetti@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/js-confetti/-/js-confetti-0.12.0.tgz#5ed74dc6f430c0137115f350b2e3f1f119840157" + integrity sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"