Skip to content

Commit

Permalink
Merge pull request #67 from BCSDLab/feature/#77
Browse files Browse the repository at this point in the history
Feature/#77 친구목록 페이지 팔로우 기능 추가
  • Loading branch information
ChoiWonBeen authored May 30, 2023
2 parents 3fb0888 + 886f1cb commit ab9e38e
Show file tree
Hide file tree
Showing 17 changed files with 476 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Routes, Route } from 'react-router-dom';
import ChangePassword from 'pages/Auth/FindIDPassword/ChangePassword';
import { Suspense } from 'react';
import SearchDetails from 'pages/SearchDetails';
import FollowPage from 'pages/Follow';
import Setting from 'pages/Setting/UserSetting';
import IdChange from 'pages/Setting/UserSetting/IdChange';
import AuthRoute from 'components/common/AuthRoute';
Expand All @@ -35,6 +36,9 @@ export default function App(): JSX.Element {
<Route element={<AuthRoute needAuth redirectRoute="/login" />}>
<Route path="/setting" element={<Setting />} />
<Route path="/setting/id-change" element={<IdChange />} />
<Route path="/" element={<DefaultLayout />}>
<Route path="/friend-list" element={<FollowPage />} />
</Route>
<Route path="/withdrawal" element={<Withdrawal />} />
</Route>
<Route element={<AuthRoute needAuth={false} redirectRoute="/" />}>
Expand Down
48 changes: 48 additions & 0 deletions src/api/follow/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { User } from 'api/user/entity';
import { FollowerInfo } from 'pages/Follow/static/entity';

export interface FollowListParams {
cursor: string;
pageSize: number;
}

export interface GetFollowListParams {
page: number;
pageSize: number;
}

export interface GetFollowListResponse {
content: {
id: number;
follower: User;
user: User;
account: string;
}[]
}

export interface CheckSendedFollowParams {
page: number;
pageSize: number;
}

export interface FollowerParams {
userAccount: string;
}

export interface DeleteFollowerParams extends FollowerParams {
}

export interface PostFollowerParams extends FollowerParams {
}

export interface SearchUsersParams {
keyword: string;
}

export interface SearchUsersResponse {
content: FollowerInfo[]
}

export interface AcceptFollowParams {
id: number;
}
18 changes: 18 additions & 0 deletions src/api/follow/followApiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { API_PATH } from 'config/constants';
import axios from 'axios';

const followApi = axios.create({
baseURL: `${API_PATH}`,
timeout: 2000,
});

followApi.interceptors.request.use(
(config) => {
const accessToken = sessionStorage.getItem('accessToken');
// eslint-disable-next-line no-param-reassign
if (config.headers && accessToken) config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
);

export default followApi;
32 changes: 32 additions & 0 deletions src/api/follow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
FollowListParams,
GetFollowListParams,
CheckSendedFollowParams,
DeleteFollowerParams,
PostFollowerParams,
SearchUsersParams,
AcceptFollowParams,
GetFollowListResponse,
SearchUsersResponse,
} from './entity';
import followApi from './followApiClient';

export const followList = (param: FollowListParams) => followApi.get(`/follow/followers?${param.cursor}&pageSize=${param.pageSize}`);

export const getFollowList = (param: GetFollowListParams) => followApi.get<GetFollowListResponse>(`/follow/requests/receive?page=${param.page}&pageSize=${param.pageSize}`);

export const checkSendedFollow = (param: CheckSendedFollowParams) => followApi.get(`/follow/requests/send?page=${param.page}&pageSize=${param.pageSize}`);

export const requestFollow = (param: PostFollowerParams) => followApi.post('/follow/requests', {
userAccount: param.userAccount,
});

export const deleteFollower = (param: DeleteFollowerParams) => followApi.delete('/follow/follwers', {
data: {
userAccount: param.userAccount,
},
});

export const acceptFollow = (param: AcceptFollowParams) => followApi.post(`/follow/requests/${param.id}/accept`);

export const searchUsers = (param: SearchUsersParams) => followApi.get<SearchUsersResponse>(`/users?keyword=${param.keyword}`);
2 changes: 1 addition & 1 deletion src/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// import makeToast from 'utils/ts/makeToast';
import userApi from './userApiClient';
import {
CheckIdDuplicateParams,
LoginParams,
Expand All @@ -13,6 +12,7 @@ import {
FindPasswordParams,
ChangePasswordParams,
} from './entity';
import userApi from './userApiClient';

export const register = (param: RegisterParams) => userApi.post<User>('/', param);

Expand Down
Binary file added src/assets/images/follow/default-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/pages/Follow/components/FailToSearch.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.template {
width: 726px;
height: 418px;
display: flex;
justify-content: center;
align-items: center;
}

.content {
width: 172px;
height: 40px;
text-align: center;
}
12 changes: 12 additions & 0 deletions src/pages/Follow/components/FailToSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import style from './FailToSearch.module.scss';

export default function FailToSearch() {
return (
<div className={style.template}>
<div className={style.content}>
해당 아이디/닉네임을 가진 친구를 찾을 수 없습니다.
</div>
</div>

);
}
29 changes: 29 additions & 0 deletions src/pages/Follow/components/FollowList.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.container {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.title {
width: 1187px;
height: 40px;
display: flex;
justify-content: space-between;
padding-bottom: 11px;
padding-left: 20px;
border-bottom: 1px solid #eeeeee;
margin-top: 20px;
background-color: white;
border-top: none;
border-right: none;
border-left: none;
cursor: pointer;

&__arrow {
&--up {
transform: rotate(180deg);
}
}
}
40 changes: 40 additions & 0 deletions src/pages/Follow/components/FollowList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ReactComponent as Arrow } from 'assets/svg/common/arrow.svg';
import cn from 'utils/ts/classNames';
import useBooleanState from 'utils/hooks/useBooleanState';
import Follower from './Follower';
import style from './FollowList.module.scss';
import { FollowerInfo } from '../static/entity';

export interface Props {
title: string;
data: FollowerInfo[];
}

// 유저 검색 시 자신의 정보는 안보이도록 자신의 정보를 받아서 비교, 자신과의 followedType은 NONE
export default function FollowList({ title, data }: Props) {
const [isShow, , ,toggle] = useBooleanState(true);

return (
<div className={style.container}>
<button onClick={toggle} type="button" className={style.title}>
{title}
<Arrow className={cn(
{
[style.title__arrow]: true,
[style['title__arrow--up']]: isShow,
},
)}
/>
</button>
{isShow && data.map((item) => (
<Follower
id={item.id}
key={item.id}
nickname={item.nickname}
account={item.account}
followedType={item.followedType}
/>
))}
</div>
);
}
45 changes: 45 additions & 0 deletions src/pages/Follow/components/Follower.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.follower {
display: flex;
align-items: center;
justify-content: center;
height: 112px;
width: 1150px;
margin-top: 39px;
margin-bottom: 39px;

&__image {
width: 112px;
height: 112px;
}

&__content {
width: 720px;
padding-left: 60px;

&--font {
font-size: 30px;
}
}

&__button {
width: 175px;
height: 49px;
color: white;
border: 1px solid #f6bf54;
border-radius: 10px;
background-color: #f6bf54;
font-size: 20px;
cursor: pointer;

&--unfollow {
width: 175px;
height: 49px;
border-radius: 10px;
border: 1px solid #c4c4c4;
background-color: white;
color: #c4c4c4;
font-size: 20px;
cursor: pointer;
}
}
}
64 changes: 64 additions & 0 deletions src/pages/Follow/components/Follower.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import defaultImage from 'assets/images/follow/default-image.png';
import cn from 'utils/ts/classNames';
import { acceptFollow, requestFollow } from 'api/follow';
import { useMutation, useQueryClient } from 'react-query';
import style from './Follower.module.scss';

// 팔로우 요청 후 유저 목록을 다시 받아와 요청중 상태로 변경
const useRequestAndUpdate = () => {
const queryClient = useQueryClient();
const { mutate: request } = useMutation('request', (account: string) => requestFollow({ userAccount: account }), {
onSuccess: () => {
queryClient.invalidateQueries('search');
},
});
return request;
};

// 팔로우 승인 후 받은 요청 목록을 다시 받아와 갱신
const useAcceptFollow = () => {
const queryClient = useQueryClient();
const { mutate: accept } = useMutation('accept', (id: number) => acceptFollow({ id }), {
onSuccess: () => {
queryClient.invalidateQueries('received');
},
});
return { accept };
};

interface Props {
account: string,
nickname: string,
followedType: string,
id: number,
}

export default function Follower({
nickname, account, followedType, id,
}: Props) {
const request = useRequestAndUpdate();
const { accept } = useAcceptFollow();

return (
<div className={style.follower}>
<img className={style.follower__image} src={defaultImage} alt="default" />
<div className={style.follower__content}>
<p className={cn({ [style['follower__content--font']]: true })}>{nickname}</p>
<p>{account}</p>
</div>
<button
className={cn({
[style.follower__button]: followedType === 'NONE' || followedType === 'RECEIVED',
[style['follower__button--unfollow']]: followedType === 'REQUESTED' || followedType === 'FOLLOWED',
})}
type="button"
onClick={() => (followedType === 'NONE' && request(account)) || (followedType === 'RECEIVED' && accept(id))}
>
{followedType === 'NONE' && '팔로우'}
{followedType === 'REQUESTED' && '요청중'}
{followedType === 'FOLLOWED' && '언팔로우'}
{followedType === 'RECEIVED' && '팔로우'}
</button>
</div>
);
}
13 changes: 13 additions & 0 deletions src/pages/Follow/components/SearchPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.friend {
width: 1207px;
padding-bottom: 11px;
padding-left: 20px;
border-bottom: 1px solid #eeeeee;
}

.new {
width: 1207px;
padding-bottom: 11px;
padding-left: 20px;
border-bottom: 1px solid #eeeeee;
}
16 changes: 16 additions & 0 deletions src/pages/Follow/components/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useAuth } from 'store/auth';
import { FollowerInfo, SearchPageInfo } from '../static/entity';
import FollowList from './FollowList';

export default function SearchPage({ data }: SearchPageInfo) {
const auth = useAuth();
const myFriends: FollowerInfo[] = data.filter((follower) => follower.followedType === 'FOLLOWED');
const newFriends: FollowerInfo[] = data.filter((follower) => follower.followedType !== 'FOLLOWED').filter((follower) => follower.account !== auth?.account);

return (
<div>
<FollowList title="나의 친구" data={myFriends} />
<FollowList title="새 친구" data={newFriends} />
</div>
);
}
Loading

0 comments on commit ab9e38e

Please sign in to comment.