Skip to content

Commit

Permalink
[FE] issue134: 내 스터디 페이지 생성 (#166)
Browse files Browse the repository at this point in the history
* feat: 내 스터디 카드 컴포넌트 생성

Co-authored-by: TaeYoon <[email protected]>

* feat: 내 스터디 조회 페이지 생성

Co-authored-by: TaeYoon <[email protected]>

* refactor: 파일 이름 변경

Co-authored-by: TaeYoon <[email protected]>

* feat: 내 스터디 페이지로 가는 링크 생성

Co-authored-by: TaeYoon <[email protected]>

* fix: storybook 수정

Co-authored-by: TaeYoon <[email protected]>

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

Co-authored-by: TaeYoon <[email protected]>

* refactor: Divider 색깔 수정

Co-authored-by: TaeYoon <[email protected]>

Co-authored-by: TaeYoon <[email protected]>
  • Loading branch information
airman5573 and nan-noo authored Jul 30, 2022
1 parent cdeb842 commit 4e7fd77
Show file tree
Hide file tree
Showing 45 changed files with 1,528 additions and 146 deletions.
3 changes: 3 additions & 0 deletions frontend/.prettierrc.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ module.exports = {
'@detail-page': resolve(__dirname, '../src/pages/detail-page'),
'@main-page': resolve(__dirname, '../src/pages/main-page'),
'@create-study-page': resolve(__dirname, '../src/pages/create-study-page'),
'@my-study-page': resolve(__dirname, '../src/pages/my-study-page'),
'@layout': resolve(__dirname, '../src/layout'),
'@hooks': resolve(__dirname, '../src/hooks'),
'@mocks': resolve(__dirname, '../src/mocks'),
};

config.module.rules[0].use[0].options.presets = [
Expand Down
27 changes: 21 additions & 6 deletions frontend/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { BrowserRouter } from 'react-router-dom';

import { ThemeProvider } from '@emotion/react';

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

import { FormProvider } from '@hooks/useForm';

import { SearchProvider } from '@context/search/SearchProvider';

export const parameters = {
Expand All @@ -18,16 +21,28 @@ export const parameters = {
},
};

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});

