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;