Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
[FE] feat: Home 페이지 구현 (#444)
Browse files Browse the repository at this point in the history
* test: 전체글 조회 mock 추가

* feat: 전체글 조회 api 구현

* chore: 사용하지 않는 파일 삭제

* feat: `Pagination` 공통 컴포넌트 구현

* feat: `home-border` 아이콘 추가

* refactor: 전체 글 api 스펙 수정으로 인한 수정

* fix: `WritingTable` 발행 시간 -> 생성 날짜로 수정

* feat: `HomePage` 페이지 구현

* feat: `HomePage` 레이아웃과 라우터 연결

* fix: API 명세 수정으로 인한 타입 수정

---------

Co-authored-by: Youngjin Park <[email protected]>
  • Loading branch information
jeonjeunghoon and yogjin authored Sep 21, 2023
1 parent 0b8a48a commit c4dbe7e
Show file tree
Hide file tree
Showing 17 changed files with 458 additions and 18 deletions.
Binary file added .DS_Store
Binary file not shown.
15 changes: 0 additions & 15 deletions frontend/__tests__/hello.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions frontend/src/apis/writings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
PublishWritingArgs,
UpdateWritingTitleArgs,
UpdateWritingOrderArgs,
GetHomeWritingsResponse,
} from 'types/apis/writings';

// 글 생성(글 업로드): POST
Expand Down Expand Up @@ -52,3 +53,7 @@ export const updateWritingTitle = ({ writingId, body }: UpdateWritingTitleArgs)
// 글 제목 순서 변경: PATCH
export const updateWritingOrder = ({ writingId, body }: UpdateWritingOrderArgs) =>
http.patch(`${writingURL}/${writingId}`, { json: body });

// 전체 글: GET
export const getHomeWritings = (option: string): Promise<GetHomeWritingsResponse> =>
http.get(`${writingURL}/home${option}`);
1 change: 1 addition & 0 deletions frontend/src/assets/icons/home-border.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { ReactComponent as NotionIcon } from './notion.svg';
export { ReactComponent as DonggleIcon } from './donggle-logo.svg';
export { ReactComponent as BlurBackgroundIcon } from './blur-background.svg';
export { ReactComponent as HyperlinkIcon } from './hyperlink.svg';
export { ReactComponent as HomeBorderIcon } from './home-border.svg';
89 changes: 89 additions & 0 deletions frontend/src/components/@common/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import styled from 'styled-components';

type Props = {
pageLength: number;
activePage: number;
changeActivePage: (pageIndex: number) => void;
};

const Pagination = ({ pageLength, activePage, changeActivePage }: Props) => {
const isShowPrevious = activePage > 1;
const isShowNext = activePage < pageLength;

// 시작 페이지와 끝 페이지의 범위를 설정해주는 함수
const setPageRange = () => {
const isBetween1and4 = activePage < 5;
const isNearLast4Pages = activePage >= pageLength - 4;

if (isBetween1and4) return [1, Math.min(9, pageLength)];

if (isNearLast4Pages) return [Math.max(pageLength - 8, 1), pageLength];

return [activePage - 4, activePage + 4];
};

const [startPage, endPage] = setPageRange();

return (
<S.Container>
<S.PaginationButton $isShow={isShowPrevious} onClick={() => changeActivePage(activePage - 1)}>
{'<'}
</S.PaginationButton>
{Array.from({ length: endPage - startPage + 1 }, (_, index) => {
const pageIndex = startPage + index;

return (
<li key={pageIndex}>
<S.PageButton
$isActive={pageIndex === activePage}
onClick={() => changeActivePage(pageIndex)}
>
{pageIndex}
</S.PageButton>
</li>
);
})}
<S.PaginationButton $isShow={isShowNext} onClick={() => changeActivePage(activePage + 1)}>
{'>'}
</S.PaginationButton>
</S.Container>
);
};

export default Pagination;

const S = {
Container: styled.div`
display: flex;
gap: 16px;
`,

PaginationButton: styled.button<{ $isShow: boolean }>`
width: 2.8rem;
height: 2.8rem;
border-radius: 4px;
font-size: 2rem;
opacity: ${({ $isShow }) => ($isShow ? 1 : 0)};
pointer-events: ${({ $isShow }) => ($isShow ? 'auto' : 'none')};
&:hover {
background-color: ${({ theme }) => theme.color.gray3};
}
`,

PageButton: styled.button<{ $isActive: boolean }>`
width: 2.8rem;
height: 2.8rem;
background-color: ${({ theme, $isActive }) => ($isActive ? theme.color.gray3 : 'inherit')};
border-radius: 4px;
font-size: 1.6rem;
font-weight: ${({ $isActive }) => ($isActive ? 600 : 400)};
&:hover {
background-color: ${({ theme }) => theme.color.gray3};
}
`,
};
43 changes: 43 additions & 0 deletions frontend/src/components/HomeButton/HomeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { HomeBorderIcon } from 'assets/icons';
import { NAVIGATE_PATH } from 'constants/path';
import { usePageNavigate } from 'hooks/usePageNavigate';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';

const HomeButton = () => {
const location = useLocation();
const { goSpacePage } = usePageNavigate();

return (
<S.Button onClick={goSpacePage} $isClicked={location.pathname === NAVIGATE_PATH.spacePage}>
<HomeBorderIcon />
<S.Title>전체 글</S.Title>
</S.Button>
);
};

export default HomeButton;

