diff --git a/ios/PoppinProject/Info.plist b/ios/PoppinProject/Info.plist index 8789b163..d961219c 100644 --- a/ios/PoppinProject/Info.plist +++ b/ios/PoppinProject/Info.plist @@ -52,7 +52,7 @@ CFBundleVersion - 7 + 9 FirebaseAppDelegateProxyEnabled ITSAppUsesNonExemptEncryption diff --git a/src/apis/popup/detailPopUp.ts b/src/apis/popup/detailPopUp.ts index b559fc28..bf62fb65 100644 --- a/src/apis/popup/detailPopUp.ts +++ b/src/apis/popup/detailPopUp.ts @@ -10,6 +10,7 @@ const getDetailPopUp = async ( }); if (response.data.success) { + console.log('DetailPopUpDataNonPublic:', response.data); return response.data; } else { return { diff --git a/src/hooks/detailPopUp/useGetDetailPopUp.tsx b/src/hooks/detailPopUp/useGetDetailPopUp.tsx index f442af7c..72423c70 100644 --- a/src/hooks/detailPopUp/useGetDetailPopUp.tsx +++ b/src/hooks/detailPopUp/useGetDetailPopUp.tsx @@ -1,7 +1,14 @@ +// src/hooks/detailPopUp/useGetDetailPopUp.ts import {useState, useEffect} from 'react'; -import {DetailPopUpDataNonPublic} from '../../types/DetailPopUpDataNonPublic.ts'; -import getDetailPopUp from '../../apis/popup/detailPopUp.ts'; -import getDetailPopUpPublic from '../../apis/public/detailPopUpPublic.ts'; +import {DetailPopUpDataNonPublic} from '../../types/DetailPopUpDataNonPublic'; +import getDetailPopUp from '../../apis/popup/detailPopUp'; +import getDetailPopUpPublic from '../../apis/public/detailPopUpPublic'; +import {useDispatch} from 'react-redux'; +import { + setPopupDetailData, + setPopupDetailLoading, + setPopupDetailError, +} from '../../redux/slices/popupDetailSlice.ts'; interface DetailPopUpState { loading: boolean; @@ -12,7 +19,7 @@ interface DetailPopUpState { function useGetDetailPopUp( popUpId: number, isPublic: boolean, - fetchTrigger: boolean, // Add fetchTrigger as a parameter + fetchTrigger: boolean, ): DetailPopUpState { const [getDetailPopUpState, setGetDetailPopUpState] = useState({ @@ -21,43 +28,55 @@ function useGetDetailPopUp( error: null, }); + const dispatch = useDispatch(); + useEffect(() => { - // 팝업 상세 정보를 가져오는 비동기 함수 const fetchDetailPopUp = async () => { setGetDetailPopUpState({ loading: true, data: null, error: null, }); + dispatch(setPopupDetailLoading(true)); + try { const response = isPublic ? await getDetailPopUpPublic(popUpId) : await getDetailPopUp(popUpId); - if (response.success) { + + if (response.success && response.data) { setGetDetailPopUpState({ loading: false, data: response.data, error: null, }); - console.log(response.data); + dispatch(setPopupDetailData(response.data)); } else { + const errorMessage = + response.error?.message || 'An unknown error occurred'; setGetDetailPopUpState({ loading: false, data: null, - error: response.error?.message || 'An unknown error occurred', + error: errorMessage, }); + dispatch(setPopupDetailError(errorMessage)); } } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'An error occurred'; setGetDetailPopUpState({ loading: false, data: null, - error: error instanceof Error ? error.message : 'An error occurred', + error: errorMessage, }); + dispatch(setPopupDetailError(errorMessage)); + } finally { + dispatch(setPopupDetailLoading(false)); } }; fetchDetailPopUp(); - }, [popUpId, isPublic, fetchTrigger]); // Add fetchTrigger as a dependency + }, [popUpId, isPublic, fetchTrigger, dispatch]); return getDetailPopUpState; } diff --git a/src/hooks/review/useCreateReview.tsx b/src/hooks/review/useCreateReview.tsx index 964e2cc3..38b32a7b 100644 --- a/src/hooks/review/useCreateReview.tsx +++ b/src/hooks/review/useCreateReview.tsx @@ -1,6 +1,10 @@ +// src/hooks/review/useCreateReview.tsx import {useState} from 'react'; import createPopUpReview from '../../apis/popup/createReview.tsx'; import {ImageType} from '../../types/ImageType.ts'; +import {useDispatch} from 'react-redux'; +import {setReviewSubmitted} from '../../redux/slices/reviewSubmittedSlice.ts'; +import getDetailPopUp from '../../apis/popup/detailPopUp'; interface CreateReviewState { loading: boolean; @@ -17,6 +21,8 @@ const useCreateReview = () => { }, ); + const dispatch = useDispatch(); + const createReview = async ( popupId: number, text: string, @@ -44,7 +50,7 @@ const useCreateReview = () => { isVisited, ); if (response.success) { - console.log(response); + dispatch(setReviewSubmitted(true)); setCreateReviewState({loading: false, error: null, success: true}); return response; } else { @@ -66,6 +72,7 @@ const useCreateReview = () => { return {success: false, error: {code: 'unknown', message: err.message}}; } }; + return {...createReviewState, createReview}; }; diff --git a/src/pages/detail/PopUpDetailScreen.tsx b/src/pages/detail/PopUpDetailScreen.tsx index 8fd4dbbf..a5048762 100644 --- a/src/pages/detail/PopUpDetailScreen.tsx +++ b/src/pages/detail/PopUpDetailScreen.tsx @@ -12,46 +12,52 @@ import { Text, View, } from 'react-native'; -import useGetDetailPopUp from '../../hooks/detailPopUp/useGetDetailPopUp.tsx'; +import {useDispatch, useSelector} from 'react-redux'; +import { + toggleInterest, + setInterest, +} from '../../redux/slices/interestedPopUpSlice'; // 추가 +import useGetDetailPopUp from '../../hooks/detailPopUp/useGetDetailPopUp'; import ShareSvg from '../../assets/detail/share.svg'; import StarOffSvg from '../../assets/detail/starOff.svg'; import StarOnSvg from '../../assets/detail/starOn.svg'; import MapSvg from '../../assets/detail/map.svg'; -import Text20B from '../../styles/texts/title/Text20B.ts'; -import Text14R from '../../styles/texts/body_medium/Text14R.ts'; -import globalColors from '../../styles/color/globalColors.ts'; +import Text20B from '../../styles/texts/title/Text20B'; +import Text14R from '../../styles/texts/body_medium/Text14R'; +import globalColors from '../../styles/color/globalColors'; import DetailDividerLine from '../../assets/detail/detailDivider.svg'; -import Text14B from '../../styles/texts/body_medium/Text14B.ts'; -import Text14M from '../../styles/texts/body_medium/Text14M.ts'; -import RealTimeVisitorsViewButton from '../../components/atoms/button/CommonButton.tsx'; -import DividerLine from '../../components/DividerLine.tsx'; -import useAddInterestPopUp from '../../hooks/detailPopUp/useAddInterestPopUp.tsx'; -import useDeleteInterestPopUp from '../../hooks/detailPopUp/useDeleteInterestPopUp.tsx'; +import Text14B from '../../styles/texts/body_medium/Text14B'; +import Text14M from '../../styles/texts/body_medium/Text14M'; +import RealTimeVisitorsViewButton from '../../components/atoms/button/CommonButton'; +import DividerLine from '../../components/DividerLine'; +import useAddInterestPopUp from '../../hooks/detailPopUp/useAddInterestPopUp'; +import useDeleteInterestPopUp from '../../hooks/detailPopUp/useDeleteInterestPopUp'; import ReviewProfileSvg from '../../assets/detail/reviewProfile.svg'; import VerifiedReviewSvg from '../../assets/detail/verifiedReview.svg'; import WriteReviewSvg from '../../assets/detail/writeReview.svg'; -import SvgWithNameBoxLabel from '../../components/SvgWithNameBoxLabel.tsx'; -import UnderlinedTextButton from '../../components/UnderlineTextButton.tsx'; +import SvgWithNameBoxLabel from '../../components/SvgWithNameBoxLabel'; +import UnderlinedTextButton from '../../components/UnderlineTextButton'; import LikeReviewSvg from '../../assets/detail/likesReview.svg'; -import Text16M from '../../styles/texts/body_medium_large/Text16M.ts'; -import OrderSvg from '../../assets/icons/order.svg'; -import ReasonItem from '../../components/ReasonItem.tsx'; -import CongestionSection from '../../components/organisms/section/CongestionSection.tsx'; -import {VisitorDataDetail} from '../../types/DetailPopUpDataNonPublic.ts'; +import Text16M from '../../styles/texts/body_medium_large/Text16M'; +import OrderSvg from '../../assets/icons/order'; +import ReasonItem from '../../components/ReasonItem'; +import CongestionSection from '../../components/organisms/section/CongestionSection'; +import {VisitorDataDetail} from '../../types/DetailPopUpDataNonPublic'; import WebSvg from '../../assets/detail/web.svg'; import InstagramTestSvg from '../../assets/detail/instagramTest.svg'; -import ToastComponent from '../../components/atoms/toast/ToastComponent.tsx'; -import VisitButton from '../../components/atoms/button/VisitButton.tsx'; +import ToastComponent from '../../components/atoms/toast/ToastComponent'; +import VisitButton from '../../components/atoms/button/VisitButton'; import VisitModalSvg from '../../assets/detail/visitModal.svg'; -import CustomModal from '../../components/atoms/modal/CustomModal.tsx'; +import CustomModal from '../../components/atoms/modal/CustomModal'; import Geolocation from 'react-native-geolocation-service'; -import useAddRecommendReview from '../../hooks/detailPopUp/useAddRecommendReview.tsx'; +import useAddRecommendReview from '../../hooks/detailPopUp/useAddRecommendReview'; import {useNavigation} from '@react-navigation/native'; -import useIsLoggedIn from '../../hooks/auth/useIsLoggedIn.tsx'; -import useAddVisitor from '../../hooks/detailPopUp/useAddVisitor.ts'; +import useIsLoggedIn from '../../hooks/auth/useIsLoggedIn'; +import useAddVisitor from '../../hooks/detailPopUp/useAddVisitor'; import {NativeStackNavigationProp} from '@react-navigation/native-stack'; -import {AppNavigatorParamList} from '../../types/AppNavigatorParamList.ts'; -import PopUpDetailOptions from '../../navigators/options/PopUpDetailOptions.tsx'; +import {AppNavigatorParamList} from '../../types/AppNavigatorParamList'; +import PopUpDetailOptions from '../../navigators/options/PopUpDetailOptions'; +import {RootState} from '../../redux/stores/reducer'; async function requestPermissions() { if (Platform.OS === 'ios') { @@ -114,6 +120,10 @@ const PopUpDetailScreen = ({route}) => { const navigation = useNavigation(); const [fetchTrigger, setFetchTrigger] = useState(false); const {id, name} = route.params; + const dispatch = useDispatch(); + const isInterested = useSelector( + (state: RootState) => state.interestedPopups[id], + ); // 추가 const { data: detailPopUpData, @@ -130,13 +140,18 @@ const PopUpDetailScreen = ({route}) => { name: detailPopUpData.name, }), ); + dispatch( + setInterest({ + popupId: detailPopUpData.id, + isInterested: detailPopUpData.isInterested, + }), + ); // 추가 } - }, [navigation, detailPopUpData]); + }, [navigation, detailPopUpData, dispatch]); const firstImageUrl = detailPopUpData?.images?.[0] ?? 'https://v1-popup-poster.s3.ap-northeast-2.amazonaws.com/4/1.jpg'; - const [isInterested, setIsInterested] = useState(false); const [isShowToast, setIsShowToast] = useState(false); const [toastMessage, setToastMessage] = useState(''); const {addRecommendCount} = useAddRecommendReview(); @@ -154,7 +169,6 @@ const PopUpDetailScreen = ({route}) => { useEffect(() => { if (detailPopUpData) { - setIsInterested(detailPopUpData.isInterested); setToastMessage('이 팝업이 근처에 있어요!!'); setIsShowToast(true); } @@ -220,7 +234,7 @@ const PopUpDetailScreen = ({route}) => { setToastMessage('관심팝업에 저장되었어요!'); } setIsShowToast(true); - setIsInterested(!isInterested); + dispatch(toggleInterest(id)); // 추가 }; const handleOpenLink = url => { @@ -250,7 +264,6 @@ const PopUpDetailScreen = ({route}) => { } setIsShowToast(true); }; - if (loading) { return ( diff --git a/src/pages/detail/PopUpEditRequestScreen.tsx b/src/pages/detail/PopUpEditRequestScreen.tsx index bbdb70b6..ecbbc4fc 100644 --- a/src/pages/detail/PopUpEditRequestScreen.tsx +++ b/src/pages/detail/PopUpEditRequestScreen.tsx @@ -70,7 +70,8 @@ function PopUpEditRequestScreen() { if (response.success) { navigation.goBack(); } else if (response.error) { - console.error('Review submission error:', response.error.message); + navigation.goBack(); + // console.error('Review submission error:', response.error.message); } setCompleteModalVisible(true); }; diff --git a/src/pages/myPage/ReviewWriteScreen.tsx b/src/pages/myPage/ReviewWriteScreen.tsx index ea3918c7..efc67147 100644 --- a/src/pages/myPage/ReviewWriteScreen.tsx +++ b/src/pages/myPage/ReviewWriteScreen.tsx @@ -22,6 +22,7 @@ import Text12R from '../../styles/texts/label/Text12R'; import {AppNavigatorParamList} from '../../types/AppNavigatorParamList.ts'; import useCreateReview from '../../hooks/review/useCreateReview.tsx'; import {useImageSelector} from '../../hooks/useImageSelector.tsx'; +import getDetailPopUp from '../../apis/popup/detailPopUp.ts'; type ReviewWriteScreenRouteProp = RouteProp< AppNavigatorParamList, @@ -71,7 +72,10 @@ function ReviewWriteScreen() { isVisited, ); if (response.success) { - navigation.goBack(); + const resp = await getDetailPopUp(popupId); + if (resp.success) { + navigation.goBack(); + } } else if (response.error) { console.error('Review submission error:', response.error.message); } diff --git a/src/redux/slices/popupDetailSlice.ts b/src/redux/slices/popupDetailSlice.ts new file mode 100644 index 00000000..ccc185e2 --- /dev/null +++ b/src/redux/slices/popupDetailSlice.ts @@ -0,0 +1,39 @@ +// src/slices/popupDetailSlice.ts +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; +import {DetailPopUpDataNonPublic} from '../../types/DetailPopUpDataNonPublic'; + +interface PopupDetailState { + data: DetailPopUpDataNonPublic | null; + loading: boolean; + error: string | null; +} + +const initialState: PopupDetailState = { + data: null, + loading: false, + error: null, +}; + +const popupDetailSlice = createSlice({ + name: 'popupDetail', + initialState, + reducers: { + setPopupDetailData( + state, + action: PayloadAction, + ) { + state.data = action.payload; + }, + setPopupDetailLoading(state, action: PayloadAction) { + state.loading = action.payload; + }, + setPopupDetailError(state, action: PayloadAction) { + state.error = action.payload; + }, + }, +}); + +export const {setPopupDetailData, setPopupDetailLoading, setPopupDetailError} = + popupDetailSlice.actions; + +export default popupDetailSlice; diff --git a/src/redux/slices/reviewSubmittedSlice.ts b/src/redux/slices/reviewSubmittedSlice.ts new file mode 100644 index 00000000..1a536dc8 --- /dev/null +++ b/src/redux/slices/reviewSubmittedSlice.ts @@ -0,0 +1,13 @@ +// src/redux/slices/reviewSubmittedSlice.ts +import {createSlice} from '@reduxjs/toolkit'; + +const reviewSubmittedSlice = createSlice({ + name: 'reviewSubmitted', + initialState: false, + reducers: { + setReviewSubmitted: (state, action) => action.payload, + }, +}); + +export const {setReviewSubmitted} = reviewSubmittedSlice.actions; +export default reviewSubmittedSlice; diff --git a/src/redux/stores/reducer.ts b/src/redux/stores/reducer.ts index 7b0fa0c9..01d0908a 100644 --- a/src/redux/stores/reducer.ts +++ b/src/redux/stores/reducer.ts @@ -3,13 +3,18 @@ import userSlice from '../slices/user.ts'; import loadingSlice from '../slices/loading.ts'; import bottomSheetSlice from '../slices/bottomSheetSlice.ts'; import preferenceSlice from '../slices/preferenceSlice.ts'; - +import interestedPopupsSlice from '../slices/interestedPopUpSlice.ts'; +import popupDetailSlice from '../slices/popupDetailSlice.ts'; +import reviewSubmittedSlice from '../slices/reviewSubmittedSlice.ts'; // 추가 // 모든 상태를 결합 const rootReducer = combineReducers({ user: userSlice.reducer, loading: loadingSlice.reducer, bottomSheet: bottomSheetSlice.reducer, preference: preferenceSlice.reducer, + interestedPopups: interestedPopupsSlice.reducer, // 추가 + popupDetail: popupDetailSlice.reducer, // 추가 + reviewSubmitted: reviewSubmittedSlice.reducer, }); export default rootReducer;