Skip to content

Commit

Permalink
refactor : article 페이지 리팩토링 & 기존 검색페이지 파일 삭제 (#338)
Browse files Browse the repository at this point in the history
* remove: delete searchPage before refactoring

* refactor: implement FilteredData component

* refactor: implement RenderArticles for newest and subscribed articles

* fix: edit searchPage type

* refactor: add topButton in articlePage

* fix: move on top obejct and delete return

* fix: comfrim type of HEADER_LABEL and label in HeaderProps

* fix: rename type
  • Loading branch information
GBAJS754 authored Apr 4, 2024
1 parent ecc0b7f commit 05c8a04
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 329 deletions.
21 changes: 16 additions & 5 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { useNavigate } from 'react-router-dom';
import { MdOutlineSearch } from 'react-icons/md';
import CloseButton from '../CloseButton';
import HeaderText from '../HeaderText';

const HEADER_LABEL = {
search: '검색',
news: '뉴스',
} as const

type Label = keyof typeof HEADER_LABEL;

type HeaderProps = {
label: 'search';
type?: 'close';
label: Label ;
type?: 'close' | 'search';
path?: string;
};

const Header = ({ label, type, path }: HeaderProps) => {
const navigate = useNavigate();
const HEADER_LABEL = {
search: '검색',
};

const HEADER_TYPE = {
close: <CloseButton onClick={() => (path ? navigate(path) : navigate(-1))} />,
search: (
<MdOutlineSearch
className="w-[1.8rem] h-[1.8rem] cursor-pointer text-tricorn-black dark:text-extra-white"
onClick={() => (path ? navigate(path) : navigate(-1))}
/>
),
};

return (
Expand Down
16 changes: 0 additions & 16 deletions src/hooks/useFilteredSearchResult.tsx

This file was deleted.

22 changes: 22 additions & 0 deletions src/pages/Articles/FailedMessage.tsx/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import SubButton from '@/components/SubButton';

type FailedMessageProps = {
path: string;
label: string;
children: ReactNode;
};

const FailedMessage = ({ path, label, children }: FailedMessageProps) => {
const navigate = useNavigate();

return (
<div className="flex flex-col items-center justify-center w-full h-full text-center font-Cafe24SurroundAir">
<span className="mb-4">{children}</span>
<SubButton label={label} color="blue" type="outline" onClick={() => navigate(path)} />
</div>
);
};

export default FailedMessage;
62 changes: 62 additions & 0 deletions src/pages/Articles/Page/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AiOutlineArrowUp } from 'react-icons/ai';
import { BsFire } from 'react-icons/bs';
import { MdStars } from 'react-icons/md';
import { Header } from '@/components';
import BottomNavigation from '@/components/BottomNavigation';
import Tabs from '@/components/Tabs';
import useScrollToTop from '@/hooks/useScrollToTop';
import RenderHottestArticles from '@/pages/ArticlesPage/RenderHottestArticles';
import RenderArticles from '../RenderArticles';

const ArticlesPage = () => {
const { ref: scrollRef, showScrollToTopButton, scrollToTop } = useScrollToTop();

return (
<Tabs defaultValue="newest">
<section className="max-w-[25.875rem] mx-auto h-screen w-screen flex flex-col relative overflow-hidden">
<header className="flex flex-col pt-[2.75rem] gap-5">
<div className="pl-7 pr-7">
<Header label="news" type="search" path="/search" />
</div>
<Tabs.List>
<Tabs.Tab value="newest">최신의</Tabs.Tab>
<Tabs.Tab value="hottest">
<BsFire className="w-[1.3rem] h-[1.3rem]" />
뜨거운
</Tabs.Tab>
<Tabs.Tab value="subscribed">
<MdStars className="w-[1.5rem] h-[1.5rem]" />
구독한
</Tabs.Tab>
</Tabs.List>
</header>
<article ref={scrollRef} className="flex-grow overflow-y-auto">
<Tabs.Panel value="newest">
<RenderArticles type="newest" />
</Tabs.Panel>
<Tabs.Panel value="hottest">
<RenderHottestArticles />
</Tabs.Panel>
<Tabs.Panel value="subscribed">
<RenderArticles type="subscribed" />
</Tabs.Panel>
<button
onClick={scrollToTop}
disabled={!showScrollToTopButton}
className={`absolute p-2 flex items-center justify-center text-white w-[3.5rem] h-[3.5rem] bg-cooled-blue drop-shadow-[0_0.25rem_0.25rem_rgba(0,0,0,0.25)] transition-opacity duration-300 ease-in-out ${
showScrollToTopButton ? 'opacity-100' : 'opacity-0 pointer-events-none'
} rounded-full bottom-24 right-4`}
aria-label="위로 가기"
>
<AiOutlineArrowUp className="w-[1.5rem] h-[1.5rem]" />
</button>
</article>
<div className="flex justify-center flex-none w-full">
<BottomNavigation currentPage="/news" />
</div>
</section>
</Tabs>
);
};

export default ArticlesPage;
61 changes: 61 additions & 0 deletions src/pages/Articles/RenderArticles/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Loader from '@/components/Loader';
import SearchSkeleton from '@/components/SearchSkeleton';
import useAuthQuery from '@/hooks/useAuthQuery';
import InfiniteScroll from '@/pages/ArticlesPage/InfiniteScroll.tsx';
import FilteredData from '@/shared/components/FilteredData';
import FailedMessage from '../FailedMessage.tsx';
import useFetchArticles from '../useFetchArticles';

type RenderArticlesProps = {
type: 'newest' | 'subscribed';
};

const RenderArticles = ({ type }: RenderArticlesProps) => {
const {
userQuery: { data: user },
} = useAuthQuery();

const followingUsersIds = Array.from(new Set(user?.following.map((user) => user.user)));

const { data, isLoading, isFetching, fetchNextPage, hasNextPage } = useFetchArticles({
type,
followingUsersIds,
});

if (isLoading) {
return <SearchSkeleton SkeletonType="title" />;
}

if (type === 'subscribed') {
if (!user) {
return (
<FailedMessage path="/login" label="로그인">
로그인이 필요합니다. <br />
로그인 페이지로 이동하시겠습니까?
</FailedMessage>
);
} else if (!followingUsersIds.length) {
return (
<FailedMessage path="/search" label="구독하기">
앗 구독한 글들이 없습니다. <br />
다른 사용자를 팔로우하러 가시겠습니까?
</FailedMessage>
)
}
}

return (
<>
<FilteredData type="article" data={data?.pages.flat()} />
{isFetching ? (
<div className="flex justify-center">
<Loader />
</div>
) : (
<InfiniteScroll fetchData={fetchNextPage} canFetchMore={hasNextPage} />
)}
</>
);
};

export default RenderArticles;
54 changes: 54 additions & 0 deletions src/pages/Articles/useFetchArticles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { fetchAllPosts, fetchUserPosts } from '@/api/Post';

type useFetchArticlesProps = {
type: 'newest' | 'subscribed';
followingUsersIds: string[];
};

const ARTICLES_LIMIT = {
newest: 10,
subscribed: 3,
};

const QUERY_KEY = {
newest: 'newestArticles',
subscribed: 'followingArticles',
};

const useFetchArticles = ({ type, followingUsersIds }: useFetchArticlesProps) => {


const FETCH_API = {
newest: async ({ pageParam = 0 }) => {
return await fetchAllPosts({ offset: pageParam, limit: ARTICLES_LIMIT[type] });
},
subscribed: async ({ pageParam = 0 }) => {
const newArticles = await Promise.all(
followingUsersIds.map((user) =>
fetchUserPosts({ offset: pageParam, limit: ARTICLES_LIMIT[type], authorId: user }),
),
);
return newArticles.flat();
},
};

const { data, fetchNextPage, hasNextPage, isLoading, isFetching } = useInfiniteQuery(
[QUERY_KEY[type]],
FETCH_API[type],
{
getNextPageParam: (lastPage, pages) => lastPage.length < ARTICLES_LIMIT[type] ? undefined : pages.length * ARTICLES_LIMIT[type]

},
);

return {
data,
fetchNextPage,
hasNextPage,
isLoading,
isFetching,
};
};

export default useFetchArticles;
6 changes: 3 additions & 3 deletions src/pages/Search/Page/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import SearchResult from '../SearchResult';
const SEARCH_RESULT_TABS = [
{
INDEX: 0,
VALUE: 'title',
VALUE: 'article',
LABEL: '글 제목',
},
{
INDEX: 1,
VALUE: 'role',
VALUE: 'user',
LABEL: '닉네임',
},
] as const;
Expand All @@ -35,7 +35,7 @@ const SearchPage = () => {
};

return (
<Tabs defaultValue="title">
<Tabs defaultValue="article">
<section className="max-w-[25.875rem] mx-auto h-screen w-screen flex flex-col font-Cafe24SurroundAir">
<div className="bg-cooled-blue dark:bg-dark-primary flex flex-col pt-[2.75rem] pl-6 pr-6 gap-4">
<Header label="search" type="close" />
Expand Down
35 changes: 7 additions & 28 deletions src/pages/Search/SearchResult/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import Article from '@/components/Article';
import FilteredData from '@/shared/components/FilteredData';
import { Post } from '@/type/Post';
import { User } from '@/type/User';
import NoResult from '../NoResult';
import UserItem from '../UserItem';

type SearchResultProps = {
searchResList: (Post | User)[] | undefined;
type: 'title' | 'role';
type: 'article' | 'user';
};

const SearchResult = ({ searchResList, type }: SearchResultProps) => {
const filteredSearchResList = searchResList?.filter((searchRes) => type in searchRes);
const reDefinedType = type === "article" ? "title" : "role"
const filteredSearchResList = searchResList?.filter((searchRes) => reDefinedType in searchRes);

if (Array.isArray(filteredSearchResList) && !filteredSearchResList.length) {
if (!filteredSearchResList?.length) {
return <NoResult />;
}

Expand All @@ -22,30 +22,9 @@ const SearchResult = ({ searchResList, type }: SearchResultProps) => {
<div className="font-Cafe24Surround text-[0.875rem] text-wall-street dark:text-lazy-gray">{`${filteredSearchResList.length}건의 검색 결과 `}</div>
)}
<div
className={`${type === 'role' && searchResList && 'flex flex-col gap-6 pl-5 pr-5 pt-2'}`}
className={`${type === 'user' && searchResList && 'flex flex-col gap-6 pl-5 pr-5 pt-2'}`}
>
{filteredSearchResList?.map((searchRes) => {
if (type === 'title') {
const { _id, title, author, createdAt, likes, image, comments } = searchRes as Post;
const { title: articleTitle } = title ? JSON.parse(title) : { title: '' };

return (
<Article
key={_id}
id={_id}
title={articleTitle ? articleTitle : '제목이 없습니다.'}
nickname={author.fullName ? `@${author.fullName}` : ''}
postedDate={createdAt}
hasImage={image !== undefined}
likes={likes?.length || 0}
comments={comments?.length || 0}
/>
);
} else {
const { _id, fullName, image } = searchRes as User;
return <UserItem key={_id} fullName={fullName} id={_id} image={image ? image : ''} />;
}
})}
<FilteredData type={type} data={filteredSearchResList} />
</div>
</div>
);
Expand Down
Loading

0 comments on commit 05c8a04

Please sign in to comment.