export const decorators = [
(Story, context) => {
return (
<BrowserRouter>
<ThemeProvider theme={theme}>
<SearchProvider>
<GlobalStyles />
<Story {...context} />
</SearchProvider>
</ThemeProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<FormProvider>
<SearchProvider>
<GlobalStyles />
<Story {...context} />
</SearchProvider>
</FormProvider>
</ThemeProvider>
</QueryClientProvider>
</BrowserRouter>
);
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/env/.env.local
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
API_URL="http://52.79.214.158:8080"
API_URL=""
CLIENT_ID="cb83d95cd5644436b090"
12 changes: 10 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CreateStudyPage from '@pages/create-study-page/CreateStudyPage';
import ErrorPage from '@pages/error-page/ErrorPage';
import LoginRedirectPage from '@pages/login-redirect-page/LoginRedirectPage';
import MainPage from '@pages/main-page/MainPage';
import MyStudyPage from '@pages/my-study-page/MyStudyPage';

import DetailPage from '@detail-page/DetailPage';

Expand All @@ -36,8 +37,15 @@ const App = () => {
<Routes>
<Route path={PATH.MAIN} element={<MainPage />} />
<Route path={PATH.STUDY_DETAIL()} element={<DetailPage />} />
<Route path="/study/new" element={isLoggedIn ? <CreateStudyPage /> : <Navigate to="/" replace={true} />} />
<Route path={PATH.LOGIN} element={isLoggedIn ? <Navigate to="/" replace={true} /> : <LoginRedirectPage />} />
<Route
path={PATH.CREATE_STUDY}
element={isLoggedIn ? <CreateStudyPage /> : <Navigate to={PATH.MAIN} replace={true} />}
/>
<Route
path={PATH.LOGIN}
element={isLoggedIn ? <Navigate to={PATH.MAIN} replace={true} /> : <LoginRedirectPage />}
/>
<Route path={PATH.MY_STUDY} element={isLoggedIn ? <MyStudyPage /> : <Navigate to="/" replace={true} />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</Main>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/api/getMyStudyList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import axiosInstance from '@api/axiosInstance';

export const getMyStudyList = async () => {
const response = await axiosInstance.get(`/api/my/studies`);
return response.data;
};
2 changes: 1 addition & 1 deletion frontend/src/components/divider/Divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type DividerProps = {
};

const Divider = styled.div<MakeOptional<DividerProps, 'space' | 'color'>>(({ space = 1, color, theme }) => {
const defaultColor = theme.colors.secondary.base;
const defaultColor = theme.colors.secondary.dark;
return css`
height: 1px;
background-color: ${color ? color : defaultColor};
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const PATH = {
MAIN: '/',
STUDY_DETAIL: (studyId = ':studyId') => `/study/${studyId}`,
CREATE_STUDY: '/study/create',
MY_STUDY: '/my/study',
LOGIN: '/login',
};

Expand Down Expand Up @@ -94,3 +96,6 @@ export const MEMBER_COUNT = {
},
},
};

export const PROFILE_IMAGE_URL =
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80';
2 changes: 1 addition & 1 deletion frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import App from './App';
if (process.env.NODE_ENV == 'development') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { worker } = require('./mocks/browser');
// worker.start();
worker.start();
}

const $root = document.getElementById('root');
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/layout/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css } from '@emotion/react';

import * as S from '@layout/footer/Footer.style';

type FooterProps = {
export type FooterProps = {
marginBottom: string;
};

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/layout/header/Header.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Story } from '@storybook/react';

import Header from '@layout/header/Header';
import type { HeaderProps } from '@layout/header/Header';

export default {
title: 'Components/Header',
component: Header,
};

const Template: Story = props => <Header {...props} />;
const Template: Story<HeaderProps> = props => <Header {...props} />;

export const Default = Template.bind({});
Default.args = {};
39 changes: 6 additions & 33 deletions frontend/src/layout/header/Header.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import styled from '@emotion/styled';

import { mqDown } from '@utils/index';

import * as Logo from '@layout/header/logo/Logo.style';
import * as SearchBar from '@layout/header/search-bar/SearchBar.style';
import * as Logo from '@layout/header/components/logo/Logo.style';
import * as SearchBar from '@layout/header/components/search-bar/SearchBar.style';

export const SearchBarContainer = styled.div`
position: absolute;
Expand Down Expand Up @@ -63,37 +63,10 @@ export const Row = styled.header`

export const Nav = styled.nav`
display: flex;
column-gap: 16px;
`;

export const NavButton = styled.button`
${({ theme }) => css`
display: flex;
justify-content: center;
align-items: center;
column-gap: 4px;
padding: 8px 4px;
color: ${theme.colors.primary.base};
border: none;
background-color: transparent;
&:hover {
border-bottom: 1px solid ${theme.colors.secondary.base};
}
& > svg {
fill: ${theme.colors.primary.base};
}
& > span {
color: ${theme.colors.primary.base};
}
`}
${mqDown('md')} {
& > span {
display: none;
}
}
export const AvatarButton = styled.button`
border: none;
background-color: transparent;
`;
52 changes: 36 additions & 16 deletions frontend/src/layout/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { useContext } from 'react';
import { useContext, useState } from 'react';
import { BiBookmark } from 'react-icons/bi';
import { MdOutlineLogin, MdOutlineLogout } from 'react-icons/md';
import { Link, useNavigate } from 'react-router-dom';

import { PATH } from '@constants';
import { PATH, PROFILE_IMAGE_URL } from '@constants';

import { useAuth } from '@hooks/useAuth';

import { LoginContext } from '@context/login/LoginProvider';
import { SearchContext } from '@context/search/SearchProvider';

import * as S from '@layout/header/Header.style';
import Logo from '@layout/header/logo/Logo';
import SearchBar from '@layout/header/search-bar/SearchBar';
import DropDownBox from '@layout/header/components/drop-down-box/DropDownBox';
import Logo from '@layout/header/components/logo/Logo';
import NavButton from '@layout/header/components/nav-button/NavButton';
import SearchBar from '@layout/header/components/search-bar/SearchBar';

import Avatar from '@components/avatar/Avatar';

type HeaderProps = {
export type HeaderProps = {
className?: string;
};

const Header: React.FC<HeaderProps> = ({ className }) => {
const { isLoggedIn } = useContext(LoginContext);
const { setKeyword } = useContext(SearchContext);

const [openDropDownBox, setOpenDropDownBox] = useState(false);

const navigate = useNavigate();

const { logout } = useAuth();
Expand All @@ -38,6 +43,9 @@ const Header: React.FC<HeaderProps> = ({ className }) => {
navigate(PATH.MAIN);
};

const handleAvatarButtonClick = () => setOpenDropDownBox(prev => !prev);
const handleOutsideDropBoxClick = () => setOpenDropDownBox(false);

return (
<S.Row className={className}>
<Link to={PATH.MAIN}>
Expand All @@ -48,22 +56,34 @@ const Header: React.FC<HeaderProps> = ({ className }) => {
</S.SearchBarContainer>
{isLoggedIn ? (
<S.Nav>
<S.NavButton onClick={logout} aria-label="로그아웃">
<MdOutlineLogout size={20} />
<span>로그아웃</span>
</S.NavButton>
<Avatar
// TODO: Context에서 정보를 가져온다
profileImg="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80"
profileAlt="프로필 이미지"
/>
<Link to={PATH.MY_STUDY}>
<NavButton ariaLabel="내 스터디">
<BiBookmark size={20} />
<span>내 스터디</span>
</NavButton>
</Link>
<S.AvatarButton onClick={handleAvatarButtonClick}>
<Avatar
// TODO: Context에서 정보를 가져온다
profileImg={PROFILE_IMAGE_URL}
profileAlt="프로필 이미지"
/>
</S.AvatarButton>
{openDropDownBox && (
<DropDownBox top={'70px'} right={'50px'} onOutOfBoxClick={handleOutsideDropBoxClick}>
<NavButton onClick={logout} ariaLabel="로그아웃">
<MdOutlineLogout size={20} />
<span>로그아웃</span>
</NavButton>
</DropDownBox>
)}
</S.Nav>
) : (
<a href={`https://github.com/login/oauth/authorize?client_id=${process.env.CLIENT_ID}`}>
<S.NavButton aria-label="로그인">
<NavButton ariaLabel="로그인">
<MdOutlineLogin size={20} />
<span>로그인</span>
</S.NavButton>
</NavButton>
</a>
)}
</S.Row>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Story } from '@storybook/react';
import { useState } from 'react';

import DropDownBox from '@layout/header/components/drop-down-box/DropDownBox';
import type { DropDownBoxProps } from '@layout/header/components/drop-down-box/DropDownBox';

export default {
title: 'Components/DropDownBox',
component: DropDownBox,
argTypes: {
children: { controls: 'text' },
top: { controls: 'text' },
right: { controls: 'text' },
left: { controls: 'text' },
bottom: { controls: 'text' },
},
};

const Template: Story<DropDownBoxProps> = props => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(prev => !prev)}>드롭박스 열기</button>
{isOpen && <DropDownBox {...props} onOutOfBoxClick={() => setIsOpen(false)} />}
</div>
);
};

export const Default = Template.bind({});
Default.args = {
children: 'hihi',
top: '40px',
left: '15px',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';

import type { DropDownBoxProps } from '@layout/header/components/drop-down-box/DropDownBox';

export const DropDownBoxContainer = styled.div`
position: fixed;
top: 0;
left: 0;
background: trnasparent;
height: 100%;
width: 100vw;
`;

export const DropDownBox = styled.div<Pick<DropDownBoxProps, 'top' | 'bottom' | 'left' | 'right'>>`
${({ theme, top, bottom, left, right }) => css`
position: absolute;
white-space: nowrap;
${top && `top: ${top};`}
${bottom && `bottom: ${bottom};`}
${left && `left: ${left};`}
${right && `right: ${right};`}
padding: 16px;
border: 1px solid ${theme.colors.secondary.dark};
border-radius: 5px;
background-color: ${theme.colors.secondary.light};
z-index: 3;
`}
`;
Loading

0 comments on commit 4e7fd77

Please sign in to comment.