const S = {
Button: styled.button<{ $isClicked: boolean }>`
display: flex;
align-items: center;
gap: 8px;
width: 100%;
height: 3.6rem;
padding: 8px;
background-color: ${({ theme, $isClicked }) => $isClicked && theme.color.gray4};
border-radius: 4px;
text-align: start;
&:hover {
background-color: ${({ theme }) => theme.color.gray3};
}
`,

Title: styled.p`
font-size: 1.4rem;
font-weight: 500;
`,
};
130 changes: 130 additions & 0 deletions frontend/src/components/HomeTable/HomeTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Fragment } from 'react';
import styled from 'styled-components';
import { usePageNavigate } from 'hooks/usePageNavigate';
import { dateFormatter } from 'utils/date';
import { blogIcon } from 'components/WritingTable/WritingTable';
import Pagination from 'components/@common/Pagination/Pagination';
import { useHomeTable } from './useHomeTable';

type Props = {
initialPageIndex?: number;
};

const HomeTable = ({ initialPageIndex = 1 }: Props) => {
const { content, totalPages, rowRef, activePage, changeActivePage } =
useHomeTable(initialPageIndex);
const { goWritingPage } = usePageNavigate();

if (!content || !totalPages) return null;

return (
<S.Container>
<S.HomeTable summary='카테고리 내부 글 목록을 나타낸다'>
<colgroup>
<col width='20%' />
<col width='40%' />
<col width='20%' />
<col width='20%' />
</colgroup>
<thead>
<tr ref={rowRef} tabIndex={0}>
<th>카테고리</th>
<th>글 제목</th>
<th>생성 날짜</th>
<th>발행한 블로그 플랫폼</th>
</tr>
</thead>
<tbody>
{content.map(
({
id,
title,
publishedDetails,
createdAt,
category: { id: categoryId, categoryName },
}) => (
<tr
key={id}
onClick={() => goWritingPage({ categoryId, writingId: id })}
role='button'
tabIndex={0}
>
<td>{categoryName}</td>
<td>{title}</td>
<td>{dateFormatter(createdAt, 'YYYY.MM.DD.')}</td>
<td>
<S.PublishedToIconContainer>
{publishedDetails.map(({ blogName }, index) => (
<Fragment key={index}>{blogIcon[blogName]}</Fragment>
))}
</S.PublishedToIconContainer>
</td>
</tr>
),
)}
</tbody>
</S.HomeTable>
<Pagination
pageLength={totalPages}
activePage={activePage}
changeActivePage={changeActivePage}
/>
</S.Container>
);
};

export default HomeTable;

const S = {
Container: styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 50px;
`,

HomeTable: styled.table`
width: 100%;
text-align: left;
font-size: 1.4rem;
th {
color: ${({ theme }) => theme.color.gray8};
}
td {
.publishedTo {
display: flex;
gap: 0.8rem;
}
}
th,
td {
padding: 1.1rem;
border: 1px solid ${({ theme }) => theme.color.gray5};
}
th:first-child,
td:first-child {
border-left: none;
}
th:last-child,
td:last-child {
border-right: none;
}
tbody tr:hover {
cursor: pointer;
transform: scale(1.01);
transition: all 300ms;
}
`,

PublishedToIconContainer: styled.div`
display: flex;
gap: 0.8rem;
`,
};
40 changes: 40 additions & 0 deletions frontend/src/components/HomeTable/useHomeTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query';

import { getHomeWritings } from 'apis/writings';
import { GetHomeWritingsResponse } from 'types/apis/writings';

export const useHomeTable = (initialPageIndex: number) => {
const [activePage, setActivePage] = useState(initialPageIndex);
const [fetchOption, setFetchOption] = useState(`?page=${activePage}&sort=createAt,desc`);
const { data } = useQuery<GetHomeWritingsResponse>(['homeWritings', fetchOption], () =>
getHomeWritings(fetchOption),
);
const rowRef = useRef<HTMLTableRowElement>(null);

const changeActivePage = (pageIndex: number) => {
setFetchOption(`?page=${pageIndex}&sort=createAt,desc`);
setActivePage(pageIndex);
};

useEffect(() => {
rowRef.current?.focus();
}, []);

return {
content: data?.content,
pageable: data?.pageable,
totalPages: data?.totalPages,
totalElements: data?.totalElements,
last: data?.last,
size: data?.size,
number: data?.number,
sort: data?.sort,
numberOfElements: data?.numberOfElements,
first: data?.first,
empty: data?.empty,
rowRef,
activePage,
changeActivePage,
};
};
4 changes: 2 additions & 2 deletions frontend/src/components/WritingTable/WritingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = {
categoryId: number;
};

const blogIcon: Record<Blog, ReactElement> = {
export const blogIcon: Record<Blog, ReactElement> = {
MEDIUM: <MediumLogoIcon width='2.4rem' height='2.4rem' />,
TISTORY: <TistoryLogoIcon width='2.4rem' height='2.4rem' />,
};
Expand All @@ -34,8 +34,8 @@ const WritingTable = ({ writings, categoryId }: Props) => {
<thead>
<tr ref={rowRef} tabIndex={0}>
<th>글 제목</th>
<th>생성 날짜</th>
<th>발행한 블로그 플랫폼</th>
<th>발행 시간</th>
</tr>
</thead>
<tbody>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const PATH = {
myPage: 'my-page',
connections: 'connections/*',
space: 'space',
home: '',
writingPage: 'writings/:categoryId/:writingId',
writingTablePage: 'writings/:categoryId',
trashCanPage: 'trash-can',
Expand Down
Loading

0 comments on commit c4dbe7e

Please sign in to comment.