Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue166 마이페이지(나의 맛집, 나의 리뷰보기) 추가 #168

Merged
merged 24 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
71c562e
feat: > svg 아이콘 추가
ashleysyheo Jun 24, 2023
27674e2
fix: Star 컴포넌트 사이즈 버그 수정
ashleysyheo Jun 25, 2023
ff045ae
feat: < svg 아이콘 추가
ashleysyheo Jun 25, 2023
59e0a4e
feat: 북마크 식당 목록 및 사용자 정보 조회 api 추가
ashleysyheo Jun 25, 2023
c77c169
feat: 북마크 식장 목록 및 사용자 정보 조회 msw 추가, 관련 목 데이터 추가
ashleysyheo Jun 25, 2023
e8a53e9
feat: BookmarkStore랑 UserProfileInformation 타입 추가
ashleysyheo Jun 25, 2023
bd50708
refactor: StoreList 컴포넌트 props 타입 수정
ashleysyheo Jun 25, 2023
6cdc291
feat: BookmarkListPage 생성
ashleysyheo Jun 25, 2023
88883eb
feat: MenuDrawer에 로그인 했을 때 마이페이지 링크 추가
ashleysyheo Jun 25, 2023
d4b8bc7
feat: MypagePage 생성
ashleysyheo Jun 25, 2023
a84e0ea
refactor: BookmarkListPage MypagePage 하위로 이동
ashleysyheo Jun 25, 2023
f5e6789
feat: 사용자 작성 리뷰 목록 조회 api 추가
ashleysyheo Jun 25, 2023
236ebc8
feat: MypagePage에서 나의 리뷰 보여주는 섹션 추가
ashleysyheo Jun 25, 2023
38a0e89
feat: 사용자 작성 리뷰 조회 msw 및 목 데이터 추가
ashleysyheo Jun 25, 2023
cf00261
fix: fetchUserReviewList useInfiniteQuery랑 함께 사용할 때 에러 발생하는 부분 수정
ashleysyheo Jun 25, 2023
f75e306
feat: MyReviewListPage 생성
ashleysyheo Jun 25, 2023
872c6bd
refactor: 나의 리뷰 아이템에서 제목 클릭 시 상세 페이지로 이동하는 기능 추가
ashleysyheo Jun 26, 2023
ccf8a4a
refactor: api 변경사항 반영
ashleysyheo Jun 26, 2023
0e6546a
refactor: MypagePage -> MyPage로 이름 변경
hafnium1923 Jun 30, 2023
95c68d7
refactor: 함수 props 분리
hafnium1923 Jun 30, 2023
30f118e
refactor: 북마크 타입에 Omit 적용
hafnium1923 Jun 30, 2023
67d3893
fix: 마이페이지 리뷰 수정/삭제 버튼 클릭시 상세페이지 이동 버그
hafnium1923 Jun 30, 2023
bcc5d66
refacotor: 로그아웃시 홈화면으로 이동
hafnium1923 Jul 1, 2023
d227f31
Merge branch 'develop' of https://github.com/The-Fellowship-of-the-ma…
hafnium1923 Jul 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/api/bookmark/fetchBookmarkList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BookmarkStore } from "types/common/bookmarkTypes";

import { ACCESS_TOKEN, ENDPOINTS } from "constants/api";

import axiosInstance from "api/axiosInstance";

const fetchBookmarkList = async () => {
const accessToken = window.sessionStorage.getItem(ACCESS_TOKEN);

if (!accessToken) {
window.sessionStorage.removeItem(ACCESS_TOKEN);
window.alert("다시 로그인 해주세요");
window.location.href = "/";
return;
}

const { data } = await axiosInstance.get<BookmarkStore[]>(
ENDPOINTS.BOOKMARKS,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);

return data;
};

export default fetchBookmarkList;
29 changes: 29 additions & 0 deletions src/api/mypage/fetchUserProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { UserProfileInformation } from "types/common";

import { ACCESS_TOKEN, ENDPOINTS } from "constants/api";

import axiosInstance from "api/axiosInstance";

const fetchUserProfile = async () => {
const accessToken = window.sessionStorage.getItem(ACCESS_TOKEN);

if (!accessToken) {
window.sessionStorage.removeItem(ACCESS_TOKEN);
window.alert("다시 로그인 해주세요");
window.location.href = "/";
return;
}

const { data } = await axiosInstance.get<UserProfileInformation>(
ENDPOINTS.USER_PROFILE,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);

return data;
};

export default fetchUserProfile;
36 changes: 36 additions & 0 deletions src/api/mypage/fetchUserReviewList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { FetchParamProps } from "types/apiTypes";
import type { UserReview } from "types/common";

import { ACCESS_TOKEN, ENDPOINTS, SIZE } from "constants/api";

import axiosInstance from "api/axiosInstance";

interface UserReviewResponse {
hasNext: boolean;
reviews: UserReview[];
}

