diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json index f7c34cfe4..e4668004f 100644 --- a/frontend/.prettierrc.json +++ b/frontend/.prettierrc.json @@ -13,6 +13,7 @@ "importOrder": [ "^@emotion", "^@assets/(.*)$", + "^@mocks/(.*)$", "^@constants$", "^@utils/(.*)$", "^@custom-types/(.*)$", @@ -25,6 +26,8 @@ "^@components/(.*)$", "^@main-page/(.*)$", "^@detail-page/(.*)$", + "^@create-study-page/(.*)$", + "^@my-study-page/(.*)$", "^[./]" ], "importOrderSeparation": true, diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index 8ba38291a..8abe01970 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -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 = [ diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 27711313f..59fd3b9d0 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -1,4 +1,5 @@ import React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { BrowserRouter } from 'react-router-dom'; import { ThemeProvider } from '@emotion/react'; @@ -6,6 +7,8 @@ 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 = { @@ -18,16 +21,28 @@ export const parameters = { }, }; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, +}); + export const decorators = [ (Story, context) => { return ( - - - - - - + + + + + + + + + + ); }, diff --git a/frontend/env/.env.local b/frontend/env/.env.local index f6eeeb599..5d27bfa26 100644 --- a/frontend/env/.env.local +++ b/frontend/env/.env.local @@ -1,2 +1,2 @@ -API_URL="http://52.79.214.158:8080" +API_URL="" CLIENT_ID="cb83d95cd5644436b090" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9657b4d0c..45f6ff537 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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'; @@ -36,8 +37,15 @@ const App = () => { } /> } /> - : } /> - : } /> + : } + /> + : } + /> + : } /> } /> diff --git a/frontend/src/api/getMyStudyList.ts b/frontend/src/api/getMyStudyList.ts new file mode 100644 index 000000000..6d39cefb7 --- /dev/null +++ b/frontend/src/api/getMyStudyList.ts @@ -0,0 +1,6 @@ +import axiosInstance from '@api/axiosInstance'; + +export const getMyStudyList = async () => { + const response = await axiosInstance.get(`/api/my/studies`); + return response.data; +}; diff --git a/frontend/src/components/divider/Divider.tsx b/frontend/src/components/divider/Divider.tsx index f259c49cf..dde203134 100644 --- a/frontend/src/components/divider/Divider.tsx +++ b/frontend/src/components/divider/Divider.tsx @@ -11,7 +11,7 @@ type DividerProps = { }; const Divider = styled.div>(({ 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}; diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 48cdc51f3..2708f8701 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,6 +1,8 @@ export const PATH = { MAIN: '/', STUDY_DETAIL: (studyId = ':studyId') => `/study/${studyId}`, + CREATE_STUDY: '/study/create', + MY_STUDY: '/my/study', LOGIN: '/login', }; @@ -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'; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index eecaa9e5b..eedd93260 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -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'); diff --git a/frontend/src/layout/footer/Footer.tsx b/frontend/src/layout/footer/Footer.tsx index d675398e6..175ed2a10 100644 --- a/frontend/src/layout/footer/Footer.tsx +++ b/frontend/src/layout/footer/Footer.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import * as S from '@layout/footer/Footer.style'; -type FooterProps = { +export type FooterProps = { marginBottom: string; }; diff --git a/frontend/src/layout/header/Header.stories.tsx b/frontend/src/layout/header/Header.stories.tsx index dca1e77db..dedac505a 100644 --- a/frontend/src/layout/header/Header.stories.tsx +++ b/frontend/src/layout/header/Header.stories.tsx @@ -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 =>
; +const Template: Story = props =>
; export const Default = Template.bind({}); Default.args = {}; diff --git a/frontend/src/layout/header/Header.style.tsx b/frontend/src/layout/header/Header.style.tsx index 9d91966a3..35ff7f210 100644 --- a/frontend/src/layout/header/Header.style.tsx +++ b/frontend/src/layout/header/Header.style.tsx @@ -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; @@ -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; `; diff --git a/frontend/src/layout/header/Header.tsx b/frontend/src/layout/header/Header.tsx index d14f8e701..d57e3b4b7 100644 --- a/frontend/src/layout/header/Header.tsx +++ b/frontend/src/layout/header/Header.tsx @@ -1,8 +1,9 @@ -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'; @@ -10,12 +11,14 @@ 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; }; @@ -23,6 +26,8 @@ const Header: React.FC = ({ className }) => { const { isLoggedIn } = useContext(LoginContext); const { setKeyword } = useContext(SearchContext); + const [openDropDownBox, setOpenDropDownBox] = useState(false); + const navigate = useNavigate(); const { logout } = useAuth(); @@ -38,6 +43,9 @@ const Header: React.FC = ({ className }) => { navigate(PATH.MAIN); }; + const handleAvatarButtonClick = () => setOpenDropDownBox(prev => !prev); + const handleOutsideDropBoxClick = () => setOpenDropDownBox(false); + return ( @@ -48,22 +56,34 @@ const Header: React.FC = ({ className }) => { {isLoggedIn ? ( - - - 로그아웃 - - + + + + 내 스터디 + + + + + + {openDropDownBox && ( + + + + 로그아웃 + + + )} ) : ( - + 로그인 - + )} diff --git a/frontend/src/layout/header/components/drop-down-box/DropDownBox.stories.tsx b/frontend/src/layout/header/components/drop-down-box/DropDownBox.stories.tsx new file mode 100644 index 000000000..17648ef6e --- /dev/null +++ b/frontend/src/layout/header/components/drop-down-box/DropDownBox.stories.tsx @@ -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 = props => { + const [isOpen, setIsOpen] = useState(false); + return ( +
+ + {isOpen && setIsOpen(false)} />} +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + children: 'hihi', + top: '40px', + left: '15px', +}; diff --git a/frontend/src/layout/header/components/drop-down-box/DropDownBox.style.tsx b/frontend/src/layout/header/components/drop-down-box/DropDownBox.style.tsx new file mode 100644 index 000000000..d42c0fb76 --- /dev/null +++ b/frontend/src/layout/header/components/drop-down-box/DropDownBox.style.tsx @@ -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>` + ${({ 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; + `} +`; diff --git a/frontend/src/layout/header/components/drop-down-box/DropDownBox.tsx b/frontend/src/layout/header/components/drop-down-box/DropDownBox.tsx new file mode 100644 index 000000000..92370273f --- /dev/null +++ b/frontend/src/layout/header/components/drop-down-box/DropDownBox.tsx @@ -0,0 +1,20 @@ +import * as S from '@layout/header/components/drop-down-box/DropDownBox.style'; + +export interface DropDownBoxProps { + children: React.ReactNode; + onOutOfBoxClick: React.MouseEventHandler; + top?: string; + bottom?: string; + left?: string; + right?: string; +} + +const DropDownBox: React.FC = ({ children, onOutOfBoxClick, ...positions }) => { + return ( + + {children} + + ); +}; + +export default DropDownBox; diff --git a/frontend/src/layout/header/logo/Logo.stories.tsx b/frontend/src/layout/header/components/logo/Logo.stories.tsx similarity index 80% rename from frontend/src/layout/header/logo/Logo.stories.tsx rename to frontend/src/layout/header/components/logo/Logo.stories.tsx index 76b990c56..15f4ead9a 100644 --- a/frontend/src/layout/header/logo/Logo.stories.tsx +++ b/frontend/src/layout/header/components/logo/Logo.stories.tsx @@ -1,6 +1,6 @@ import { Story } from '@storybook/react'; -import Logo from '@layout/header/logo/Logo'; +import Logo from '@layout/header/components/logo/Logo'; export default { title: 'Components/Logo', diff --git a/frontend/src/layout/header/logo/Logo.style.tsx b/frontend/src/layout/header/components/logo/Logo.style.tsx similarity index 100% rename from frontend/src/layout/header/logo/Logo.style.tsx rename to frontend/src/layout/header/components/logo/Logo.style.tsx diff --git a/frontend/src/layout/header/logo/Logo.tsx b/frontend/src/layout/header/components/logo/Logo.tsx similarity index 84% rename from frontend/src/layout/header/logo/Logo.tsx rename to frontend/src/layout/header/components/logo/Logo.tsx index 913a34c7a..f8131b21d 100644 --- a/frontend/src/layout/header/logo/Logo.tsx +++ b/frontend/src/layout/header/components/logo/Logo.tsx @@ -1,6 +1,6 @@ import logoImage from '@assets/images/logo.png'; -import * as S from '@layout/header/logo/Logo.style'; +import * as S from '@layout/header/components/logo/Logo.style'; import Image from '@components/image/Image'; diff --git a/frontend/src/layout/header/components/nav-button/NavButton.stories.tsx b/frontend/src/layout/header/components/nav-button/NavButton.stories.tsx new file mode 100644 index 000000000..06d60c72d --- /dev/null +++ b/frontend/src/layout/header/components/nav-button/NavButton.stories.tsx @@ -0,0 +1,19 @@ +import { Story } from '@storybook/react'; + +import NavButton from '@layout/header/components/nav-button/NavButton'; +import type { NavButtonProps } from '@layout/header/components/nav-button/NavButton'; + +export default { + title: 'Components/NavButton', + component: NavButton, + argTypes: { + children: { controls: 'text' }, + }, +}; + +const Template: Story = props => ; + +export const Default = Template.bind({}); +Default.args = { + children: '➕ hihi', +}; diff --git a/frontend/src/layout/header/components/nav-button/NavButton.style.tsx b/frontend/src/layout/header/components/nav-button/NavButton.style.tsx new file mode 100644 index 000000000..d73067f86 --- /dev/null +++ b/frontend/src/layout/header/components/nav-button/NavButton.style.tsx @@ -0,0 +1,37 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { mqDown } from '@utils/index'; + +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; + } + } +`; diff --git a/frontend/src/layout/header/components/nav-button/NavButton.tsx b/frontend/src/layout/header/components/nav-button/NavButton.tsx new file mode 100644 index 000000000..fb15b1215 --- /dev/null +++ b/frontend/src/layout/header/components/nav-button/NavButton.tsx @@ -0,0 +1,19 @@ +import { noop } from '@utils/index'; + +import * as S from '@layout/header/components/nav-button/NavButton.style'; + +export interface NavButtonProps { + children: React.ReactNode; + ariaLabel: string; + onClick?: React.MouseEventHandler; +} + +const NavButton: React.FC = ({ children, ariaLabel, onClick = noop }) => { + return ( + + {children} + + ); +}; + +export default NavButton; diff --git a/frontend/src/layout/header/search-bar/SearchBar.stories.tsx b/frontend/src/layout/header/components/search-bar/SearchBar.stories.tsx similarity index 62% rename from frontend/src/layout/header/search-bar/SearchBar.stories.tsx rename to frontend/src/layout/header/components/search-bar/SearchBar.stories.tsx index cf3b3f238..ac792bb09 100644 --- a/frontend/src/layout/header/search-bar/SearchBar.stories.tsx +++ b/frontend/src/layout/header/components/search-bar/SearchBar.stories.tsx @@ -1,7 +1,7 @@ import { Story } from '@storybook/react'; -import SearchBar from '@layout/header/search-bar/SearchBar'; -import type { SearchBarProps } from '@layout/header/search-bar/SearchBar'; +import SearchBar from '@layout/header/components/search-bar/SearchBar'; +import type { SearchBarProps } from '@layout/header/components/search-bar/SearchBar'; export default { title: 'Components/SearchBar', diff --git a/frontend/src/layout/header/search-bar/SearchBar.style.tsx b/frontend/src/layout/header/components/search-bar/SearchBar.style.tsx similarity index 100% rename from frontend/src/layout/header/search-bar/SearchBar.style.tsx rename to frontend/src/layout/header/components/search-bar/SearchBar.style.tsx diff --git a/frontend/src/layout/header/search-bar/SearchBar.tsx b/frontend/src/layout/header/components/search-bar/SearchBar.tsx similarity index 88% rename from frontend/src/layout/header/search-bar/SearchBar.tsx rename to frontend/src/layout/header/components/search-bar/SearchBar.tsx index 87a789c8a..ebd586799 100644 --- a/frontend/src/layout/header/search-bar/SearchBar.tsx +++ b/frontend/src/layout/header/components/search-bar/SearchBar.tsx @@ -1,6 +1,6 @@ import { FiSearch } from 'react-icons/fi'; -import * as S from '@layout/header/search-bar/SearchBar.style'; +import * as S from '@layout/header/components/search-bar/SearchBar.style'; export type SearchBarProps = { onSubmit: (e: React.FormEvent, inputName: string) => void; diff --git a/frontend/src/mocks/detail-study-handlers.ts b/frontend/src/mocks/detail-study-handlers.ts deleted file mode 100644 index 1ebe698e2..000000000 --- a/frontend/src/mocks/detail-study-handlers.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { rest } from 'msw'; - -import reviewJSON from './reviews.json'; -import studiesJSON from './studies.json'; - -const detailStudyHandlers = [ - rest.get('/api/studies/:studyId', (req, res, ctx) => { - const studyId = req.params.studyId; - - const study = studiesJSON.studies.find(study => String(study.id) === studyId); - - return res(ctx.status(200), ctx.json(study)); - }), - - rest.get('/api/studies/:studyId/reviews', (req, res, ctx) => { - const size = req.url.searchParams.get('size'); - if (size) { - const sizeNum = Number(size); - return res( - ctx.status(200), - ctx.json({ - reviews: reviewJSON.reviews.slice(0, sizeNum), - totalResults: reviewJSON.reviews.length, - }), - ); - } - return res( - ctx.status(200), - ctx.json({ - reviews: reviewJSON.reviews, - totalResults: reviewJSON.reviews.length, - }), - ); - }), -]; - -export default detailStudyHandlers; diff --git a/frontend/src/mocks/detailStudyHandlers.ts b/frontend/src/mocks/detailStudyHandlers.ts new file mode 100644 index 000000000..f70a0be27 --- /dev/null +++ b/frontend/src/mocks/detailStudyHandlers.ts @@ -0,0 +1,16 @@ +import { rest } from 'msw'; + +import reviewJSON from './reviews.json'; +import studiesJSON from './studies.json'; + +const detailStudyHandlers = [ + rest.get('/api/studies/:studyId', (req, res, ctx) => { + const studyId = req.params.studyId; + + const study = studiesJSON.studies.find(study => String(study.id) === studyId); + + return res(ctx.status(200), ctx.json(study)); + }), +]; + +export default detailStudyHandlers; diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts index 042ab60b1..2d98b6a2c 100644 --- a/frontend/src/mocks/handlers.ts +++ b/frontend/src/mocks/handlers.ts @@ -1,9 +1,11 @@ import { rest } from 'msw'; -import detailStudyHandlers from './detail-study-handlers'; -import studyJSON from './studies.json'; -import { tagHandlers } from './tagHandlers'; -import { tokenHandlers } from './tokenHandlers'; +import detailStudyHandlers from '@mocks/detailStudyHandlers'; +import { myHandlers } from '@mocks/myHandlers'; +import { reviewHandlers } from '@mocks/reviewHandler'; +import studyJSON from '@mocks/studies.json'; +import { tagHandlers } from '@mocks/tagHandlers'; +import { tokenHandlers } from '@mocks/tokenHandlers'; export const handlers = [ rest.get('/api/studies', (req, res, ctx) => { @@ -89,4 +91,6 @@ export const handlers = [ ...detailStudyHandlers, ...tagHandlers, ...tokenHandlers, + ...myHandlers, + ...reviewHandlers, ]; diff --git a/frontend/src/mocks/my-studies.json b/frontend/src/mocks/my-studies.json new file mode 100644 index 000000000..bc254f19b --- /dev/null +++ b/frontend/src/mocks/my-studies.json @@ -0,0 +1,466 @@ +{ + "studies": [ + { + "id": 53172832, + "title": "2022-daily-planner", + "studyStatus": "IN_PROGRESS", + "currentMemberCount": 5, + "maxMemberCount": 23, + "startDate": "2022-07-12", + "endDate": "2022-08-18", + "owner": { + "id": 19749913, + "username": "xRC3N", + "imageUrl": "https://picsum.photos/id/39/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" } + ] + }, + { + "id": 74198334, + "title": "2022-lv3-algorithm-study", + "studyStatus": "IN_PROGRESS", + "currentMemberCount": 8, + "maxMemberCount": 39, + "startDate": "2022-07-24", + "endDate": "2022-08-08", + "owner": { + "id": 73846379, + "username": "i8ZTZ", + "imageUrl": "https://picsum.photos/id/62/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" }, + { "id": 11, "name": "Network" }, + { "id": 12, "name": "CS" } + ] + }, + { + "id": 43942846, + "title": "2022-woowahan-bansanghwe", + "studyStatus": "PREPARE", + "currentMemberCount": 11, + "maxMemberCount": 27, + "startDate": "2022-07-20", + "endDate": "2022-08-31", + "owner": { + "id": 82157345, + "username": "QtbTJ", + "imageUrl": "https://picsum.photos/id/36/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" } + ] + }, + { + "id": 19264566, + "title": "2022-gugu-spring-study", + "studyStatus": "PREPARE", + "currentMemberCount": 36, + "maxMemberCount": 37, + "startDate": "2022-07-05", + "endDate": "2022-08-27", + "owner": { + "id": 73501209, + "username": "xE5Gh", + "imageUrl": "https://picsum.photos/id/56/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" } + ] + }, + { + "id": 26808604, + "title": "2022-ConquerCS", + "studyStatus": "DONE", + "currentMemberCount": 3, + "maxMemberCount": 37, + "startDate": "2022-07-25", + "endDate": "2022-08-18", + "owner": { + "id": 19148355, + "username": "-LBJx", + "imageUrl": "https://picsum.photos/id/16/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [{ "id": 1, "name": "JS" }] + }, + { + "id": 36977774, + "title": "2022-looking-for-the-sound-of-an-object", + "studyStatus": "IN_PROGRESS", + "currentMemberCount": 40, + "maxMemberCount": 40, + "startDate": "2022-07-11", + "endDate": "2022-08-22", + "owner": { + "id": 61097544, + "username": "50a_j", + "imageUrl": "https://picsum.photos/id/12/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" } + ] + }, + { + "id": 28043313, + "title": "2022-kotudy", + "studyStatus": "DONE", + "currentMemberCount": 5, + "maxMemberCount": 36, + "startDate": "2022-07-24", + "endDate": "2022-08-20", + "owner": { + "id": 47838157, + "username": "-02j_", + "imageUrl": "https://picsum.photos/id/48/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" } + ] + }, + { + "id": 92951646, + "title": "2022-no-posting-you-die", + "studyStatus": "DONE", + "currentMemberCount": 20, + "maxMemberCount": 22, + "startDate": "2022-07-03", + "endDate": "2022-08-11", + "owner": { + "id": 74007869, + "username": "dCMAR", + "imageUrl": "https://picsum.photos/id/56/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [{ "id": 1, "name": "JS" }] + }, + { + "id": 27364158, + "title": "2022-Real-MySQL", + "studyStatus": "DONE", + "currentMemberCount": 23, + "maxMemberCount": 38, + "startDate": "2022-07-25", + "endDate": "2022-08-02", + "owner": { + "id": 13018728, + "username": "pmnMx", + "imageUrl": "https://picsum.photos/id/67/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" }, + { "id": 11, "name": "Network" } + ] + }, + { + "id": 87571081, + "title": "2022-weekly-log", + "studyStatus": "DONE", + "currentMemberCount": 16, + "maxMemberCount": 31, + "startDate": "2022-07-04", + "endDate": "2022-08-26", + "owner": { + "id": 43897221, + "username": "cCcfy", + "imageUrl": "https://picsum.photos/id/29/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" }, + { "id": 11, "name": "Network" }, + { "id": 12, "name": "CS" } + ] + }, + { + "id": 48700521, + "title": "2022-spring-study", + "studyStatus": "DONE", + "currentMemberCount": 5, + "maxMemberCount": 32, + "startDate": "2022-07-25", + "endDate": "2022-08-06", + "owner": { + "id": 56121153, + "username": "-ShmC", + "imageUrl": "https://picsum.photos/id/33/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" } + ] + }, + { + "id": 37351958, + "title": "http-network-basic-level2-study", + "studyStatus": "DONE", + "currentMemberCount": 17, + "maxMemberCount": 31, + "startDate": "2022-07-17", + "endDate": "2022-08-26", + "owner": { + "id": 64342856, + "username": "udN0b", + "imageUrl": "https://picsum.photos/id/62/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" } + ] + }, + { + "id": 47071629, + "title": "2022-code-review-study-2", + "studyStatus": "PREPARE", + "currentMemberCount": 16, + "maxMemberCount": 36, + "startDate": "2022-07-06", + "endDate": "2022-08-29", + "owner": { + "id": 16398644, + "username": "8yC-b", + "imageUrl": "https://picsum.photos/id/28/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" } + ] + }, + { + "id": 11340744, + "title": "2022-http-web-basic-for-all-developer", + "studyStatus": "IN_PROGRESS", + "currentMemberCount": 39, + "maxMemberCount": 39, + "startDate": "2022-07-07", + "endDate": "2022-08-19", + "owner": { + "id": 17465872, + "username": "qNknz", + "imageUrl": "https://picsum.photos/id/93/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [{ "id": 1, "name": "JS" }] + }, + { + "id": 37178275, + "title": "2022-woowa-retrospect", + "studyStatus": "DONE", + "currentMemberCount": 26, + "maxMemberCount": 38, + "startDate": "2022-07-05", + "endDate": "2022-08-11", + "owner": { + "id": 82056531, + "username": "XGbcR", + "imageUrl": "https://picsum.photos/id/39/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" } + ] + }, + { + "id": 20176840, + "title": "2022-http-network-basic-study", + "studyStatus": "DONE", + "currentMemberCount": 32, + "maxMemberCount": 32, + "startDate": "2022-07-05", + "endDate": "2022-08-30", + "owner": { + "id": 68132562, + "username": "M9MbV", + "imageUrl": "https://picsum.photos/id/84/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" } + ] + }, + { + "id": 88252874, + "title": "2022-lv2-effective-java-interview", + "studyStatus": "DONE", + "currentMemberCount": 37, + "maxMemberCount": 39, + "startDate": "2022-07-28", + "endDate": "2022-08-06", + "owner": { + "id": 76714791, + "username": "kHuuu", + "imageUrl": "https://picsum.photos/id/53/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" }, + { "id": 11, "name": "Network" } + ] + }, + { + "id": 35867370, + "title": "2022-book-muscle", + "studyStatus": "DONE", + "currentMemberCount": 10, + "maxMemberCount": 21, + "startDate": "2022-07-29", + "endDate": "2022-08-06", + "owner": { + "id": 75253381, + "username": "N-7qD", + "imageUrl": "https://picsum.photos/id/12/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" }, + { "id": 8, "name": "Alg" }, + { "id": 9, "name": "Book" }, + { "id": 10, "name": "Health" }, + { "id": 11, "name": "Network" } + ] + }, + { + "id": 95367364, + "title": "2022-woowahan-everybook", + "studyStatus": "PREPARE", + "currentMemberCount": 19, + "maxMemberCount": 36, + "startDate": "2022-07-29", + "endDate": "2022-08-04", + "owner": { + "id": 98385754, + "username": "GwfDt", + "imageUrl": "https://picsum.photos/id/56/200/200", + "profileUrl": "https://github.com/user/jaejae-yoo" + }, + "tags": [ + { "id": 1, "name": "JS" }, + { "id": 2, "name": "Java" }, + { "id": 3, "name": "React" }, + { "id": 4, "name": "Spring" }, + { "id": 5, "name": "TS" }, + { "id": 6, "name": "JPA" }, + { "id": 7, "name": "TDD" } + ] + } + ] +} diff --git a/frontend/src/mocks/myHandlers.ts b/frontend/src/mocks/myHandlers.ts new file mode 100644 index 000000000..88067aa01 --- /dev/null +++ b/frontend/src/mocks/myHandlers.ts @@ -0,0 +1,9 @@ +import { rest } from 'msw'; + +import myStudiesJson from '@mocks/my-studies.json'; + +export const myHandlers = [ + rest.get('/api/my/studies', (req, res, ctx) => { + return res(ctx.status(200), ctx.json(myStudiesJson)); + }), +]; diff --git a/frontend/src/pages/create-study-page/CreateStudyPage.stories.tsx b/frontend/src/pages/create-study-page/CreateStudyPage.stories.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/pages/create-study-page/components/category/Category.stories.tsx b/frontend/src/pages/create-study-page/components/category/Category.stories.tsx index 80fe8c8d0..393745573 100644 --- a/frontend/src/pages/create-study-page/components/category/Category.stories.tsx +++ b/frontend/src/pages/create-study-page/components/category/Category.stories.tsx @@ -1,20 +1,22 @@ -import Category from '@create-study-page/components/category/Category'; import { Story } from '@storybook/react'; import { css } from '@emotion/react'; +import Category from '@create-study-page/components/category/Category'; +import type { CategoryProps } from '@create-study-page/components/category/Category'; + export default { title: 'Components/Category', component: Category, }; -const Template: Story = props => ( +const Template: Story = props => (
- +
); diff --git a/frontend/src/pages/create-study-page/components/category/Category.tsx b/frontend/src/pages/create-study-page/components/category/Category.tsx index 06b669d2d..46098a056 100644 --- a/frontend/src/pages/create-study-page/components/category/Category.tsx +++ b/frontend/src/pages/create-study-page/components/category/Category.tsx @@ -1,6 +1,3 @@ -import * as S from '@create-study-page/components/category/Category.style'; -import MetaBox from '@create-study-page/components/meta-box/MetaBox'; - import { css } from '@emotion/react'; import type { Tag } from '@custom-types/index'; @@ -9,7 +6,10 @@ import { useFormContext } from '@hooks/useForm'; import useFetchTagList from '@pages/create-study-page/hooks/useFetchTagList'; -type CategoryProps = { +import * as S from '@create-study-page/components/category/Category.style'; +import MetaBox from '@create-study-page/components/meta-box/MetaBox'; + +export type CategoryProps = { className?: string; }; diff --git a/frontend/src/pages/main-page/MainPage.tsx b/frontend/src/pages/main-page/MainPage.tsx index 22c96ba20..1909d9770 100644 --- a/frontend/src/pages/main-page/MainPage.tsx +++ b/frontend/src/pages/main-page/MainPage.tsx @@ -2,7 +2,7 @@ import { useContext, useState } from 'react'; import { useInfiniteQuery } from 'react-query'; import { Link, useNavigate } from 'react-router-dom'; -import { DEFAULT_STUDY_CARD_QUERY_PARAM } from '@constants'; +import { DEFAULT_STUDY_CARD_QUERY_PARAM, PATH } from '@constants'; import type { Study, StudyListQueryData, TagInfo } from '@custom-types/index'; @@ -70,7 +70,7 @@ const MainPage: React.FC = () => { return; } window.scrollTo(0, 0); - navigate('/study/new'); + navigate(PATH.CREATE_STUDY); }; return ( diff --git a/frontend/src/pages/main-page/style.ts b/frontend/src/pages/main-page/style.ts deleted file mode 100644 index 12556e340..000000000 --- a/frontend/src/pages/main-page/style.ts +++ /dev/null @@ -1,28 +0,0 @@ -import styled from '@emotion/styled'; - -import { mqDown } from '@utils/index'; - -export const CardList = styled.ul` - display: grid; - grid-template-columns: repeat(4, minmax(auto, 1fr)); - grid-template-rows: 1fr; - gap: 32px; - place-items: center; - - & > li { - cursor: pointer; - } - - ${mqDown('lg')} { - grid-template-columns: repeat(3, 1fr); - } - ${mqDown('md')} { - grid-template-columns: repeat(2, 1fr); - } - ${mqDown('sm')} { - grid-template-columns: repeat(1, 256px); - place-content: center; - } -`; - -export const Page = styled.div``; diff --git a/frontend/src/pages/my-study-page/MyStudyPage.style.tsx b/frontend/src/pages/my-study-page/MyStudyPage.style.tsx new file mode 100644 index 000000000..4434d1068 --- /dev/null +++ b/frontend/src/pages/my-study-page/MyStudyPage.style.tsx @@ -0,0 +1,10 @@ +import styled from '@emotion/styled'; + +export const PageTitle = styled.h1` + font-size: 32px; + text-align: center; +`; + +export const SectionContainer = styled.div` + margin-bottom: 20px; +`; diff --git a/frontend/src/pages/my-study-page/MyStudyPage.tsx b/frontend/src/pages/my-study-page/MyStudyPage.tsx new file mode 100644 index 000000000..49a85c685 --- /dev/null +++ b/frontend/src/pages/my-study-page/MyStudyPage.tsx @@ -0,0 +1,59 @@ +import { useQuery } from 'react-query'; + +import { css } from '@emotion/react'; + +import type { MyStudyQueryData } from '@custom-types/index'; +import { MyStudyData } from '@custom-types/index'; + +import { getMyStudyList } from '@api/getMyStudyList'; + +import MyStudyCardListSection from '@pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection'; + +import Divider from '@components/divider/Divider'; +import Wrapper from '@components/wrapper/Wrapper'; + +import * as S from '@my-study-page/MyStudyPage.style'; + +const studies: Record> = { + prepare: [], + inProgress: [], + done: [], +}; + +const MyStudyPage: React.FC = () => { + const { data, isFetching, isError } = useQuery('my-studies', getMyStudyList); + + const myStudies = + data?.studies.reduce((acc, study) => { + if (study.studyStatus === 'IN_PROGRESS') { + acc.inProgress.push(study); + } + if (study.studyStatus === 'PREPARE') { + acc.prepare.push(study); + } + if (study.studyStatus === 'DONE') { + acc.done.push(study); + } + return acc; + }, studies) || studies; + + const mb20 = css` + margin-bottom: 20px; + `; + + return ( +
+ + 가입한 스터디 목록 + + {isFetching &&
로딩 중...
} + {isError &&
내 스터디 불러오기를 실패했습니다
} + + + +
+
+ ); +}; + +export default MyStudyPage; diff --git a/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.stories.tsx b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.stories.tsx new file mode 100644 index 000000000..690301733 --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.stories.tsx @@ -0,0 +1,414 @@ +import { Story } from '@storybook/react'; + +import Wrapper from '@components/wrapper/Wrapper'; + +import MyStudyCardListSection from '@my-study-page/components/my-study-card-list-section/MyStudyCardListSection'; +import type { MyStudyCardListSectionProps } from '@my-study-page/components/my-study-card-list-section/MyStudyCardListSection'; + +export default { + title: 'Components/MyStudyCardListSection', + component: MyStudyCardListSection, + argTypes: { + sectionTitle: { controls: 'text' }, + myStudies: { controls: 'object' }, + }, +}; + +const Template: Story = props => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + sectionTitle: '활동 중!', + myStudies: [ + { + id: 53172832, + title: '2022-daily-planner', + studyStatus: 'IN_PROGRESS', + startDate: '2022-07-12', + endDate: '2022-08-18', + owner: { + id: 19749913, + username: 'xRC3N', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + ], + }, + { + id: 74198334, + title: '2022-lv3-algorithm-study', + studyStatus: 'IN_PROGRESS', + startDate: '2022-07-24', + endDate: '2022-08-08', + owner: { + id: 73846379, + username: 'i8ZTZ', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + { id: 11, name: 'Network' }, + { id: 12, name: 'CS' }, + ], + }, + { + id: 43942846, + title: '2022-woowahan-bansanghwe', + studyStatus: 'PREPARE', + startDate: '2022-07-20', + endDate: '2022-08-31', + owner: { + id: 82157345, + username: 'QtbTJ', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + ], + }, + { + id: 19264566, + title: '2022-gugu-spring-study', + studyStatus: 'PREPARE', + startDate: '2022-07-05', + endDate: '2022-08-27', + owner: { + id: 73501209, + username: 'xE5Gh', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + ], + }, + { + id: 26808604, + title: '2022-ConquerCS', + studyStatus: 'DONE', + startDate: '2022-07-25', + endDate: '2022-08-18', + owner: { + id: 19148355, + username: '-LBJx', + }, + tags: [{ id: 1, name: 'JS' }], + }, + { + id: 36977774, + title: '2022-looking-for-the-sound-of-an-object', + studyStatus: 'IN_PROGRESS', + startDate: '2022-07-11', + endDate: '2022-08-22', + owner: { + id: 61097544, + username: '50a_j', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + ], + }, + { + id: 28043313, + title: '2022-kotudy', + studyStatus: 'DONE', + startDate: '2022-07-24', + endDate: '2022-08-20', + owner: { + id: 47838157, + username: '-02j_', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + ], + }, + { + id: 92951646, + title: '2022-no-posting-you-die', + studyStatus: 'DONE', + startDate: '2022-07-03', + endDate: '2022-08-11', + owner: { + id: 74007869, + username: 'dCMAR', + }, + tags: [{ id: 1, name: 'JS' }], + }, + { + id: 27364158, + title: '2022-Real-MySQL', + studyStatus: 'DONE', + startDate: '2022-07-25', + endDate: '2022-08-02', + owner: { + id: 13018728, + username: 'pmnMx', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + { id: 11, name: 'Network' }, + ], + }, + { + id: 87571081, + title: '2022-weekly-log', + studyStatus: 'DONE', + startDate: '2022-07-04', + endDate: '2022-08-26', + owner: { + id: 43897221, + username: 'cCcfy', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + { id: 11, name: 'Network' }, + { id: 12, name: 'CS' }, + ], + }, + { + id: 48700521, + title: '2022-spring-study', + studyStatus: 'DONE', + startDate: '2022-07-25', + endDate: '2022-08-06', + owner: { + id: 56121153, + username: '-ShmC', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + ], + }, + { + id: 37351958, + title: 'http-network-basic-level2-study', + studyStatus: 'DONE', + startDate: '2022-07-17', + endDate: '2022-08-26', + owner: { + id: 64342856, + username: 'udN0b', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + ], + }, + { + id: 47071629, + title: '2022-code-review-study-2', + studyStatus: 'PREPARE', + startDate: '2022-07-06', + endDate: '2022-08-29', + owner: { + id: 16398644, + username: '8yC-b', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + ], + }, + { + id: 11340744, + title: '2022-http-web-basic-for-all-developer', + studyStatus: 'IN_PROGRESS', + startDate: '2022-07-07', + endDate: '2022-08-19', + owner: { + id: 17465872, + username: 'qNknz', + }, + tags: [{ id: 1, name: 'JS' }], + }, + { + id: 37178275, + title: '2022-woowa-retrospect', + studyStatus: 'DONE', + startDate: '2022-07-05', + endDate: '2022-08-11', + owner: { + id: 82056531, + username: 'XGbcR', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + ], + }, + { + id: 20176840, + title: '2022-http-network-basic-study', + studyStatus: 'DONE', + startDate: '2022-07-05', + endDate: '2022-08-30', + owner: { + id: 68132562, + username: 'M9MbV', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + ], + }, + { + id: 88252874, + title: '2022-lv2-effective-java-interview', + studyStatus: 'DONE', + startDate: '2022-07-28', + endDate: '2022-08-06', + owner: { + id: 76714791, + username: 'kHuuu', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + { id: 11, name: 'Network' }, + ], + }, + { + id: 35867370, + title: '2022-book-muscle', + studyStatus: 'DONE', + startDate: '2022-07-29', + endDate: '2022-08-06', + owner: { + id: 75253381, + username: 'N-7qD', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + { id: 8, name: 'Alg' }, + { id: 9, name: 'Book' }, + { id: 10, name: 'Health' }, + { id: 11, name: 'Network' }, + ], + }, + { + id: 95367364, + title: '2022-woowahan-everybook', + studyStatus: 'PREPARE', + startDate: '2022-07-29', + endDate: '2022-08-04', + owner: { + id: 98385754, + username: 'GwfDt', + }, + tags: [ + { id: 1, name: 'JS' }, + { id: 2, name: 'Java' }, + { id: 3, name: 'React' }, + { id: 4, name: 'Spring' }, + { id: 5, name: 'TS' }, + { id: 6, name: 'JPA' }, + { id: 7, name: 'TDD' }, + ], + }, + ], +}; diff --git a/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.style.tsx b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.style.tsx new file mode 100644 index 000000000..4b38a5096 --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.style.tsx @@ -0,0 +1,33 @@ +import styled from '@emotion/styled'; + +import { mqDown } from '@utils/index'; + +export const SectionTitle = styled.h3` + margin-bottom: 20px; + + font-size: 24px; + font-weight: 700; +`; + +export const MyStudyList = styled.ul` + display: grid; + grid-template-columns: repeat(3, minmax(auto, 1fr)); + grid-template-rows: 1fr; + gap: 20px; + + & > li { + cursor: pointer; + width: 100%; + } + + ${mqDown('lg')} { + grid-template-columns: repeat(2, minmax(auto, 1fr)); + } + ${mqDown('sm')} { + grid-template-columns: repeat(1, minmax(auto, 1fr)); + } +`; + +export const MyStudyCardListSection = styled.section` + padding: 8px; +`; diff --git a/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx new file mode 100644 index 000000000..2d278c452 --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card-list-section/MyStudyCardListSection.tsx @@ -0,0 +1,43 @@ +import type { MakeOptional, MyStudyData } from '@custom-types/index'; + +import MyStudyCard from '@pages/my-study-page/components/my-study-card/MyStudyCard'; + +import * as S from '@my-study-page/components/my-study-card-list-section/MyStudyCardListSection.style'; + +export type MyStudyCardListSectionProps = { + className?: string; + sectionTitle: string; + myStudies: Array; + disabled: boolean; +}; + +type OptionalMyStudyCardListSectionProps = MakeOptional; + +const MyStudyCardListSection: React.FC = ({ + className, + sectionTitle, + myStudies, + disabled = false, +}) => { + return ( + + {sectionTitle} + + {myStudies.map(myStudy => ( +
  • + +
  • + ))} +
    +
    + ); +}; + +export default MyStudyCardListSection; diff --git a/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.stories.tsx b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.stories.tsx new file mode 100644 index 000000000..a3b88ac83 --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.stories.tsx @@ -0,0 +1,40 @@ +import { Story } from '@storybook/react'; + +import MyStudyCard from '@my-study-page/components/my-study-card/MyStudyCard'; +import type { MyStudyCardProps } from '@my-study-page/components/my-study-card/MyStudyCard'; + +export default { + title: 'Components/MyStudyCard', + component: MyStudyCard, + argTypes: { + title: { controls: 'text' }, + ownerName: { controls: 'text' }, + tags: { controls: 'object' }, + startDate: { controls: 'text' }, + endDate: { controls: 'text' }, + }, +}; + +const Template: Story = props => ; + +export const Default = Template.bind({}); +Default.args = { + title: '2022 모아모아 스터디', + ownerName: 'airman5573', + tags: [ + { + id: 1, + name: 'Java', + }, + { + id: 2, + name: 'JS', + }, + { + id: 3, + name: 'FE', + }, + ], + startDate: '2022.08.13', + endDate: '2022.08.20', +}; diff --git a/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.style.tsx b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.style.tsx new file mode 100644 index 000000000..13a12dd3f --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.style.tsx @@ -0,0 +1,100 @@ +import { css } from '@emotion/react'; +import type { Theme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { MyStudyCardProps } from '@pages/my-study-page/components/my-study-card/MyStudyCard'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; +`; + +export const Top = styled.div``; + +export const Bottom = styled.div` + display: flex; + justify-content: space-between; +`; + +export const Title = styled.h4` + display: -webkit-box; + overflow: clip; + text-overflow: ellipsis; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + word-break: break-all; + + margin-bottom: 12px; + + font-size: 20px; + font-weight: 700; +`; + +export const Owner = styled.p` + display: flex; + align-items: center; + column-gap: 2px; + + margin-bottom: 12px; + + & > svg { + position: relative; + top: -2px; + } +`; + +export const Tags = styled.p` + margin-bottom: 12px; + display: flex; + column-gap: 10px; + flex-wrap: wrap; +`; + +export const Period = styled.p``; + +const disabledStyle = (theme: Theme) => css` + border: 3px solid ${theme.colors.secondary.dark}; + box-shadow: 4px 4px 0 0 ${theme.colors.secondary.base}; + + & * { + color: ${theme.colors.secondary.dark} !important; + } + svg { + stroke: ${theme.colors.secondary.dark} !important; + } +`; + +export const MyStudyCard = styled.div>` + ${({ theme, disabled }) => css` + position: relative; + padding: 12px; + overflow: hidden; + + height: 100%; + + border: 3px solid ${theme.colors.primary.base}; + border-radius: 15px; + box-shadow: 4px 4px 0 0 ${theme.colors.secondary.dark}; + + ${disabled && disabledStyle(theme)} + `} +`; + +export const TrashButton = styled.button` + ${({ theme }) => css` + background-color: transparent; + border: none; + outline: none; + + & > svg { + stroke: ${theme.colors.primary.base}; + + &:hover, + &:active { + stroke: ${theme.colors.primary.dark}; + } + } + `}; +`; diff --git a/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.tsx b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.tsx new file mode 100644 index 000000000..7fc553961 --- /dev/null +++ b/frontend/src/pages/my-study-page/components/my-study-card/MyStudyCard.tsx @@ -0,0 +1,55 @@ +import { HiOutlineTrash } from 'react-icons/hi'; +import { TbCrown } from 'react-icons/tb'; + +import { MakeOptional, Tag } from '@custom-types/index'; + +import * as S from '@my-study-page/components/my-study-card/MyStudyCard.style'; + +export type MyStudyCardProps = { + title: string; + ownerName: string; + tags: Array>; + startDate: string; + endDate: string; + disabled: boolean; +}; + +type OptionalMyStudyCardProps = MakeOptional; + +const MyStudyCard: React.FC = ({ + title, + ownerName, + tags, + startDate, + endDate, + disabled = false, +}) => { + return ( + + + + {title} + + + {ownerName} + + + {tags.map(tag => ( +
  • #{tag.name}
  • + ))} +
    +
    + + + {startDate} ~ {endDate} + + + + + +
    +
    + ); +}; + +export default MyStudyCard; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 6037db826..ed6aa65b7 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -29,8 +29,10 @@ "@main-page/*": ["pages/main-page/*"], "@detail-page/*": ["pages/detail-page/*"], "@create-study-page/*": ["pages/create-study-page/*"], + "@my-study-page/*": ["pages/my-study-page/*"], "@layout/*": ["layout/*"], - "@hooks/*": ["hooks/*"] + "@hooks/*": ["hooks/*"], + "@mocks/*": ["mocks/*"] }, "typeRoots": ["src/custom-types"] }, diff --git a/frontend/webpack/webpack.common.js b/frontend/webpack/webpack.common.js index 2ce6151f2..4d272bbad 100644 --- a/frontend/webpack/webpack.common.js +++ b/frontend/webpack/webpack.common.js @@ -50,8 +50,10 @@ module.exports = { '@main-page': resolve(__dirname, '../src/pages/main-page'), '@detail-page': resolve(__dirname, '../src/pages/detail-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'), }, }, };