diff --git a/src/bookmarks/ui/BookmarkUserInfo.tsx b/src/bookmarks/ui/BookmarkUserInfo.tsx index 2daf1ecd..9e22a10d 100644 --- a/src/bookmarks/ui/BookmarkUserInfo.tsx +++ b/src/bookmarks/ui/BookmarkUserInfo.tsx @@ -13,6 +13,7 @@ interface BookmarkUserInfoProps { isFollowing: boolean; friendId: number; memberId: number; + isBlocked: boolean; }; } @@ -43,12 +44,14 @@ const BookmarkUserInfo = ({ )} {!isFriendPage?.isFollowing && ( )} diff --git a/src/friend/api/friends.tsx b/src/friend/api/friends.tsx index 7228a8b8..6e0b2771 100644 --- a/src/friend/api/friends.tsx +++ b/src/friend/api/friends.tsx @@ -20,6 +20,7 @@ export interface FollowingInfo { memberId: number; nickname: string; emoji: string; + isBlocked: boolean; } interface GETFollowingListRequest { @@ -81,6 +82,7 @@ interface FollowerInfo { memberId: number; nickname: string; isFollowing: boolean; + isBlocked: boolean; emoji: string; } @@ -392,6 +394,7 @@ export interface SearchList { nickname: string; emoji: string; isFollowing: boolean; + isBlocked: boolean; } interface GETSearchListRequest { @@ -431,8 +434,6 @@ export const GET_SEARCH_LIST_KEY = (params: GETSearchListQueryRequest) => [ 'GET_SEARCH_LIST', params.memberId, params.keyword, - params.cursorId, - params.pageSize, ]; export const useGETSearchListQuery = (params: GETSearchListQueryRequest) => { diff --git a/src/friend/ui/Friends.tsx b/src/friend/ui/Friends.tsx index 95bc2cce..4a3bbda5 100644 --- a/src/friend/ui/Friends.tsx +++ b/src/friend/ui/Friends.tsx @@ -51,6 +51,7 @@ const Friends = () => { name={info.nickname} profileEmoji={info.emoji} isFollowing={true} + isBlocked={info.isBlocked} /> ))} {selectedType === FriendType.Following && !followings.length && ( @@ -65,6 +66,7 @@ const Friends = () => { name={info.nickname} profileEmoji={info.emoji} isFollowing={info.isFollowing} + isBlocked={info.isBlocked} /> ))} diff --git a/src/friend/ui/buttons/FollowButton.tsx b/src/friend/ui/buttons/FollowButton.tsx index 31c782c8..e21f7c9c 100644 --- a/src/friend/ui/buttons/FollowButton.tsx +++ b/src/friend/ui/buttons/FollowButton.tsx @@ -6,18 +6,34 @@ import { theme } from '@/styles/theme'; import { type MouseEvent } from 'react'; import { usePOSTFollowUserQuery } from '@/friend/api/friends'; import useSearchStore from '@/store/search'; +import useToast from '@/common-ui/Toast/hooks/useToast'; interface FollowButtonProps { memberId: number; followerId: number; + isBlocked?: boolean; } -const FollowButton = ({ memberId, followerId }: FollowButtonProps) => { +const FollowButton = ({ + memberId, + followerId, + isBlocked = false, +}: FollowButtonProps) => { const { setSelectedMemberId } = useSearchStore(); const { mutate } = usePOSTFollowUserQuery({ memberId: followerId }); + const { fireToast } = useToast(); + //TODO: 하드 코딩 개선 const onClick = (e: MouseEvent) => { e.stopPropagation(); + if (isBlocked) { + fireToast({ + message: '차단된 사용자는 팔로우 할 수 없어요', + mode: 'ERROR', + }); + return; + } + setSelectedMemberId(memberId); mutate({ memberId, followerId }); }; diff --git a/src/friend/ui/buttons/UnFollowButton.tsx b/src/friend/ui/buttons/UnFollowButton.tsx index 5fbe3132..4a530daf 100644 --- a/src/friend/ui/buttons/UnFollowButton.tsx +++ b/src/friend/ui/buttons/UnFollowButton.tsx @@ -5,18 +5,32 @@ import { theme } from '@/styles/theme'; import { type MouseEvent } from 'react'; import { useDELETEUnFollowQuery } from '@/friend/api/friends'; import useSearchStore from '@/store/search'; +import useToast from '@/common-ui/Toast/hooks/useToast'; interface UnFollowButtonProps { memberId: number; followerId: number; + isBlocked?: boolean; } -const UnFollowButton = ({ memberId, followerId }: UnFollowButtonProps) => { +const UnFollowButton = ({ + memberId, + followerId, + isBlocked = false, +}: UnFollowButtonProps) => { const { setSelectedMemberId } = useSearchStore(); const { mutate } = useDELETEUnFollowQuery({ memberId: followerId }); - //TODO: 하드코딩 개선 + const { fireToast } = useToast(); + const onClick = (e: MouseEvent) => { e.stopPropagation(); + if (isBlocked) { + fireToast({ + message: '차단된 사용자는 팔로우 할 수 없어요', + mode: 'ERROR', + }); + return; + } setSelectedMemberId(memberId); mutate({ memberId, followerId }); }; diff --git a/src/friend/ui/friend/FriendFollowerItem.tsx b/src/friend/ui/friend/FriendFollowerItem.tsx index 9d6d9270..7708fd93 100644 --- a/src/friend/ui/friend/FriendFollowerItem.tsx +++ b/src/friend/ui/friend/FriendFollowerItem.tsx @@ -8,6 +8,7 @@ type FriendFollowerProps = { name: string; profileEmoji: string; isFollowing: boolean; + isBlocked: boolean; }; const FriendFollowerItem = ({ id, @@ -15,6 +16,7 @@ const FriendFollowerItem = ({ name, profileEmoji, isFollowing, + isBlocked, }: FriendFollowerProps) => { return ( + ) : ( - + ) } /> diff --git a/src/friend/ui/friend/FriendFollowingItem.tsx b/src/friend/ui/friend/FriendFollowingItem.tsx index acbc02c5..9d20c66c 100644 --- a/src/friend/ui/friend/FriendFollowingItem.tsx +++ b/src/friend/ui/friend/FriendFollowingItem.tsx @@ -8,6 +8,7 @@ type FriendFollowingProps = { name: string; profileEmoji: string; isFollowing: boolean; + isBlocked: boolean; }; const FriendFollowingItem = ({ id, @@ -15,6 +16,7 @@ const FriendFollowingItem = ({ name, profileEmoji, isFollowing, + isBlocked, }: FriendFollowingProps) => { return ( + ) : ( - + ) } /> diff --git a/src/friend/ui/friend/FriendList.tsx b/src/friend/ui/friend/FriendList.tsx index a76d8413..8ac572a6 100644 --- a/src/friend/ui/friend/FriendList.tsx +++ b/src/friend/ui/friend/FriendList.tsx @@ -40,7 +40,8 @@ const FriendList = ({ keyword }: FriendListProps) => { memberId={memberId} name={info.nickname} profileEmoji={info.emoji} - isFollowing={Boolean(info.isFollowing)} + isFollowing={info.isFollowing} + isBlocked={info.isBlocked} /> ))} diff --git a/src/members/api/member.ts b/src/members/api/member.ts index 32a326da..452de7b3 100644 --- a/src/members/api/member.ts +++ b/src/members/api/member.ts @@ -1,7 +1,11 @@ import { GET_USER_PROFILE } from '@/auth/api/profile'; import useToast from '@/common-ui/Toast/hooks/useToast'; import client from '@/common/service/client'; +import { GET_SEARCH_LIST_KEY, SearchList } from '@/friend/api/friends'; +import useFriendStore from '@/store/friend'; +import useSearchStore from '@/store/search'; import { + InfiniteData, useInfiniteQuery, useMutation, useQuery, @@ -328,18 +332,53 @@ interface POSTBlockMemberQueryParams { memberId: number; } +type InfiniteSearchList = + | InfiniteData<{ + contents: SearchList[]; + hasNext: boolean; + }> + | undefined; + export const usePOSTBlockMemberQuery = ({ memberId, }: POSTBlockMemberQueryParams) => { const router = useNavigate(); const { fireToast } = useToast(); const queryClient = useQueryClient(); + const { keyword, selectedMemberId } = useSearchStore(); + console.log(keyword, memberId); return useMutation(postBlockMemberAPI, { onSuccess: () => { fireToast({ message: '차단 되었습니다' }); router(-1); queryClient.refetchQueries(GET_BLOCK_MEMBER_LIST_KEY({ memberId })); queryClient.refetchQueries(GET_USER_PROFILE({ loginId: memberId })); + if (keyword && selectedMemberId) { + queryClient.setQueryData( + GET_SEARCH_LIST_KEY({ memberId, keyword }), + (searchList) => { + if (searchList) { + return { + ...searchList, + pages: searchList.pages.map((page) => ({ + ...page, + contents: page.contents.map((content) => { + if (content.memberId === selectedMemberId) { + return { + ...content, + isFollowing: false, + isBlocked: true, + }; + } + return content; + }), + })), + }; + } + return searchList; + }, + ); + } }, }); }; @@ -351,6 +390,7 @@ export interface MemberProfile { nickname: string; profileEmoji: string; isFollowing: boolean; + isBlocked: boolean; } interface GETMemberProfileParams { @@ -411,7 +451,6 @@ interface GetBlockMemberListParams { pageSize?: number; } -// NOTE : Backend API가 수정되면 삭제 const GETBlockMemberList = { API: async (params: GetBlockMemberListParams) => { const { data } = await client({ @@ -477,9 +516,13 @@ export const useUnblockUserQuery = ({ }: DELETEBlockMemberQueryParams) => { const queryClient = useQueryClient(); const { fireToast } = useToast(); + const { friendId } = useFriendStore(); return useMutation(unblockUserAPI, { onSuccess: () => { queryClient.invalidateQueries(GET_BLOCK_MEMBER_LIST_KEY({ memberId })); + queryClient.refetchQueries( + GET_FRIEND_PROFILE({ memberId: friendId, loginId: memberId }), + ); fireToast({ message: '차단이 해제 되었습니다' }); }, }); diff --git a/src/pages/FriendBookmarkPage.tsx b/src/pages/FriendBookmarkPage.tsx index 73d18074..ef5ab1df 100644 --- a/src/pages/FriendBookmarkPage.tsx +++ b/src/pages/FriendBookmarkPage.tsx @@ -13,18 +13,26 @@ import IconButton from '@/common/ui/IconButton'; import { useGETFriendProfileQuery, usePOSTBlockMemberQuery, + useUnblockUserQuery, } from '@/members/api/member'; import useAuthStore from '@/store/auth'; import BookmarkListView from '@/bookmarks/ui/Main/BookmarkListView'; -import { Suspense } from 'react'; +import { Suspense, useEffect } from 'react'; import SkeletonWrapper from '@/common-ui/SkeletonWrapper'; import BookmarkSkeletonItem from '@/bookmarks/ui/Main/BookmarkSkeletonItem'; +import useFriendStore from '@/store/friend'; const FriendBookmarkPage = () => { // FIRST RENDER const { memberId } = useAuthStore(); const { id: friendId } = useParams<{ id: string }>(); + const { setFriendId } = useFriendStore(); + + useEffect(() => { + setFriendId(Number(friendId)); + }, [friendId]); + // SERVER // 1. 친구 프로필 조회 const { data: profileInfo } = useGETFriendProfileQuery({ @@ -38,6 +46,10 @@ const FriendBookmarkPage = () => { const onClick_차단하기 = () => { postBlockMember({ blockeeId: Number(friendId), blockerId: memberId }); }; + const { mutate: deleteUnBlockMember } = useUnblockUserQuery({ memberId }); + const onClick_차단해제 = () => { + deleteUnBlockMember({ blockeeId: Number(friendId), blockerId: memberId }); + }; // 2. 카테고리 선택 const { selectedCategoryId, categoryOptions, onChangeCategory } = useCategory( @@ -59,21 +71,29 @@ const FriendBookmarkPage = () => { as={ {}} name="more" size="s" />} /> - - 차단하기 - + {!!profileInfo?.isBlocked && ( + + 차단해제 + + )} + {!profileInfo?.isBlocked && ( + + 차단하기 + + )} } /> diff --git a/src/store/friend.ts b/src/store/friend.ts index b85f9f1c..d40e8a18 100644 --- a/src/store/friend.ts +++ b/src/store/friend.ts @@ -8,6 +8,8 @@ export enum FriendType { interface FriendStore { selectedType: FriendType; setSelectedType: (mode: FriendType) => void; + friendId: number; + setFriendId: (friendId: number) => void; } const useFriendStore = create((set) => ({ @@ -15,6 +17,10 @@ const useFriendStore = create((set) => ({ setSelectedType: (selectedType) => { set({ selectedType }); }, + friendId: 0, + setFriendId: (friendId) => { + set({ friendId }); + }, })); export default useFriendStore; diff --git a/src/store/toast.ts b/src/store/toast.ts index e6ad80ac..32a72bbb 100644 --- a/src/store/toast.ts +++ b/src/store/toast.ts @@ -9,7 +9,8 @@ export type ToastMessage = | '앗! 유효하지 않은 주소에요' | '신고 되었습니다' | '이미 신고한 북마크에요' - | '앗! 알림 설정 기준일은 1일 이상이어야 해요'; + | '앗! 알림 설정 기준일은 1일 이상이어야 해요' + | '차단된 사용자는 팔로우 할 수 없어요'; export type ToastMode = 'SUCCESS' | 'DELETE' | 'ERROR';