const fetchUserReviewList = async ({ pageParam = 0 }: FetchParamProps) => {
const accessToken = window.sessionStorage.getItem(ACCESS_TOKEN);

if (!accessToken) {
window.sessionStorage.removeItem(ACCESS_TOKEN);
window.alert("다시 로그인 해주세요");
window.location.href = "/";
throw new Error("엑세스토큰이 유효하지 않습니다");
}

const { data } = await axiosInstance.get<UserReviewResponse>(
ENDPOINTS.USER_REVIEWS,
{
params: { page: pageParam, size: SIZE.REVIEW },
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);

return { ...data, nextPageParam: pageParam + 1 };
};

export default fetchUserReviewList;
2 changes: 2 additions & 0 deletions src/asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { ReactComponent as CloseIcon } from "./close-icon.svg";
export { ReactComponent as LogoLight } from "./logo-light.svg";
export { ReactComponent as PlusIcon } from "./plus-icon.svg";
export { ReactComponent as SearchIcon } from "./search-icon.svg";
export { ReactComponent as RightIcon } from "./right-icon.svg";
export { ReactComponent as LeftIcon } from "./left-icon.svg";
3 changes: 3 additions & 0 deletions src/asset/left-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/asset/right-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/common/StoreList/StoreList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Divider from "../Divider/Divider";
import { Fragment } from "react";
import { Store } from "types/common";
import type { BookmarkStore, Store } from "types/common";

import * as S from "components/common/StoreList/StoreList.style";
import StoreListItem from "components/common/StoreListItem/StoreListItem";

interface StoreListProps {
stores?: Store[];
stores?: Store[] | BookmarkStore[];
}

function StoreList({ stores }: StoreListProps) {
Expand Down
4 changes: 4 additions & 0 deletions src/components/layout/MenuDrawer/MenuDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useContext, useEffect } from "react";
import ReactDOM from "react-dom";
import { useNavigate } from "react-router-dom";
import { Campus } from "types/campus";

import { AUTH_LINK } from "constants/api";
Expand All @@ -26,6 +27,7 @@ function MenuDrawer({ closeMenu, isLoggedIn }: MenuDrawerProps) {
const campus = useContext(campusContext);
const otherCampus = getOtherCampus(campus as Campus);
const setCampus = useContext(setCampusContext);
const navigate = useNavigate();

const { logout } = useLogin();

Expand All @@ -49,6 +51,7 @@ function MenuDrawer({ closeMenu, isLoggedIn }: MenuDrawerProps) {
logout();
closeMenu();
window.alert(MESSAGES.LOGOUT_COMPLETE);
navigate(PATHNAME.HOME);
};

useEffect(() => {
Expand All @@ -74,6 +77,7 @@ function MenuDrawer({ closeMenu, isLoggedIn }: MenuDrawerProps) {
<Button variant="textButton" onClick={handleLogout}>
로그아웃
</Button>
<S.CustomLink to={PATHNAME.MY_PAGE}>마이페이지</S.CustomLink>
</>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styled, { css } from "styled-components";

export const Container = styled.div`
position: relative;
padding: ${({ theme }) => theme.spacer.spacing3};
`;

export const HeaderWrapper = styled.header`
display: flex;
justify-content: space-between;
background-color: white;
`;

export const headerStyle = css`
font-weight: bold;
padding-bottom: ${({ theme }) => theme.spacer.spacing3};
margin-bottom: ${({ theme }) => theme.spacer.spacing4};
`;
51 changes: 51 additions & 0 deletions src/components/pages/MyPage/BookmarkListPage/BookmarkListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as S from "./BookmarkListPage.style";
import { useQuery } from "react-query";
import { useNavigate } from "react-router-dom";

import { NETWORK } from "constants/api";

import { LeftIcon } from "asset";

import fetchBookmarkList from "api/bookmark/fetchBookmarkList";

import ErrorImage from "components/common/ErrorImage/ErrorImage";
import ErrorText from "components/common/ErrorText/ErrorText";
import Spinner from "components/common/Spinner/Spinner";
import StoreList from "components/common/StoreList/StoreList";
import Text from "components/common/Text/Text";

function BookmarkListPage() {
const navigate = useNavigate();

const { data, isLoading, isFetching, isError, error } = useQuery(
"bookmarkStore",
() => fetchBookmarkList(),
{
retry: NETWORK.RETRY_COUNT,
refetchOnWindowFocus: false,
}
);

const bookmarkedStoreData = data ?? [];

return (
<S.Container>
<S.HeaderWrapper>
<LeftIcon onClick={() => navigate(-1)} />
<Text css={S.headerStyle}>나의 맛집</Text>
<Text>지도</Text>
</S.HeaderWrapper>
{(isLoading || isFetching) && <Spinner />}
{isError && error instanceof Error && (
<ErrorImage errorMessage={error.message} />
)}
{bookmarkedStoreData.length > 0 ? (
<StoreList stores={bookmarkedStoreData} />
) : (
<ErrorText>가게 정보가 없습니다.</ErrorText>
)}
</S.Container>
);
}

export default BookmarkListPage;
47 changes: 47 additions & 0 deletions src/components/pages/MyPage/MyPage.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Link } from "react-router-dom";

import styled from "styled-components";

export const Container = styled.section`
position: relative;
padding: ${({ theme }) => theme.spacer.spacing3};

& > section:not(:first-child) {
margin-top: ${({ theme }) => theme.spacer.spacing5};
}
`;

export const SectionHeaderWrapper = styled.div`
margin-bottom: ${({ theme }) => theme.spacer.spacing4};

display: flex;
justify-content: space-between;
align-items: center;

& > header {
margin-bottom: 0;
}
`;

export const ShowAllLink = styled(Link)`
display: flex;
gap: ${({ theme }) => theme.spacer.spacing1};
align-items: center;

& > p {
color: ${({ theme }) => theme.color.gray600};
}
`;

export const EmptyList = styled.div`
padding: ${({ theme }) => theme.spacer.spacing7} 0;
color: ${({ theme }) => theme.color.gray600};
text-align: center;
`;

export const ReviewItemWrapper = styled.div`
margin-bottom: ${({ theme }) => theme.spacer.spacing3};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacer.spacing3};
`;
114 changes: 114 additions & 0 deletions src/components/pages/MyPage/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as S from "./MyPage.style";
import MyReviewItem from "./MyReviewItem/MyReviewItem";
import UserProfile from "./UserProfile/UserProfile";
import { MdArrowBackIos } from "react-icons/md";
import { useQuery } from "react-query";
import { useNavigate } from "react-router-dom";

import { NETWORK, SIZE } from "constants/api";
import { PATHNAME } from "constants/routes";

import { RightIcon } from "asset";

import fetchBookmarkList from "api/bookmark/fetchBookmarkList";
import fetchUserProfile from "api/mypage/fetchUserProfile";
import fetchUserReviewList from "api/mypage/fetchUserReviewList";

import Divider from "components/common/Divider/Divider";
import ErrorImage from "components/common/ErrorImage/ErrorImage";
import SectionHeader from "components/common/SectionHeader/SectionHeader";
import Spinner from "components/common/Spinner/Spinner";
import StoreList from "components/common/StoreList/StoreList";
import Text from "components/common/Text/Text";

function MyPage() {
const navigate = useNavigate();

const {
data: profileData,
isLoading,
isError,
error,
} = useQuery("userProfile", () => fetchUserProfile(), {
retry: NETWORK.RETRY_COUNT,
refetchOnWindowFocus: false,
});

const { data: bookmarkedStoreData = [] } = useQuery(
"bookmarkedStore",
() => fetchBookmarkList(),
{
retry: NETWORK.RETRY_COUNT,
refetchOnWindowFocus: false,
}
);

const { data: myReviewData } = useQuery("myReview", fetchUserReviewList, {
retry: NETWORK.RETRY_COUNT,
refetchOnWindowFocus: false,
});

const myReviews = myReviewData?.reviews ?? [];

if (!profileData) return null;

return (
<S.Container>
<SectionHeader
leadingIcon={<MdArrowBackIos />}
onClick={() => {
navigate(-1);
}}
>
마이페이지
</SectionHeader>
<section>
{isLoading && <Spinner />}
{isError && error instanceof Error && (
<ErrorImage errorMessage={error.message} />
)}
<UserProfile {...profileData} />
</section>
<Divider />
<section>
<S.SectionHeaderWrapper>
<SectionHeader>나의 맛집</SectionHeader>
<S.ShowAllLink to={PATHNAME.BOOKMARK_LIST_PAGE}>
<Text size="sm">전체보기</Text>
<RightIcon />
</S.ShowAllLink>
</S.SectionHeaderWrapper>
{bookmarkedStoreData.length > 0 ? (
<StoreList stores={bookmarkedStoreData.slice(0, SIZE.MY_PAGE_ITEM)} />
) : (
<S.EmptyList>
<Text size="sm">저장된 맛집이 없습니다</Text>
</S.EmptyList>
)}
</section>
<section>
<S.SectionHeaderWrapper>
<SectionHeader>나의 리뷰</SectionHeader>
<S.ShowAllLink to={PATHNAME.MY_REVIEWS}>
<Text size="sm">전체보기</Text>
<RightIcon />
</S.ShowAllLink>
</S.SectionHeaderWrapper>
{myReviews.length > 0 ? (
myReviews.slice(0, SIZE.MY_PAGE_ITEM).map((review) => (
<S.ReviewItemWrapper>
<MyReviewItem key={review.id} {...review} />
<Divider />
</S.ReviewItemWrapper>
))
) : (
<S.EmptyList>
<Text size="sm">작성한 리뷰가 없습니다</Text>
</S.EmptyList>
)}
</section>
</S.Container>
);
}

export default MyPage;
Loading