diff --git a/web/.eslintignore b/web/.eslintignore new file mode 100644 index 0000000..a3fdcbc --- /dev/null +++ b/web/.eslintignore @@ -0,0 +1,3 @@ +src/shared/generated/*.tsx +src/shared/generated/*.ts +src/react-app-env.d.ts diff --git a/web/package.json b/web/package.json index ca93c15..316d9bc 100644 --- a/web/package.json +++ b/web/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@apollo/react-common": "^3.1.4", "@apollo/react-hooks": "^3.1.3", "@fortawesome/fontawesome-svg-core": "^1.2.27", "@fortawesome/free-brands-svg-icons": "^5.12.1", diff --git a/web/src/App/index.tsx b/web/src/App/index.tsx index aab9e76..8f3a54b 100644 --- a/web/src/App/index.tsx +++ b/web/src/App/index.tsx @@ -1,12 +1,12 @@ import React, { useState, useEffect } from 'react'; import { createBrowserHistory } from 'history'; import { setAccessToken } from 'shared/utils/accessToken'; -import NormalizeStyles from './NormalizeStyles'; -import BaseStyles from './BaseStyles'; -import Routes from './Routes'; import Navbar from 'shared/components/Navbar'; import GlobalTopNavbar from 'App/TopNavbar'; import styled from 'styled-components'; +import NormalizeStyles from './NormalizeStyles'; +import BaseStyles from './BaseStyles'; +import Routes from './Routes'; const history = createBrowserHistory(); diff --git a/web/src/Projects/Project/index.tsx b/web/src/Projects/Project/index.tsx index f3c256e..f0f0fa9 100644 --- a/web/src/Projects/Project/index.tsx +++ b/web/src/Projects/Project/index.tsx @@ -1,7 +1,5 @@ import React, { useState } from 'react'; import styled from 'styled-components/macro'; -import { useQuery, useMutation } from '@apollo/react-hooks'; -import gql from 'graphql-tag'; import { useParams } from 'react-router-dom'; import { useFindProjectQuery, @@ -120,7 +118,7 @@ const Project = () => { const { loading, data } = useFindProjectQuery({ variables: { projectId }, onCompleted: newData => { - let newListsData: State = { tasks: {}, columns: {} }; + const newListsData: State = { tasks: {}, columns: {} }; newData.findProject.taskGroups.forEach((taskGroup: TaskGroup) => { newListsData.columns[taskGroup.taskGroupID] = { taskGroupID: taskGroup.taskGroupID, @@ -166,16 +164,13 @@ const Project = () => { }; const onCardCreate = (taskGroupID: string, name: string) => { const taskGroupTasks = Object.values(listsData.tasks).filter((task: Task) => task.taskGroupID === taskGroupID); - var position = 65535; - console.log(taskGroupID); - console.log(taskGroupTasks); + let position = 65535; if (taskGroupTasks.length !== 0) { const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1); - console.log(`last tasks position ${lastTask.position}`); position = Math.ceil(lastTask.position) * 2 + 1; } - createTask({ variables: { taskGroupID: taskGroupID, name: name, position: position } }); + createTask({ variables: { taskGroupID, name, position } }); }; const onQuickEditorOpen = (e: ContextMenuEvent) => { const currentTask = Object.values(listsData.tasks).find(task => task.taskID === e.taskID); @@ -211,16 +206,16 @@ const Project = () => { {quickCardEditor.isOpen && ( setQuickCardEditor(initialQuickCardEditorState)} - onEditCard={(listId: string, cardId: string, cardName: string) => - updateTaskName({ variables: { taskID: cardId, name: cardName } }) - } + onEditCard={(_listId: string, cardId: string, cardName: string) => { + updateTaskName({ variables: { taskID: cardId, name: cardName } }); + }} onOpenPopup={() => console.log()} - onArchiveCard={(listId: string, cardId: string) => deleteTask({ variables: { taskID: cardId } })} + onArchiveCard={(_listId: string, cardId: string) => deleteTask({ variables: { taskID: cardId } })} labels={[]} top={quickCardEditor.top} left={quickCardEditor.left} diff --git a/web/src/index.tsx b/web/src/index.tsx index c3e2a61..28c88ac 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -13,6 +13,7 @@ import App from './App'; // https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8 +let forward$; let isRefreshing = false; let pendingRequests: any = []; @@ -21,56 +22,57 @@ const resolvePendingRequests = () => { pendingRequests = []; }; +const resolvePromise = (resolve: () => void) => { + pendingRequests.push(() => resolve()); +}; + +const resetPendingRequests = () => { + pendingRequests = []; +}; + +const setRefreshing = (newVal: boolean) => { + isRefreshing = newVal; +}; + const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { if (graphQLErrors) { for (const err of graphQLErrors) { - switch (err!.extensions!.code) { - case 'UNAUTHENTICATED': - // error code is set to UNAUTHENTICATED - // when AuthenticationError thrown in resolver - let forward$; - - if (!isRefreshing) { - isRefreshing = true; - forward$ = fromPromise( - getNewToken() - .then((response: any) => { - // Store the new tokens for your auth link - setAccessToken(response.accessToken); - resolvePendingRequests(); - return response.accessToken; - }) - .catch((error: any) => { - pendingRequests = []; - // TODO - // Handle token refresh errors e.g clear stored tokens, redirect to login, ... - return; - }) - .finally(() => { - isRefreshing = false; - }), - ).filter(value => Boolean(value)); - } else { - // Will only emit once the Promise is resolved - forward$ = fromPromise( - new Promise(resolve => { - pendingRequests.push(() => resolve()); - }), - ); - } - - return forward$.flatMap(() => forward(operation)); - default: - // pass + if (err.extensions && err.extensions.code) { + switch (err.extensions.code) { + case 'UNAUTHENTICATED': + if (!isRefreshing) { + setRefreshing(true); + forward$ = fromPromise( + getNewToken() + .then((response: any) => { + setAccessToken(response.accessToken); + resolvePendingRequests(); + return response.accessToken; + }) + .catch(() => { + resetPendingRequests(); + // TODO + // Handle token refresh errors e.g clear stored tokens, redirect to login, ... + return undefined; + }) + .finally(() => { + setRefreshing(false); + }), + ).filter(value => Boolean(value)); + } else { + forward$ = fromPromise(new Promise(resolvePromise)); + } + return forward$.flatMap(() => forward(operation)); + default: + // pass + } } } } if (networkError) { console.log(`[Network error]: ${networkError}`); - // if you would also like to retry automatically on - // network errors, we recommend that you use - // apollo-link-retry } + return undefined; }); const requestLink = new ApolloLink( @@ -78,10 +80,10 @@ const requestLink = new ApolloLink( new Observable((observer: any) => { let handle: any; Promise.resolve(operation) - .then((operation: any) => { + .then((op: any) => { const accessToken = getAccessToken(); if (accessToken) { - operation.setContext({ + op.setContext({ headers: { Authorization: `Bearer ${accessToken}`, }, @@ -98,7 +100,9 @@ const requestLink = new ApolloLink( .catch(observer.error.bind(observer)); return () => { - if (handle) handle.unsubscribe(); + if (handle) { + handle.unsubscribe(); + } }; }), ); @@ -106,11 +110,14 @@ const requestLink = new ApolloLink( const client = new ApolloClient({ link: ApolloLink.from([ onError(({ graphQLErrors, networkError }) => { - if (graphQLErrors) + if (graphQLErrors) { graphQLErrors.forEach(({ message, locations, path }) => console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), ); - if (networkError) console.log(`[Network error]: ${networkError}`); + } + if (networkError) { + console.log(`[Network error]: ${networkError}`); + } }), errorLink, requestLink, diff --git a/web/src/shared/components/Card/Card.stories.tsx b/web/src/shared/components/Card/Card.stories.tsx index 5241e2f..86e060e 100644 --- a/web/src/shared/components/Card/Card.stories.tsx +++ b/web/src/shared/components/Card/Card.stories.tsx @@ -1,7 +1,7 @@ import React, { useRef } from 'react'; import { action } from '@storybook/addon-actions'; import LabelColors from 'shared/constants/labelColors'; -import Card from './index'; +import Card from '.'; export default { component: Card, diff --git a/web/src/shared/components/CardComposer/CardComposer.stories.tsx b/web/src/shared/components/CardComposer/CardComposer.stories.tsx index 8447b87..326eafe 100644 --- a/web/src/shared/components/CardComposer/CardComposer.stories.tsx +++ b/web/src/shared/components/CardComposer/CardComposer.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import CardComposer from './index'; +import CardComposer from '.'; export default { component: CardComposer, diff --git a/web/src/shared/components/CardComposer/index.tsx b/web/src/shared/components/CardComposer/index.tsx index dacd617..6407b07 100644 --- a/web/src/shared/components/CardComposer/index.tsx +++ b/web/src/shared/components/CardComposer/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import TextareaAutosize from 'react-autosize-textarea'; +import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import { CardComposerWrapper, @@ -15,7 +15,6 @@ import { ComposerControlsSaveSection, ComposerControlsActionsSection, } from './Styles'; -import useOnOutsideClick from 'shared/hooks/onOutsideClick'; type Props = { isOpen: boolean; diff --git a/web/src/shared/components/DropdownMenu/DropdownMenu.stories.tsx b/web/src/shared/components/DropdownMenu/DropdownMenu.stories.tsx index 10d0ffe..fef40d6 100644 --- a/web/src/shared/components/DropdownMenu/DropdownMenu.stories.tsx +++ b/web/src/shared/components/DropdownMenu/DropdownMenu.stories.tsx @@ -1,8 +1,7 @@ import React, { createRef, useState } from 'react'; import styled from 'styled-components'; -import { action } from '@storybook/addon-actions'; -import DropdownMenu from './index'; +import DropdownMenu from '.'; export default { component: DropdownMenu, diff --git a/web/src/shared/components/List/List.stories.tsx b/web/src/shared/components/List/List.stories.tsx index adbff3b..8cbfed1 100644 --- a/web/src/shared/components/List/List.stories.tsx +++ b/web/src/shared/components/List/List.stories.tsx @@ -3,7 +3,7 @@ import { action } from '@storybook/addon-actions'; import Card from 'shared/components/Card'; import CardComposer from 'shared/components/CardComposer'; import LabelColors from 'shared/constants/labelColors'; -import List, { ListCards } from './index'; +import List, { ListCards } from '.'; export default { component: List, diff --git a/web/src/shared/components/Lists/Lists.stories.tsx b/web/src/shared/components/Lists/Lists.stories.tsx index cd766d1..a61bff6 100644 --- a/web/src/shared/components/Lists/Lists.stories.tsx +++ b/web/src/shared/components/Lists/Lists.stories.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import Lists from './index'; import { action } from '@storybook/addon-actions'; +import Lists from '.'; export default { component: Lists, diff --git a/web/src/shared/components/Lists/Styles.ts b/web/src/shared/components/Lists/Styles.ts index 60b52fc..2f5a384 100644 --- a/web/src/shared/components/Lists/Styles.ts +++ b/web/src/shared/components/Lists/Styles.ts @@ -9,3 +9,5 @@ export const Container = styled.div` overflow-y: hidden; padding-bottom: 8px; `; + +export default Container; diff --git a/web/src/shared/components/Lists/index.tsx b/web/src/shared/components/Lists/index.tsx index f5e8469..9aa3fdb 100644 --- a/web/src/shared/components/Lists/index.tsx +++ b/web/src/shared/components/Lists/index.tsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; - import List, { ListCards } from 'shared/components/List'; import Card from 'shared/components/Card'; -import { Container } from './Styles'; import CardComposer from 'shared/components/CardComposer'; import { isPositionChanged, @@ -12,6 +10,8 @@ import { getAfterDropDraggableList, } from 'shared/utils/draggables'; +import { Container } from './Styles'; + interface Columns { [key: string]: TaskGroup; } @@ -28,13 +28,6 @@ type Props = { onQuickEditorOpen: (e: ContextMenuEvent) => void; }; -type OnDragEndProps = { - draggableId: any; - source: any; - destination: any; - type: any; -}; - const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEditorOpen }: Props) => { const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => { if (typeof destination === 'undefined') return; @@ -137,7 +130,7 @@ const Lists = ({ columns, tasks, onCardDrop, onListDrop, onCardCreate, onQuickEd setCurrentComposer(''); onCardCreate(column.taskGroupID, name); }} - isOpen={true} + isOpen /> )} diff --git a/web/src/shared/components/Login/Login.stories.tsx b/web/src/shared/components/Login/Login.stories.tsx index 9661407..2617280 100644 --- a/web/src/shared/components/Login/Login.stories.tsx +++ b/web/src/shared/components/Login/Login.stories.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; +import React from 'react'; import { action } from '@storybook/addon-actions'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; import styled from 'styled-components'; -import Login from './index'; +import Login from '.'; const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/web/src/shared/components/Modal/Modal.stories.tsx b/web/src/shared/components/Modal/Modal.stories.tsx index 5a7eeb2..765c73d 100644 --- a/web/src/shared/components/Modal/Modal.stories.tsx +++ b/web/src/shared/components/Modal/Modal.stories.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import { action } from '@storybook/addon-actions'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; -import styled from 'styled-components'; -import Modal from './index'; +import Modal from '.'; export default { component: Modal, diff --git a/web/src/shared/components/Modal/index.tsx b/web/src/shared/components/Modal/index.tsx index 70b4e69..005cd74 100644 --- a/web/src/shared/components/Modal/index.tsx +++ b/web/src/shared/components/Modal/index.tsx @@ -6,7 +6,7 @@ import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown'; import { ScrollOverlay, ClickableOverlay, StyledModal } from './Styles'; -const $root: HTMLElement = document.getElementById('root')!; +const $root: HTMLElement = document.getElementById('root')!; // eslint-disable-line @typescript-eslint/no-non-null-assertion type ModalProps = { width: number; diff --git a/web/src/shared/components/Navbar/Navbar.stories.tsx b/web/src/shared/components/Navbar/Navbar.stories.tsx index 6c09196..701d3b8 100644 --- a/web/src/shared/components/Navbar/Navbar.stories.tsx +++ b/web/src/shared/components/Navbar/Navbar.stories.tsx @@ -2,8 +2,8 @@ import React from 'react'; import styled from 'styled-components'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; -import { Home, Stack, Users, Question } from 'shared/icons'; -import Navbar, { ActionButton, ButtonContainer, PrimaryLogo } from './index'; +import { Home } from 'shared/icons'; +import Navbar, { ActionButton, ButtonContainer, PrimaryLogo } from '.'; export default { component: Navbar, diff --git a/web/src/shared/components/PopupMenu/PopupMenu.stories.tsx b/web/src/shared/components/PopupMenu/PopupMenu.stories.tsx index d76b1bc..5c61750 100644 --- a/web/src/shared/components/PopupMenu/PopupMenu.stories.tsx +++ b/web/src/shared/components/PopupMenu/PopupMenu.stories.tsx @@ -1,8 +1,8 @@ -import React, { createRef, useState } from 'react'; +import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; import LabelColors from 'shared/constants/labelColors'; import MenuTypes from 'shared/constants/menuTypes'; -import PopupMenu from './index'; +import PopupMenu from '.'; export default { component: PopupMenu, diff --git a/web/src/shared/components/ProjectGridItem/ProjectGridItem.stories.tsx b/web/src/shared/components/ProjectGridItem/ProjectGridItem.stories.tsx index 63734e9..4b3d53c 100644 --- a/web/src/shared/components/ProjectGridItem/ProjectGridItem.stories.tsx +++ b/web/src/shared/components/ProjectGridItem/ProjectGridItem.stories.tsx @@ -1,7 +1,6 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; -import { action } from '@storybook/addon-actions'; -import ProjectGridItem from './'; +import ProjectGridItem from '.'; export default { component: ProjectGridItem, diff --git a/web/src/shared/components/Sidebar/Sidebar.stories.tsx b/web/src/shared/components/Sidebar/Sidebar.stories.tsx index 961a5dd..7d5956c 100644 --- a/web/src/shared/components/Sidebar/Sidebar.stories.tsx +++ b/web/src/shared/components/Sidebar/Sidebar.stories.tsx @@ -1,9 +1,8 @@ import React from 'react'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; -import Sidebar from './index'; - import Navbar from 'shared/components/Navbar'; +import Sidebar from '.'; export default { component: Sidebar, diff --git a/web/src/shared/components/Sidebar/Styles.ts b/web/src/shared/components/Sidebar/Styles.ts index 2f4064c..e00cf09 100644 --- a/web/src/shared/components/Sidebar/Styles.ts +++ b/web/src/shared/components/Sidebar/Styles.ts @@ -1,6 +1,6 @@ import styled from 'styled-components'; -export const Container = styled.div` +const Container = styled.div` position: fixed; z-index: 99; top: 0px; @@ -13,3 +13,5 @@ export const Container = styled.div` background: rgb(244, 245, 247); border-right: 1px solid rgb(223, 225, 230); `; + +export default Container; diff --git a/web/src/shared/components/Sidebar/index.tsx b/web/src/shared/components/Sidebar/index.tsx index 3e256e9..1469a56 100644 --- a/web/src/shared/components/Sidebar/index.tsx +++ b/web/src/shared/components/Sidebar/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Container } from './Styles'; +import Container from './Styles'; const Sidebar = () => { - return ; + return ; }; export default Sidebar; diff --git a/web/src/shared/components/TaskDetails/TaskDetails.stories.tsx b/web/src/shared/components/TaskDetails/TaskDetails.stories.tsx index 101c835..57fbba8 100644 --- a/web/src/shared/components/TaskDetails/TaskDetails.stories.tsx +++ b/web/src/shared/components/TaskDetails/TaskDetails.stories.tsx @@ -2,9 +2,8 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; -import styled from 'styled-components'; import Modal from 'shared/components/Modal'; -import TaskDetails from './'; +import TaskDetails from '.'; export default { component: TaskDetails, @@ -35,9 +34,9 @@ export const Default = () => { name: 'Hello, world', position: 1, labels: [], - description: description, + description, }} - onTaskDescriptionChange={(task, desc) => setDescription(desc)} + onTaskDescriptionChange={(_task, desc) => setDescription(desc)} /> ); }} diff --git a/web/src/shared/components/TopNavbar/TopNavbar.stories.tsx b/web/src/shared/components/TopNavbar/TopNavbar.stories.tsx index 9d2bcf6..76d3f07 100644 --- a/web/src/shared/components/TopNavbar/TopNavbar.stories.tsx +++ b/web/src/shared/components/TopNavbar/TopNavbar.stories.tsx @@ -1,11 +1,9 @@ -import React, { createRef, useState } from 'react'; +import React, { useState } from 'react'; import NormalizeStyles from 'App/NormalizeStyles'; import BaseStyles from 'App/BaseStyles'; - -import TopNavbar from './index'; import { action } from '@storybook/addon-actions'; - import DropdownMenu from 'shared/components/DropdownMenu'; +import TopNavbar from '.'; export default { component: TopNavbar, diff --git a/web/yarn.lock b/web/yarn.lock index a987806..b36e4e3 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -20,6 +20,14 @@ ts-invariant "^0.4.4" tslib "^1.10.0" +"@apollo/react-common@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@apollo/react-common/-/react-common-3.1.4.tgz#ec13c985be23ea8e799c9ea18e696eccc97be345" + integrity sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA== + dependencies: + ts-invariant "^0.4.4" + tslib "^1.10.0" + "@apollo/react-hooks@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@apollo/react-hooks/-/react-hooks-3.1.3.tgz#ad42c7af78e81fee0f30e53242640410d5bd0293"