diff --git a/src/api/service/follower-service/index.ts b/src/api/service/follower-service/index.ts index 8bd2c16a..473ee604 100644 --- a/src/api/service/follower-service/index.ts +++ b/src/api/service/follower-service/index.ts @@ -1,10 +1,17 @@ import { api } from '@/api/core'; -import { Follower } from '@/types/service/follow'; +import { GetFollowerResponse } from '@/types/service/follow'; +import { FollowPathParams } from '@/types/service/user'; export const followerServiceRemote = () => ({ // 팔로워 목록 조회 - // 임시주소로 작성. 나중에 수정 필요. - getFollowers: async () => { - return api.get('/followers'); + getFollowers: async ({ userId }: { userId: number }) => { + return api.get(`/users/${userId}/follow`); + }, + + // 팔로워 등록 + addFollower: async (params: FollowPathParams) => { + return api.post(`/users/follow`, null, { + params: { followNickname: params.followNickname }, + }); }, }); diff --git a/src/app/message/layout.tsx b/src/app/message/layout.tsx new file mode 100644 index 00000000..3fdd9532 --- /dev/null +++ b/src/app/message/layout.tsx @@ -0,0 +1,16 @@ +import { redirect } from 'next/navigation'; + +interface Props { + children: React.ReactNode; +} + +const FollowPageLayout = async ({ children }: Props) => { + const { cookies } = await import('next/headers'); + const cookieStore = await cookies(); + const myId = Number(cookieStore.get('userId')?.value); + + if (!myId) redirect('/login'); + return <>{children}; +}; + +export default FollowPageLayout; diff --git a/src/app/message/page.tsx b/src/app/message/page.tsx index be52fc9e..ffc54176 100644 --- a/src/app/message/page.tsx +++ b/src/app/message/page.tsx @@ -2,7 +2,11 @@ import { useSearchParams } from 'next/navigation'; -import { Chat, FollowingList, FollowingSearch } from '@/components/pages/message'; +import { useEffect, useState } from 'react'; + +import Cookies from 'js-cookie'; + +import { Chat, FollowingList, FollowingNone, FollowingSearch } from '@/components/pages/message'; import { TabNavigation } from '@/components/shared'; import { useGetFollowers } from '@/hooks/use-follower/use-follower-get'; @@ -12,11 +16,17 @@ const SOCIAL_TABS = [ ]; export default function FollowingPage() { - const { data: followers } = useGetFollowers(); + const [userId, setUserId] = useState(0); + const { data: followers } = useGetFollowers({ userId }, { enabled: !!userId }); + useEffect(() => { + const id = Cookies.get('userId'); + // eslint-disable-next-line react-hooks/set-state-in-effect + setUserId(Number(id)); + }, []); + console.log(followers); const params = useSearchParams(); const tab = params.get('tab') || 'following'; - if (!followers) return null; return (
@@ -24,8 +34,15 @@ export default function FollowingPage() { {tab === 'chat' && } {tab === 'following' && ( <> - - + + + {followers && followers.items.length > 0 ? ( + + ) : ( +
+ +
+ )} )}
diff --git a/src/components/pages/message/index.ts b/src/components/pages/message/index.ts index 0573a09a..da855e1b 100644 --- a/src/components/pages/message/index.ts +++ b/src/components/pages/message/index.ts @@ -1,4 +1,5 @@ export { Chat } from './chat'; export { FollowingCard } from './message-following-card'; export { FollowingList } from './message-following-list'; +export { FollowingNone } from './message-following-none'; export { FollowingSearch } from './message-following-search'; diff --git a/src/components/pages/message/message-following-card/index.tsx b/src/components/pages/message/message-following-card/index.tsx index 9f131e51..d6aeebfc 100644 --- a/src/components/pages/message/message-following-card/index.tsx +++ b/src/components/pages/message/message-following-card/index.tsx @@ -1,6 +1,6 @@ -import Image from 'next/image'; import { useRouter } from 'next/navigation'; +import { ImageWithFallback } from '@/components/ui'; import { cn } from '@/lib/utils'; interface FollowingCardProps { @@ -32,13 +32,18 @@ export const FollowingCard = ({ className='flex cursor-pointer items-center gap-3 bg-white p-5' onClick={handleClick} > - {nickname} + {/*
+ +
*/} +
+ +
{nickname} ({ useRouter: jest.fn(), })); @@ -41,7 +49,7 @@ describe('Following List 컴포넌트 테스트', () => { test('모든 아이템이 렌더링 되는지 테스트', () => { render(); - TEST_ITEMS.forEach((item) => { + TEST_ITEMS.items.forEach((item) => { expect(screen.getByText(item.nickname)).toBeInTheDocument(); }); }); diff --git a/src/components/pages/message/message-following-list/index.tsx b/src/components/pages/message/message-following-list/index.tsx index 9c113c51..1f45e09d 100644 --- a/src/components/pages/message/message-following-list/index.tsx +++ b/src/components/pages/message/message-following-list/index.tsx @@ -1,17 +1,17 @@ 'use client'; -import { Follower } from '@/types/service/follow'; +import { GetFollowerResponse } from '@/types/service/follow'; import { FollowingCard } from '../message-following-card'; interface FollowingListProps { - items: Follower[]; + items: GetFollowerResponse; } export const FollowingList = ({ items }: FollowingListProps) => { return (
- {items.map((item) => ( + {items.items.map((item) => ( { + return
아직 팔로우 한 사람이 없어요.
; +}; diff --git a/src/components/pages/message/message-following-search/index.test.tsx b/src/components/pages/message/message-following-search/index.test.tsx index 081fad03..74f09216 100644 --- a/src/components/pages/message/message-following-search/index.test.tsx +++ b/src/components/pages/message/message-following-search/index.test.tsx @@ -1,15 +1,31 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen } from '@testing-library/react'; import { ModalProvider } from '@/components/ui'; import { FollowingSearch } from '.'; +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }); + describe('Following Search 테스트', () => { + const queryClient = createQueryClient(); test('Following Search 렌더링 테스트', () => { render( - - - , + + + + + , ); expect(screen.getByText('팔로우 추가')).toBeInTheDocument(); @@ -17,9 +33,11 @@ describe('Following Search 테스트', () => { test('팔로우 추가 클릭 시 모달 생성', () => { render( - - - , + + + + + , ); expect(screen.queryByText('팔로우 할 닉네임을 입력하세요')).toBeNull(); diff --git a/src/components/pages/message/message-following-search/index.tsx b/src/components/pages/message/message-following-search/index.tsx index 990fdcc0..657adec4 100644 --- a/src/components/pages/message/message-following-search/index.tsx +++ b/src/components/pages/message/message-following-search/index.tsx @@ -1,16 +1,25 @@ 'use client'; +import { useState } from 'react'; + import { Icon } from '@/components/icon'; import { Button, Input, ModalContent, ModalTitle, useModal } from '@/components/ui'; +import { useAddFollowers } from '@/hooks/use-follower'; -const FollowerModal = () => { +const FollowerModal = ({ userId }: { userId: number }) => { const { close } = useModal(); + const [nickname, setNickname] = useState(''); + const { mutate: addFollower } = useAddFollowers({ userId }); const handleConfirm = () => { - // 유저 팔로우 기능 ... - console.log('팔로우 성공'); + addFollower({ followNickname: nickname }); close(); }; + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setNickname(value); + }; + // 모달 모양 바뀌면 적용하기! return ( @@ -19,6 +28,7 @@ const FollowerModal = () => { className='text-text-sm-medium mb-3 w-full rounded-3xl bg-gray-100 px-4 py-2.5 text-gray-800' iconButton={} placeholder='nickname' + onChange={handleChange} onKeyDown={(e) => { if (e.key === 'Enter') { handleConfirm(); @@ -38,12 +48,12 @@ const FollowerModal = () => { ); }; -export const FollowingSearch = () => { +export const FollowingSearch = ({ userId }: { userId: number }) => { const { open } = useModal(); return (
open()} + onClick={() => open()} >
diff --git a/src/hooks/use-follower/index.ts b/src/hooks/use-follower/index.ts new file mode 100644 index 00000000..89006a67 --- /dev/null +++ b/src/hooks/use-follower/index.ts @@ -0,0 +1,2 @@ +export { useAddFollowers } from './use-follower-add/index'; +export { useGetFollowers } from './use-follower-get/index'; diff --git a/src/hooks/use-follower/use-follower-add/index.ts b/src/hooks/use-follower/use-follower-add/index.ts new file mode 100644 index 00000000..8bded4a7 --- /dev/null +++ b/src/hooks/use-follower/use-follower-add/index.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { API } from '@/api'; +import { AddFollowParams } from '@/types/service/follow'; + +export const useAddFollowers = ( + { userId }: { userId: number }, + options?: { enabled?: boolean }, +) => { + const queryClient = useQueryClient(); + const query = useMutation({ + mutationFn: (params: AddFollowParams) => API.followerService.addFollower(params), + ...options, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['followers', userId] }); + console.log('팔로워 추가 성공'); + }, + onError: () => { + console.log('팔로워 추가 실패'); + }, + }); + return query; +}; diff --git a/src/hooks/use-follower/use-follower-get/index.ts b/src/hooks/use-follower/use-follower-get/index.ts index d44d0b18..fabb10fc 100644 --- a/src/hooks/use-follower/use-follower-get/index.ts +++ b/src/hooks/use-follower/use-follower-get/index.ts @@ -2,10 +2,14 @@ import { useQuery } from '@tanstack/react-query'; import { API } from '@/api'; -export const useGetFollowers = () => { +export const useGetFollowers = ( + { userId }: { userId: number }, + options?: { enabled?: boolean }, +) => { const query = useQuery({ - queryKey: ['followers'], - queryFn: () => API.followerService.getFollowers(), + queryKey: ['followers', userId], + ...options, + queryFn: () => API.followerService.getFollowers({ userId }), }); return query; }; diff --git a/src/mock/service/followers/followers-handler.ts b/src/mock/service/followers/followers-handler.ts index 4a461b72..f95875df 100644 --- a/src/mock/service/followers/followers-handler.ts +++ b/src/mock/service/followers/followers-handler.ts @@ -3,7 +3,7 @@ import { http, HttpResponse } from 'msw'; import { createMockErrorResponse, createMockSuccessResponse } from '../common/common-mock'; import { mockFollowingItems } from './followers-mocks'; -const getFollowersMock = http.get('*/followers', () => { +const getFollowersMock = http.get(`*/users/:userId/follow`, () => { if (!mockFollowingItems) { return HttpResponse.json( createMockErrorResponse({ diff --git a/src/types/service/follow.ts b/src/types/service/follow.ts index 33689ab2..ff6aadb8 100644 --- a/src/types/service/follow.ts +++ b/src/types/service/follow.ts @@ -1,6 +1,19 @@ +// 기본 팔로우 타입 export interface Follower { + followId: number; userId: number; nickname: string; profileImage: string; profileMessage: string; } + +// 팔로우 목록 조회 응답 +export interface GetFollowerResponse { + items: Follower[]; + nextCursor: number | null; +} + +// 팔로우 등록 Parameters +export interface AddFollowParams { + followNickname: string; +}