Skip to content

Commit

Permalink
인증/인가에 따른 라우팅 구현, API 통신 실패 및 존재하지 않는 페이지(Not Found)에 대한 Fallback UI …
Browse files Browse the repository at this point in the history
…구현 (#343)

* chore: (#183) 불필요한 모듈 삭제

* refactor: (#183) 의존성 배열 추가

* feat: (#253) NotFound 컴포넌트 구현

* feat: (#183) PrivateRoute 구현 및 필요한 페이지에 적용

* feat: (#253) 헤더 및 로고 추가

* feat: (#325) Error 컴포넌트 구현 및 소셜 로그인 요청 실패 케이스에 적용

* refactor: (#325) 다시 시도 라는 문구로 수정

* chore: (#325) 불필요한 코드 삭제

* feat: (#325) Error 컴포넌트 구현 및 get 요청 실패 케이스에 적용

* feat: (#325) IconButton에 retry 아이콘 추가 및 Error 컴포넌트 디자인 수정

* refactor: (#325) Redirection 페이지에 로그인 요청에 대한 로딩 및 에러 컴포넌트 적용, errorElement 삭제

* refactor: (#183) 전역 상태 대신 cookie 유무로 navigate하도록 수정

* refactor: (#183) navigate 대신 Navigate로 수정, 권한 관련 props 추가

* feat: (#183) 작성자인 경우에만 글 수정, 투표 통계 페이지 접근하도록 라우팅 설정

* refactor: (#183) Navigate 불필요한 속성 제거, 페이지 접근 불가능한 경우 alert 구현

* refactor: (#183) props에 할당한 값에 대한 타입 단언 대신, 조건부로 대체 컴포넌트 렌더링하도록 수정

* refactor: (#183) Error 컴포넌트 이름 ErrorMessage로 수정, Error 페이지 및 ErrorMessage 컴포넌트 텍스트 수정
  • Loading branch information
inyeong-kang authored Aug 12, 2023
1 parent 0f01338 commit c4049ba
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 27 deletions.
3 changes: 3 additions & 0 deletions frontend/src/assets/retry.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion frontend/src/components/PostForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UseMutateFunction } from '@tanstack/react-query';

import React, { HTMLAttributes, useContext, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Navigate, useNavigate } from 'react-router-dom';

import { PostInfo } from '@type/post';

Expand All @@ -20,9 +20,11 @@ import SquareButton from '@components/common/SquareButton';
import TimePickerOptionList from '@components/common/TimePickerOptionList';
import WritingVoteOptionList from '@components/optionList/WritingVoteOptionList';

import { PATH } from '@constants/path';
import { POST_DESCRIPTION_MAX_LENGTH, POST_TITLE_MAX_LENGTH } from '@constants/post';

import { changeCategoryToOption } from '@utils/post/changeCategoryToOption';
import { checkWriter } from '@utils/post/checkWriter';
import { addTimeToDate, formatTimeWithOption } from '@utils/post/formatTime';
import { getDeadlineTime } from '@utils/post/getDeadlineTime';

Expand All @@ -41,13 +43,15 @@ const CATEGORY_COUNT_LIMIT = 3;

export default function PostForm({ data, mutate }: PostFormProps) {
const {
postId,
title,
content,
category: categoryIds,
createTime,
deadline,
voteInfo,
imageUrl,
writer,
} = data ?? {};

const navigate = useNavigate();
Expand Down Expand Up @@ -140,6 +144,8 @@ export default function PostForm({ data, mutate }: PostFormProps) {
}
};

if (postId && writer && !checkWriter(writer.id)) return <Navigate to={PATH.HOME} />;

return (
<>
<S.HeaderWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import ErrorMessage from '.';

const meta: Meta<typeof ErrorMessage> = {
component: ErrorMessage,
};

export default meta;
type Story = StoryObj<typeof ErrorMessage>;

export const Default: Story = {
render: () => <ErrorMessage errorHandler={() => {}} />,
};
21 changes: 21 additions & 0 deletions frontend/src/components/common/ErrorMessage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import IconButton from '../IconButton';
import SquareButton from '../SquareButton';

import * as S from './style';

export default function ErrorMessage({ errorHandler }: { errorHandler: () => void }) {
return (
<S.Wrapper>
<S.Title>⚠ 잠시 후 다시 시도해주세요.</S.Title>
<S.Description>요청하신 데이터를 불러오는데 실패했습니다.</S.Description>
<S.Direction>
<SquareButton onClick={errorHandler} aria-label="다시 시도" theme="blank">
<S.RetryText>
<IconButton category="retry" />
다시 시도
</S.RetryText>
</SquareButton>
</S.Direction>
</S.Wrapper>
);
}
67 changes: 67 additions & 0 deletions frontend/src/components/common/ErrorMessage/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { styled } from 'styled-components';

import { theme } from '@styles/theme';

export const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
position: relative;
`;

export const HeaderWrapper = styled.div`
width: 100%;
position: fixed;
z-index: ${theme.zIndex.header};
@media (min-width: ${theme.breakpoint.md}) {
display: none;
}
`;

export const Title = styled.h1`
width: 90%;
margin-top: 60px;
font-size: 20px;
font-weight: bold;
text-align: center;
`;

export const Description = styled.p`
width: 90%;
margin: 20px 0;
font: var(--text-body);
text-align: center;
`;

export const Direction = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
`;

export const RetryText = styled.p`
display: flex;
justify-content: space-around;
gap: 10px;
padding: 12px;
font: var(--text-body);
font-weight: bold;
`;

export const ButtonWrapper = styled.div`
width: 120px;
height: 50px;
`;
7 changes: 6 additions & 1 deletion frontend/src/components/common/IconButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { ButtonHTMLAttributes } from 'react';

import backIcon from '@assets/back.svg';
import categoryIcon from '@assets/category.svg';
import retryIcon from '@assets/retry.svg';
import searchIcon from '@assets/search_white.svg';

import * as S from './style';

type IconCategory = 'category' | 'back' | 'search';
type IconCategory = 'category' | 'back' | 'search' | 'retry';

const ICON_CATEGORY: Record<IconCategory, { name: string; url: string }> = {
category: {
Expand All @@ -21,6 +22,10 @@ const ICON_CATEGORY: Record<IconCategory, { name: string; url: string }> = {
name: '검색',
url: searchIcon,
},
retry: {
name: '다시시도',
url: retryIcon,
},
};

interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/common/SquareButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ButtonHTMLAttributes } from 'react';
import { ButtonHTMLAttributes, ReactNode } from 'react';

import * as S from './style';

interface SquareButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
theme: 'blank' | 'fill' | 'gray';
children: string;
children: ReactNode;
}

/* 마감시간, 확인, 취소 등 사용될 버튼
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/context/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
if (userInfo && loggedInfo.isLoggedIn) {
setLoggedInfo(origin => ({ ...origin, userInfo }));
}
}, [userInfo]);
}, [loggedInfo.isLoggedIn, userInfo]);

useEffect(() => {
const accessToken = getCookieToken().accessToken;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/pages/Error/Error.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import Error from '.';

const meta: Meta<typeof Error> = {
component: Error,
};

export default meta;
type Story = StoryObj<typeof Error>;

export const Default: Story = {
render: () => <Error />,
};
36 changes: 36 additions & 0 deletions frontend/src/pages/Error/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useNavigate } from 'react-router-dom';

import Layout from '@components/common/Layout';
import SquareButton from '@components/common/SquareButton';

import * as S from './style';

export default function Error({ message }: { message?: string }) {
const navigate = useNavigate();

return (
<Layout isSidebarVisible={false}>
<S.Wrapper>
<S.Description>{message ? message : '요청 중 오류가 발생했습니다.'}</S.Description>
<S.ButtonWrapper>
<SquareButton
theme="fill"
onClick={() => {
navigate('/');
}}
>
홈으로 가기
</SquareButton>
<SquareButton
theme="gray"
onClick={() => {
window.location.reload();
}}
>
새로 고침
</SquareButton>
</S.ButtonWrapper>
</S.Wrapper>
</Layout>
);
}
38 changes: 38 additions & 0 deletions frontend/src/pages/Error/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { styled } from 'styled-components';

import { theme } from '@styles/theme';

export const Wrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 40px;
position: relative;
`;

export const HeaderWrapper = styled.div`
width: 100%;
position: fixed;
z-index: ${theme.zIndex.header};
`;

export const Description = styled.p`
width: 90%;
margin-top: 60px;
font: var(--text-title);
text-align: center;
`;

export const ButtonWrapper = styled.div`
display: flex;
justify-content: space-between;
gap: 20px;
width: 280px;
height: 50px;
`;
11 changes: 4 additions & 7 deletions frontend/src/pages/MyInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AuthContext } from '@hooks/context/auth';
import { useToggle } from '@hooks/useToggle';

import Accordion from '@components/common/Accordion';
import GuestProfile from '@components/common/Dashboard/GuestProfile';
import UserProfile from '@components/common/Dashboard/UserProfile';
import IconButton from '@components/common/IconButton';
import Layout from '@components/common/Layout';
Expand All @@ -18,12 +19,8 @@ export default function MyInfo() {
const navigate = useNavigate();
const { isOpen, openComponent, closeComponent } = useToggle();

const { userInfo } = useContext(AuthContext).loggedInfo;

if (!userInfo) {
navigate('/');
return <></>;
}
const { loggedInfo } = useContext(AuthContext);
const { userInfo } = loggedInfo;

return (
<Layout isSidebarVisible={true}>
Expand All @@ -39,7 +36,7 @@ export default function MyInfo() {
</NarrowTemplateHeader>
</S.HeaderWrapper>
<S.ProfileSection>
<UserProfile userInfo={userInfo} />
{userInfo ? <UserProfile userInfo={userInfo} /> : <GuestProfile />}
</S.ProfileSection>
<S.UserControlSection>
<Accordion title="닉네임 변경">
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/pages/NotFound/NotFound.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import NotFound from '.';

const meta: Meta<typeof NotFound> = {
component: NotFound,
};

export default meta;
type Story = StoryObj<typeof NotFound>;

export const Default: Story = {
render: () => <NotFound />,
};
42 changes: 42 additions & 0 deletions frontend/src/pages/NotFound/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useNavigate } from 'react-router-dom';

import IconButton from '@components/common/IconButton';
import Layout from '@components/common/Layout';
import LogoButton from '@components/common/LogoButton';
import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader';
import SquareButton from '@components/common/SquareButton';

import * as S from './style';

export default function NotFound() {
const navigate = useNavigate();
return (
<Layout isSidebarVisible={false}>
<S.Wrapper>
<S.HeaderWrapper>
<NarrowTemplateHeader>
<IconButton
category="back"
onClick={() => {
navigate(-1);
}}
/>
</NarrowTemplateHeader>
</S.HeaderWrapper>
<S.Title>404</S.Title>
<LogoButton content="icon" style={{ width: '150px', height: '150px' }} />
<S.Description>요청하신 페이지를 찾을 수 없어요.</S.Description>
<S.ButtonWrapper>
<SquareButton
theme="fill"
onClick={() => {
navigate('/');
}}
>
홈으로 가기
</SquareButton>
</S.ButtonWrapper>
</S.Wrapper>
</Layout>
);
}
Loading

0 comments on commit c4049ba

Please sign in to comment.