({});
+
+ const handleClickCheckBox = (e: React.MouseEvent) => {
+ const target = e.target as HTMLInputElement;
+ const checkNumber = target.id;
+ if (checkNumber === '') return;
+
+ const toggleBoolean = isChecked[checkNumber] === true ? false : true;
+ if (toggleBoolean)
+ setQuery({
+ closed: null,
+ author: null,
+ assignee: checkNumber,
+ label: null,
+ milestone: null,
+ });
+
+ setChecked({
+ ...isChecked,
+ [checkNumber]: toggleBoolean,
+ });
+ };
return (
-
+ fetchOnMouseEnter(assigneeList, setAssigneeList)}>
+
+
);
}
export default AssigneeFilter;
+
+const ModalWrap = styled.div`
+ width: 100%;
+ height: 100%;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gr_inputBackground};
+ }
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableHeader/AuthorFilter.tsx b/FE/issue-tracker/src/components/issues/tableHeader/AuthorFilter.tsx
index 4841a2e7e..c4a6fd543 100644
--- a/FE/issue-tracker/src/components/issues/tableHeader/AuthorFilter.tsx
+++ b/FE/issue-tracker/src/components/issues/tableHeader/AuthorFilter.tsx
@@ -1,3 +1,7 @@
+import { useState } from 'react';
+import { useRecoilState } from 'recoil';
+import styled from 'styled-components';
+
import {
Menu,
MenuButton,
@@ -6,36 +10,94 @@ import {
Button,
Checkbox,
} from '@chakra-ui/react';
-
-import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
-import MenuTitle from '@components/common/MenuTitle';
import { checkBoxStyle, menuItemStyle } from '@styles/chakraStyle';
+import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
import { menuBtnStyle } from './style';
+import { querySet } from '@store/atoms/issueList';
+import { authorFilterList } from '@store/atoms/issueFilter';
+import { fetchOnMouseEnter } from '@utils/fetchOnEnter';
+
+import MenuTitle from '@components/common/MenuTitle';
+import type { CheckBoxs } from './MilestoneFilter';
+
function AuthorFilter() {
+ const [authorList, setAuthorList] = useRecoilState(authorFilterList);
+ const { data, errorMsg } = authorList;
+ const [query, setQuery] = useRecoilState(querySet);
+ const [isChecked, setChecked] = useState({});
+
+ const handleClickCheckBox = (e: React.MouseEvent) => {
+ const target = e.target as HTMLInputElement;
+ const checkNumber = target.id;
+ if (checkNumber === '') return;
+
+ const toggleBoolean = isChecked[checkNumber] === true ? false : true;
+ if (toggleBoolean)
+ setQuery({
+ closed: null,
+ author: checkNumber,
+ assignee: null,
+ label: null,
+ milestone: null,
+ });
+
+ setChecked({
+ ...isChecked,
+ [checkNumber]: toggleBoolean,
+ });
+ };
+
return (
-
+ fetchOnMouseEnter(authorList, setAuthorList)}>
+
+
);
}
export default AuthorFilter;
+
+const ModalWrap = styled.div`
+ width: 100%;
+ height: 100%;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gr_inputBackground};
+ }
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableHeader/LabelFilter.tsx b/FE/issue-tracker/src/components/issues/tableHeader/LabelFilter.tsx
index 235165dea..4ef9cfe85 100644
--- a/FE/issue-tracker/src/components/issues/tableHeader/LabelFilter.tsx
+++ b/FE/issue-tracker/src/components/issues/tableHeader/LabelFilter.tsx
@@ -1,3 +1,7 @@
+import { useState } from 'react';
+import { useRecoilState } from 'recoil';
+import styled from 'styled-components';
+
import {
Menu,
MenuButton,
@@ -6,40 +10,87 @@ import {
Button,
Checkbox,
} from '@chakra-ui/react';
-
-import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
-import MenuTitle from '@components/common/MenuTitle';
import { checkBoxStyle, menuItemStyle } from '@styles/chakraStyle';
+import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
import { menuBtnStyle } from './style';
+import { querySet } from '@store/atoms/issueList';
+import { labelFilterList } from '@store/atoms/issueFilter';
+import { fetchOnMouseEnter } from '@utils/fetchOnEnter';
+
+import MenuTitle from '@components/common/MenuTitle';
+import type { CheckBoxs } from './MilestoneFilter';
+
function LabelFilter() {
+ const [labelList, setLabelList] = useRecoilState(labelFilterList);
+ const [query, setQuery] = useRecoilState(querySet);
+
+ const { data } = labelList;
+ const [isChecked, setChecked] = useState({});
+
+ const handleClickCheckBox = (e: React.MouseEvent) => {
+ const target = e.target as HTMLInputElement;
+ const checkNumber = target.id;
+ if (checkNumber === '') return;
+
+ const toggleBoolean = isChecked[checkNumber] === true ? false : true;
+ if (toggleBoolean)
+ setQuery({
+ closed: null,
+ author: null,
+ assignee: null,
+ label: Number(checkNumber),
+ milestone: null,
+ });
+
+ setChecked({
+ ...isChecked,
+ [checkNumber]: toggleBoolean,
+ });
+ };
+
return (
-
+ fetchOnMouseEnter(labelList, setLabelList)}>
+
+
);
}
export default LabelFilter;
+
+const ModalWrap = styled.div`
+ width: 100%;
+ height: 100%;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gr_inputBackground};
+ }
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableHeader/MilestoneFilter.tsx b/FE/issue-tracker/src/components/issues/tableHeader/MilestoneFilter.tsx
index 4ae494856..e066febdc 100644
--- a/FE/issue-tracker/src/components/issues/tableHeader/MilestoneFilter.tsx
+++ b/FE/issue-tracker/src/components/issues/tableHeader/MilestoneFilter.tsx
@@ -1,3 +1,7 @@
+import { useState } from 'react';
+import { useRecoilState } from 'recoil';
+import styled from 'styled-components';
+
import {
Menu,
MenuButton,
@@ -6,36 +10,88 @@ import {
Button,
Checkbox,
} from '@chakra-ui/react';
-
-import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
-import MenuTitle from '@components/common/MenuTitle';
import { checkBoxStyle, menuItemStyle } from '@styles/chakraStyle';
+import { ReactComponent as DropDownIcon } from '@assets/dropDown.svg';
import { menuBtnStyle } from './style';
+import { querySet } from '@store/atoms/issueList';
+import { milestoneFilterList } from '@store/atoms/issueFilter';
+
+import MenuTitle from '@components/common/MenuTitle';
+import { fetchOnMouseEnter } from '@utils/fetchOnEnter';
+
function MilestoneFilter() {
+ const [milestoneList, setMilestoneList] = useRecoilState(milestoneFilterList);
+ const [query, setQuery] = useRecoilState(querySet);
+ const { data } = milestoneList;
+ const [isChecked, setChecked] = useState({});
+
+ const handleClickCheckBox = (e: React.MouseEvent) => {
+ const target = e.target as HTMLInputElement;
+ const checkNumber = target.id;
+ if (checkNumber === '') return;
+
+ const toggleBoolean = isChecked[checkNumber] === true ? false : true;
+ if (toggleBoolean)
+ setQuery({
+ ...query,
+ milestone: Number(checkNumber),
+ });
+
+ setChecked({
+ ...isChecked,
+ [checkNumber]: toggleBoolean,
+ });
+ };
+
return (
-
+ fetchOnMouseEnter(milestoneList, setMilestoneList)}
+ >
+
+
);
}
export default MilestoneFilter;
+
+export type CheckBoxs = {
+ [id: string]: boolean;
+};
+
+const ModalWrap = styled.div`
+ width: 100%;
+ height: 100%;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gr_inputBackground};
+ }
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableHeader/TableHeadLeft.tsx b/FE/issue-tracker/src/components/issues/tableHeader/TableHeadLeft.tsx
new file mode 100644
index 000000000..797a33194
--- /dev/null
+++ b/FE/issue-tracker/src/components/issues/tableHeader/TableHeadLeft.tsx
@@ -0,0 +1,87 @@
+import { useCallback } from 'react';
+import { useRecoilState, useRecoilValueLoadable } from 'recoil';
+import styled from 'styled-components';
+
+import { issueCounts, querySet } from '@store/atoms/issueList';
+
+function TableHeadLeft() {
+ const [query, setQuery] = useRecoilState(querySet);
+ const { state, contents } = useRecoilValueLoadable(issueCounts);
+ const { closed } = query;
+
+ const handleClickTab = useCallback(
+ (e: React.MouseEvent): void => {
+ const target = e.target as HTMLLIElement;
+ const targetState = target.dataset.state;
+ if (targetState === 'open') setQuery({ ...query, closed: 'false' });
+ if (targetState === 'close') setQuery({ ...query, closed: 'true' });
+ },
+ [query, setQuery]
+ );
+
+ return (
+
+
+
+
+ {state === 'loading' && `열린 이슈 (0)`}
+ {state === 'hasValue' && `열린 이슈 (${contents.openIssueCount})`}
+ {state === 'hasError' && `열린 이슈 (??)`}
+
+
+ {state === 'loading' && `닫힌 이슈 (0)`}
+ {state === 'hasValue' && `닫힌 이슈 (${contents.closeIssueCount})`}
+ {state === 'hasError' && `닫힌 이슈 (??)`}
+
+
+
+ );
+}
+
+export default TableHeadLeft;
+
+type issueTab = {
+ closed: string | null | undefined;
+};
+
+const HeaderLeft = styled.div`
+ display: flex;
+ align-items: center;
+ width: 848px;
+
+ input[type='checkbox'] {
+ width: 16px;
+ height: 16px;
+ }
+`;
+
+const IssueTab = styled.ul`
+ display: flex;
+`;
+
+const IssueTabList = styled.li`
+ ${({ theme }) => theme.flexCenter}
+ margin-left: 24px;
+ width: 120px;
+ height: 28px;
+ cursor: pointer;
+ font-weight: ${({ theme }) => theme.fontWeights.bold};
+`;
+
+const IssueOpenTab = styled(IssueTabList)`
+ color: ${({ theme, closed }) =>
+ closed === 'true' ? theme.colors.gr_label : theme.colors.gr_titleActive};
+`;
+
+const IssueCloseTab = styled(IssueTabList)`
+ color: ${({ theme, closed }) =>
+ closed === 'true' ? theme.colors.gr_titleActive : theme.colors.gr_label};
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableHeader/TableHeader.tsx b/FE/issue-tracker/src/components/issues/tableHeader/TableHeader.tsx
index 9b1f53313..0b8fde079 100644
--- a/FE/issue-tracker/src/components/issues/tableHeader/TableHeader.tsx
+++ b/FE/issue-tracker/src/components/issues/tableHeader/TableHeader.tsx
@@ -4,23 +4,18 @@ import AssigneeFilter from './AssigneeFilter';
import LabelFilter from './LabelFilter';
import MilestoneFilter from './MilestoneFilter';
import AuthorFilter from './AuthorFilter';
+import TableHeadLeft from './TableHeadLeft';
function TableHeader() {
return (
-
-
-
- 열린 이슈(2)
- 닫힌 이슈(2)
-
-
+
-
+
-
+
@@ -60,14 +55,3 @@ const HeaderRight = styled.div`
const FilterLists = styled.div`
display: flex;
`;
-
-const IssueTab = styled.ul`
- display: flex;
-
- li {
- ${({ theme }) => theme.flexCenter}
- margin-left: 24px;
- width: 100px;
- height: 28px;
- }
-`;
diff --git a/FE/issue-tracker/src/components/issues/tableMain/ErrorIssueList.tsx b/FE/issue-tracker/src/components/issues/tableMain/ErrorIssueList.tsx
new file mode 100644
index 000000000..52126998f
--- /dev/null
+++ b/FE/issue-tracker/src/components/issues/tableMain/ErrorIssueList.tsx
@@ -0,0 +1,46 @@
+import { useState } from 'react';
+import styled from 'styled-components';
+
+type errorMsg = {
+ children: string;
+};
+
+function ErrorIssueList({ children }: errorMsg) {
+ const [time, setTime] = useState(3000);
+ const text = `이 창은 ${time / 1000}초 후에 자동으로 사라집니다.`;
+
+ setTimeout(() => setTime(0), time);
+ return (
+
+ {children}
+ {text}
+
+ );
+}
+
+export default ErrorIssueList;
+
+type seconds = {
+ time: number;
+};
+
+const ErrorWrap = styled.div`
+ position: absolute;
+ display: ${({ time }) => (time ? 'flex' : 'none')};
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: #313030dc;
+ top: 50%;
+ left: 50%;
+ width: 50%;
+ height: 200px;
+ transform: translate3d(-50%, -50%, 0);
+ color: ${({ theme }) => theme.colors.gr_offWhite};
+ font-size: ${({ theme }) => theme.fontSizes.xl};
+ border-radius: 16px;
+
+ span {
+ font-size: ${({ theme }) => theme.fontSizes.xs};
+ }
+`;
diff --git a/FE/issue-tracker/src/components/issues/tableMain/Issue.tsx b/FE/issue-tracker/src/components/issues/tableMain/Issue.tsx
index 157e82f17..69c9649e1 100644
--- a/FE/issue-tracker/src/components/issues/tableMain/Issue.tsx
+++ b/FE/issue-tracker/src/components/issues/tableMain/Issue.tsx
@@ -1,43 +1,93 @@
+import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Avatar } from '@chakra-ui/avatar';
+import { ReactComponent as MilestoneIcon } from '@assets/milestone.svg';
+
import Label from '@components/common/Label';
+import type { IssueInfo } from './IssueList';
+
+import {
+ checkIfDayPassedFromCreation,
+ getCreatedTime,
+ getRenderingText,
+ getTimeGapFromCreation,
+ getTime,
+ getTotalMinutesBetweenGap,
+} from '@utils/renderTimeText';
+import pipe from '@utils/pipe';
+
+type Props = {
+ info: IssueInfo;
+};
-function Issue() {
+function Issue({ info }: Props) {
+ const {
+ id,
+ title,
+ author,
+ assignees,
+ label_list,
+ issue_number,
+ created_time,
+ milestone_title,
+ } = info;
const defaultAvatarPosition = '32px';
+ const { user_id, name } = author;
+ const currentTime = new Date().getTime();
+ const noticeTimePassed = pipe(
+ getCreatedTime,
+ getTimeGapFromCreation(currentTime),
+ getTotalMinutesBetweenGap,
+ checkIfDayPassedFromCreation,
+ getTime,
+ getRenderingText
+ )(created_time);
+
+ const linkPath = {
+ pathname: `/issues/detail/${id}`,
+ };
return (
-
+
- 이것은 제목입니다.
-
+
+ {title}
+
+ {label_list.map(({ id, title, color_code }) => (
+
+ ))}
- #이슈번호
- 작성자 및 타임스탬프
- 마일스톤
+ #{issue_number}
+
+ {name} 및 {noticeTimePassed}
+
+
+
+ {milestone_title}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {assignees.map((assignee) => {
+ if (assignee === null) return null;
+ const { user_id, avatar_url } = assignee;
+ return (
+
+
+
+ );
+ })}
);
@@ -108,9 +158,20 @@ const CheckBox = styled.input`
const Description = styled.div`
margin: 8px 0 0 40px;
display: flex;
+ justify-content: flex-start;
+ align-items: center;
+
+ div {
+ display: flex;
+ align-items: center;
+ }
span {
padding-right: 16px;
color: ${({ theme }) => theme.colors.gr_label};
+
+ &:last-child {
+ padding-left: 8px;
+ }
}
`;
diff --git a/FE/issue-tracker/src/components/issues/tableMain/IssueList.tsx b/FE/issue-tracker/src/components/issues/tableMain/IssueList.tsx
new file mode 100644
index 000000000..7f23e166b
--- /dev/null
+++ b/FE/issue-tracker/src/components/issues/tableMain/IssueList.tsx
@@ -0,0 +1,77 @@
+import { useEffect } from 'react';
+import {
+ useRecoilValue,
+ useRecoilValueLoadable,
+ useSetRecoilState,
+} from 'recoil';
+
+import {
+ formerDataKey,
+ queryString,
+ wholeIssueLists,
+} from '@store/atoms/issueList';
+
+import { IssueSkeleton } from '@components/common/Skeleton';
+import Issue from './Issue';
+import ErrorIssueList from './ErrorIssueList';
+import NoIssue from './NoIssue';
+
+function IssueList() {
+ const query = useRecoilValue(queryString);
+ const { state, contents } = useRecoilValueLoadable(wholeIssueLists);
+ const setReRenderKeyUpdate = useSetRecoilState(formerDataKey);
+ const renderNoIssue = (contents: any): JSX.Element | undefined => {
+ if (typeof contents === 'string' || contents.length === 0) {
+ return ;
+ }
+ };
+
+ useEffect(() => {
+ const currentQuery = window.location.search;
+ if (`?${query}` === currentQuery) return;
+
+ window.history.pushState({ query }, query, `?${query}`);
+ setReRenderKeyUpdate((num) => num + 1);
+ }, [query, setReRenderKeyUpdate]);
+
+ return (
+ <>
+ {state === 'hasError' && {contents}}
+ {state === 'loading' && }
+ {state === 'hasValue' &&
+ contents.map((issueInfo: IssueInfo) => (
+
+ ))}
+ {renderNoIssue(contents)}
+ >
+ );
+}
+
+export default IssueList;
+
+export type IssueInfo = {
+ id: number;
+ title: string;
+ description: string;
+ author: Person;
+ assignees: Person[];
+ label_list: Label[];
+ issue_number: number;
+ created_time: string;
+ milestone_title: string;
+};
+
+type Label = {
+ id: number;
+ title: string;
+ color_code: string;
+};
+
+type Person = {
+ user_id: number;
+ name: string;
+ avatar_url: string;
+};
+function setReRenderKeyUpdat(arg0: (num: any) => any) {
+ throw new Error('Function not implemented.');
+}
diff --git a/FE/issue-tracker/src/components/issues/tableMain/IssueTable.tsx b/FE/issue-tracker/src/components/issues/tableMain/IssueTable.tsx
index c22a89a23..f381c0a5e 100644
--- a/FE/issue-tracker/src/components/issues/tableMain/IssueTable.tsx
+++ b/FE/issue-tracker/src/components/issues/tableMain/IssueTable.tsx
@@ -1,13 +1,13 @@
import styled from 'styled-components';
import TableHeader from '../tableHeader/TableHeader';
-import Issue from './Issue';
+import IssueList from './IssueList';
function IssueTable() {
return (
-
+
);
}
diff --git a/FE/issue-tracker/src/components/issues/tableMain/NoIssue.tsx b/FE/issue-tracker/src/components/issues/tableMain/NoIssue.tsx
new file mode 100644
index 000000000..a63585b3e
--- /dev/null
+++ b/FE/issue-tracker/src/components/issues/tableMain/NoIssue.tsx
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+
+interface Prop {
+ isSearched: boolean;
+}
+
+function NoIssue({ isSearched }: Prop) {
+ const issueWithSearch = '검색과 일치하는 결과가 없습니다.';
+ const issueNone = '등록된 이슈가 없습니다.';
+ return (
+
+ {isSearched ? issueWithSearch : issueNone}
+
+ );
+}
+
+export default NoIssue;
+
+const NoIssueWrap = styled.div`
+ height: 100px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: ${({ theme }) => theme.colors.gr_offWhite};
+`;
diff --git a/FE/issue-tracker/src/components/labels/table/LabelCell.tsx b/FE/issue-tracker/src/components/labels/table/LabelCell.tsx
index beaf6b644..993bca99f 100644
--- a/FE/issue-tracker/src/components/labels/table/LabelCell.tsx
+++ b/FE/issue-tracker/src/components/labels/table/LabelCell.tsx
@@ -1,4 +1,6 @@
+import { useState } from 'react';
import styled from 'styled-components';
+
import Label from '@components/common/Label';
import EditMiniButton from '@components/common/EditMiniButton';
import DeleteMiniButton from '@components/common/DeleteMiniButton';
@@ -8,6 +10,8 @@ interface Props {
}
function LabelCell({ isLastItemStyle }: Props) {
+ const [isDisabled, setIsDisabled] = useState(true);
+
return (
@@ -18,7 +22,7 @@ function LabelCell({ isLastItemStyle }: Props) {
- 편집
+ 편집
삭제
diff --git a/FE/issue-tracker/src/components/login/LoginMain.tsx b/FE/issue-tracker/src/components/login/LoginMain.tsx
index 86bbbb2b7..7ce1f66f0 100644
--- a/FE/issue-tracker/src/components/login/LoginMain.tsx
+++ b/FE/issue-tracker/src/components/login/LoginMain.tsx
@@ -1,31 +1,90 @@
-import { Link } from 'react-router-dom';
+import React, { useState, useEffect } from 'react';
+import { Link, useHistory } from 'react-router-dom';
import styled from 'styled-components';
+import queryString from 'query-string';
+import { useRecoilState, useSetRecoilState } from 'recoil';
import { Button } from '@chakra-ui/button';
import { Stack } from '@chakra-ui/layout';
import { Input } from '@chakra-ui/input';
+import { LinkBox, LinkOverlay } from '@chakra-ui/react';
import { ReactComponent as LogoLarge } from '@assets/LogotypeLarge.svg';
-import { gitLoginStyle, idLoginStyle, inputStyle } from './style';
+import { LOGIN_URL } from '@const/var';
+import fetchToken from './fetchToken';
+import { isLoginState, loginInfoState } from '@store/atoms/login';
+import {
+ gitLoginStyle,
+ activeLoginStyle,
+ inactiveLoginStyle,
+ inputStyle,
+} from './style';
function LoginMain() {
+ const [isLoginActive, setIsActiveLogin] = useState(false);
+ const [isInputtedID, setIsInputtedID] = useState(false);
+ const [isInputtedPW, setIsInputtedPW] = useState(false);
+ const [password, setPassword] = useState('');
+ const history = useHistory();
+
+ const handleChangeID = (e: React.ChangeEvent) => {
+ const target = e.target as HTMLInputElement;
+ if (!isInputtedID && target.value.length > 0) setIsInputtedID(true);
+ else if (isInputtedID && target.value.length === 0) setIsInputtedID(false);
+ };
+
+ const handleChangePW = (e: React.ChangeEvent) => {
+ const target = e.target as HTMLInputElement;
+ target.value.length > 0 ? setPassword(target.value) : setPassword('');
+ if (!isInputtedPW && target.value.length > 0) setIsInputtedPW(true);
+ else if (isInputtedPW && target.value.length === 0) setIsInputtedPW(false);
+ };
+
+ useEffect(() => {
+ if (isInputtedID && isInputtedPW) setIsActiveLogin(true);
+ else setIsActiveLogin(false);
+ }, [isInputtedID, isInputtedPW]);
+
+ useEffect(() => {
+ const { code } = queryString.parse(window.location.search);
+ if (!code) return;
+ else if (typeof code === 'string') {
+ fetchToken({ code, history });
+ }
+ }, []);
+
return (
-
+
+
+
or
-
-
+
+
-
+ {isLoginActive ? (
+
+ ) : (
+
+ )}
회원가입
);
}
+export default LoginMain;
+
const MainWrap = styled.div`
display: flex;
flex-direction: column;
@@ -50,5 +109,3 @@ const Register = styled.button`
font-size: ${({ theme }) => theme.fontSizes.xs};
font-weight: ${({ theme }) => theme.fontWeights.bold};
`;
-
-export default LoginMain;
diff --git a/FE/issue-tracker/src/components/login/fetchToken.ts b/FE/issue-tracker/src/components/login/fetchToken.ts
new file mode 100644
index 000000000..5b8c8a0a2
--- /dev/null
+++ b/FE/issue-tracker/src/components/login/fetchToken.ts
@@ -0,0 +1,39 @@
+import jwt_decode from 'jwt-decode';
+import { TOKEN_URL } from '@const/var';
+
+type decodedType = {
+ avatar_url: string;
+ name: string;
+ id: number;
+ iss: string;
+};
+
+const getDecodedOauthToken = (jwt: string) => {
+ const decoded: decodedType = jwt_decode(jwt);
+ const loginInfo = {
+ avatar_url: decoded.avatar_url,
+ name: decoded.name,
+ id: decoded.id,
+ };
+ return loginInfo;
+};
+
+type Arg = {
+ code: string;
+ history: any;
+};
+
+const fetchToken = async ({ code, history }: Arg) => {
+ try {
+ const response = await fetch(TOKEN_URL + code);
+ const { jwt } = await response.json();
+ const decodedOauthToken = getDecodedOauthToken(jwt);
+ localStorage.setItem('oauth_login_token', jwt);
+ localStorage.setItem('login_info', JSON.stringify(decodedOauthToken));
+ history.push('/issues');
+ } catch (error) {
+ console.error('getAccessToken Error');
+ }
+};
+
+export default fetchToken;
diff --git a/FE/issue-tracker/src/components/login/queryString.d.ts b/FE/issue-tracker/src/components/login/queryString.d.ts
new file mode 100644
index 000000000..46dbdd1ad
--- /dev/null
+++ b/FE/issue-tracker/src/components/login/queryString.d.ts
@@ -0,0 +1 @@
+declare module 'query-string';
diff --git a/FE/issue-tracker/src/components/login/style.ts b/FE/issue-tracker/src/components/login/style.ts
index 6a9d04789..316a7335c 100644
--- a/FE/issue-tracker/src/components/login/style.ts
+++ b/FE/issue-tracker/src/components/login/style.ts
@@ -13,15 +13,21 @@ const gitLoginStyle = {
colorScheme: 'blackAlpha',
};
-const idLoginStyle = {
+const activeLoginStyle = {
...loginBtnStyle,
background: 'bl_initial',
colorScheme: 'blue',
};
+const inactiveLoginStyle = {
+ ...activeLoginStyle,
+ opacity: '.4',
+ isDisabled: true,
+};
+
const inputStyle = {
width: '340px',
variant: 'filled',
};
-export { gitLoginStyle, idLoginStyle, inputStyle };
+export { gitLoginStyle, activeLoginStyle, inactiveLoginStyle, inputStyle };
diff --git a/FE/issue-tracker/src/components/milestones/table/MilestoneCell.tsx b/FE/issue-tracker/src/components/milestones/table/MilestoneCell.tsx
index a4099f2bc..c171f4899 100644
--- a/FE/issue-tracker/src/components/milestones/table/MilestoneCell.tsx
+++ b/FE/issue-tracker/src/components/milestones/table/MilestoneCell.tsx
@@ -5,13 +5,16 @@ import { ReactComponent as CalendarIcon } from '@assets/calendar.svg';
import EditMiniButton from '@components/common/EditMiniButton';
import DeleteMiniButton from '@components/common/DeleteMiniButton';
import CloseMiniButton from '@components/common/CloseMiniButton';
+import { useState } from 'react';
interface Props {
isLastItemStyle: boolean;
}
function MilestoneCell({ isLastItemStyle }: Props) {
+ const [isDisabled, setIsDisabled] = useState(true);
let progressValue = 80;
+
return (
{/* 마일스톤 왼쪽 */}
@@ -34,7 +37,9 @@ function MilestoneCell({ isLastItemStyle }: Props) {
닫기
- 편집
+
+ 편집
+
삭제