From 0d99911699f9bf195d05a1c4c26da1e34e25dca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Autumn=20=28=EC=A1=B0=ED=99=8D=EB=B9=84=29?= <60209518+dyongdi@users.noreply.github.com> Date: Tue, 15 Jun 2021 16:38:40 +0900 Subject: [PATCH 01/42] =?UTF-8?q?[#37]=20Feat:=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=97=90=EC=84=9C=20filter=20list=20=EB=B0=9B=EC=95=84?= =?UTF-8?q?=EC=84=9C=20=ED=99=94=EB=A9=B4=EC=97=90=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - recoil의 selector를 사용해서 하는 중 - 담당자, 작성자 조회 쪽 API에 문제가 있어 주석처리 해둠 - 로딩 처리 및 데이터 업데이트에 관해서 고민 더 필요 --- fe/src/App.tsx | 50 ++++++----- fe/src/components/Issues/IssuesHeader.tsx | 16 ++-- fe/src/components/common/Filter.tsx | 28 ++++-- fe/src/components/common/FilterItem.tsx | 31 +++++-- fe/src/components/common/IssueFilter.tsx | 8 +- fe/src/store.ts | 101 ++++++++++++++++++++++ fe/src/types/filterType.ts | 10 ++- 7 files changed, 197 insertions(+), 47 deletions(-) create mode 100644 fe/src/store.ts diff --git a/fe/src/App.tsx b/fe/src/App.tsx index bec08a44c..2d5b91b83 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -8,30 +8,38 @@ import OAuthPage from 'pages/OAuthPage'; import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles'; import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles'; import NewIssuePage from 'pages/NewIssuePage'; +import { RecoilRoot } from 'recoil'; +import { Suspense } from 'react'; + const MuiTheme = unstable_createMuiStrictModeTheme(); + function App() { return ( - - - - - - - - - - - - - - - - - - - - - + + Loading...}> + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/fe/src/components/Issues/IssuesHeader.tsx b/fe/src/components/Issues/IssuesHeader.tsx index be3e073d6..aa1ac2a06 100644 --- a/fe/src/components/Issues/IssuesHeader.tsx +++ b/fe/src/components/Issues/IssuesHeader.tsx @@ -6,10 +6,10 @@ import Filter from 'components/common/Filter'; const IssuesHeader = () => { const testArray = [ - { description: '테스트필터1' }, - { description: '테스트필터2' }, - { description: '테스트필터3' }, - { description: '테스트필터4' }, + { id: 1, description: '테스트필터1' }, + { id: 2, description: '테스트필터2' }, + { id: 3, description: '테스트필터3' }, + { id: 4, description: '테스트필터4' }, ]; return ( @@ -25,10 +25,10 @@ const IssuesHeader = () => { - - - - + + + + ); diff --git a/fe/src/components/common/Filter.tsx b/fe/src/components/common/Filter.tsx index e3857054f..3256ab4e7 100644 --- a/fe/src/components/common/Filter.tsx +++ b/fe/src/components/common/Filter.tsx @@ -4,9 +4,11 @@ import styled from 'styled-components'; import FilterList from './FilterList'; import { ReactComponent as ArrowDown } from 'icons/arrow-down.svg'; import Popover from '@material-ui/core/Popover'; -import { FilterPropsType } from 'types/filterType'; +import { FilterPropsType, FilterSelectorType } from 'types/filterType'; +import { useRecoilValue } from 'recoil'; +import { filterSelector, labelQuery } from 'store'; -export default function Filter({ filterTitle, filterList }: FilterPropsType) { +export default function Filter({ filterType }: FilterPropsType) { const [anchorEl, setAnchorEl] = useState(null); const ref = createRef(); const handleClick = (event: React.MouseEvent) => { @@ -17,10 +19,24 @@ export default function Filter({ filterTitle, filterList }: FilterPropsType) { setAnchorEl(null); }; + const dataList = useRecoilValue(filterSelector); + // let list; + // if (dataList) { + // list = dataList[filterType]; + // } + const getFilterTitle = (filterType: FilterSelectorType) => + ({ + milestoneList: '마일스톤', + labelList: '레이블', + }[filterType]); + + // authorList: '작성자', + // assigneeList: '담당자', return ( <> - {filterTitle} + {getFilterTitle(filterType)} + - + ); @@ -50,7 +69,6 @@ const FilterButton = styled(Button)` `; const ArrowDownIcon = styled(ArrowDown)` - &[aria-checked='true'] { transform: rotate(180deg); } diff --git a/fe/src/components/common/FilterItem.tsx b/fe/src/components/common/FilterItem.tsx index f10098060..a744a0466 100644 --- a/fe/src/components/common/FilterItem.tsx +++ b/fe/src/components/common/FilterItem.tsx @@ -1,5 +1,5 @@ import { Divider, MenuItem, Checkbox } from '@material-ui/core'; -import { forwardRef, Ref } from 'react'; +import { forwardRef, MouseEvent, Ref } from 'react'; import styled from 'styled-components'; import { FilterItemPropsType } from '../../types/filterType'; import { ReactComponent as CheckOff } from 'icons/check-off-circle.svg'; @@ -8,21 +8,40 @@ const FilterItem = ( { filterItem, isEnd }: FilterItemPropsType, ref: Ref ) => { + const handleClick = (e: MouseEvent) => { + console.log(filterItem.id); + }; return ( <> - - {filterItem.description} + + + {filterItem.labelColor ? ( + + ) : null} + {filterItem.description} + } checkedIcon={} /> {isEnd ? null : } ); }; - +const StyledSpan = styled.span` + ${({ theme }) => theme.style.flexAlignItemsCenter} + font-size: ${({ theme }) => theme.fontSize.S}; +`; const StyledFilterItem = styled(MenuItem)` ${({ theme }) => theme.style.flexSpaceBetween} - width: 12rem; + width: 15rem; background-color: white; `; - +const StyledColor = styled.div<{ colorCode: string }>` + width: 1rem; + height: 1rem; + border-radius: 50%; + background-color: #00ffff; + border: 1px solid ${({ theme }) => theme.color.grayscale.line}; + margin-right: 0.5rem; +`; export default forwardRef(FilterItem); +// ${({ colorCode }) => colorCode} diff --git a/fe/src/components/common/IssueFilter.tsx b/fe/src/components/common/IssueFilter.tsx index d7c660c8d..9948fed6e 100644 --- a/fe/src/components/common/IssueFilter.tsx +++ b/fe/src/components/common/IssueFilter.tsx @@ -5,10 +5,10 @@ import FilterList from './FilterList'; import { ReactComponent as ArrowDown } from 'icons/arrow-down.svg'; import Popover from '@material-ui/core/Popover'; const testArray = [ - { description: '테스트필터1' }, - { description: '테스트필터2' }, - { description: '테스트필터3' }, - { description: '테스트필터4' }, + { id: 1, description: '테스트필터1' }, + { id: 2, description: '테스트필터2' }, + { id: 3, description: '테스트필터3' }, + { id: 4, description: '테스트필터4' }, ]; export default function IssueFilter() { diff --git a/fe/src/store.ts b/fe/src/store.ts new file mode 100644 index 000000000..339d161dc --- /dev/null +++ b/fe/src/store.ts @@ -0,0 +1,101 @@ +import axios from 'axios'; +import useAxios from 'hook/useAxios'; +import { atom, selector } from 'recoil'; +import { FilterItemType } from 'types/filterType'; +type LabelDataType = { + id: number; + title: string; + description: string; + color_code: string; +}; + +type MilestoneDataType = { + id: number; + title: string; + description: string; + due_date: string; + opened_issue_count: number; + closed_issue_count: number; +}; + +type UserDataType = { + // 작성자, 담당자 공통 + user_id: number; + name: string; + avatar_url: string | undefined; +}; + +export const labelQuery = selector({ + key: 'labelQuery', + get: async () => { + const { data } = await axios.get( + `${process.env.REACT_APP_API_URL}/api/labels` + ); + return data.map((labelItem: LabelDataType) => ({ + id: labelItem.id, + description: labelItem.title, + labelColor: labelItem.color_code, + })); + }, +}); + +export const milestoneQuery = selector({ + key: 'milestoneQuery', + get: async () => { + const { data } = await axios.get( + `${process.env.REACT_APP_API_URL}/api/milestones` + ); + return data.map((milestoneItem: MilestoneDataType) => ({ + id: milestoneItem.id, + description: milestoneItem.title, + })); + }, +}); + +// export const authorQuery = selector({ +// key: 'authorQuery', +// get: async () => { +// const { data } = await axios.get( +// `${process.env.REACT_APP_API_URL}/api/authors` +// ); + +// return data.map((user: UserDataType) => ({ +// id: user.user_id, +// description: user.name, +// imgurl: user.avatar_url, +// })); +// }, +// }); + +// export const assigneeQuery = selector({ +// key: 'assigneeQuery', +// get: async () => { +// const { data } = await axios.get( +// `${process.env.REACT_APP_API_URL}/api/assignees` +// ); +// return data.map((user: UserDataType) => ({ +// id: user.user_id, +// description: user.name, +// imgurl: user.avatar_url, +// })); +// }, +// }); + +export const filterSelector = selector({ + key: 'filterSelector', + get: ({ get }) => { + return { + labelList: get(labelQuery), + milestoneList: get(milestoneQuery), + // authorList: get(authorQuery), + // assigneeList: get(assigneeQuery), + }; + }, +}); + +type TestType = { + labelList: FilterItemType[]; + milestoneList: FilterItemType[]; + // authorList: FilterItemType[]; + // assigneeList: FilterItemType[]; +}; diff --git a/fe/src/types/filterType.ts b/fe/src/types/filterType.ts index 26d08454c..cf0936598 100644 --- a/fe/src/types/filterType.ts +++ b/fe/src/types/filterType.ts @@ -1,4 +1,5 @@ export type FilterItemType = { + id: number; description: string; imgurl?: string | null; labelColor?: string; @@ -15,6 +16,9 @@ export type FilterItemPropsType = { }; export type FilterPropsType = { - filterTitle: string; - filterList: FilterItemType[]; -} \ No newline at end of file + filterType: FilterSelectorType; +}; + +export type FilterSelectorType = 'milestoneList' | 'labelList'; +// | 'authorList' +// | 'assigneeList'; From 479a78208ad047a82d428390f2546b848198ae5a Mon Sep 17 00:00:00 2001 From: Dongmin Ahn <68339352+eamon3481@users.noreply.github.com> Date: Thu, 17 Jun 2021 12:08:04 +0900 Subject: [PATCH 02/42] [#42] label list page & milestone Page (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#31]commentTextarea생성 & useAxios 수정 $ utils/url 생성 * [#31]Style: newStyle Right 와 완료 버튼 생성 * [#42]Style: MilestonePage 구성 * [#42]Stlye: milestonePage UI 완성 --- fe/src/App.tsx | 8 ++ fe/src/components/Issues/IssueItem.tsx | 4 - fe/src/components/Issues/IssueItemLeft.tsx | 4 +- fe/src/components/Issues/Issues.tsx | 30 ++--- fe/src/components/Issues/IssuesHeader.tsx | 6 - fe/src/components/buttons/CreateButton.tsx | 31 +++++ fe/src/components/buttons/CustomButton.tsx | 23 ++++ fe/src/components/common/CommentInput.tsx | 47 -------- fe/src/components/common/CommentTextarea.tsx | 55 +++++++++ fe/src/components/common/Filter.tsx | 41 +++---- fe/src/components/common/FilterItem.tsx | 5 +- fe/src/components/common/FilterList.tsx | 14 ++- fe/src/components/common/IssueFilter.tsx | 17 ++- fe/src/components/common/Table.tsx | 18 +++ fe/src/components/labels/Labels.tsx | 23 ++++ fe/src/components/labels/LabelsItem.tsx | 28 +++++ fe/src/components/labels/LabelsItemLeft.tsx | 27 +++++ fe/src/components/labels/LabelsItemRight.tsx | 25 ++++ fe/src/components/milestones/MilestoneBar.tsx | 40 +++++++ fe/src/components/milestones/Milestones.tsx | 55 +++++++++ .../components/milestones/MilestonesItem.tsx | 28 +++++ .../milestones/MilestonesItemLeft.tsx | 47 ++++++++ .../milestones/MilestonesItemRight.tsx | 58 ++++++++++ fe/src/components/navbar/Navbar.tsx | 109 ++---------------- fe/src/components/navbar/NavbarButtons.tsx | 67 +++++++++++ fe/src/components/new-issue/NewIssueLeft.tsx | 5 +- fe/src/components/new-issue/NewIssueRight.tsx | 27 +++++ fe/src/components/new-issue/SidebarList.tsx | 23 ++++ fe/src/hook/useAxios.tsx | 28 ++--- fe/src/icons/calendar.svg | 6 + fe/src/icons/milestoneBlue.svg | 4 + fe/src/pages/LabelPage.tsx | 23 ++++ fe/src/pages/MilestoneListPage.tsx | 22 ++++ fe/src/pages/NewIssuePage.tsx | 16 ++- fe/src/pages/OAuthPage.tsx | 4 +- fe/src/types/filterType.ts | 7 +- fe/src/types/issueType.ts | 28 +++++ fe/src/types/newIssueType.ts | 5 + fe/src/utils/util.ts | 16 +++ 39 files changed, 790 insertions(+), 234 deletions(-) create mode 100644 fe/src/components/buttons/CreateButton.tsx create mode 100644 fe/src/components/buttons/CustomButton.tsx delete mode 100644 fe/src/components/common/CommentInput.tsx create mode 100644 fe/src/components/common/CommentTextarea.tsx create mode 100644 fe/src/components/common/Table.tsx create mode 100644 fe/src/components/labels/Labels.tsx create mode 100644 fe/src/components/labels/LabelsItem.tsx create mode 100644 fe/src/components/labels/LabelsItemLeft.tsx create mode 100644 fe/src/components/labels/LabelsItemRight.tsx create mode 100644 fe/src/components/milestones/MilestoneBar.tsx create mode 100644 fe/src/components/milestones/Milestones.tsx create mode 100644 fe/src/components/milestones/MilestonesItem.tsx create mode 100644 fe/src/components/milestones/MilestonesItemLeft.tsx create mode 100644 fe/src/components/milestones/MilestonesItemRight.tsx create mode 100644 fe/src/components/navbar/NavbarButtons.tsx create mode 100644 fe/src/components/new-issue/NewIssueRight.tsx create mode 100644 fe/src/components/new-issue/SidebarList.tsx create mode 100644 fe/src/icons/calendar.svg create mode 100644 fe/src/icons/milestoneBlue.svg create mode 100644 fe/src/pages/LabelPage.tsx create mode 100644 fe/src/pages/MilestoneListPage.tsx create mode 100644 fe/src/types/newIssueType.ts create mode 100644 fe/src/utils/util.ts diff --git a/fe/src/App.tsx b/fe/src/App.tsx index 2d5b91b83..5c63777bc 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -10,6 +10,8 @@ import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles'; import NewIssuePage from 'pages/NewIssuePage'; import { RecoilRoot } from 'recoil'; import { Suspense } from 'react'; +import LabelPage from 'pages/LabelPage'; +import MilestoneListPage from 'pages/MilestoneListPage'; const MuiTheme = unstable_createMuiStrictModeTheme(); @@ -34,6 +36,12 @@ function App() { + + + + + + diff --git a/fe/src/components/Issues/IssueItem.tsx b/fe/src/components/Issues/IssueItem.tsx index 0fc8b65b7..3d3754a83 100644 --- a/fe/src/components/Issues/IssueItem.tsx +++ b/fe/src/components/Issues/IssueItem.tsx @@ -1,7 +1,3 @@ -import { Checkbox } from '@material-ui/core'; -import { ReactComponent as Open } from 'icons/openIssue.svg'; -import { ReactComponent as Close } from 'icons/closeIssue.svg'; -import { ReactComponent as Milestone } from 'icons/openMilestone.svg'; import styled from 'styled-components'; import { IssueItemType } from 'types/issueType'; import AuthorAvatar from 'components/common/AuthorAvatar'; diff --git a/fe/src/components/Issues/IssueItemLeft.tsx b/fe/src/components/Issues/IssueItemLeft.tsx index 8205ce0a5..9c6731bf5 100644 --- a/fe/src/components/Issues/IssueItemLeft.tsx +++ b/fe/src/components/Issues/IssueItemLeft.tsx @@ -41,9 +41,7 @@ const StyledIssueItemLeft = styled.div` const IssueTitle = styled.div` ${({ theme }) => theme.style.flexAlignItemsCenter}; `; -const IssueItemRight = styled.div` - padding-right: 1.4rem; -`; + const OpenSvg = styled(Open)` path { diff --git a/fe/src/components/Issues/Issues.tsx b/fe/src/components/Issues/Issues.tsx index a9eac948a..ed7e4fb9b 100644 --- a/fe/src/components/Issues/Issues.tsx +++ b/fe/src/components/Issues/Issues.tsx @@ -1,34 +1,18 @@ -import styled from 'styled-components'; +import { Wrapper, Upper, Lower } from 'components/common/Table'; import IssueList from './IssueList'; import IssuesHeader from './IssuesHeader'; const Issues = () => { return ( - - + + - - + + - - + + ); }; export default Issues; - -const StyledIssues = styled.div` - ${({ theme }) => theme.style.flexColum} - box-sizing: border-box; -`; - -const StyledIssuesHeader = styled.div` - ${({ theme }) => theme.style.upperWrapper} - width: 100%; - box-sizing: border-box; -`; - -const StyledIssuesContent = styled.div` - ${({ theme }) => theme.style.lowerWrapper} - box-sizing: border-box; -`; diff --git a/fe/src/components/Issues/IssuesHeader.tsx b/fe/src/components/Issues/IssuesHeader.tsx index aa1ac2a06..02a0ff1e1 100644 --- a/fe/src/components/Issues/IssuesHeader.tsx +++ b/fe/src/components/Issues/IssuesHeader.tsx @@ -5,12 +5,6 @@ import { ReactComponent as Close } from 'icons/closeIssue.svg'; import Filter from 'components/common/Filter'; const IssuesHeader = () => { - const testArray = [ - { id: 1, description: '테스트필터1' }, - { id: 2, description: '테스트필터2' }, - { id: 3, description: '테스트필터3' }, - { id: 4, description: '테스트필터4' }, - ]; return ( diff --git a/fe/src/components/buttons/CreateButton.tsx b/fe/src/components/buttons/CreateButton.tsx new file mode 100644 index 000000000..39e4785a2 --- /dev/null +++ b/fe/src/components/buttons/CreateButton.tsx @@ -0,0 +1,31 @@ +import { Button } from '@material-ui/core'; +import { ReactComponent as PlusIconSvg } from 'icons/pluse.svg'; +import { ReactChild } from 'react'; +import styled from 'styled-components'; + +const CreateButton = ({ children }: { children: ReactChild }) => { + return ( + }>{children} + ); +}; + +export default CreateButton; + +const StyledCreateButton = styled(Button)` + background-color: ${({ theme }) => theme.color.blue}; + color: ${({ theme }) => theme.color.grayscale.offWhite}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + width: 7.5rem; + height: 2.5rem; + border-radius: ${({ theme }) => theme.border.radius.S}; + margin-left: 1rem; + &:hover { + background-color: ${({ theme }) => theme.color.darkBlue}; + } +`; + +const PlusIcon = styled(PlusIconSvg)` + path { + stroke: ${({ theme }) => theme.color.grayscale.offWhite}; + } +`; diff --git a/fe/src/components/buttons/CustomButton.tsx b/fe/src/components/buttons/CustomButton.tsx new file mode 100644 index 000000000..dfb8bb35f --- /dev/null +++ b/fe/src/components/buttons/CustomButton.tsx @@ -0,0 +1,23 @@ +import { Button } from '@material-ui/core'; +import { ReactChild } from 'react'; +import styled from 'styled-components'; + +const CustomButton = ({ children }: { children: ReactChild }) => { + return {children}; +}; + +export default CustomButton; + +const StyledCreateButton = styled(Button)` + background-color: ${({ theme }) => theme.color.blue}; + color: ${({ theme }) => theme.color.grayscale.offWhite}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + /* width: 7.5rem; + height: 2.5rem; */ + width: 15rem; + border-radius: ${({ theme }) => theme.border.radius.S}; + margin-left: 1rem; + &:hover { + background-color: ${({ theme }) => theme.color.darkBlue}; + } +`; diff --git a/fe/src/components/common/CommentInput.tsx b/fe/src/components/common/CommentInput.tsx deleted file mode 100644 index 48f1212c2..000000000 --- a/fe/src/components/common/CommentInput.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import styled from 'styled-components'; -import { TextField } from '@material-ui/core'; - -const CommentInput = () => { - return ( - - ); -}; - -export default CommentInput; - -const CustomTextField = styled(TextField)` - width: 100%; - height: 20rem; - .MuiFilledInput-root.Mui-focused { - background-color: ${({ theme }) => theme.color.grayscale.offWhite}; - border: 2px solid ${({ theme }) => theme.color.grayscale.line}; - } - - .MuiInputLabel-filled { - margin-left: 1rem; - } - - .MuiFilledInput-root { - border-top-left-radius: 14px; - border-top-right-radius: 14px; - padding: 2rem 2rem; - } - .MuiFilledInput-underline::before { - border-bottom: 0px solid; - } - .MuiFilledInput-underline::after { - border-bottom: 0px solid; - } - label.MuiFormLabel-root.Mui-focused { - color: ${({ theme }) => theme.color.grayscale.label}; - } - .MuiInputLabel-filled.MuiInputLabel-shrink { - transform: translate(12px, 10px) scale(0.6); - } -`; diff --git a/fe/src/components/common/CommentTextarea.tsx b/fe/src/components/common/CommentTextarea.tsx new file mode 100644 index 000000000..a827833e6 --- /dev/null +++ b/fe/src/components/common/CommentTextarea.tsx @@ -0,0 +1,55 @@ +import { ChangeEvent, useState } from 'react'; +import styled from 'styled-components'; + +const CommentTextarea = () => { + const [text, setText] = useState(''); + const handleChange = (e: ChangeEvent) => + setText(e.target.value); + + return ( + + + + + ); +}; + +export default CommentTextarea; +const StyledCommentTextarea = styled.div` + position: relative; + &:focus { + label { + transform: translate(12px, 10px) scale(0.6); + } + } +`; +const Label = styled.label` + position: absolute; + top: 30px; + left: 33px; + transition: transform 200ms cubic-bezier(0, 0, 0.2, 1) 0ms; + color: ${({ theme }) => theme.color.grayscale.label}; + &[aria-checked='true'] { + transform: translate(-28px, -20px) scale(0.6); + } +`; +const CustomTextField = styled.textarea` + box-sizing: border-box; + width: 100%; + height: 18rem; + background-color: rgba(0, 0, 0, 0.09); + transition: background-color 200ms cubic-bezier(0, 0, 0.2, 1) 0ms; + border-top-left-radius: 14px; + border-top-right-radius: 14px; + padding: 2rem 2rem; + outline-style: none; + border: transparent; + resize: vertical; + &:focus { + background-color: ${({ theme }) => theme.color.grayscale.offWhite}; + border: 2px solid ${({ theme }) => theme.color.grayscale.line}; + } + &:focus + label { + transform: translate(-28px, -20px) scale(0.6); + } +`; \ No newline at end of file diff --git a/fe/src/components/common/Filter.tsx b/fe/src/components/common/Filter.tsx index 3256ab4e7..060949b41 100644 --- a/fe/src/components/common/Filter.tsx +++ b/fe/src/components/common/Filter.tsx @@ -1,16 +1,17 @@ import { Button } from '@material-ui/core'; -import { createRef, useState } from 'react'; +import { useState } from 'react'; import styled from 'styled-components'; import FilterList from './FilterList'; import { ReactComponent as ArrowDown } from 'icons/arrow-down.svg'; +import { ReactComponent as PlusIconSvg } from 'icons/pluse.svg'; import Popover from '@material-ui/core/Popover'; -import { FilterPropsType, FilterSelectorType } from 'types/filterType'; +import { FilterPropsType } from 'types/filterType'; import { useRecoilValue } from 'recoil'; -import { filterSelector, labelQuery } from 'store'; +import { filterSelector } from 'store'; +import { getTitle } from 'utils/util'; -export default function Filter({ filterType }: FilterPropsType) { +export default function Filter({ isPluse, filterType }: FilterPropsType) { const [anchorEl, setAnchorEl] = useState(null); - const ref = createRef(); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -20,23 +21,18 @@ export default function Filter({ filterType }: FilterPropsType) { }; const dataList = useRecoilValue(filterSelector); - // let list; - // if (dataList) { - // list = dataList[filterType]; - // } - const getFilterTitle = (filterType: FilterSelectorType) => - ({ - milestoneList: '마일스톤', - labelList: '레이블', - }[filterType]); - // authorList: '작성자', - // assigneeList: '담당자', return ( <> - {getFilterTitle(filterType)} - + {isPluse ? ( + + ) : ( + <> + {getTitle(filterType)} + + + )} @@ -85,3 +80,9 @@ const CustomMenu = styled(Popover)` background-color: ${({ theme }) => theme.color.grayscale.inputBG}; } `; + +const PlusIcon = styled(PlusIconSvg)` + path { + stroke: ${({ theme }) => theme.color.grayscale.label}; + } +`; diff --git a/fe/src/components/common/FilterItem.tsx b/fe/src/components/common/FilterItem.tsx index a744a0466..87748865e 100644 --- a/fe/src/components/common/FilterItem.tsx +++ b/fe/src/components/common/FilterItem.tsx @@ -5,8 +5,7 @@ import { FilterItemPropsType } from '../../types/filterType'; import { ReactComponent as CheckOff } from 'icons/check-off-circle.svg'; import { ReactComponent as CheckOn } from 'icons/check-on-circle.svg'; const FilterItem = ( - { filterItem, isEnd }: FilterItemPropsType, - ref: Ref + { filterItem, isEnd }: FilterItemPropsType ) => { const handleClick = (e: MouseEvent) => { console.log(filterItem.id); @@ -43,5 +42,5 @@ const StyledColor = styled.div<{ colorCode: string }>` border: 1px solid ${({ theme }) => theme.color.grayscale.line}; margin-right: 0.5rem; `; -export default forwardRef(FilterItem); +export default FilterItem; // ${({ colorCode }) => colorCode} diff --git a/fe/src/components/common/FilterList.tsx b/fe/src/components/common/FilterList.tsx index 4cdcb0c5c..c625708e8 100644 --- a/fe/src/components/common/FilterList.tsx +++ b/fe/src/components/common/FilterList.tsx @@ -1,14 +1,20 @@ import FilterItem from './FilterItem'; import { FilterItemType, FilterListType } from '../../types/filterType'; import styled from 'styled-components'; -import { forwardRef, Ref } from 'react'; -const FilterList = ({ filterTitle, filterList }: FilterListType,ref:Ref) => { + +const FilterList = ({ filterTitle, filterList }: FilterListType) => { return ( <> {filterTitle} 필터 {filterList.map((filterItem: FilterItemType, idx) => { - return ; + return ( + + ); })} ); @@ -18,4 +24,4 @@ const MenuTitle = styled.div` ${({ theme }) => theme.style.upperWrapper} padding: 0.7rem 1rem; `; -export default forwardRef(FilterList); +export default FilterList; diff --git a/fe/src/components/common/IssueFilter.tsx b/fe/src/components/common/IssueFilter.tsx index 9948fed6e..0cc6c86a2 100644 --- a/fe/src/components/common/IssueFilter.tsx +++ b/fe/src/components/common/IssueFilter.tsx @@ -5,10 +5,11 @@ import FilterList from './FilterList'; import { ReactComponent as ArrowDown } from 'icons/arrow-down.svg'; import Popover from '@material-ui/core/Popover'; const testArray = [ - { id: 1, description: '테스트필터1' }, - { id: 2, description: '테스트필터2' }, - { id: 3, description: '테스트필터3' }, - { id: 4, description: '테스트필터4' }, + { id: 1, description: '열린 이슈' }, + { id: 2, description: '내가 작성한 이슈' }, + { id: 3, description: '나에게 할당된 이슈' }, + { id: 4, description: '내가 댓글을 남긴 이슈' }, + { id: 5, description: '닫힌 이슈' }, ]; export default function IssueFilter() { @@ -54,8 +55,12 @@ const FilterButton = styled(Button)` font-weight: ${({ theme }) => theme.fontWeight.bold2}; border: 1px solid ${({ theme }) => theme.color.grayscale.line}; box-shadow: none; - border-top-left-radius: 20px; - border-bottom-left-radius: 20px; + border-top-left-radius: ${({ theme }) => theme.border.radius.S}; + border-bottom-left-radius: ${({ theme }) => theme.border.radius.S}; + height: 2.5rem; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; const ArrowDownIcon = styled(ArrowDown)` diff --git a/fe/src/components/common/Table.tsx b/fe/src/components/common/Table.tsx new file mode 100644 index 000000000..3a4330d27 --- /dev/null +++ b/fe/src/components/common/Table.tsx @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + + +export const Wrapper = styled.div` + ${({ theme }) => theme.style.flexColum} + box-sizing: border-box; +`; + +export const Upper = styled.div` + ${({ theme }) => theme.style.upperWrapper} + width: 100%; + box-sizing: border-box; +`; + +export const Lower = styled.div` + ${({ theme }) => theme.style.lowerWrapper} + box-sizing: border-box; +`; diff --git a/fe/src/components/labels/Labels.tsx b/fe/src/components/labels/Labels.tsx new file mode 100644 index 000000000..e7b95aed6 --- /dev/null +++ b/fe/src/components/labels/Labels.tsx @@ -0,0 +1,23 @@ +import { Wrapper, Upper, Lower } from 'components/common/Table'; +import styled from 'styled-components'; +import LabelsItem from './LabelsItem'; + +const Labels = () => { + return ( + + + 3개의 레이블 + + + + + + + ); +}; + +export default Labels; + +const LabelsHeader = styled.div` + padding: 1.3rem 1.6rem; +`; diff --git a/fe/src/components/labels/LabelsItem.tsx b/fe/src/components/labels/LabelsItem.tsx new file mode 100644 index 000000000..d043c6bb5 --- /dev/null +++ b/fe/src/components/labels/LabelsItem.tsx @@ -0,0 +1,28 @@ +import styled from 'styled-components'; +import { LabelsItemProps } from 'types/issueType'; +import LabelsItemLeft from './LabelsItemLeft'; +import LabelsItemRight from './LabelsItemRight'; + +const LabelsItem = ({ + id, + description, + title, + textColor, + colorCode, +}: LabelsItemProps) => { + return ( + + + + + ); +}; + +export default LabelsItem; + +const StyledLabelsItem = styled.li` + ${({ theme }) => theme.style.flexSpaceBetween} + padding: 1rem; + box-sizing: border-box; + border-top: 1px solid ${({ theme }) => theme.color.grayscale.line}; +`; diff --git a/fe/src/components/labels/LabelsItemLeft.tsx b/fe/src/components/labels/LabelsItemLeft.tsx new file mode 100644 index 000000000..16e15468c --- /dev/null +++ b/fe/src/components/labels/LabelsItemLeft.tsx @@ -0,0 +1,27 @@ +import { LabelsItemLeftProps } from 'types/issueType'; +import Label from 'components/common/Label'; +import styled from 'styled-components'; + +const LabelsItemLeft = ({ + description, + title, + colorCode, + textColor, +}: LabelsItemLeftProps) => { + return ( + + + ); +}; + +export default LabelsItemLeft; + +const StyledLabelsItemLeft = styled.div` + display: flex; +`; + +const StyledSpan = styled.span` + margin-left: 5rem; +`; diff --git a/fe/src/components/labels/LabelsItemRight.tsx b/fe/src/components/labels/LabelsItemRight.tsx new file mode 100644 index 000000000..3d956d9a7 --- /dev/null +++ b/fe/src/components/labels/LabelsItemRight.tsx @@ -0,0 +1,25 @@ +import { Button } from '@material-ui/core'; +import styled from 'styled-components'; +import { ReactComponent as EditSvg } from 'icons/edit.svg'; +import { ReactComponent as DeleteSvg } from 'icons/delete.svg'; +const LabelsItemRight = () => { + return ( + + + + + ); +}; + +export default LabelsItemRight; + +const StlyedLabelsItemRight = styled.div` + display: flex; +`; + +const EditIcon = styled(EditSvg)``; + + +const DeleteIcon = styled(DeleteSvg)``; diff --git a/fe/src/components/milestones/MilestoneBar.tsx b/fe/src/components/milestones/MilestoneBar.tsx new file mode 100644 index 000000000..e907ac3d1 --- /dev/null +++ b/fe/src/components/milestones/MilestoneBar.tsx @@ -0,0 +1,40 @@ +import styled from 'styled-components'; +import { MilestoneBarProps } from 'types/issueType'; + +const MilestoneBar = ({ + openedIssueCount, + closedIssueCount, +}: MilestoneBarProps) => { + const percent = + (openedIssueCount / (openedIssueCount + closedIssueCount)) * 100; + return ( + + + + + ); +}; + +export default MilestoneBar; + +const StyledMilestoneBar = styled.div` + position: relative; +`; + +const DefaultBar = styled.div` + width: 100%; + height: 0.5rem; + border-radius: ${({ theme }) => theme.border.radius.S}; + background-color: ${({ theme }) => theme.color.grayscale.inputBG}; +`; + +const ProgressBar = styled.div<{ percent: number }>` + position: absolute; + top: 0; + left: 0; + width: ${({ percent }) => `${percent}%`}; + border-radius: ${({ theme }) => theme.border.radius.S}; + background-color: ${({ theme }) => theme.color.blue}; + z-index: 99; + height: 0.5rem; +`; diff --git a/fe/src/components/milestones/Milestones.tsx b/fe/src/components/milestones/Milestones.tsx new file mode 100644 index 000000000..5b21c40aa --- /dev/null +++ b/fe/src/components/milestones/Milestones.tsx @@ -0,0 +1,55 @@ +import { Wrapper, Upper, Lower } from 'components/common/Table'; +import { ReactComponent as Open } from 'icons/openIssue.svg'; +import { ReactComponent as Close } from 'icons/closeIssue.svg'; +import styled from 'styled-components'; +import { Button } from '@material-ui/core'; +import MilestonesItem from './MilestonesItem'; + +const Milestones = () => { + return ( + + + + }> + 열린 마일스톤(2) + + }> + 닫힌 마일스톤(0) + + + + + + + + ); +}; + +export default Milestones; + +const MilestonesHeader = styled.div` + padding: 1rem; +`; + +const MilestonesButton = styled(Button)` + color: ${({ theme }) => theme.color.grayscale.titleActive}; + stroke: ${({ theme }) => theme.color.grayscale.titleActive}; + font-weight: 700; +`; +const OpenSvg = styled(Open)` + path { + stroke: inherit; + } +`; +const CloseSvg = styled(Close)` + path { + stroke: inherit; + } +`; diff --git a/fe/src/components/milestones/MilestonesItem.tsx b/fe/src/components/milestones/MilestonesItem.tsx new file mode 100644 index 000000000..99b022e7a --- /dev/null +++ b/fe/src/components/milestones/MilestonesItem.tsx @@ -0,0 +1,28 @@ +import styled from 'styled-components'; +import { MilestonesItemProps } from 'types/issueType'; +import MilestonesItemLeft from './MilestonesItemLeft'; +import MilestonesItemRight from './MilestonesItemRight'; + +const MilestonesItem = ({ + id, + title, + description, + dueDate, + openedIssueCount, + closedIssueCount, +}: MilestonesItemProps) => { + return ( + + + + + ); +}; + +export default MilestonesItem; + +const StyledMilestonesItem = styled.div` +box-sizing: border-box; + ${({ theme }) => theme.style.flexSpaceBetween} + padding: 1rem 1.5rem; +`; diff --git a/fe/src/components/milestones/MilestonesItemLeft.tsx b/fe/src/components/milestones/MilestonesItemLeft.tsx new file mode 100644 index 000000000..c2eb90c3d --- /dev/null +++ b/fe/src/components/milestones/MilestonesItemLeft.tsx @@ -0,0 +1,47 @@ +import styled from 'styled-components'; +import { MilestonesItemLeftProps } from 'types/issueType'; +import { ReactComponent as MilestoneSvg } from 'icons/milestoneBlue.svg'; +import { ReactComponent as CalendarSvg } from 'icons/calendar.svg'; +const MilestonesItemLeft = ({ + title, + description, + dueDate, +}: MilestonesItemLeftProps) => { + return ( + +
+ + {title} + + + {dueDate} + +
+ {description} +
+ ); +}; + +export default MilestonesItemLeft; + +const StyledMilestonesItemLeft = styled.div` + ${({ theme }) => theme.style.flexColum} + +`; + +const MilestionesDescription = styled.span` + color: ${({ theme }) => theme.color.grayscale.label}; + margin-top: 0.5rem; + font-size: 0.8rem; +`; + +const MilestonesItemTitle = styled.span` + margin: 0 0.5rem; + font-weight: ${({ theme }) => theme.fontWeight.bold}; +`; +const MilestonesIcon = styled(MilestoneSvg)``; + +const DueDateIcon = styled(CalendarSvg)``; +const DueDate = styled.span` + color: ${({ theme }) => theme.color.grayscale.label}; +`; diff --git a/fe/src/components/milestones/MilestonesItemRight.tsx b/fe/src/components/milestones/MilestonesItemRight.tsx new file mode 100644 index 000000000..1c21838d1 --- /dev/null +++ b/fe/src/components/milestones/MilestonesItemRight.tsx @@ -0,0 +1,58 @@ +import styled from 'styled-components'; +import { Button } from '@material-ui/core'; +import { ReactComponent as EditSvg } from 'icons/edit.svg'; +import { ReactComponent as DeleteSvg } from 'icons/delete.svg'; +import { ReactComponent as CloseIssue } from 'icons/closeIssue.svg'; +import { MilestoneBarProps } from 'types/issueType'; +import MilestoneBar from './MilestoneBar'; + +const MilestonesItemRight = ({ + openedIssueCount, + closedIssueCount, +}: MilestoneBarProps) => { + const percent = + (openedIssueCount / (openedIssueCount + closedIssueCount)) * 100; + return ( + + + + + + + + + {percent}% + + 열린 이슈 {openedIssueCount} 닫힌이슈 {closedIssueCount} + + + + ); +}; + +export default MilestonesItemRight; +const StyledButtons = styled.div` + display: flex; + justify-content: flex-end; +`; +const StyledDiv = styled.div` + ${({ theme }) => theme.style.flexColum} + width: 20rem; +`; + +const MilestonesItemSubtitle = styled.div` + color: ${({ theme }) => theme.color.grayscale.label}; + ${({ theme }) => theme.style.flexSpaceBetween} + font-size:${({ theme }) => theme.fontSize.S}; + margin-top: 0.7rem; +`; + +const CloseIcon= styled(CloseIssue)``; + + +const EditIcon = styled(EditSvg)``; + + +const DeleteIcon = styled(DeleteSvg)``; diff --git a/fe/src/components/navbar/Navbar.tsx b/fe/src/components/navbar/Navbar.tsx index e64c6dfeb..44b890859 100644 --- a/fe/src/components/navbar/Navbar.tsx +++ b/fe/src/components/navbar/Navbar.tsx @@ -1,57 +1,20 @@ -import { - Button, - ButtonBase, - Divider, - InputAdornment, - InputBase, - TextField, -} from '@material-ui/core'; +import { InputBase } from '@material-ui/core'; import IssueFilter from 'components/common/IssueFilter'; import styled from 'styled-components'; import { ReactComponent as SearchIconSvg } from 'icons/search.svg'; -import { ReactComponent as PlusIconSvg } from 'icons/pluse.svg'; -import { ReactComponent as LabelIconSvg } from 'icons/label.svg'; -import { ReactComponent as MilestoneIconSvg } from 'icons/openMilestone.svg'; +import NavbarButtons from './NavbarButtons'; const Navbar = () => { return ( ); @@ -71,10 +34,11 @@ const FilterSearchBar = styled(InputBase)` border-left: 1px solid ${({ theme }) => theme.color.grayscale.line}; background-color: ${({ theme }) => theme.color.grayscale.inputBG}; color: ${({ theme }) => theme.color.grayscale.placeholder}; - padding-left: 1.625rem; + padding-left: 2rem; `; const NavbarLeft = styled.div` + position: relative; display: flex; border: 1px solid ${({ theme }) => theme.color.grayscale.line}; border-radius: ${({ theme }) => theme.border.radius.S}; @@ -85,60 +49,11 @@ const NavbarRight = styled.div` align-items: center; `; -const LabelAndMilestone = styled.div` - border-radius: ${({ theme }) => theme.border.radius.S}; - border: 1px solid ${({ theme }) => theme.color.grayscale.line}; - display: flex; -`; - -const NewIssueButton = styled(Button)` - background-color: ${({ theme }) => theme.color.blue}; - color: ${({ theme }) => theme.color.grayscale.offWhite}; - font-weight: ${({ theme }) => theme.fontWeight.bold2}; - width: 7.5rem; - height: 2.5rem; - border-radius: ${({ theme }) => theme.border.radius.S}; - margin-left: 1rem; -`; - -const SearchIcon = styled(SearchIconSvg)``; - -const PlusIcon = styled(PlusIconSvg)` - path { - stroke: ${({ theme }) => theme.color.grayscale.offWhite}; - } -`; - -const LabelIcon = styled(LabelIconSvg)` - path { - stroke: ${({ theme }) => theme.color.grayscale.label}; - } -`; - -const MilestoneIcon = styled(MilestoneIconSvg)``; - -const LabelButton = styled(Button)` - width: 10rem; - height: 2.5rem; - border-radius: 0.7rem 0 0 0.7rem; - color: ${({ theme }) => theme.color.grayscale.label}; - font-weight: ${({ theme }) => theme.fontWeight.bold2}; - - .button-text { - margin-right: 0.5rem; - } -`; - -const MilestoneButton = styled(Button)` - width: 10rem; - height: 2.5rem; - border-radius: 0 0.7rem 0.7rem 0; - color: ${({ theme }) => theme.color.grayscale.label}; - font-weight: ${({ theme }) => theme.fontWeight.bold2}; - - .button-text { - margin-right: 0.5rem; - } +const SearchIcon = styled(SearchIconSvg)` + position: absolute; + top: 12px; + left: 107px; + z-index: 99; `; export default Navbar; diff --git a/fe/src/components/navbar/NavbarButtons.tsx b/fe/src/components/navbar/NavbarButtons.tsx new file mode 100644 index 000000000..11b390817 --- /dev/null +++ b/fe/src/components/navbar/NavbarButtons.tsx @@ -0,0 +1,67 @@ +import { Button, Divider } from '@material-ui/core'; +import { ReactComponent as LabelIconSvg } from 'icons/label.svg'; +import { ReactComponent as MilestoneIconSvg } from 'icons/openMilestone.svg'; +import CreateButton from 'components/buttons/CreateButton'; +import styled from 'styled-components'; + +const NavbarButtons = ({ buttonType }: { buttonType: string }) => { + return ( + + + }> +
레이블
+
(0)
+
+ + }> +
마일스톤
+
(0)
+
+
+ {buttonType} +
+ ); +}; + +export default NavbarButtons; + +const StyledNavbarButtons = styled.div` + ${({ theme }) => theme.style.flexSpaceBetween}; +`; +const LabelAndMilestone = styled.div` + border-radius: ${({ theme }) => theme.border.radius.S}; + border: 1px solid ${({ theme }) => theme.color.grayscale.line}; + display: flex; +`; + +const LabelIcon = styled(LabelIconSvg)` + path { + stroke: ${({ theme }) => theme.color.grayscale.label}; + } +`; + +const MilestoneIcon = styled(MilestoneIconSvg)``; + +const LabelButton = styled(Button)` + width: 10rem; + height: 2.5rem; + border-radius: 0.7rem 0 0 0.7rem; + color: ${({ theme }) => theme.color.grayscale.label}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + + .button-text { + margin-right: 0.5rem; + } +`; + +const MilestoneButton = styled(Button)` + width: 10rem; + height: 2.5rem; + border-radius: 0 0.7rem 0.7rem 0; + color: ${({ theme }) => theme.color.grayscale.label}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + + .button-text { + margin-right: 0.5rem; + } +`; diff --git a/fe/src/components/new-issue/NewIssueLeft.tsx b/fe/src/components/new-issue/NewIssueLeft.tsx index e69b4cae5..df3a500da 100644 --- a/fe/src/components/new-issue/NewIssueLeft.tsx +++ b/fe/src/components/new-issue/NewIssueLeft.tsx @@ -1,6 +1,5 @@ -import { TextField } from '@material-ui/core'; import AuthorAvatar from 'components/common/AuthorAvatar'; -import CommentInput from 'components/common/CommentInput'; +import CommentTextarea from 'components/common/CommentTextarea'; import IssueTitleInput from 'components/common/IssueTitleInput'; import styled from 'styled-components'; @@ -12,7 +11,7 @@ const NewIssueLeft = () => { - + ); diff --git a/fe/src/components/new-issue/NewIssueRight.tsx b/fe/src/components/new-issue/NewIssueRight.tsx new file mode 100644 index 000000000..eb6fa7931 --- /dev/null +++ b/fe/src/components/new-issue/NewIssueRight.tsx @@ -0,0 +1,27 @@ +import { Divider } from '@material-ui/core'; +import styled from 'styled-components'; +import SidebarList from './SidebarList'; + +const NewIssueRight = () => { + return ( + + + + + + + + ); +}; + +export default NewIssueRight; + +const StyledNewIssueRight = styled.div` + ${({ theme }) => theme.style.flexColum}; + border-radius: ${({ theme }) => theme.border.radius.M}; + border: 2px solid ${({ theme }) => theme.color.grayscale.line}; + width: 25%; + height: fit-content; + margin-left: 2rem; +`; + diff --git a/fe/src/components/new-issue/SidebarList.tsx b/fe/src/components/new-issue/SidebarList.tsx new file mode 100644 index 000000000..9c57b1d78 --- /dev/null +++ b/fe/src/components/new-issue/SidebarList.tsx @@ -0,0 +1,23 @@ +import Filter from 'components/common/Filter'; +import styled from 'styled-components'; +import { SidebarListProps } from 'types/newIssueType'; +import { getTitle } from 'utils/util'; + + +const SidebarList = ({type} : SidebarListProps) => { + return ( + + {getTitle(type)} + + + ) +} + +export default SidebarList + +const StyledSidebarList = styled.li` + ${({ theme }) => theme.style.flexSpaceBetween}; + padding : 1.5rem 2rem; + box-sizing: border-box; +`; + \ No newline at end of file diff --git a/fe/src/hook/useAxios.tsx b/fe/src/hook/useAxios.tsx index c71942adb..6ffd9cdcc 100644 --- a/fe/src/hook/useAxios.tsx +++ b/fe/src/hook/useAxios.tsx @@ -6,27 +6,26 @@ type Action = | { type: 'FETCH_SUCCESS'; payload: any } | { type: 'FETCH_FAILURE'; payload: null }; -interface FetchOption { - headers?: OptionData; - body?: OptionData; -} interface OptionData { [key: string]: string; } -const createFetchOptions = (headerData?: OptionData, bodyData?: OptionData) => { - const headers = { 'Content-Type': 'application/json', ...headerData }; - let options: OptionData = { headers: JSON.stringify(headers) }; - if (bodyData) options = { ...options, body: JSON.stringify(bodyData) }; - return options; -}; +axios.defaults.baseURL = process.env.REACT_APP_API_URL; +axios.defaults.headers.post['Content-Type'] = 'application/json'; +const createFetchOptions = (bodyData?: OptionData) => { + if (bodyData) return; + return { + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(bodyData), + }; +}; function useAxios( Props: boolean, initialUrl: string, methods: Method, - option?: FetchOption + bodyData?: OptionData ) { const [url] = useState(initialUrl); @@ -42,22 +41,25 @@ function useAxios( dispatch({ type: 'FETCH_INIT', payload: null }); try { if (!url) throw new Error(`Error: URL IS NULL`); - await axios(url).then((result) => + await axios(url, createFetchOptions(bodyData)).then((result) => dispatch({ type: 'FETCH_SUCCESS', payload: result.data }) ); } catch (error) { console.error(error) + if(error.response.status === 401) console.error("Unauthorized Request"); + dispatch({ type: 'FETCH_FAILURE', payload: null }); } }; if (!Props) return; fetchData(); - }, [url, methods, Props]); + }, [url, methods, Props, bodyData]); return { ...state }; } + function requestReducer( state: { isInit: boolean; diff --git a/fe/src/icons/calendar.svg b/fe/src/icons/calendar.svg new file mode 100644 index 000000000..a26d37c22 --- /dev/null +++ b/fe/src/icons/calendar.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/fe/src/icons/milestoneBlue.svg b/fe/src/icons/milestoneBlue.svg new file mode 100644 index 000000000..95621c581 --- /dev/null +++ b/fe/src/icons/milestoneBlue.svg @@ -0,0 +1,4 @@ + + + + diff --git a/fe/src/pages/LabelPage.tsx b/fe/src/pages/LabelPage.tsx new file mode 100644 index 000000000..dadf1106c --- /dev/null +++ b/fe/src/pages/LabelPage.tsx @@ -0,0 +1,23 @@ + +import Header from 'components/header/Header'; +import Labels from 'components/labels/Labels'; +import NavbarButtons from 'components/navbar/NavbarButtons'; +import styled from 'styled-components'; + +const LabelPage = () => { + return ( + <> +
+ + + + + + ); +}; + +export default LabelPage; + +const StyledSpace = styled.div` + margin-top: 1.5rem; +`; diff --git a/fe/src/pages/MilestoneListPage.tsx b/fe/src/pages/MilestoneListPage.tsx new file mode 100644 index 000000000..0fa353551 --- /dev/null +++ b/fe/src/pages/MilestoneListPage.tsx @@ -0,0 +1,22 @@ +import Header from 'components/header/Header'; +import Milestones from 'components/milestones/Milestones'; + +import NavbarButtons from 'components/navbar/NavbarButtons'; +import styled from 'styled-components'; + +const MilestoneListPage = () => { + return ( + <> +
+ + + + + + ); +}; +export default MilestoneListPage; + +const StyledSpace = styled.div` + margin-top: 1.5rem; +`; diff --git a/fe/src/pages/NewIssuePage.tsx b/fe/src/pages/NewIssuePage.tsx index 8d6b665cc..c4e46dad2 100644 --- a/fe/src/pages/NewIssuePage.tsx +++ b/fe/src/pages/NewIssuePage.tsx @@ -1,6 +1,8 @@ import { Divider } from '@material-ui/core'; +import CustomButton from 'components/buttons/CustomButton'; import Header from 'components/header/Header'; import NewIssueLeft from 'components/new-issue/NewIssueLeft'; +import NewIssueRight from 'components/new-issue/NewIssueRight'; import styled from 'styled-components'; const NewIssuePage = () => { @@ -11,13 +13,25 @@ const NewIssuePage = () => { + + + + 완료 + - ); + + ); }; export default NewIssuePage; +const StyledDiv = styled.div` +display: flex; +margin: 1.2rem; +justify-content: flex-end; +` + const StlyedNewIssuePage = styled.div` ${({ theme }) => theme.style.flexColum} `; diff --git a/fe/src/pages/OAuthPage.tsx b/fe/src/pages/OAuthPage.tsx index 7803ae06a..2d627431c 100644 --- a/fe/src/pages/OAuthPage.tsx +++ b/fe/src/pages/OAuthPage.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import qs from 'qs'; import useAxios from 'hook/useAxios'; import { useEffect } from 'react'; +import { getUrl } from 'utils/util'; const OAuthPage = () => { const location = useLocation(); @@ -10,7 +11,8 @@ const OAuthPage = () => { const res = qs.parse(location.search, { ignoreQueryPrefix: true, }); - const url = `${process.env.REACT_APP_API_URL}/api/login/auth?client=web&code=${res.code}`; + + const url = getUrl.LOGIN(res.code); const { isSuccess, data } = useAxios(true, url, 'get'); useEffect(() => { diff --git a/fe/src/types/filterType.ts b/fe/src/types/filterType.ts index cf0936598..ff0c6fbfe 100644 --- a/fe/src/types/filterType.ts +++ b/fe/src/types/filterType.ts @@ -15,10 +15,11 @@ export type FilterItemPropsType = { isEnd: boolean; }; -export type FilterPropsType = { +export type FilterPropsType = { filterType: FilterSelectorType; + isPluse?: boolean; }; export type FilterSelectorType = 'milestoneList' | 'labelList'; -// | 'authorList' -// | 'assigneeList'; +//| 'authorList'| 'assigneeList'; + diff --git a/fe/src/types/issueType.ts b/fe/src/types/issueType.ts index 759f82069..0cb11aace 100644 --- a/fe/src/types/issueType.ts +++ b/fe/src/types/issueType.ts @@ -21,3 +21,31 @@ export type authorType = { name: string; profileImg?: string | undefined; }; + +export interface LabelsItemLeftProps { + description: string; + title: string; + colorCode: string; + textColor: 'black' | 'white'; +} + +export interface LabelsItemProps extends LabelsItemLeftProps { + id: number; +} + +// export interface MilestonesItem extends +export type MilestonesItemLeftProps = { + title: string; + description: string; + dueDate: string; +}; + +export type MilestoneBarProps = { + openedIssueCount: number; + closedIssueCount: number; +}; + +export type MilestonesItemProps = MilestonesItemLeftProps & +MilestoneBarProps & { + id: number; + }; diff --git a/fe/src/types/newIssueType.ts b/fe/src/types/newIssueType.ts new file mode 100644 index 000000000..3ff38d413 --- /dev/null +++ b/fe/src/types/newIssueType.ts @@ -0,0 +1,5 @@ +export type SidebarListProps = { type: SidebarListType }; + +export type SidebarListType = 'milestoneList' | 'labelList' ; + +export type titleType = 'milestoneList' | 'labelList' | 'authorList' | 'assigneeList'; \ No newline at end of file diff --git a/fe/src/utils/util.ts b/fe/src/utils/util.ts new file mode 100644 index 000000000..fd8059677 --- /dev/null +++ b/fe/src/utils/util.ts @@ -0,0 +1,16 @@ +import { ParsedQs } from 'qs'; +import { titleType } from 'types/newIssueType'; + +export const getUrl = { + LOGIN: (code: string | ParsedQs | string[] | ParsedQs[] | undefined) => + `/api/login/auth?client=web&code=${code}`, +}; + +export const getTitle = (type: titleType) => + ({ + milestoneList: '마일스톤', + labelList: '레이블', + authorList: '작성자', + assigneeList: '담당자', + }[type]); + From 96043c214e2fc1e38dfd7187321b1976e142f0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Autumn=20=28=EC=A1=B0=ED=99=8D=EB=B9=84=29?= <60209518+dyongdi@users.noreply.github.com> Date: Thu, 17 Jun 2021 15:46:09 +0900 Subject: [PATCH 03/42] =?UTF-8?q?[#40]=20=EC=9D=B4=EC=8A=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20(=EC=98=A4=EB=A5=B8=EC=AA=BD=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B0=94=20=EC=A0=9C=EC=99=B8)=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#40] Feat: 이슈 상세 페이지 헤더 ui - 상태를 사용하지 않고 하드코딩된 상태 - 일부 컴포넌트는 open/close 여부에 따라 다르게 렌더링되도록 했음 * [#40] Feat: Comment 컴포넌트 생성 * [#40] Feat: 이슈 상세 페이지 body 왼쪽 부분 ui 작업 --- fe/src/App.tsx | 4 + fe/src/components/issue-detail/Comment.tsx | 106 ++++++++++++ .../issue-detail/IssueDetailBody.tsx | 55 ++++++ .../issue-detail/IssueDetailHeader.tsx | 156 ++++++++++++++++++ .../issue-detail/IssueStateSign.tsx | 61 +++++++ fe/src/pages/IssueDetailPage.tsx | 22 +++ 6 files changed, 404 insertions(+) create mode 100644 fe/src/components/issue-detail/Comment.tsx create mode 100644 fe/src/components/issue-detail/IssueDetailBody.tsx create mode 100644 fe/src/components/issue-detail/IssueDetailHeader.tsx create mode 100644 fe/src/components/issue-detail/IssueStateSign.tsx create mode 100644 fe/src/pages/IssueDetailPage.tsx diff --git a/fe/src/App.tsx b/fe/src/App.tsx index 5c63777bc..1c4689246 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -10,6 +10,7 @@ import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles'; import NewIssuePage from 'pages/NewIssuePage'; import { RecoilRoot } from 'recoil'; import { Suspense } from 'react'; +import IssueDetailPage from 'pages/IssueDetailPage'; import LabelPage from 'pages/LabelPage'; import MilestoneListPage from 'pages/MilestoneListPage'; @@ -36,6 +37,9 @@ function App() { + + + diff --git a/fe/src/components/issue-detail/Comment.tsx b/fe/src/components/issue-detail/Comment.tsx new file mode 100644 index 000000000..9b345f79d --- /dev/null +++ b/fe/src/components/issue-detail/Comment.tsx @@ -0,0 +1,106 @@ +import { Box, Button } from '@material-ui/core'; +import AuthorAvatar from 'components/common/AuthorAvatar'; +import styled from 'styled-components'; +import { ReactComponent as EditSvg } from 'icons/edit.svg'; +import { ReactComponent as EmojiSvg } from 'icons/emoji.svg'; + +const Comment = () => ( + + + + + +
Autumn
+
20분 전
+
+ + {/* 작성자 라벨 - comment author === issue author이면 노출 */} + + 작성자 + + {/* 편집 버튼 - 로그인 유저 === comment author이면 노출 */} + + + + + +
+ +
+ 서버에서 받아온 텍스트가 이곳에 표시.. {/* */} + 줄바꿈.. 안된다 ^^ 처리 필요... +
+
+
+
+); + +const CommentWrapper = styled.div` + ${({ theme }) => theme.style.flexColum} + box-sizing: border-box; + margin-left: 1rem; +`; + +const CommentHeader = styled(Box)` + ${({ theme }) => theme.style.upperWrapper} + ${({ theme }) => theme.style.flexAlignItemsCenter} + width: 100%; + box-sizing: border-box; + padding: 1rem 1.5rem; + justify-content: space-between; + + div { + font-size: ${({ theme }) => theme.fontSize.M}; + } + + .comment-author { + color: ${({ theme }) => theme.color.grayscale.titleActive}; + } + + .comment-created-time { + color: ${({ theme }) => theme.color.grayscale.label}; + margin-left: 0.5rem; + } +`; + +const IssueAuthorLabel = styled(Box)` + width: 4.125rem; + height: 1.5rem; + border: 1px solid ${({ theme }) => theme.color.grayscale.line}; + border-radius: ${({ theme }) => theme.border.radius.XL}; + color: ${({ theme }) => theme.color.grayscale.label}; + margin-right: 1rem; + + span { + font-size: ${({ theme }) => theme.fontSize.S}; + font-weight: ${({ theme }) => theme.fontWeight.bold}; + } +`; + +const EditIcon = styled(EditSvg)``; + +const EmojiButton = styled.button` + all: unset; + margin-left: 1rem; + + &:hover { + cursor: pointer; + } +`; + +const CommentBody = styled.div` + ${({ theme }) => theme.style.lowerWrapper} + box-sizing: border-box; + padding: 1rem 1.5rem; + + .comment-content { + color: ${({ theme }) => theme.color.grayscale.titleActive}; + font-size: ${({ theme }) => theme.fontSize.M}; + } +`; + +export default Comment; diff --git a/fe/src/components/issue-detail/IssueDetailBody.tsx b/fe/src/components/issue-detail/IssueDetailBody.tsx new file mode 100644 index 000000000..694fcae14 --- /dev/null +++ b/fe/src/components/issue-detail/IssueDetailBody.tsx @@ -0,0 +1,55 @@ +import { Box } from '@material-ui/core'; +import AuthorAvatar from 'components/common/AuthorAvatar'; +import Comment from 'components/issue-detail/Comment'; +import CommentInput from 'components/common/CommentInput'; +import styled from 'styled-components'; + +const IssueDetailBody = () => ( + + + + {/* 이슈 작성할 때 본문 부분 - 없으면 생략 가능 */} + + + + {/* 배열 map 돌려서 Comment 생성하기 */} + + + + + + + + + + + + +); + +const CommentArea = styled.section` + width: 70%; +`; + +const IssueDescription = styled.div``; + +const Comments = styled.ul` + ${({ theme }) => theme.style.flexColum} + gap: 24px; +`; + +const NewCommentWrapper = styled(Box)` + margin-top: 2.5rem; +`; + +const Spacer = styled.div` + width: 1rem; +`; + +const AssignArea = styled.section` + width: 30%; + height: 400px; + margin-left: 2rem; + background-color: red; +`; +export default IssueDetailBody; diff --git a/fe/src/components/issue-detail/IssueDetailHeader.tsx b/fe/src/components/issue-detail/IssueDetailHeader.tsx new file mode 100644 index 000000000..2bc01d187 --- /dev/null +++ b/fe/src/components/issue-detail/IssueDetailHeader.tsx @@ -0,0 +1,156 @@ +import styled from 'styled-components'; +import IssueStateSign from './IssueStateSign'; +import { ReactComponent as EditSvg } from 'icons/edit.svg'; +import { ReactComponent as CloseSvg } from 'icons/closeIssue.svg'; +import { ReactComponent as OpenSvg } from 'icons/openIssue.svg'; +import { Button } from '@material-ui/core'; + +const temp = { + issueNumber: 2, + isOpen: false, + author: 'Eamon', +}; +const { issueNumber, isOpen, author } = temp; + +const IssueDetailHeader = () => ( +
+ + + <div className="title">FE 이슈트래커 디자인 시스템 구현</div> + <div className="issue-number">#{issueNumber}</div> + + + + +
+ {isOpen + ? `이 이슈가 20분 전에 ${author}님에 의해 열렸습니다` + : `이 이슈가 20분 전에 ${author}님에 의해 닫혔습니다`} +
+
+
코멘트 1개
+ +
+ + + + + 제목 편집 + + + {isOpen ? ( + + + 이슈 닫기 + + ) : ( + + + 다시 열기 + + )} + +
+); + +const Header = styled.header` + display: flex; + justify-content: space-between; +`; + +const IssueDetailHeaderLeft = styled.div``; + +const Title = styled.div` + display: flex; + margin-bottom: 1rem; + + .title { + color: ${({ theme }) => theme.color.grayscale.titleActive}; + font-size: ${({ theme }) => theme.fontSize.XXL}; + } + + .issue-number { + margin-left: 1rem; + color: ${({ theme }) => theme.color.grayscale.label}; + font-size: ${({ theme }) => theme.fontSize.XXL}; + } +`; + +const Caption = styled.div` + display: flex; + align-items: center; + + .issue-description, + .divider, + .comment-count { + color: ${({ theme }) => theme.color.grayscale.body}; + font-size: ${({ theme }) => theme.fontSize.L}; + } + + .divider { + margin: 0 0.2rem; + } + + .issue-description { + margin-left: 0.5rem; + } +`; + +const IssueDetailHeaderRight = styled.div` + display: flex; +`; + +const EditButton = styled(Button)` + width: 7.5rem; + height: 2.5rem; + border: 2px solid ${({ theme }) => theme.color.blue}; + border-radius: ${({ theme }) => theme.border.radius.S}; + color: ${({ theme }) => theme.color.blue}; + background-color: ${({ theme }) => theme.color.grayscale.offWhite}; + font-size: ${({ theme }) => theme.fontSize.S}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + display: flex; + align-items: center; + + span { + margin-left: 0.2rem; + } +`; + +const EditIcon = styled(EditSvg)` + path { + stroke: ${({ theme }) => theme.color.blue}; + } +`; + +const IssueOpenCloseButton = styled(Button)` + width: 7.5rem; + height: 2.5rem; + border: 2px solid ${({ theme }) => theme.color.blue}; + border-radius: ${({ theme }) => theme.border.radius.S}; + color: ${({ theme }) => theme.color.blue}; + background-color: ${({ theme }) => theme.color.grayscale.offWhite}; + font-size: ${({ theme }) => theme.fontSize.S}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + display: flex; + align-items: center; + margin-left: 0.5rem; + + span { + margin-left: 0.2rem; + } +`; + +const CloseIcon = styled(CloseSvg)` + path { + stroke: ${({ theme }) => theme.color.blue}; + } +`; + +const OpenIcon = styled(OpenSvg)` + path { + stroke: ${({ theme }) => theme.color.blue}; + } +`; + +export default IssueDetailHeader; diff --git a/fe/src/components/issue-detail/IssueStateSign.tsx b/fe/src/components/issue-detail/IssueStateSign.tsx new file mode 100644 index 000000000..8dbf68e66 --- /dev/null +++ b/fe/src/components/issue-detail/IssueStateSign.tsx @@ -0,0 +1,61 @@ +import styled from 'styled-components'; +import { ReactComponent as Open } from 'icons/openIssue.svg'; +import { ReactComponent as Close } from 'icons/closeIssue.svg'; + +type IssueStateSignPropsType = { + isOpen: boolean; +}; + +const IssueStateSign = ({ isOpen }: IssueStateSignPropsType) => ( +
+ {isOpen ? ( + <> + + 열린 이슈 + + ) : ( + <> + + 닫힌 이슈 + + )} +
+); + +const Div = styled.div` + display: flex; + justify-content: center; + align-items: center; + font-size: ${({ theme }) => theme.fontSize.S}; + width: 6.25rem; + height: 2.5rem; + border-radius: ${({ theme }) => theme.border.radius.XL}; + border: 1px solid + ${({ isOpen, theme }) => (isOpen ? theme.color.blue : theme.color.purple)}; + color: ${({ isOpen, theme }) => + isOpen ? theme.color.blue : theme.color.purple}; + background-color: ${({ isOpen, theme }) => + isOpen ? theme.color.lightBlue : theme.color.lightPurple}; + + span { + margin-left: 0.2rem; + } +`; + +const OpenSvg = styled(Open)` + path { + stroke: ${({ theme }) => theme.color.blue}; + fill: ${({ theme }) => theme.color.lightBlue}; + } + margin-right: 0.2rem; +`; + +const CloseSvg = styled(Close)` + path { + stroke: ${({ theme }) => theme.color.purple}; + fill: ${({ theme }) => theme.color.lightPurple}; + } + margin-right: 0.2rem; +`; + +export default IssueStateSign; diff --git a/fe/src/pages/IssueDetailPage.tsx b/fe/src/pages/IssueDetailPage.tsx new file mode 100644 index 000000000..58800491f --- /dev/null +++ b/fe/src/pages/IssueDetailPage.tsx @@ -0,0 +1,22 @@ +import { Divider } from '@material-ui/core'; +import Header from 'components/header/Header'; +import IssueDetailBody from 'components/issue-detail/IssueDetailBody'; +import IssueDetailHeader from 'components/issue-detail/IssueDetailHeader'; +import styled from 'styled-components'; + +const IssueDetailPage = () => ( + <> +
+ + + + + + +); + +const Spacer = styled.div` + height: 2rem; +`; + +export default IssueDetailPage; From 8e66fbda052fa6f540d77fbfd3835a4e1c1e6f5c Mon Sep 17 00:00:00 2001 From: Hongbi Date: Thu, 17 Jun 2021 15:54:32 +0900 Subject: [PATCH 04/42] =?UTF-8?q?[#40]=20Fix:=20new=20comment=20ui=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=B4=20=EC=A4=91=EA=B0=84=EC=97=90=20?= =?UTF-8?q?=EB=B0=94=EB=80=8C=EC=96=B4=EC=84=9C=20=EC=9D=BC=EB=8B=A8=20?= =?UTF-8?q?=EC=A7=80=EC=9B=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/components/issue-detail/IssueDetailBody.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fe/src/components/issue-detail/IssueDetailBody.tsx b/fe/src/components/issue-detail/IssueDetailBody.tsx index 694fcae14..2a35ca343 100644 --- a/fe/src/components/issue-detail/IssueDetailBody.tsx +++ b/fe/src/components/issue-detail/IssueDetailBody.tsx @@ -1,7 +1,6 @@ import { Box } from '@material-ui/core'; import AuthorAvatar from 'components/common/AuthorAvatar'; import Comment from 'components/issue-detail/Comment'; -import CommentInput from 'components/common/CommentInput'; import styled from 'styled-components'; const IssueDetailBody = () => ( @@ -20,7 +19,7 @@ const IssueDetailBody = () => ( - + {/* comment input ui */} From 8ab9696e0fd8d0f22ad04bd42995b25c821695de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Autumn=20=28=EC=A1=B0=ED=99=8D=EB=B9=84=29?= <60209518+dyongdi@users.noreply.github.com> Date: Thu, 17 Jun 2021 17:01:47 +0900 Subject: [PATCH 05/42] =?UTF-8?q?[#40]=20Style:=200=EB=B2=88=EC=A7=B8=20co?= =?UTF-8?q?mment=20margin=EA=B0=92=20=EC=88=98=EC=A0=95,=20=EC=83=88=20?= =?UTF-8?q?=EC=BD=94=EB=A9=98=ED=8A=B8=20ui=20=EC=B6=94=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사이드바 컴포넌트(현재 이름 NewIssueRight)는 재사용을 위해 수정이 필요해서 일단 비워놨음 --- fe/src/components/common/CommentTextarea.tsx | 3 ++- fe/src/components/issue-detail/IssueDetailBody.tsx | 10 +++++++--- fe/src/components/new-issue/NewIssueRight.tsx | 1 - 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fe/src/components/common/CommentTextarea.tsx b/fe/src/components/common/CommentTextarea.tsx index a827833e6..1494e0151 100644 --- a/fe/src/components/common/CommentTextarea.tsx +++ b/fe/src/components/common/CommentTextarea.tsx @@ -16,6 +16,7 @@ const CommentTextarea = () => { export default CommentTextarea; const StyledCommentTextarea = styled.div` + width: 100%; position: relative; &:focus { label { @@ -52,4 +53,4 @@ const CustomTextField = styled.textarea` &:focus + label { transform: translate(-28px, -20px) scale(0.6); } -`; \ No newline at end of file +`; diff --git a/fe/src/components/issue-detail/IssueDetailBody.tsx b/fe/src/components/issue-detail/IssueDetailBody.tsx index 2a35ca343..596ab0921 100644 --- a/fe/src/components/issue-detail/IssueDetailBody.tsx +++ b/fe/src/components/issue-detail/IssueDetailBody.tsx @@ -2,6 +2,7 @@ import { Box } from '@material-ui/core'; import AuthorAvatar from 'components/common/AuthorAvatar'; import Comment from 'components/issue-detail/Comment'; import styled from 'styled-components'; +import CommentTextarea from 'components/common/CommentTextarea'; const IssueDetailBody = () => ( @@ -19,7 +20,7 @@ const IssueDetailBody = () => ( - {/* comment input ui */} + @@ -30,11 +31,14 @@ const CommentArea = styled.section` width: 70%; `; -const IssueDescription = styled.div``; +const IssueDescription = styled.div` + margin-bottom: 1.5rem; +`; const Comments = styled.ul` + all: unset; ${({ theme }) => theme.style.flexColum} - gap: 24px; + gap: 1.5rem; `; const NewCommentWrapper = styled(Box)` diff --git a/fe/src/components/new-issue/NewIssueRight.tsx b/fe/src/components/new-issue/NewIssueRight.tsx index eb6fa7931..f9b66794c 100644 --- a/fe/src/components/new-issue/NewIssueRight.tsx +++ b/fe/src/components/new-issue/NewIssueRight.tsx @@ -24,4 +24,3 @@ const StyledNewIssueRight = styled.div` height: fit-content; margin-left: 2rem; `; - From 56b4a672705856a873cd80b5376e533b698de3db Mon Sep 17 00:00:00 2001 From: Dongmin Ahn <68339352+eamon3481@users.noreply.github.com> Date: Thu, 17 Jun 2021 17:15:54 +0900 Subject: [PATCH 06/42] [#42] button & router (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#42]Feat: 버튼을 이용해서 router 링크 이동 가능하게 설정함 * [#42]Refactor: router 분리와 header 컴포넌트 고정 * [#42]Refactor: loginPage \ * [#42]Style: loginPage --- fe/src/App.tsx | 38 ++------------ fe/src/Router.tsx | 39 +++++++++++++++ fe/src/components/buttons/CreateButton.tsx | 13 ++++- .../components/buttons/GitHubLoginButton.tsx | 20 +++++++- fe/src/components/common/FilterItem.tsx | 8 ++- fe/src/components/navbar/Navbar.tsx | 8 +-- fe/src/components/navbar/NavbarButtons.tsx | 50 +++++++++++++------ fe/src/index.tsx | 1 + fe/src/pages/IssuesPage.tsx | 1 - fe/src/pages/LabelPage.tsx | 4 +- fe/src/pages/LoginPage.tsx | 8 +++ fe/src/pages/MilestoneListPage.tsx | 3 +- fe/src/pages/NewIssuePage.tsx | 21 +++----- fe/src/pages/OAuthPage.tsx | 5 +- fe/src/store.ts | 15 ++++++ fe/src/style/GlobalStyle.tsx | 1 + fe/src/types/issueType.ts | 3 ++ fe/src/utils/util.ts | 4 ++ 18 files changed, 161 insertions(+), 81 deletions(-) create mode 100644 fe/src/Router.tsx diff --git a/fe/src/App.tsx b/fe/src/App.tsx index 1c4689246..4e755741f 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -1,19 +1,14 @@ -import { Route, BrowserRouter, Switch } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import GlobalStyle from 'style/GlobalStyle'; import { theme } from 'style/theme'; -import IssuesPage from 'pages/IssuesPage'; -import LoginPage from 'pages/LoginPage'; -import OAuthPage from 'pages/OAuthPage'; + import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles'; import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles'; -import NewIssuePage from 'pages/NewIssuePage'; +import Header from 'components/header/Header'; import { RecoilRoot } from 'recoil'; import { Suspense } from 'react'; -import IssueDetailPage from 'pages/IssueDetailPage'; -import LabelPage from 'pages/LabelPage'; -import MilestoneListPage from 'pages/MilestoneListPage'; +import Router from 'Router'; const MuiTheme = unstable_createMuiStrictModeTheme(); function App() { @@ -23,31 +18,8 @@ function App() { - - - - - - - - - - - - - - - - - - - - - - - - - +
+ diff --git a/fe/src/Router.tsx b/fe/src/Router.tsx new file mode 100644 index 000000000..8976c4d44 --- /dev/null +++ b/fe/src/Router.tsx @@ -0,0 +1,39 @@ +import { Route, BrowserRouter, Switch } from 'react-router-dom'; +import IssuesPage from 'pages/IssuesPage'; +import LoginPage from 'pages/LoginPage'; +import OAuthPage from 'pages/OAuthPage'; +import NewIssuePage from 'pages/NewIssuePage'; +import LabelPage from 'pages/LabelPage'; +import MilestoneListPage from 'pages/MilestoneListPage'; + + +const Router = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Router; diff --git a/fe/src/components/buttons/CreateButton.tsx b/fe/src/components/buttons/CreateButton.tsx index 39e4785a2..c7579ce3d 100644 --- a/fe/src/components/buttons/CreateButton.tsx +++ b/fe/src/components/buttons/CreateButton.tsx @@ -1,11 +1,20 @@ import { Button } from '@material-ui/core'; import { ReactComponent as PlusIconSvg } from 'icons/pluse.svg'; +import { MouseEvent } from 'react'; import { ReactChild } from 'react'; import styled from 'styled-components'; -const CreateButton = ({ children }: { children: ReactChild }) => { +const CreateButton = ({ + children, + onClick, +}: { + children: ReactChild; + onClick: ( event : MouseEvent) => void +}) => { return ( - }>{children} + }> + {children} + ); }; diff --git a/fe/src/components/buttons/GitHubLoginButton.tsx b/fe/src/components/buttons/GitHubLoginButton.tsx index 1bbf7411f..4e3c69e7e 100644 --- a/fe/src/components/buttons/GitHubLoginButton.tsx +++ b/fe/src/components/buttons/GitHubLoginButton.tsx @@ -1,13 +1,29 @@ import { Button } from '@material-ui/core'; +import styled from 'styled-components'; const GitHubLoginButton = () => { return ( - + ); }; export default GitHubLoginButton; + + +const StyledCreateButton = styled(Button)` + background-color: ${({ theme }) => theme.color.grayscale.body}; + color: ${({ theme }) => theme.color.grayscale.offWhite}; + font-weight: ${({ theme }) => theme.fontWeight.bold2}; + width: 20rem; + height: 3rem; + border-radius: ${({ theme }) => theme.border.radius.S}; + margin-left: 1rem; + margin-top: 5rem; + &:hover { + background-color: ${({ theme }) => theme.color.grayscale.label}; + } +`; \ No newline at end of file diff --git a/fe/src/components/common/FilterItem.tsx b/fe/src/components/common/FilterItem.tsx index 87748865e..4a4bf7bff 100644 --- a/fe/src/components/common/FilterItem.tsx +++ b/fe/src/components/common/FilterItem.tsx @@ -1,14 +1,12 @@ import { Divider, MenuItem, Checkbox } from '@material-ui/core'; -import { forwardRef, MouseEvent, Ref } from 'react'; +import { MouseEvent } from 'react'; import styled from 'styled-components'; import { FilterItemPropsType } from '../../types/filterType'; import { ReactComponent as CheckOff } from 'icons/check-off-circle.svg'; import { ReactComponent as CheckOn } from 'icons/check-on-circle.svg'; -const FilterItem = ( - { filterItem, isEnd }: FilterItemPropsType -) => { +const FilterItem = ({ filterItem, isEnd }: FilterItemPropsType) => { const handleClick = (e: MouseEvent) => { - console.log(filterItem.id); + console.log(filterItem.id); }; return ( <> diff --git a/fe/src/components/navbar/Navbar.tsx b/fe/src/components/navbar/Navbar.tsx index 44b890859..daf8e75e7 100644 --- a/fe/src/components/navbar/Navbar.tsx +++ b/fe/src/components/navbar/Navbar.tsx @@ -2,19 +2,19 @@ import { InputBase } from '@material-ui/core'; import IssueFilter from 'components/common/IssueFilter'; import styled from 'styled-components'; import { ReactComponent as SearchIconSvg } from 'icons/search.svg'; -import NavbarButtons from './NavbarButtons'; +import NavbarButtons from './NavbarButtons'; const Navbar = () => { - return ( + return ( ); diff --git a/fe/src/components/navbar/NavbarButtons.tsx b/fe/src/components/navbar/NavbarButtons.tsx index 11b390817..85a39cae5 100644 --- a/fe/src/components/navbar/NavbarButtons.tsx +++ b/fe/src/components/navbar/NavbarButtons.tsx @@ -2,23 +2,41 @@ import { Button, Divider } from '@material-ui/core'; import { ReactComponent as LabelIconSvg } from 'icons/label.svg'; import { ReactComponent as MilestoneIconSvg } from 'icons/openMilestone.svg'; import CreateButton from 'components/buttons/CreateButton'; +import { NavType } from 'types/issueType'; import styled from 'styled-components'; +import { getNavButtonTitle } from 'utils/util'; +import { useRecoilValue } from 'recoil'; +import { totalCountOfLabels, totalCountOfMilestone } from 'store'; +import { Link, useHistory } from 'react-router-dom'; +import { MouseEvent } from 'react'; -const NavbarButtons = ({ buttonType }: { buttonType: string }) => { +const NavbarButtons = ({ type }: { type: NavType }) => { + const MilestoneCnt = useRecoilValue(totalCountOfMilestone); + const LabelCnt = useRecoilValue(totalCountOfLabels); + const history = useHistory(); + const clickHandler = (event: MouseEvent) => { + if (type === 'All') history.push('/issues/new-issue'); + }; return ( - }> -
레이블
-
(0)
-
+ + }> +
레이블
+
({LabelCnt})
+
+ - }> -
마일스톤
-
(0)
-
+ + }> +
마일스톤
+
({MilestoneCnt})
+
+
- {buttonType} + + {getNavButtonTitle(type)} +
); }; @@ -42,25 +60,29 @@ const LabelIcon = styled(LabelIconSvg)` const MilestoneIcon = styled(MilestoneIconSvg)``; -const LabelButton = styled(Button)` +const LabelButton = styled(Button)<{ buttontype: NavType }>` width: 10rem; height: 2.5rem; border-radius: 0.7rem 0 0 0.7rem; color: ${({ theme }) => theme.color.grayscale.label}; font-weight: ${({ theme }) => theme.fontWeight.bold2}; - + background-color: ${({ buttontype, theme }) => { + if (buttontype === 'Label') return theme.color.grayscale.line; + }}; .button-text { margin-right: 0.5rem; } `; -const MilestoneButton = styled(Button)` +const MilestoneButton = styled(Button)<{ buttontype: NavType }>` width: 10rem; height: 2.5rem; border-radius: 0 0.7rem 0.7rem 0; color: ${({ theme }) => theme.color.grayscale.label}; font-weight: ${({ theme }) => theme.fontWeight.bold2}; - + background-color: ${({ buttontype, theme }) => { + if (buttontype === 'Milestone') return theme.color.grayscale.line; + }}; .button-text { margin-right: 0.5rem; } diff --git a/fe/src/index.tsx b/fe/src/index.tsx index 5ac0d7b20..7ec258536 100644 --- a/fe/src/index.tsx +++ b/fe/src/index.tsx @@ -12,3 +12,4 @@ ReactDOM.render( , document.getElementById('root') ); + \ No newline at end of file diff --git a/fe/src/pages/IssuesPage.tsx b/fe/src/pages/IssuesPage.tsx index a7e688b02..4343aa6d6 100644 --- a/fe/src/pages/IssuesPage.tsx +++ b/fe/src/pages/IssuesPage.tsx @@ -5,7 +5,6 @@ import Navbar from 'components/navbar/Navbar'; const IssuesPage = () => { return ( <> -
diff --git a/fe/src/pages/LabelPage.tsx b/fe/src/pages/LabelPage.tsx index dadf1106c..727f82987 100644 --- a/fe/src/pages/LabelPage.tsx +++ b/fe/src/pages/LabelPage.tsx @@ -1,4 +1,3 @@ - import Header from 'components/header/Header'; import Labels from 'components/labels/Labels'; import NavbarButtons from 'components/navbar/NavbarButtons'; @@ -7,8 +6,7 @@ import styled from 'styled-components'; const LabelPage = () => { return ( <> -
- + diff --git a/fe/src/pages/LoginPage.tsx b/fe/src/pages/LoginPage.tsx index 78d36fa26..22956e872 100644 --- a/fe/src/pages/LoginPage.tsx +++ b/fe/src/pages/LoginPage.tsx @@ -12,9 +12,17 @@ const LoginPage = () => { }; const LoginPageContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + background-color: white; + align-items: center; display: flex; flex-direction: column; align-items: center; + justify-content: center; `; export default LoginPage; diff --git a/fe/src/pages/MilestoneListPage.tsx b/fe/src/pages/MilestoneListPage.tsx index 0fa353551..3c88a071d 100644 --- a/fe/src/pages/MilestoneListPage.tsx +++ b/fe/src/pages/MilestoneListPage.tsx @@ -7,8 +7,7 @@ import styled from 'styled-components'; const MilestoneListPage = () => { return ( <> -
- + diff --git a/fe/src/pages/NewIssuePage.tsx b/fe/src/pages/NewIssuePage.tsx index c4e46dad2..5c05b3ab7 100644 --- a/fe/src/pages/NewIssuePage.tsx +++ b/fe/src/pages/NewIssuePage.tsx @@ -1,14 +1,12 @@ import { Divider } from '@material-ui/core'; import CustomButton from 'components/buttons/CustomButton'; -import Header from 'components/header/Header'; import NewIssueLeft from 'components/new-issue/NewIssueLeft'; import NewIssueRight from 'components/new-issue/NewIssueRight'; import styled from 'styled-components'; const NewIssuePage = () => { return ( - -
+ <> 새로운 이슈 작성 @@ -17,23 +15,18 @@ const NewIssuePage = () => { - 완료 + 완료 - - - ); + + ); }; export default NewIssuePage; const StyledDiv = styled.div` -display: flex; -margin: 1.2rem; -justify-content: flex-end; -` - -const StlyedNewIssuePage = styled.div` - ${({ theme }) => theme.style.flexColum} + display: flex; + margin: 1.2rem; + justify-content: flex-end; `; const NewIssueTitle = styled.span` diff --git a/fe/src/pages/OAuthPage.tsx b/fe/src/pages/OAuthPage.tsx index 2d627431c..2e951605c 100644 --- a/fe/src/pages/OAuthPage.tsx +++ b/fe/src/pages/OAuthPage.tsx @@ -31,15 +31,18 @@ const OAuthPage = () => { }; const Div = styled.div` + background-color: white; display: flex; justify-content: center; align-items: center; position: fixed; + top:0; width: 100%; - height: 70vh; + height: 100vh; font-size: 3rem; span { + margin-top: 5rem; margin-left: 1rem; } `; diff --git a/fe/src/store.ts b/fe/src/store.ts index 339d161dc..56710a558 100644 --- a/fe/src/store.ts +++ b/fe/src/store.ts @@ -25,12 +25,20 @@ type UserDataType = { avatar_url: string | undefined; }; +export const totalCountOfLabels = selector({ + key: 'labellength', + get: ({ get }) => { + return get(labelQuery).length; + }, +}); + export const labelQuery = selector({ key: 'labelQuery', get: async () => { const { data } = await axios.get( `${process.env.REACT_APP_API_URL}/api/labels` ); + return data.map((labelItem: LabelDataType) => ({ id: labelItem.id, description: labelItem.title, @@ -39,6 +47,13 @@ export const labelQuery = selector({ }, }); +export const totalCountOfMilestone = selector({ + key: 'milestonelength', + get: ({ get }) => { + return get(milestoneQuery).length; + }, +}); + export const milestoneQuery = selector({ key: 'milestoneQuery', get: async () => { diff --git a/fe/src/style/GlobalStyle.tsx b/fe/src/style/GlobalStyle.tsx index 27178dd5e..ea537b9c1 100644 --- a/fe/src/style/GlobalStyle.tsx +++ b/fe/src/style/GlobalStyle.tsx @@ -23,6 +23,7 @@ const GlobalStyle = createGlobalStyle` } a{ color: inherit; + text-decoration: none; } body { padding: 0 80px; diff --git a/fe/src/types/issueType.ts b/fe/src/types/issueType.ts index 0cb11aace..f75f5e898 100644 --- a/fe/src/types/issueType.ts +++ b/fe/src/types/issueType.ts @@ -1,3 +1,6 @@ + +export type NavType = 'All' | 'Milestone' | 'Label' + export interface IssueItemType extends IssueItemLeftPropsType { isOpen: boolean; author: authorType; diff --git a/fe/src/utils/util.ts b/fe/src/utils/util.ts index fd8059677..e3b7db540 100644 --- a/fe/src/utils/util.ts +++ b/fe/src/utils/util.ts @@ -1,4 +1,5 @@ import { ParsedQs } from 'qs'; +import { NavType } from 'types/issueType'; import { titleType } from 'types/newIssueType'; export const getUrl = { @@ -14,3 +15,6 @@ export const getTitle = (type: titleType) => assigneeList: '담당자', }[type]); + + +export const getNavButtonTitle = (type: NavType) => type === 'All' ? '이슈 작성' : '추가' \ No newline at end of file From 2899b062e15085553ffa3be80cb51fad4a2fa64d Mon Sep 17 00:00:00 2001 From: Hongbi Date: Thu, 17 Jun 2021 17:25:07 +0900 Subject: [PATCH 07/42] =?UTF-8?q?Fix:=20=EC=9D=B4=EC=8A=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EC=B6=94=EA=B0=80,=20flexColum=20->=20flexColumn?= =?UTF-8?q?=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/App.tsx | 2 +- fe/src/Router.tsx | 7 ++++--- fe/src/components/Issues/IssueItemLeft.tsx | 5 ++--- fe/src/components/common/Table.tsx | 3 +-- fe/src/components/issue-detail/Comment.tsx | 2 +- .../components/issue-detail/IssueDetailBody.tsx | 2 +- .../components/milestones/MilestonesItemLeft.tsx | 3 +-- .../milestones/MilestonesItemRight.tsx | 16 +++++++--------- fe/src/components/new-issue/NewIssueLeft.tsx | 2 +- fe/src/components/new-issue/NewIssueRight.tsx | 2 +- fe/src/pages/IssueDetailPage.tsx | 2 -- fe/src/style/theme.ts | 4 ++-- 12 files changed, 22 insertions(+), 28 deletions(-) diff --git a/fe/src/App.tsx b/fe/src/App.tsx index 4e755741f..ab1f4e918 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -18,7 +18,7 @@ function App() { -
+
diff --git a/fe/src/Router.tsx b/fe/src/Router.tsx index 8976c4d44..712b3ccdc 100644 --- a/fe/src/Router.tsx +++ b/fe/src/Router.tsx @@ -5,11 +5,10 @@ import OAuthPage from 'pages/OAuthPage'; import NewIssuePage from 'pages/NewIssuePage'; import LabelPage from 'pages/LabelPage'; import MilestoneListPage from 'pages/MilestoneListPage'; - +import IssueDetailPage from 'pages/IssueDetailPage'; const Router = () => { return ( - <> @@ -30,9 +29,11 @@ const Router = () => { + + + - ); }; diff --git a/fe/src/components/Issues/IssueItemLeft.tsx b/fe/src/components/Issues/IssueItemLeft.tsx index 9c6731bf5..8ab3ccab1 100644 --- a/fe/src/components/Issues/IssueItemLeft.tsx +++ b/fe/src/components/Issues/IssueItemLeft.tsx @@ -22,7 +22,7 @@ const IssueItemLeft = ({ {title} {labeList.length - ? labeList.map((label,idx) =>