diff --git a/src/api/diary/get.ts b/src/api/diary/get.ts index 6794cd1..b512112 100644 --- a/src/api/diary/get.ts +++ b/src/api/diary/get.ts @@ -11,7 +11,7 @@ export const fetchDiaryList = async (targetDate: string): Promise { return useQuery({ - queryKey: ['diaryList', targetDate], + queryKey: ['diary', 'list', targetDate], queryFn: () => fetchDiaryList(targetDate), enabled: !!dateStatus, staleTime: Infinity, @@ -35,14 +35,13 @@ export const useDiaryList = (targetDate: string, dateStatus: DateStatus | null) }; export const fetchDiaryCounts = async ({ year, month }: IDate): Promise => { - console.log('fetchDiaryCounts', year, month); const response = await instance.get(`/diary/${year}/${month}`); return response.data; }; export const useDiaryCounts = ({ year, month }: IDate): UseQueryResult => { return useQuery({ - queryKey: ['diaryCounts', year, month], + queryKey: ['diary', 'counts', year, month], queryFn: () => fetchDiaryCounts({ year, month }), enabled: !!year && !!month, staleTime: Infinity, diff --git a/src/components/setting/DeleteAccountModal.tsx b/src/components/common/BasicConfirmModal.tsx similarity index 58% rename from src/components/setting/DeleteAccountModal.tsx rename to src/components/common/BasicConfirmModal.tsx index 0be8e29..ef7c364 100644 --- a/src/components/setting/DeleteAccountModal.tsx +++ b/src/components/common/BasicConfirmModal.tsx @@ -3,21 +3,15 @@ import MyText from '@components/common/MyText'; import TextButton from '@components/common/TextButton'; import { StyleSheet, View } from 'react-native'; import MyModal from '@components/common/MyModal'; -import useLogout from '@hooks/login/logoutHook'; - -interface IDeleteAccountModalProps { - visible: boolean; - setIsVisible: React.Dispatch>; -} - -const DeleteAccountModal = ({ visible, setIsVisible }: IDeleteAccountModalProps) => { - const { deleteAccountMutation } = useLogout(); - - const onDeleteAccount = () => { - deleteAccountMutation.mutate(); - setIsVisible(false); - }; +import { IBasicModalProps } from '@type/Modal'; +const BasicConfirmModal = ({ + visible, + setIsVisible, + onConfirm, + content, + confirmText = '확인', +}: IBasicModalProps) => { const onCancel = () => { setIsVisible(false); }; @@ -25,12 +19,11 @@ const DeleteAccountModal = ({ visible, setIsVisible }: IDeleteAccountModalProps) return ( - 정말로 탈퇴하시겠습니까? - 작성한 모든 심심기록이 지워집니다. + {content} 취소 - 확인 + {confirmText} ); @@ -52,4 +45,4 @@ const styles = StyleSheet.create({ }, }); -export default DeleteAccountModal; +export default BasicConfirmModal; diff --git a/src/components/common/MyModal.tsx b/src/components/common/MyModal.tsx index 6b971aa..97d909a 100644 --- a/src/components/common/MyModal.tsx +++ b/src/components/common/MyModal.tsx @@ -1,12 +1,6 @@ import React from 'react'; -import { Modal, ModalProps, Pressable, StyleProp, StyleSheet, ViewStyle } from 'react-native'; - -interface IMyModalProps extends ModalProps { - visible: boolean; - setIsVisible: (visible: boolean) => void; - children: React.ReactNode; - containerStyle?: StyleProp; -} +import { Modal, Pressable, StyleSheet } from 'react-native'; +import { IMyModalProps } from '@type/Modal'; const MyModal = ({ visible, setIsVisible, children, containerStyle, ...rest }: IMyModalProps) => { const onClose = () => { diff --git a/src/components/diary/carousel/DiaryCard.tsx b/src/components/diary/carousel/DiaryCard.tsx index ce5c1df..44bb6cc 100644 --- a/src/components/diary/carousel/DiaryCard.tsx +++ b/src/components/diary/carousel/DiaryCard.tsx @@ -12,6 +12,7 @@ import { patchDiary } from '@api/diary/patch'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { snackMessage } from '@stores/snackMessage'; import { tense } from '@stores/tense'; +import BasicConfirmModal from '@components/common/BasicConfirmModal'; const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiaryCardProps) => { const [diaryInput, setDiaryInput] = useState(''); @@ -19,6 +20,7 @@ const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiary const dateStatus = useRecoilValue(tense); const setSnackbar = useSetRecoilState(snackMessage); const queryClient = useQueryClient(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); useEffect(() => { setDiaryInput(id === NEW_DIARY ? '' : content); @@ -28,13 +30,12 @@ const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiary const addNewDiary = useMutation({ mutationFn: (data: IDiaryPostRequest) => postDiary(data), onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['diaryCounts'] }); - queryClient.invalidateQueries({ queryKey: ['diaryList'] }); + queryClient.invalidateQueries({ queryKey: ['diary'] }); setSnackbar('저장이 완료되었습니다.'); setTimeStartWriting(''); }, onError: (error) => { - setSnackbar(error.response.data.message); + setSnackbar(error.response.data.message || '오류가 발생했습니다.'); }, onSettled: () => { setIsEditing(false); @@ -43,27 +44,27 @@ const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiary const removeDiary = useMutation({ mutationFn: (id: number) => deleteDiary(id), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['diaryCounts'] }); - queryClient.invalidateQueries({ queryKey: ['diaryList'] }); + queryClient.invalidateQueries({ queryKey: ['diary'] }); setSnackbar('삭제가 완료되었습니다.'); }, onError: (error) => { - setSnackbar(error.response.data.message); + setSnackbar(error.response.data.message || '오류가 발생했습니다.'); }, onSettled: () => { setIsEditing(false); setDiaryInput(''); + setIsDeleteModalVisible(false); }, }); const editDiary = useMutation({ mutationFn: (data: IDiaryPatchRequest) => patchDiary(data), onSuccess: (data) => { setTimeStartWriting(data.createdDate); - queryClient.invalidateQueries({ queryKey: ['diaryList'] }); + queryClient.invalidateQueries({ queryKey: ['diary', 'list'] }); setSnackbar('수정이 완료되었습니다.'); }, onError: (error) => { - setSnackbar(error.response.data.message); + setSnackbar(error.response.data.message || '오류가 발생했습니다.'); }, onSettled: () => { setIsEditing(false); @@ -78,6 +79,13 @@ const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiary setSnackbar('수정이 취소되었습니다.'); return; } + }; + + const onDelete = () => { + setIsDeleteModalVisible(true); + }; + + const onConfirmDelete = () => { removeDiary.mutate(id); }; @@ -109,40 +117,49 @@ const DiaryCard = ({ id, createdTime, content, isEditing, setIsEditing }: IDiary }; return ( - - - - {dateStatus === 'TODAY' ? ( - + + + - ) : ( - - )} - - + {dateStatus === 'TODAY' ? ( + + ) : ( + + )} + + + + ); }; diff --git a/src/components/diary/carousel/DiaryCardHeader.tsx b/src/components/diary/carousel/DiaryCardHeader.tsx index 05def81..fd47c16 100644 --- a/src/components/diary/carousel/DiaryCardHeader.tsx +++ b/src/components/diary/carousel/DiaryCardHeader.tsx @@ -13,6 +13,7 @@ interface DiaryCardHeaderProps { isEditing: boolean; onSave: () => void; onClose: () => void; + onDelete: () => void; } const formatTime = (time: string) => { @@ -27,6 +28,7 @@ const DiaryCardHeader = ({ isEditing, onSave, onClose, + onDelete, }: DiaryCardHeaderProps) => { return ( @@ -43,7 +45,7 @@ const DiaryCardHeader = ({ iconSet="Feather" name="trash-2" size={16} - onPress={onClose} + onPress={onDelete} style={styles.icon} /> ))} diff --git a/src/components/diary/carousel/DiaryCarousel.web.tsx b/src/components/diary/carousel/DiaryCarousel.web.tsx index 3e988af..2bfa17b 100644 --- a/src/components/diary/carousel/DiaryCarousel.web.tsx +++ b/src/components/diary/carousel/DiaryCarousel.web.tsx @@ -13,7 +13,7 @@ const DiaryCarousel = ({ selectedDate }: IDiaryCarouselProps) => { useEffect(() => { setActiveIndex(0); - }, [data]); + }, [selectedDate]); if (isPending) { return ; diff --git a/src/hooks/login/useSendUserToken.ts b/src/hooks/login/useSendUserToken.ts index 9d67ab7..802cef8 100644 --- a/src/hooks/login/useSendUserToken.ts +++ b/src/hooks/login/useSendUserToken.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { postUserGoogleToken, postUserAppleToken } from '@api/login/post'; import { saveToken } from '@components/login/AuthService'; import { useSetRecoilState } from 'recoil'; @@ -17,6 +17,7 @@ const useSendUserToken = (loginType: 'google' | 'apple') => { const setAuthToken = useSetRecoilState(authTokenState); const setUserInfo = useSetRecoilState(userInfoState); const setIsLoggedIn = useSetRecoilState(isLoggedInState); + const queryClient = useQueryClient(); const mutationFn = loginType === 'google' ? postUserGoogleToken : postUserAppleToken; @@ -31,6 +32,7 @@ const useSendUserToken = (loginType: 'google' | 'apple') => { onSuccess: async (data) => { await saveToken(data.accessToken); setIsLoggedIn(true); + queryClient.resetQueries(); }, onError: (error) => { console.error(error.response.data.message); diff --git a/src/hooks/setting/settingHook.ts b/src/hooks/setting/settingHook.ts index 09a48dd..9e47ffd 100644 --- a/src/hooks/setting/settingHook.ts +++ b/src/hooks/setting/settingHook.ts @@ -4,7 +4,7 @@ import { Alert, Linking } from 'react-native'; const useSetting = () => { const [deleteModalVisible, setDeleteModalVisible] = useState(false); - const { logoutMutation } = useLogout(); + const { logoutMutation, deleteAccountMutation } = useLogout(); const onFeedback = async () => { const url = 'https://forms.gle/nUCrj6JLNCnU8Dez6'; // env에 추가하기 @@ -20,12 +20,18 @@ const useSetting = () => { setDeleteModalVisible(true); }; + const onConfirmDeleteAccount = () => { + deleteAccountMutation.mutate(); + setDeleteModalVisible(false); + }; + const onLogout = () => { logoutMutation.mutate(); }; return { deleteModalVisible, + onConfirmDeleteAccount, setDeleteModalVisible, onFeedback, onLogout, diff --git a/src/screens/setting/SettingScreen.tsx b/src/screens/setting/SettingScreen.tsx index 4d5cb9a..704d814 100644 --- a/src/screens/setting/SettingScreen.tsx +++ b/src/screens/setting/SettingScreen.tsx @@ -3,7 +3,6 @@ import MyText from '@components/common/MyText'; import { SafeAreaView, StyleSheet, ScrollView, View, Platform } from 'react-native'; import SettingSection from '@components/setting/SettingSection'; import NotiSection from '@components/setting/NotiSection'; -import DeleteAccountModal from '@components/setting/DeleteAccountModal'; import useAiPersonaGet from '@hooks/setting/aiPersonaGetHook'; import useNotification from '@hooks/setting/notificationHook'; import useSetting from '@hooks/setting/settingHook'; @@ -16,10 +15,17 @@ import { ko } from 'date-fns/locale'; import MySnackbar from '@components/common/MySnackbar'; import AiPersonaSelectModal from '@components/setting/AiPersonaSelectModal'; import { userInfoState } from '@stores/login'; +import BasicConfirmModal from '@components/common/BasicConfirmModal'; const SettingScreen = () => { - const { deleteModalVisible, setDeleteModalVisible, onFeedback, onLogout, onDeleteAccount } = - useSetting(); + const { + deleteModalVisible, + setDeleteModalVisible, + onConfirmDeleteAccount, + onFeedback, + onLogout, + onDeleteAccount, + } = useSetting(); const { diaryNotiEnabled, letterNotiEnabled, @@ -82,7 +88,12 @@ const SettingScreen = () => { setIsVisible={setAiPickerVisible} aiPersonaList={aiPersonaList} /> - + 심심조각 초판 diff --git a/src/types/Modal.ts b/src/types/Modal.ts new file mode 100644 index 0000000..85394cb --- /dev/null +++ b/src/types/Modal.ts @@ -0,0 +1,17 @@ +import React from 'react'; +import { ModalProps, StyleProp, ViewStyle } from 'react-native'; + +export interface IMyModalProps extends ModalProps { + visible: boolean; + setIsVisible: (visible: boolean) => void; + children: React.ReactNode; + containerStyle?: StyleProp; +} + +export interface IBasicModalProps { + visible: boolean; + setIsVisible: React.Dispatch>; + onConfirm: () => void; + content: string; + confirmText?: string; +}