diff --git a/index.html b/index.html index baf4a5c..e4377ad 100644 --- a/index.html +++ b/index.html @@ -4,10 +4,10 @@ Gachi Taxi - + > -->
diff --git a/src/assets/icon/chattingRoomIcon.svg b/src/assets/icon/chattingRoomIcon.svg new file mode 100644 index 0000000..2e19fa2 --- /dev/null +++ b/src/assets/icon/chattingRoomIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/chat/bottomMenu/index.tsx b/src/components/chat/bottomMenu/index.tsx index 914ebb5..5141bd0 100644 --- a/src/components/chat/bottomMenu/index.tsx +++ b/src/components/chat/bottomMenu/index.tsx @@ -14,6 +14,8 @@ import { getCloseMatching } from '@/libs/apis/getCloseMatching.api'; import getExitChatRoom from '@/libs/apis/getExitChatRoom'; import useSSEStore from '@/store/useSSEStore'; import useUserStore from '@/store/useUserStore'; +import useChattingRoomIdStore from '@/store/useChattingRoomId'; +import exitManualMatchingRoom from '@/libs/apis/manual/exitManualMatchingRoom.api'; const BottomMenu = ({ onSendAccount, @@ -31,6 +33,7 @@ const BottomMenu = ({ const { messages } = useSSEStore(); const [isOwner, setIsOwner] = useState(false); const { user } = useUserStore(); + const { setChattingRoomId } = useChattingRoomIdStore(); const accountNumber = user?.accountNumber || '계좌번호 없음'; messages.forEach((eventMessage) => { @@ -55,15 +58,21 @@ const BottomMenu = ({ const handleExitClick = async () => { try { const { reset } = useTimerStore.getState(); - const [res, closeRes] = await Promise.all([ + const [res, closeRes, closeManual] = await Promise.all([ getExitChatRoom(roomId), getCloseMatching(roomId), + exitManualMatchingRoom(roomId), ]); - if (res.chatExit.code === 200 && closeRes.matchingExit.code === 200) { + if ( + res.chatExit.code === 200 && + closeRes.matchingExit.code === 200 && + closeManual.code === 200 + ) { closeModal(); reset(); nav('/home'); handleDisconnect(); + setChattingRoomId(''); openToast('채팅방을 나가고 매칭을 종료했습니다.', 'success'); } } catch (error) { diff --git a/src/components/commons/Button.tsx b/src/components/commons/Button.tsx index 8f77633..4a1bbeb 100644 --- a/src/components/commons/Button.tsx +++ b/src/components/commons/Button.tsx @@ -27,7 +27,7 @@ const Button = ({ variantStyle = `${isDisabled || isLoading ? 'bg-matchLine' : 'bg-primary'} h-[50px] rounded-modal font-semibold text-neutral outline-none text-center`; } else if (variant === 'secondary') { variantStyle = - 'bg-transparent h-[50px] rounded-modal font-semibold text-button text-textLightGray outline-none border-2 border-primary text-center'; + 'bg-transparent h-[50px] rounded-modal font-semibold text-button outline-none border-2 border-primary text-center'; } else if (variant === 'icon') { variantStyle = 'bg-transparent outline-none w-fit h-fit'; } diff --git a/src/components/home/FriendList/BlackListPage.tsx b/src/components/home/FriendList/BlackListPage.tsx index 8204c0f..df6aba7 100644 --- a/src/components/home/FriendList/BlackListPage.tsx +++ b/src/components/home/FriendList/BlackListPage.tsx @@ -11,7 +11,7 @@ interface BlackListPageProps { const BlackListPage = ({ isOpen }: BlackListPageProps) => { const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useBlackList(); - const blackList = data.pages.flatMap((page) => page.response || []); + const blackList = data.pages.flatMap((page) => page.blacklists || []); const { isIntersecting, ref } = useIntersectionObserver({ threshold: 0.5 }); if (isIntersecting && hasNextPage && !isFetchingNextPage) { @@ -24,14 +24,17 @@ const BlackListPage = ({ isOpen }: BlackListPageProps) => { className={`flex flex-col gap-[16px] ${isOpen ? '' : 'pb-[calc(100dvh-430px)]'} h-[calc(100dvh-225px)] max-h-[calc(100dvh-225px)] overflow-y-scroll scroll-hidden`} > {blackList.length > 0 ? ( - blackList.map((blackMember) => { - return ( - - ); - }) + <> + {blackList.map((blackMember) => { + return ( + + ); + })} +
+ ) : ( 블랙리스트에 추가한 사람이 없어요! )} @@ -39,7 +42,6 @@ const BlackListPage = ({ isOpen }: BlackListPageProps) => { {isFetchingNextPage && ( )} -
); }; diff --git a/src/components/home/FriendList/FriendInfoItem.tsx b/src/components/home/FriendList/FriendInfoItem.tsx index 7d6d7c3..294bccb 100644 --- a/src/components/home/FriendList/FriendInfoItem.tsx +++ b/src/components/home/FriendList/FriendInfoItem.tsx @@ -18,7 +18,7 @@ const FriendInfoItem = ({ friend, setCurrentPage }: FriendInfoItemProps) => { if (setCurrentPage) { openModal( , ); @@ -31,10 +31,10 @@ const FriendInfoItem = ({ friend, setCurrentPage }: FriendInfoItemProps) => {
- {friend.friendProfileUrl ? ( + {friend.friendsProfileUrl ? ( 친구 프로필 이미지 ) : ( @@ -43,7 +43,7 @@ const FriendInfoItem = ({ friend, setCurrentPage }: FriendInfoItemProps) => {

- {friend.friendNickName} + {friend.friendsNickName}

{friend.gender === 'MALE' ? '남자' : '여자'} diff --git a/src/components/home/FriendList/FriendListPage.tsx b/src/components/home/FriendList/FriendListPage.tsx index 593e9dd..78c38ba 100644 --- a/src/components/home/FriendList/FriendListPage.tsx +++ b/src/components/home/FriendList/FriendListPage.tsx @@ -23,18 +23,21 @@ const FriendListPage = ({ isOpen, setCurrentPage }: FriendListPageProps) => { return ( <>
{friendList.length > 0 ? ( - friendList.map((friend) => { - return ( - - ); - }) + <> + {friendList.map((friend) => { + return ( + + ); + })} +
+ ) : ( 친구를 추가해보세요! )} @@ -42,9 +45,8 @@ const FriendListPage = ({ isOpen, setCurrentPage }: FriendListPageProps) => { {isFetchingNextPage && ( )} -
{isOpen && ( -
+
diff --git a/src/components/home/FriendList/index.tsx b/src/components/home/FriendList/index.tsx index 9fc8307..88551bb 100644 --- a/src/components/home/FriendList/index.tsx +++ b/src/components/home/FriendList/index.tsx @@ -45,7 +45,7 @@ const FriendList = ({ isOpen }: { isOpen: boolean }) => { return (
-
+

{currentPage === 'FRIEND_LIST' ? '친구 목록' : '블랙리스트'}

diff --git a/src/components/home/Navbar.tsx b/src/components/home/Navbar.tsx index 74f2da9..83f1a8a 100644 --- a/src/components/home/Navbar.tsx +++ b/src/components/home/Navbar.tsx @@ -116,7 +116,7 @@ const Navbar = ({ modalContent }: NavbarProps) => { 네비바 유저 프로필 이미지 ) : ( diff --git a/src/components/home/autoMatching/index.tsx b/src/components/home/autoMatching/index.tsx index 5a99b02..bfee2f2 100644 --- a/src/components/home/autoMatching/index.tsx +++ b/src/components/home/autoMatching/index.tsx @@ -11,11 +11,12 @@ import { autoMatchingSchema } from '@/libs/schemas/match'; import InviteMembers from '@/components/home/autoMatching/inviteMembers'; import useGeoLocation from '@/hooks/useGeoLocation'; import getCoordinateByAddress from '@/libs/apis/getCoordinateByAddress'; -import { useCallback, useEffect } from 'react'; +import { Suspense, useCallback, useEffect } from 'react'; import { useToast } from '@/contexts/ToastContext'; import useLocationStore from '@/store/useLocationStore'; import startAutoMatching from '@/libs/apis/matching/startAutoMatching.api'; import useSSEStore from '@/store/useSSEStore'; +import SpinnerIcon from '@/assets/icon/spinnerIcon.svg?react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; @@ -32,7 +33,8 @@ const AutoMatching = ({ isOpen }: { isOpen: boolean }) => { defaultValues: { startPoint: '', startName: '가천대 반도체대학', - destinationPoint: autoDestinationPoint || '', + destinationPoint: + autoDestinationPoint || '127.134247729944,37.45524157529484', destinationName: '가천대 AI 공학관', members: [], criteria: [], @@ -172,7 +174,19 @@ const AutoMatching = ({ isOpen }: { isOpen: boolean }) => { {isOpen && ( <> - + + +
+ } + > + + )} diff --git a/src/components/home/autoMatching/inviteMembers/index.tsx b/src/components/home/autoMatching/inviteMembers/index.tsx index 03e6492..3feef2f 100644 --- a/src/components/home/autoMatching/inviteMembers/index.tsx +++ b/src/components/home/autoMatching/inviteMembers/index.tsx @@ -1,6 +1,9 @@ import { Controller, Control, Path } from 'react-hook-form'; import { MatchingSchema } from 'gachTaxi-types'; import MemberItem from '@/components/home/autoMatching/inviteMembers/MemberItem'; +import useFriends from '@/hooks/queries/useFriends'; +import { useIntersectionObserver } from '@/store/useIntersectionObserver'; +import { Link } from 'react-router-dom'; interface InviteMembersProps { control: Control; @@ -9,16 +12,22 @@ interface InviteMembersProps { const InviteMembers = ({ control, }: InviteMembersProps) => { - const members: string[] = ['친구 1', '친구 2', '친구 3']; + const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useFriends(); + const members = data.pages.flatMap((page) => page.response || []); + const { isIntersecting, ref } = useIntersectionObserver(); + + if (isIntersecting && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } // safeValue 친구 리스트에 선택된 값이 포함되어 있는지 검사하고 업데이트시키는 함수 const handleUpdateMembers = ( safeValue: string[], - selectedMemnbers: string, + selectedMembers: string, ) => { - const updatedMembers = safeValue.includes(selectedMemnbers) - ? safeValue.filter((member) => member !== selectedMemnbers) - : [...safeValue, selectedMemnbers]; + const updatedMembers = safeValue.includes(selectedMembers) + ? safeValue.filter((member) => member !== selectedMembers) + : [...safeValue, selectedMembers]; return updatedMembers; }; @@ -34,20 +43,32 @@ const InviteMembers = ({

친구 초대

- {members.map((member) => ( - { - const updatedMembers = handleUpdateMembers( - safeValue, - selectedMemnbers, - ); - onChange(updatedMembers); - }} - /> - ))} + {members.length > 0 ? ( + <> + {members.map((member) => ( + { + const updatedMembers = handleUpdateMembers( + safeValue, + selectedMembers, + ); + onChange(updatedMembers); + }} + /> + ))} +
+ + ) : ( + + 친구를 추가해보세요! + + )}
); diff --git a/src/components/home/chatLink/index.tsx b/src/components/home/chatLink/index.tsx new file mode 100644 index 0000000..b08f2c1 --- /dev/null +++ b/src/components/home/chatLink/index.tsx @@ -0,0 +1,18 @@ +import { Link } from 'react-router-dom'; +import ChatIcon from '@/assets/icon/chattingRoomIcon.svg?react'; + +const ChatLinkButton = ({ chattingRoomId }: { chattingRoomId: string }) => { + return ( + + +

+ 진행중인 매칭 채팅방 들어가기 +

+ + ); +}; + +export default ChatLinkButton; diff --git a/src/components/home/manualMatching/MatchingPage.tsx b/src/components/home/manualMatching/MatchingPage.tsx index fc6c50b..943521c 100644 --- a/src/components/home/manualMatching/MatchingPage.tsx +++ b/src/components/home/manualMatching/MatchingPage.tsx @@ -32,16 +32,17 @@ const MatchingPage = ({ className={`flex flex-col gap-4 ${isOpen ? '' : 'pb-[calc(100dvh-430px)]'} h-[calc(100dvh-225px)] max-h-[calc(100dvh-225px)] overflow-y-scroll scroll-hidden`} > {manualMatchingList.length > 0 ? ( - manualMatchingList.map((manualInfo) => { - return ( + <> + {manualMatchingList.map((manualInfo) => ( - ); - }) + ))} +
+ ) : ( 현재 등록된 매칭이 없어요! )} @@ -49,9 +50,8 @@ const MatchingPage = ({ {isFetchingNextPage && ( )} -
{isOpen && ( -
+
diff --git a/src/components/home/manualMatching/MyMatchingPage.tsx b/src/components/home/manualMatching/MyMatchingPage.tsx index 2c3de85..c9d4dc0 100644 --- a/src/components/home/manualMatching/MyMatchingPage.tsx +++ b/src/components/home/manualMatching/MyMatchingPage.tsx @@ -22,18 +22,22 @@ const MyMatchingPage = ({ isOpen }: { isOpen: boolean }) => {

참여중인 매칭 리스트를 확인할 수 있어요!

- {myMatchingList.length > 0 ? ( - myMatchingList.map((myInfo) => { - return ; - }) - ) : ( - 매칭 내역이 없어요! - )} + <> + {myMatchingList.length > 0 ? ( + myMatchingList.map((myInfo) => { + return ( + + ); + }) + ) : ( + 매칭 내역이 없어요! + )} +
+
{isFetchingNextPage && ( )} -
); }; diff --git a/src/components/home/manualMatching/matchingInfoItem/index.tsx b/src/components/home/manualMatching/matchingInfoItem/index.tsx index 0ef9720..ef49e8d 100644 --- a/src/components/home/manualMatching/matchingInfoItem/index.tsx +++ b/src/components/home/manualMatching/matchingInfoItem/index.tsx @@ -5,10 +5,12 @@ import MatchingComplete from '@/components/modal/MatchingComplete'; import { useModal } from '@/contexts/ModalContext'; import { useToast } from '@/contexts/ToastContext'; import joinManualMatchingRoom from '@/libs/apis/manual/joinManualMatchingRoom.api'; +import formatToKoreanTime from '@/utils/formatToKoreanTIme'; import axios from 'axios'; import { motion } from 'framer-motion'; import { Room } from 'gachTaxi-types'; import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; interface MatchingInfoItem { manualInfo: Room; @@ -25,6 +27,7 @@ const MatchingInfoItem = ({ const animateState = isExpand ? 'expanded' : 'collapsed'; const { openModal } = useModal(); const { openToast } = useToast(); + const navigate = useNavigate(); const handleJoinMatching = async () => { try { @@ -44,6 +47,10 @@ const MatchingInfoItem = ({ } }; + const handleJoinChatting = () => { + navigate(`/chat/${manualInfo.chattingRoomId}`); + }; + return ( <>
- {manualInfo.departureTime} + {formatToKoreanTime(manualInfo.departureTime)} {manualInfo.currentMembers}/4 @@ -88,16 +95,29 @@ const MatchingInfoItem = ({
)} - + {manualInfo.tags.length > 0 ? ( + + ) : ( +

+ - 등록된 태그가 없어요! +

+ )}
- {isExpand && currentPage! && ( + + {isExpand && (
)} diff --git a/src/components/modal/AgreementModal.tsx b/src/components/modal/AgreementModal.tsx index 13b1e83..1503c28 100644 --- a/src/components/modal/AgreementModal.tsx +++ b/src/components/modal/AgreementModal.tsx @@ -14,6 +14,7 @@ import requestAgreement from '@/libs/apis/auth/requestAgreement'; import { useToast } from '@/contexts/ToastContext'; import handleAxiosError from '@/libs/apis/axiosError.api'; import useRequestStatus from '@/hooks/useRequestStatus'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; const AgreementModal = () => { const navigate = useNavigate(); @@ -60,7 +61,8 @@ const AgreementModal = () => { } catch (error: unknown) { setError(); const errorMessage = handleAxiosError(error); - openToast(errorMessage, 'error'); + console.error(errorMessage); + openToast(ERROR_MESSAGE, 'error'); } finally { closeModal(); } diff --git a/src/components/modal/FriendDeleteOrBlack.tsx b/src/components/modal/FriendDeleteOrBlack.tsx index 75a53ec..2e92904 100644 --- a/src/components/modal/FriendDeleteOrBlack.tsx +++ b/src/components/modal/FriendDeleteOrBlack.tsx @@ -1,5 +1,6 @@ import Button from '@/components/commons/Button'; import Modal from '@/components/modal'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; import { useModal } from '@/contexts/ModalContext'; import { useToast } from '@/contexts/ToastContext'; import useDeleteFriend from '@/hooks/mutations/useDeleteFriend'; @@ -24,8 +25,8 @@ const FriendDeleteOrBlack = ({ closeModal(); setCurrentPage('BLACK_LIST'); }, - onError: (error) => { - openToast(error.message, 'error'); + onError: () => { + openToast(ERROR_MESSAGE, 'error'); }, }); }; @@ -37,8 +38,8 @@ const FriendDeleteOrBlack = ({ closeModal(); setCurrentPage('FRIEND_LIST'); }, - onError: (error) => { - openToast(error.message, 'error'); + onError: () => { + openToast(ERROR_MESSAGE, 'error'); }, }); }; diff --git a/src/components/notification/FriendRequestNotification.tsx b/src/components/notification/FriendRequestNotification.tsx index d5bfa85..1dd5768 100644 --- a/src/components/notification/FriendRequestNotification.tsx +++ b/src/components/notification/FriendRequestNotification.tsx @@ -1,8 +1,9 @@ import Button from '@/components/commons/Button'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; import { useToast } from '@/contexts/ToastContext'; -import useAcceptFriend from '@/hooks/mutations/useAcceptFriend'; -import useDeleteFriend from '@/hooks/mutations/useDeleteFriend'; import useDeleteNotification from '@/hooks/mutations/useDeleteNotification'; +import useFriendReply from '@/hooks/mutations/useFriendReply'; +import useInviteReply from '@/hooks/mutations/useInviteReply'; import { NotificationResponse } from '@gachTaxi-types'; import { InfiniteData, useQueryClient } from '@tanstack/react-query'; import { motion } from 'framer-motion'; @@ -10,18 +11,22 @@ import { motion } from 'framer-motion'; interface FriendRequestNotificationProps { senderId: number; content: string; + matchingRoomId: number; notificationId: string; + type: 'FRIEND_REQUEST' | 'MATCH_INVITE'; } const FriendRequestNotification = ({ senderId, content, + matchingRoomId, notificationId, + type, }: FriendRequestNotificationProps) => { const { openToast } = useToast(); const { mutate: deleteNotification } = useDeleteNotification(); - const { mutate: acceptFriend } = useAcceptFriend(); - const { mutate: rejectFriend } = useDeleteFriend(); + const { mutate: replyFriend } = useFriendReply(); + const { mutate: replyInite } = useInviteReply(); const queryClient = useQueryClient(); // 낙관적 업데이트용 함수 @@ -43,12 +48,17 @@ const FriendRequestNotification = ({ const acceptFriendRequest = () => { handleQueryData(); - acceptFriend(senderId, { + const data = { + memberId: senderId, + notificationId, + status: 'ACCEPTED' as const, + }; + replyFriend(data, { onSuccess: (response) => { openToast(response.message, 'success'); }, - onError: (error) => { - openToast(error.message, 'error'); + onError: () => { + openToast(ERROR_MESSAGE, 'error'); }, }); }; @@ -60,8 +70,36 @@ const FriendRequestNotification = ({ onSuccess: (response) => { openToast(response.message, 'success'); }, - onError: (error) => { - openToast(error.message, 'error'); + onError: () => { + openToast(ERROR_MESSAGE, 'error'); + }, + }); + }; + + const handleAcceptInvite = () => { + handleQueryData(); + + const data = { matchingRoomId, notificationId, status: 'ACCEPT' as const }; + replyInite(data, { + onSuccess: (response) => { + openToast(response.message, 'success'); + }, + onError: () => { + openToast(ERROR_MESSAGE, 'error'); + }, + }); + }; + + const handleRejectInvite = () => { + handleQueryData(); + + const data = { matchingRoomId, notificationId, status: 'REJECT' as const }; + replyInite(data, { + onSuccess: (response) => { + openToast(response.message, 'success'); + }, + onError: () => { + openToast(ERROR_MESSAGE, 'error'); }, }); }; @@ -69,12 +107,17 @@ const FriendRequestNotification = ({ const rejectFriendRequest = () => { handleQueryData(); - rejectFriend(senderId, { + const data = { + memberId: senderId, + notificationId, + status: 'REJECTED' as const, + }; + replyFriend(data, { onSuccess: (response) => { openToast(response.message, 'success'); }, - onError: (error) => { - openToast(error.message, 'error'); + onError: () => { + openToast(ERROR_MESSAGE, 'error'); }, }); }; @@ -103,13 +146,20 @@ const FriendRequestNotification = ({ >

{content}

- diff --git a/src/components/notification/index.tsx b/src/components/notification/index.tsx index b9a73da..7422eb6 100644 --- a/src/components/notification/index.tsx +++ b/src/components/notification/index.tsx @@ -31,9 +31,11 @@ const NotificationList = () => { {notification.type === 'FRIEND_REQUEST' || notification.type === 'MATCH_INVITE' ? ( ) : ( { const { openModal } = useModal(); @@ -38,7 +39,8 @@ const AuthCodeVerification = ({ emailInfo }: { emailInfo: string }) => { } catch (error: unknown) { setError(); const errorMessage = handleAxiosError(error); - openToast(errorMessage, 'error'); + console.error(errorMessage); + openToast(ERROR_MESSAGE, 'error'); } }; diff --git a/src/components/sign/EmailVerification.tsx b/src/components/sign/EmailVerification.tsx index 5888018..7a25c67 100644 --- a/src/components/sign/EmailVerification.tsx +++ b/src/components/sign/EmailVerification.tsx @@ -11,6 +11,7 @@ import { useToast } from '@/contexts/ToastContext'; import { requestEmailVerification } from '@/libs/apis/auth'; import handleAxiosError from '@/libs/apis/axiosError.api'; import useRequestStatus from '@/hooks/useRequestStatus'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; const TIMER_DURATION = 300; @@ -50,7 +51,8 @@ const EmailVerification = ({ } catch (error: unknown) { const errorMessage = handleAxiosError(error); setError(); - openToast(errorMessage, 'error'); + console.error(errorMessage); + openToast(ERROR_MESSAGE, 'error'); } }; diff --git a/src/components/sign/userInfoVerification/index.tsx b/src/components/sign/userInfoVerification/index.tsx index 2727807..87a5d51 100644 --- a/src/components/sign/userInfoVerification/index.tsx +++ b/src/components/sign/userInfoVerification/index.tsx @@ -13,6 +13,8 @@ import handleAxiosError from '@/libs/apis/axiosError.api'; import useUploadImage from '@/hooks/useUploadImage'; import useRequestStatus from '@/hooks/useRequestStatus'; import useUserStore from '@/store/useUserStore'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; +import { useNavigate } from 'react-router-dom'; const UserInfoVerification = () => { const userInfoForm = useForm>({ @@ -34,6 +36,7 @@ const UserInfoVerification = () => { const { openToast } = useToast(); const { status, setSuccess, setError, setPending } = useRequestStatus(); const { setUser } = useUserStore(); + const navigate = useNavigate(); const handleSubmitToUserInfo: SubmitHandler< UserInfoVerificationTypes @@ -52,6 +55,7 @@ const UserInfoVerification = () => { setUser(userData); setSuccess(); openToast(res.message, 'success'); + navigate('/home'); } } else { const res = await requestUserInfo(data); @@ -68,7 +72,8 @@ const UserInfoVerification = () => { } catch (error: unknown) { setError(); const errorMessage = handleAxiosError(error); - openToast(errorMessage, 'error'); + console.log(errorMessage); + openToast(ERROR_MESSAGE, 'error'); } }; diff --git a/src/constants/errorMessage.constant.ts b/src/constants/errorMessage.constant.ts new file mode 100644 index 0000000..e2cdc1f --- /dev/null +++ b/src/constants/errorMessage.constant.ts @@ -0,0 +1,3 @@ +const ERROR_MESSAGE = '요청에 문제가 발생했습니다!' as const; + +export default ERROR_MESSAGE; diff --git a/src/constants/index.ts b/src/constants/index.ts index cef5d8b..5b6a09a 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -6,3 +6,4 @@ export * from './styles.constant'; export * from './menuItems.constant'; export * from './landingText.constant'; export * from './tags.constant'; +export * from './errorMessage.constant'; diff --git a/src/hooks/mutations/useAcceptFriend.ts b/src/hooks/mutations/useAcceptFriend.ts deleted file mode 100644 index c50c214..0000000 --- a/src/hooks/mutations/useAcceptFriend.ts +++ /dev/null @@ -1,25 +0,0 @@ -import acceptFriendRequest from '@/libs/apis/friends/acceptFriendRequest.api'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -const useAcceptFriend = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async (memberId: number) => { - const res = await acceptFriendRequest(memberId); - return res; - }, - onSettled: (_, error) => { - if (error) { - queryClient.invalidateQueries({ queryKey: ['friend'] }); - queryClient.invalidateQueries({ queryKey: ['notification'] }); - return error; - } else { - queryClient.invalidateQueries({ queryKey: ['friend'] }); - queryClient.invalidateQueries({ queryKey: ['notification'] }); - } - }, - }); -}; - -export default useAcceptFriend; diff --git a/src/hooks/mutations/useDeleteBlackList.ts b/src/hooks/mutations/useDeleteBlackList.ts index 05b9a0f..0b359e3 100644 --- a/src/hooks/mutations/useDeleteBlackList.ts +++ b/src/hooks/mutations/useDeleteBlackList.ts @@ -1,5 +1,5 @@ -import deleteBlackList from '@/libs/apis/blackList/deleteBlackList.api'; import { useMutation, useQueryClient } from '@tanstack/react-query'; +import deleteBlackList from '@/libs/apis/deleteBlackList.api'; const useDeleteBlackList = () => { const queryClient = useQueryClient(); @@ -9,13 +9,8 @@ const useDeleteBlackList = () => { const res = await deleteBlackList(receiverId); return res; }, - onSettled: (_, error) => { - if (error) { - queryClient.invalidateQueries({ queryKey: ['blackList'] }); - return error; - } else { - queryClient.invalidateQueries({ queryKey: ['blackList'] }); - } + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['blackList'] }); }, }); }; diff --git a/src/hooks/mutations/useDeleteNotification.ts b/src/hooks/mutations/useDeleteNotification.ts index 4e6e5e9..a3f4b00 100644 --- a/src/hooks/mutations/useDeleteNotification.ts +++ b/src/hooks/mutations/useDeleteNotification.ts @@ -9,13 +9,8 @@ const useDeleteNotification = () => { const res = await deleteNotification(notificationId); return res; }, - onSettled: (_, error) => { - if (error) { - queryClient.invalidateQueries({ queryKey: ['notification'] }); - return error; - } else { - queryClient.invalidateQueries({ queryKey: ['notification'] }); - } + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['notification'] }); }, }); }; diff --git a/src/hooks/mutations/useFriendReply.ts b/src/hooks/mutations/useFriendReply.ts new file mode 100644 index 0000000..bf1792e --- /dev/null +++ b/src/hooks/mutations/useFriendReply.ts @@ -0,0 +1,26 @@ +import replyFriendRequest from '@/libs/apis/friends/replyFriendRequest.api'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +const useFriendReply = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ + memberId, + notificationId, + status, + }: { + memberId: number; + notificationId: string; + status: 'ACCEPTED' | 'REJECTED'; + }) => { + const res = await replyFriendRequest(memberId, notificationId, status); + return res; + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['notification'] }); + }, + }); +}; + +export default useFriendReply; diff --git a/src/hooks/mutations/useFriendToBlack.ts b/src/hooks/mutations/useFriendToBlack.ts index d7f9def..004df83 100644 --- a/src/hooks/mutations/useFriendToBlack.ts +++ b/src/hooks/mutations/useFriendToBlack.ts @@ -1,5 +1,5 @@ -import addBlackList from '@/libs/apis/blackList/addBlackList.api'; import { useMutation, useQueryClient } from '@tanstack/react-query'; +import addBlackList from '@/libs/apis/addBlackList.api'; const useFriendToBlack = () => { const queryClient = useQueryClient(); @@ -9,13 +9,8 @@ const useFriendToBlack = () => { const res = await addBlackList(friendId); return res; }, - onSettled: (_, error) => { - if (error) { - queryClient.invalidateQueries({ queryKey: ['blackList'] }); - return error; - } else { - queryClient.invalidateQueries({ queryKey: ['blackList'] }); - } + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['blackList'] }); }, }); }; diff --git a/src/hooks/mutations/useInviteReply.ts b/src/hooks/mutations/useInviteReply.ts new file mode 100644 index 0000000..7ca27d6 --- /dev/null +++ b/src/hooks/mutations/useInviteReply.ts @@ -0,0 +1,31 @@ +import replyManualMatchingInvite from '@/libs/apis/manual/replyManualMatchingInvite.api'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +const useInviteReply = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ + matchingRoomId, + notificationId, + status, + }: { + matchingRoomId: number; + notificationId: string; + status: 'ACCEPT' | 'REJECT'; + }) => { + const res = await replyManualMatchingInvite( + matchingRoomId, + notificationId, + status, + ); + return res; + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['notification'] }); + queryClient.invalidateQueries({ queryKey: ['friend'] }); + }, + }); +}; + +export default useInviteReply; diff --git a/src/hooks/queries/useBlackList.ts b/src/hooks/queries/useBlackList.ts index ad7eb3c..b5b1663 100644 --- a/src/hooks/queries/useBlackList.ts +++ b/src/hooks/queries/useBlackList.ts @@ -1,5 +1,5 @@ import useInfiniteScroll from '@/hooks/queries/useInfiniteScroll'; -import getBlackList from '@/libs/apis/blackList/getBlackList.api'; +import getBlackList from '@/libs/apis/getBlackList.api'; const fetchBlackList = async ({ pageParam = 0 }: { pageParam?: number }) => { const pageSize = 10; diff --git a/src/libs/apis/blackList/addBlackList.api.ts b/src/libs/apis/addBlackList.api.ts similarity index 100% rename from src/libs/apis/blackList/addBlackList.api.ts rename to src/libs/apis/addBlackList.api.ts diff --git a/src/libs/apis/blackList/addToBlackList.api.ts b/src/libs/apis/blackList/addToBlackList.api.ts new file mode 100644 index 0000000..a855676 --- /dev/null +++ b/src/libs/apis/blackList/addToBlackList.api.ts @@ -0,0 +1,12 @@ +import client from '@/libs/apis/clients'; +import { AxiosResponse } from 'axios'; +import { BasicBlackListResponse } from 'gachTaxi-types'; + +const addToBlackList = async (receiverId: number) => { + const res: AxiosResponse = await client.post( + `/api/blacklists?receiverId=${receiverId}`, + ); + return res.data; +}; + +export default addToBlackList; diff --git a/src/libs/apis/blackList/deleteToBlackList.api.ts b/src/libs/apis/blackList/deleteToBlackList.api.ts new file mode 100644 index 0000000..c5d848b --- /dev/null +++ b/src/libs/apis/blackList/deleteToBlackList.api.ts @@ -0,0 +1,12 @@ +import client from '@/libs/apis/clients'; +import { AxiosResponse } from 'axios'; +import { BasicBlackListResponse } from 'gachTaxi-types'; + +const deleteToBlackList = async (receiverId: number) => { + const res: AxiosResponse = await client.delete( + `/api/blacklists?receiverId=${receiverId}`, + ); + return res.data; +}; + +export default deleteToBlackList; diff --git a/src/libs/apis/blackList/getToBlackList.api.ts b/src/libs/apis/blackList/getToBlackList.api.ts new file mode 100644 index 0000000..ea1e8d1 --- /dev/null +++ b/src/libs/apis/blackList/getToBlackList.api.ts @@ -0,0 +1,22 @@ +import client from '@/libs/apis/clients'; +import { AxiosResponse } from 'axios'; +import { BlackListResponse } from 'gachTaxi-types'; + +const getToBlackList = async ({ + pageNum = 0, + pageSize = 10, +}: { + pageNum?: number; + pageSize?: number; +}) => { + const params = new URLSearchParams({ + pageNum: pageNum.toString(), + pageSize: pageSize.toString(), + }); + const res: AxiosResponse = await client.get( + `/api/blacklists?${params}`, + ); + return res.data; +}; + +export default getToBlackList; diff --git a/src/libs/apis/blackList/deleteBlackList.api.ts b/src/libs/apis/deleteBlackList.api.ts similarity index 100% rename from src/libs/apis/blackList/deleteBlackList.api.ts rename to src/libs/apis/deleteBlackList.api.ts diff --git a/src/libs/apis/friends/getFriends.api.ts b/src/libs/apis/friends/getFriends.api.ts index 65afa3f..abe3b19 100644 --- a/src/libs/apis/friends/getFriends.api.ts +++ b/src/libs/apis/friends/getFriends.api.ts @@ -9,12 +9,8 @@ const getFriends = async ({ pageNum?: number; pageSize?: number; }) => { - const params = new URLSearchParams({ - pageNum: pageNum.toString(), - pageSize: pageSize.toString(), - }); const res: AxiosResponse = await client.get( - `/api/friends?${params}`, + `/api/friends?pageNum=${pageNum}&pageSize=${pageSize}`, ); return res.data; }; diff --git a/src/libs/apis/friends/replyFriendRequest.api.ts b/src/libs/apis/friends/replyFriendRequest.api.ts new file mode 100644 index 0000000..b73dbb9 --- /dev/null +++ b/src/libs/apis/friends/replyFriendRequest.api.ts @@ -0,0 +1,16 @@ +import client from '@/libs/apis/clients'; + +const replyFriendRequest = async ( + memberId: number, + notificationId: string, + status: 'ACCEPTED' | 'REJECTED', +) => { + const res = await client.patch('/api/friends', { + memberId, + notificationId, + status, + }); + return res.data; +}; + +export default replyFriendRequest; diff --git a/src/libs/apis/blackList/getBlackList.api.ts b/src/libs/apis/getBlackList.api.ts similarity index 100% rename from src/libs/apis/blackList/getBlackList.api.ts rename to src/libs/apis/getBlackList.api.ts diff --git a/src/libs/apis/manual/replyManualMatchingInvite.api.ts b/src/libs/apis/manual/replyManualMatchingInvite.api.ts new file mode 100644 index 0000000..c756543 --- /dev/null +++ b/src/libs/apis/manual/replyManualMatchingInvite.api.ts @@ -0,0 +1,16 @@ +import client from '@/libs/apis/clients'; + +const replyManualMatchingInvite = async ( + matchingRoomId: number, + notificationId: string, + status: 'ACCEPT' | 'REJECT', +) => { + const res = await client.post('/api/matching/manual/invite/reply', { + matchingRoomId, + notificationId, + status, + }); + return res.data; +}; + +export default replyManualMatchingInvite; diff --git a/src/libs/schemas/match.ts b/src/libs/schemas/match.ts index 0511d95..8eb957b 100644 --- a/src/libs/schemas/match.ts +++ b/src/libs/schemas/match.ts @@ -19,7 +19,7 @@ const membersSchema = z.string().array().default([]); const criteriaSchema = z.string().array().default([]); -const contentSchema = z.string().optional(); +const descriptionSchema = z.string().optional(); const expectedTotalChargeSchema = z.number(); @@ -31,7 +31,7 @@ export const manualMatchingSchema = z.object({ members: membersSchema, expectedTotalCharge: expectedTotalChargeSchema, time: timeSchema, - content: contentSchema, + description: descriptionSchema, }); export const autoMatchingSchema = z.object({ diff --git a/src/pages/friend-request/index.tsx b/src/pages/friend-request/index.tsx index 1e638e4..b906d12 100644 --- a/src/pages/friend-request/index.tsx +++ b/src/pages/friend-request/index.tsx @@ -9,6 +9,7 @@ import { useToast } from '@/contexts/ToastContext'; import { z } from 'zod'; import requestFriends from '@/libs/apis/friends/requestFriend.api'; import useRequestStatus from '@/hooks/useRequestStatus'; +import ERROR_MESSAGE from '@/constants/errorMessage.constant'; const FriendRequestPage = () => { const friendRequestForm = useForm>({ @@ -35,6 +36,7 @@ const FriendRequestPage = () => { } catch (e) { setError(); console.error(e); + openToast(ERROR_MESSAGE, 'error'); } }; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 569cb17..c06790b 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,15 +1,30 @@ import BottomSheet from '@/components/home/BottomSheet'; +import ChatLinkButton from '@/components/home/chatLink'; import Navbar from '@/components/home/Navbar'; +import useChattingRoomIdStore from '@/store/useChattingRoomId'; import useSheetStore from '@/store/useSheetStore'; import { Suspense, lazy } from 'react'; +import { motion } from 'framer-motion'; const KakaoMap = lazy(() => import('@/components/home/KakaoMap')); const HomePage = () => { const { modalContent } = useSheetStore(); + const { chattingRoomId } = useChattingRoomIdStore(); return (
+ {chattingRoomId && ( + + + + )} 로딩중...
}> diff --git a/src/pages/manual-register/index.tsx b/src/pages/manual-register/index.tsx index 319be39..a81db36 100644 --- a/src/pages/manual-register/index.tsx +++ b/src/pages/manual-register/index.tsx @@ -18,9 +18,11 @@ import { formatTimeToSelect } from '@/utils'; import TimeSelect from '@/components/manual-register/timeSelect'; import { useToast } from '@/contexts/ToastContext'; import { useNavigate } from 'react-router-dom'; +import SpinnerIcon from '@/assets/icon/spinnerIcon.svg?react'; import useSheetStore from '@/store/useSheetStore'; import createManualMatchingRoom from '@/libs/apis/manual/createManualMatchingRoom.api'; import axios from 'axios'; +import { Suspense } from 'react'; const ManualMatchingRegister = () => { const manualMatchingForm = useForm>({ @@ -31,7 +33,7 @@ const ManualMatchingRegister = () => { time: formatTimeToSelect(new Date(new Date().setHours(1, 0, 0, 0))), members: [], criteria: [], - content: '', + description: '', expectedTotalCharge: 4800, }, }); @@ -90,7 +92,15 @@ const ManualMatchingRegister = () => { )} /> - + + +
+ } + > + + diff --git a/src/pages/matching/index.tsx b/src/pages/matching/index.tsx index e358ad0..4ff01f8 100644 --- a/src/pages/matching/index.tsx +++ b/src/pages/matching/index.tsx @@ -6,17 +6,18 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import CircleIcon from '@/assets/icon/matching-loading/circleIcon.svg?react'; import TaxiIcon from '@/assets/icon/matching-loading/taxiSideIcon.svg?react'; +import useChattingRoomIdStore from '@/store/useChattingRoomId'; const MatchingInfoPage = () => { const { reset } = useTimerStore(); const { initializeSSE, messages } = useSSEStore(); + const { chattingRoomId, setChattingRoomId } = useChattingRoomIdStore(); const navigate = useNavigate(); const [roomCapacity, setRoomCapacity] = useState(0); const [roomStatus, setRoomStatus] = useState<'searching' | 'matching'>( 'searching', ); - const [roomId, setRoomId] = useState(null); useEffect(() => { initializeSSE(); @@ -24,7 +25,7 @@ const MatchingInfoPage = () => { useEffect(() => { messages.forEach((eventMessage) => { - switch (eventMessage.eventType) { + switch (eventMessage.message.topic) { case 'match_member_joined': setRoomCapacity((prev) => Math.max(prev + 1, 4)); break; @@ -35,7 +36,7 @@ const MatchingInfoPage = () => { case 'match_room_created': setRoomCapacity((prev) => Math.max(prev + 1, 4)); - setRoomId(eventMessage.message.roomId); + setChattingRoomId(eventMessage.message.roomId.toString()); setRoomStatus('matching'); break; @@ -43,7 +44,7 @@ const MatchingInfoPage = () => { break; } }); - }, [messages, reset]); + }, [messages, reset, setChattingRoomId]); return (
@@ -77,7 +78,7 @@ const MatchingInfoPage = () => {
diff --git a/src/store/useChattingRoomId.ts b/src/store/useChattingRoomId.ts new file mode 100644 index 0000000..490d27d --- /dev/null +++ b/src/store/useChattingRoomId.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +interface ChattingRoomIdState { + chattingRoomId: string; + setChattingRoomId: (roomId: string) => void; +} + +const useChattingRoomIdStore = create((set) => ({ + chattingRoomId: '', + setChattingRoomId: (roomId: string) => set({ chattingRoomId: roomId }), +})); + +export default useChattingRoomIdStore; diff --git a/src/types/friend.d.ts b/src/types/friend.d.ts index ba5e4b4..1fb630b 100644 --- a/src/types/friend.d.ts +++ b/src/types/friend.d.ts @@ -15,9 +15,9 @@ declare module 'gachTaxi-types' { // FriendListResponse 종속 interface Friend { - friendId: number; - friendNickName: string; - friendProfileUrl: string; + friendsId: number; + friendsNickName: string; + friendsProfileUrl: string; gender: 'MALE' | 'FEMALE'; } diff --git a/src/types/match.d.ts b/src/types/match.d.ts index a775bf3..7dc01d3 100644 --- a/src/types/match.d.ts +++ b/src/types/match.d.ts @@ -23,7 +23,7 @@ declare module 'gachTaxi-types' { members: string[]; expectedTotalCharge: number; time: string; - content?: string; + description?: string; } type MatchingSchema = AutoMatchingTypes | ManualMatchingTypes; diff --git a/src/types/notification.d.ts b/src/types/notification.d.ts index 3aafe62..3d75c72 100644 --- a/src/types/notification.d.ts +++ b/src/types/notification.d.ts @@ -9,6 +9,11 @@ declare module '@gachTaxi-types' { senderId: number; } + interface MatchingRequestPayload { + senderNickname: string; + matchingRoomId: number; + } + type Notification = | { notificationId: string; @@ -25,6 +30,14 @@ declare module '@gachTaxi-types' { content: string; payload: FriendRequestPayload; createdAt: string; + } + | { + notificationId: string; + receiverId: number; + type: 'MATCH_INVITE'; + content: string; + payload: MatchingRequestPayload; + createdAt: string; }; interface Pageable {