feat: add pre commit hook to lint frontend & fix warnings

This commit is contained in:
Jordan Knott 2020-08-23 12:27:56 -05:00
parent b6194d552f
commit 9214508ca2
46 changed files with 256 additions and 730 deletions

View File

@ -1,4 +1,13 @@
repos: repos:
- repo: local
hooks:
- id: eslint
name: eslint
entry: go run cmd/mage/main.go frontend:lint
language: system
files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
types: [file]
pass_filenames: false
- hooks: - hooks:
- id: check-yaml - id: check-yaml
- id: end-of-file-fixer - id: end-of-file-fixer

View File

@ -71,7 +71,8 @@
"storybook": "start-storybook -p 9009 -s public", "storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public", "build-storybook": "build-storybook -s public",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"lint": "eslint --ext js,ts,tsx src" "lint": "eslint --ext js,ts,tsx src",
"tsc": "tsc"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useContext } from 'react'; import React, { useEffect } from 'react';
import Admin from 'shared/components/Admin'; import Admin from 'shared/components/Admin';
import Select from 'shared/components/Select'; import Select from 'shared/components/Select';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
@ -16,8 +16,9 @@ import { useForm, Controller } from 'react-hook-form';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer'; import produce from 'immer';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import UserContext, { useCurrentUser } from 'App/context'; import { useCurrentUser } from 'App/context';
import { Redirect } from 'react-router'; import { Redirect } from 'react-router';
import NOOP from 'shared/utils/noop';
const DeleteUserWrapper = styled.div` const DeleteUserWrapper = styled.div`
display: flex; display: flex;
@ -37,6 +38,7 @@ const DeleteUserButton = styled(Button)`
type DeleteUserPopupProps = { type DeleteUserPopupProps = {
onDeleteUser: () => void; onDeleteUser: () => void;
}; };
const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => { const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
return ( return (
<DeleteUserWrapper> <DeleteUserWrapper>
@ -47,10 +49,12 @@ const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
</DeleteUserWrapper> </DeleteUserWrapper>
); );
}; };
type RoleCodeOption = { type RoleCodeOption = {
label: string; label: string;
value: string; value: string;
}; };
type CreateUserData = { type CreateUserData = {
email: string; email: string;
username: string; username: string;
@ -65,6 +69,7 @@ const CreateUserForm = styled.form`
flex-direction: column; flex-direction: column;
margin: 0 12px; margin: 0 12px;
`; `;
const CreateUserButton = styled(Button)` const CreateUserButton = styled(Button)`
margin-top: 8px; margin-top: 8px;
padding: 6px 12px; padding: 6px 12px;
@ -85,7 +90,7 @@ type AddUserPopupProps = {
}; };
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => { const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors, setValue, control } = useForm<CreateUserData>(); const { register, handleSubmit, errors, control } = useForm<CreateUserData>();
const createUser = (data: CreateUserData) => { const createUser = (data: CreateUserData) => {
onAddUser(data); onAddUser(data);
@ -115,7 +120,7 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
control={control} control={control}
name="roleCode" name="roleCode"
rules={{ required: 'Role is required' }} rules={{ required: 'Role is required' }}
render={({ onChange, onBlur, value }) => ( render={({ onChange, value }) => (
<Select <Select
label="Role" label="Role"
value={value} value={value}
@ -198,21 +203,21 @@ const AdminRoute = () => {
}, },
}); });
if (loading) { if (loading) {
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />; return <GlobalTopNavbar projectID={null} onSaveProjectName={NOOP} name={null} />;
} }
if (data && user) { if (data && user) {
if (user.roles.org != 'admin') { if (user.roles.org !== 'admin') {
return <Redirect to="/" />; return <Redirect to="/" />;
} }
return ( return (
<> <>
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar projectID={null} onSaveProjectName={NOOP} name={null} />
<Admin <Admin
initialTab={0} initialTab={0}
users={data.users} users={data.users}
canInviteUser={user.roles.org == 'admin'} canInviteUser={user.roles.org === 'admin'}
onInviteUser={() => {}} onInviteUser={NOOP}
onUpdateUserPassword={(user, password) => { onUpdateUserPassword={() => {
hidePopup(); hidePopup();
}} }}
onDeleteUser={(userID, newOwnerID) => { onDeleteUser={(userID, newOwnerID) => {
@ -224,8 +229,8 @@ const AdminRoute = () => {
$target, $target,
<Popup tab={0} title="Add member" onClose={() => hidePopup()}> <Popup tab={0} title="Add member" onClose={() => hidePopup()}>
<AddUserPopup <AddUserPopup
onAddUser={user => { onAddUser={u => {
const { roleCode, ...userData } = user; const { roleCode, ...userData } = u;
createUser({ variables: { ...userData, roleCode: roleCode.value } }); createUser({ variables: { ...userData, roleCode: roleCode.value } });
hidePopup(); hidePopup();
}} }}

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {Router, Switch, Route} from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import * as H from 'history'; import * as H from 'history';
import Dashboard from 'Dashboard'; import Dashboard from 'Dashboard';
@ -20,11 +20,12 @@ const MainContent = styled.div`
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
`; `;
type RoutesProps = { type RoutesProps = {
history: H.History; history: H.History;
}; };
const Routes = ({history}: RoutesProps) => ( const Routes: React.FC<RoutesProps> = () => (
<Switch> <Switch>
<Route exact path="/login" component={Login} /> <Route exact path="/login" component={Login} />
<Route exact path="/install" component={Install} /> <Route exact path="/install" component={Install} />

View File

@ -1,4 +1,4 @@
import { createGlobalStyle, DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
const theme: DefaultTheme = { const theme: DefaultTheme = {
borderRadius: { borderRadius: {
@ -25,4 +25,4 @@ const theme: DefaultTheme = {
}, },
}; };
export { theme }; export default theme;

View File

@ -1,10 +1,10 @@
import React, { useState, useContext, useEffect } from 'react'; import React from 'react';
import TopNavbar, { MenuItem } from 'shared/components/TopNavbar'; import TopNavbar, { MenuItem } from 'shared/components/TopNavbar';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu'; import { ProfileMenu } from 'shared/components/DropdownMenu';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings'; import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { UserContext, PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context'; import { PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context';
import { import {
RoleCode, RoleCode,
useMeQuery, useMeQuery,
@ -18,6 +18,7 @@ import produce from 'immer';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
import cache from 'App/cache'; import cache from 'App/cache';
import NOOP from 'shared/utils/noop';
const TeamContainer = styled.div` const TeamContainer = styled.div`
display: flex; display: flex;
@ -130,7 +131,7 @@ const ProjectFinder = () => {
return <span>loading</span>; return <span>loading</span>;
} }
if (data) { if (data) {
const { projects, teams, organizations } = data; const { projects, teams } = data;
const projectTeams = teams.map(team => { const projectTeams = teams.map(team => {
return { return {
id: team.id, id: team.id,
@ -238,7 +239,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab, currentTab,
onSetTab, onSetTab,
menuType, menuType,
projectID,
teamID, teamID,
onChangeProjectOwner, onChangeProjectOwner,
onChangeRole, onChangeRole,
@ -248,19 +248,18 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onInviteUser, onInviteUser,
onSaveProjectName, onSaveProjectName,
onRemoveFromBoard, onRemoveFromBoard,
nameOnly,
}) => { }) => {
const { user, setUserRoles, setUser } = useCurrentUser(); const { user, setUserRoles, setUser } = useCurrentUser();
const { loading, data } = useMeQuery({ const { data } = useMeQuery({
onCompleted: data => { onCompleted: response => {
if (user && user.roles) { if (user && user.roles) {
setUserRoles({ setUserRoles({
org: user.roles.org, org: user.roles.org,
teams: data.me.teamRoles.reduce((map, obj) => { teams: response.me.teamRoles.reduce((map, obj) => {
map.set(obj.teamID, obj.roleCode); map.set(obj.teamID, obj.roleCode);
return map; return map;
}, new Map<string, string>()), }, new Map<string, string>()),
projects: data.me.projectRoles.reduce((map, obj) => { projects: response.me.projectRoles.reduce((map, obj) => {
map.set(obj.projectID, obj.roleCode); map.set(obj.projectID, obj.roleCode);
return map; return map;
}, new Map<string, string>()), }, new Map<string, string>()),
@ -268,7 +267,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
} }
}, },
}); });
const { showPopup, hidePopup, setTab } = usePopup(); const { showPopup, hidePopup } = usePopup();
const history = useHistory(); const history = useHistory();
const onLogout = () => { const onLogout = () => {
fetch('/auth/logout', { fetch('/auth/logout', {
@ -367,7 +366,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onInviteUser={onInviteUser} onInviteUser={onInviteUser}
onChangeRole={onChangeRole} onChangeRole={onChangeRole}
onChangeProjectOwner={onChangeProjectOwner} onChangeProjectOwner={onChangeProjectOwner}
onNotificationClick={() => {}} onNotificationClick={NOOP}
onSetTab={onSetTab} onSetTab={onSetTab}
onRemoveFromBoard={onRemoveFromBoard} onRemoveFromBoard={onRemoveFromBoard}
onDashboardClick={() => { onDashboardClick={() => {

View File

@ -7,7 +7,7 @@ import { setAccessToken } from 'shared/utils/accessToken';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import NormalizeStyles from './NormalizeStyles'; import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles'; import BaseStyles from './BaseStyles';
import { theme } from './ThemeStyles'; import theme from './ThemeStyles';
import Routes from './Routes'; import Routes from './Routes';
import { UserContext, CurrentUserRaw, CurrentUserRoles, PermissionLevel, PermissionObjectType } from './context'; import { UserContext, CurrentUserRaw, CurrentUserRoles, PermissionLevel, PermissionObjectType } from './context';

View File

@ -1,13 +1,10 @@
import React, { useState, useEffect, useContext } from 'react'; import React, { useState, useEffect, useContext } from 'react';
import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { setAccessToken } from 'shared/utils/accessToken';
import Login from 'shared/components/Login';
import { Container, LoginWrapper } from './Styles';
import UserContext, { PermissionLevel, PermissionObjectType } from 'App/context';
import JwtDecode from 'jwt-decode'; import JwtDecode from 'jwt-decode';
import { setAccessToken } from 'shared/utils/accessToken';
import Login from 'shared/components/Login';
import UserContext from 'App/context';
import { Container, LoginWrapper } from './Styles';
const Auth = () => { const Auth = () => {
const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0); const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0);

View File

@ -1,18 +1,13 @@
import React, { useEffect, useContext } from 'react'; import React, { useEffect, useContext } from 'react';
import axios from 'axios'; import axios from 'axios';
import Register from 'shared/components/Register'; import Register from 'shared/components/Register';
import { Container, LoginWrapper } from './Styles';
import { useCreateUserAccountMutation, useMeQuery, MeDocument, MeQuery } from 'shared/generated/graphql';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { getAccessToken, setAccessToken } from 'shared/utils/accessToken'; import { getAccessToken, setAccessToken } from 'shared/utils/accessToken';
import updateApolloCache from 'shared/utils/cache'; import UserContext from 'App/context';
import produce from 'immer';
import { useApolloClient } from '@apollo/react-hooks';
import UserContext, { PermissionLevel, PermissionObjectType } from 'App/context';
import jwtDecode from 'jwt-decode'; import jwtDecode from 'jwt-decode';
import { Container, LoginWrapper } from './Styles';
const Install = () => { const Install = () => {
const client = useApolloClient();
const history = useHistory(); const history = useHistory();
const { setUser } = useContext(UserContext); const { setUser } = useContext(UserContext);
useEffect(() => { useEffect(() => {
@ -63,7 +58,7 @@ const Install = () => {
history.replace('/login'); history.replace('/login');
} else { } else {
const response: RefreshTokenResponse = await x.data; const response: RefreshTokenResponse = await x.data;
const { accessToken, isInstalled } = response; const { accessToken: newToken, isInstalled } = response;
const claims: JWTToken = jwtDecode(accessToken); const claims: JWTToken = jwtDecode(accessToken);
const currentUser = { const currentUser = {
id: claims.userId, id: claims.userId,

View File

@ -1,18 +1,12 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
import { Link } from 'react-router-dom';
import { getAccessToken } from 'shared/utils/accessToken'; import { getAccessToken } from 'shared/utils/accessToken';
import Settings from 'shared/components/Settings'; import Settings from 'shared/components/Settings';
import { useMeQuery, useClearProfileAvatarMutation, useUpdateUserPasswordMutation } from 'shared/generated/graphql'; import { useMeQuery, useClearProfileAvatarMutation, useUpdateUserPasswordMutation } from 'shared/generated/graphql';
import axios from 'axios'; import axios from 'axios';
import { useCurrentUser } from 'App/context'; import { useCurrentUser } from 'App/context';
import NOOP from 'shared/utils/noop';
const MainContent = styled.div`
padding: 0 0 50px 80px;
height: 100%;
background: #262c49;
`;
const Projects = () => { const Projects = () => {
const $fileUpload = useRef<HTMLInputElement>(null); const $fileUpload = useRef<HTMLInputElement>(null);
@ -54,7 +48,7 @@ const Projects = () => {
} }
}} }}
/> />
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar projectID={null} onSaveProjectName={NOOP} name={null} />
{!loading && data && ( {!loading && data && (
<Settings <Settings
profile={data.me.user} profile={data.me.user}

View File

@ -1,39 +1,26 @@
import React, { useState, useRef, useContext, useEffect } from 'react'; import React, { useState, useRef } from 'react';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import LabelManagerEditor from '../LabelManagerEditor';
import styled, { css } from 'styled-components/macro'; import styled, { css } from 'styled-components/macro';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons'; import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation } from 'react-router-dom'; import { useRouteMatch, useHistory } from 'react-router-dom';
import { import {
useUpdateProjectMemberRoleMutation,
useCreateProjectMemberMutation,
useDeleteProjectMemberMutation,
useSetTaskCompleteMutation, useSetTaskCompleteMutation,
useToggleTaskLabelMutation, useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery, useFindProjectQuery,
useUpdateTaskGroupNameMutation, useUpdateTaskGroupNameMutation,
useUpdateTaskNameMutation, useUpdateTaskNameMutation,
useUpdateProjectLabelMutation,
useCreateTaskMutation, useCreateTaskMutation,
useDeleteProjectLabelMutation,
useDeleteTaskMutation, useDeleteTaskMutation,
useUpdateTaskLocationMutation, useUpdateTaskLocationMutation,
useUpdateTaskGroupLocationMutation, useUpdateTaskGroupLocationMutation,
useCreateTaskGroupMutation, useCreateTaskGroupMutation,
useDeleteTaskGroupMutation, useDeleteTaskGroupMutation,
useUpdateTaskDescriptionMutation,
useAssignTaskMutation, useAssignTaskMutation,
DeleteTaskDocument,
FindProjectDocument, FindProjectDocument,
useCreateProjectLabelMutation,
useUnassignTaskMutation, useUnassignTaskMutation,
useUpdateTaskDueDateMutation, useUpdateTaskDueDateMutation,
FindProjectQuery, FindProjectQuery,
useUsersQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import QuickCardEditor from 'shared/components/QuickCardEditor'; import QuickCardEditor from 'shared/components/QuickCardEditor';
@ -43,10 +30,9 @@ import SimpleLists from 'shared/components/Lists';
import produce from 'immer'; import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager'; import DueDateManager from 'shared/components/DueDateManager';
import UserContext, { useCurrentUser } from 'App/context';
import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import EmptyBoard from 'shared/components/EmptyBoard'; import EmptyBoard from 'shared/components/EmptyBoard';
import NOOP from 'shared/utils/noop';
import LabelManagerEditor from 'Projects/Project/LabelManagerEditor';
const ProjectBar = styled.div` const ProjectBar = styled.div`
display: flex; display: flex;
@ -155,7 +141,6 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const taskLabelsRef = useRef<Array<TaskLabel>>([]); const taskLabelsRef = useRef<Array<TaskLabel>>([]);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState); const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const { user } = useCurrentUser();
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({}); const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const history = useHistory(); const history = useHistory();
const [deleteTaskGroup] = useDeleteTaskGroupMutation({ const [deleteTaskGroup] = useDeleteTaskGroupMutation({
@ -308,12 +293,6 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
if (data) { if (data) {
labelsRef.current = data.findProject.labels; labelsRef.current = data.findProject.labels;
const onQuickEditorOpen = ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => { const onQuickEditorOpen = ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => {
if ($target && $target.current) {
const pos = $target.current.getBoundingClientRect();
const height = 120;
if (window.innerHeight - pos.bottom < height) {
}
}
const taskGroup = data.findProject.taskGroups.find(t => t.id === taskGroupID); const taskGroup = data.findProject.taskGroups.find(t => t.id === taskGroupID);
const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === taskID) : null; const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === taskID) : null;
if (currentTask) { if (currentTask) {
@ -381,7 +360,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
onTaskClick={task => { onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`); history.push(`${match.url}/c/${task.id}`);
}} }}
onCardLabelClick={onCardLabelClick ?? (() => {})} onCardLabelClick={onCardLabelClick ?? NOOP}
cardLabelVariant={cardLabelVariant ?? 'large'} cardLabelVariant={cardLabelVariant ?? 'large'}
onTaskDrop={(droppedTask, previousTaskGroupID) => { onTaskDrop={(droppedTask, previousTaskGroupID) => {
updateTaskLocation({ updateTaskLocation({
@ -427,7 +406,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
taskGroups={data.findProject.taskGroups} taskGroups={data.findProject.taskGroups}
onCreateTask={onCreateTask} onCreateTask={onCreateTask}
onCreateTaskGroup={onCreateList} onCreateTaskGroup={onCreateList}
onCardMemberClick={($targetRef, taskID, memberID) => { onCardMemberClick={($targetRef, _taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID); const member = data.findProject.members.find(m => m.id === memberID);
if (member) { if (member) {
showPopup( showPopup(
@ -486,7 +465,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
</Popup>, </Popup>,
); );
}} }}
onCardMemberClick={($targetRef, taskID, memberID) => { onCardMemberClick={($targetRef, _taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID); const member = data.findProject.members.find(m => m.id === memberID);
if (member) { if (member) {
showPopup( showPopup(
@ -516,8 +495,8 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
/>, />,
); );
}} }}
onArchiveCard={(_listId: string, cardId: string) => onArchiveCard={(_listId: string, cardId: string) => {
deleteTask({ return deleteTask({
variables: { taskID: cardId }, variables: { taskID: cardId },
update: client => { update: client => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
@ -533,8 +512,8 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
{ projectID }, { projectID },
); );
}, },
}) });
} }}
onOpenDueDatePopup={($targetRef, task) => { onOpenDueDatePopup={($targetRef, task) => {
showPopup( showPopup(
$targetRef, $targetRef,
@ -549,7 +528,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } }); updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup(); hidePopup();
}} }}
onCancel={() => {}} onCancel={NOOP}
/> />
</Popup>, </Popup>,
); );

View File

@ -1,7 +1,7 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState } from 'react';
import Modal from 'shared/components/Modal'; import Modal from 'shared/components/Modal';
import TaskDetails from 'shared/components/TaskDetails'; import TaskDetails from 'shared/components/TaskDetails';
import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu'; import { Popup, usePopup } from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager'; import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router'; import { useRouteMatch, useHistory } from 'react-router';
import { import {
@ -22,7 +22,7 @@ import {
FindTaskDocument, FindTaskDocument,
FindTaskQuery, FindTaskQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import UserContext, { useCurrentUser } from 'App/context'; import { useCurrentUser } from 'App/context';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager'; import DueDateManager from 'shared/components/DueDateManager';
import produce from 'immer'; import produce from 'immer';
@ -31,6 +31,7 @@ import Button from 'shared/components/Button';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import NOOP from 'shared/utils/noop';
const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => { const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => {
const total = checklists.reduce((prev: any, next: any) => { const total = checklists.reduce((prev: any, next: any) => {
@ -75,15 +76,12 @@ const CreateChecklistInput = styled(Input)`
margin-bottom: 8px; margin-bottom: 8px;
`; `;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type CreateChecklistPopupProps = { type CreateChecklistPopupProps = {
onCreateChecklist: (data: CreateChecklistData) => void; onCreateChecklist: (data: CreateChecklistData) => void;
}; };
const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => { const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => {
const { register, handleSubmit, errors } = useForm<CreateChecklistData>(); const { register, handleSubmit } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => { const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data); onCreateChecklist(data);
}; };
@ -132,9 +130,6 @@ const Details: React.FC<DetailsProps> = ({
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const history = useHistory(); const history = useHistory();
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [updateTaskChecklistLocation] = useUpdateTaskChecklistLocationMutation(); const [updateTaskChecklistLocation] = useUpdateTaskChecklistLocationMutation();
const [updateTaskChecklistItemLocation] = useUpdateTaskChecklistItemLocationMutation({ const [updateTaskChecklistItemLocation] = useUpdateTaskChecklistItemLocationMutation({
update: (client, response) => { update: (client, response) => {
@ -148,7 +143,7 @@ const Details: React.FC<DetailsProps> = ({
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID); const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID); const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID);
if (oldIdx > -1 && newIdx > -1) { if (oldIdx > -1 && newIdx > -1) {
const item = cache.findTask.checklists[oldIdx].items.find(item => item.id === checklistItem.id); const item = cache.findTask.checklists[oldIdx].items.find(i => i.id === checklistItem.id);
if (item) { if (item) {
draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter( draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
i => i.id !== checklistItem.id, i => i.id !== checklistItem.id,
@ -398,7 +393,7 @@ const Details: React.FC<DetailsProps> = ({
if (member) { if (member) {
showPopup( showPopup(
$targetRef, $targetRef,
<Popup title={null} onClose={() => {}} tab={0}> <Popup title={null} onClose={NOOP} tab={0}>
<MiniProfile <MiniProfile
user={member} user={member}
bio="None" bio="None"
@ -412,19 +407,19 @@ const Details: React.FC<DetailsProps> = ({
); );
} }
}} }}
onOpenAddMemberPopup={(task, $targetRef) => { onOpenAddMemberPopup={(_task, $targetRef) => {
showPopup( showPopup(
$targetRef, $targetRef,
<Popup title="Members" tab={0} onClose={() => {}}> <Popup title="Members" tab={0} onClose={NOOP}>
<MemberManager <MemberManager
availableMembers={availableMembers} availableMembers={availableMembers}
activeMembers={data.findTask.assigned} activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => { onMemberChange={(member, isActive) => {
if (user) { if (user) {
if (isActive) { if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: user.id } }); assignTask({ variables: { taskID: data.findTask.id, userID: member.id } });
} else { } else {
unassignTask({ variables: { taskID: data.findTask.id, userID: user.id } }); unassignTask({ variables: { taskID: data.findTask.id, userID: member.id } });
} }
} }
}} }}
@ -486,7 +481,7 @@ const Details: React.FC<DetailsProps> = ({
showPopup( showPopup(
$targetRef, $targetRef,
<Popup <Popup
title={'Change Due Date'} title="Change Due Date"
tab={0} tab={0}
onClose={() => { onClose={() => {
hidePopup(); hidePopup();
@ -502,7 +497,7 @@ const Details: React.FC<DetailsProps> = ({
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } }); updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup(); hidePopup();
}} }}
onCancel={() => {}} onCancel={NOOP}
/> />
</Popup>, </Popup>,
); );

View File

@ -4,7 +4,6 @@ import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar'; import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import LabelManagerEditor from './LabelManagerEditor';
import { import {
useParams, useParams,
Route, Route,
@ -36,9 +35,11 @@ import produce from 'immer';
import UserContext, { useCurrentUser } from 'App/context'; import UserContext, { useCurrentUser } from 'App/context';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import Member from 'shared/components/Member'; import Member from 'shared/components/Member';
import EmptyBoard from 'shared/components/EmptyBoard';
import NOOP from 'shared/utils/noop';
import Board, { BoardLoading } from './Board'; import Board, { BoardLoading } from './Board';
import Details from './Details'; import Details from './Details';
import EmptyBoard from 'shared/components/EmptyBoard'; import LabelManagerEditor from './LabelManagerEditor';
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant'; const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
@ -124,6 +125,7 @@ const Project = () => {
const match = useRouteMatch(); const match = useRouteMatch();
const [updateTaskDescription] = useUpdateTaskDescriptionMutation(); const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
const [toggleTaskLabel] = useToggleTaskLabelMutation({ const [toggleTaskLabel] = useToggleTaskLabelMutation({
onCompleted: newTaskLabel => { onCompleted: newTaskLabel => {
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels; taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
@ -190,7 +192,6 @@ const Project = () => {
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null); const $labelsRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<Array<ProjectLabel>>([]); const labelsRef = useRef<Array<ProjectLabel>>([]);
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
document.title = `${data.findProject.name} | Taskcafé`; document.title = `${data.findProject.name} | Taskcafé`;
@ -199,7 +200,7 @@ const Project = () => {
if (loading) { if (loading) {
return ( return (
<> <>
<GlobalTopNavbar onSaveProjectName={projectName => {}} name="" projectID={null} /> <GlobalTopNavbar onSaveProjectName={NOOP} name="" projectID={null} />
<BoardLoading /> <BoardLoading />
</> </>
); );
@ -261,7 +262,7 @@ const Project = () => {
path={`${match.path}/board/c/:taskID`} path={`${match.path}/board/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => ( render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details <Details
refreshCache={() => {}} refreshCache={NOOP}
availableMembers={data.findProject.members} availableMembers={data.findProject.members}
projectURL={`${match.url}/board`} projectURL={`${match.url}/board`}
taskID={routeProps.match.params.taskID} taskID={routeProps.match.params.taskID}

View File

@ -1,4 +1,4 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
import Empty from 'shared/undraw/Empty'; import Empty from 'shared/undraw/Empty';
@ -10,16 +10,17 @@ import {
GetProjectsQuery, GetProjectsQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import NewProject from 'shared/components/NewProject'; import NewProject from 'shared/components/NewProject';
import UserContext, { PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context'; import { PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import produce from 'immer'; import produce from 'immer';
import NOOP from 'shared/utils/noop';
const EmptyStateContent = styled.div` const EmptyStateContent = styled.div`
display: flex; display: flex;
justy-content: center; justy-content: center;
@ -37,21 +38,26 @@ const EmptyStatePrompt = styled.span`
font-size: 16px; font-size: 16px;
margin-top: 8px; margin-top: 8px;
`; `;
const EmptyState = styled(Empty)` const EmptyState = styled(Empty)`
display: block; display: block;
margin: 0 auto; margin: 0 auto;
`; `;
const CreateTeamButton = styled(Button)` const CreateTeamButton = styled(Button)`
width: 100%; width: 100%;
`; `;
type CreateTeamData = { teamName: string }; type CreateTeamData = { teamName: string };
type CreateTeamFormProps = { type CreateTeamFormProps = {
onCreateTeam: (teamName: string) => void; onCreateTeam: (teamName: string) => void;
}; };
const CreateTeamFormContainer = styled.form``; const CreateTeamFormContainer = styled.form``;
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => { const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
const { register, handleSubmit, errors } = useForm<CreateTeamData>(); const { register, handleSubmit } = useForm<CreateTeamData>();
const createTeam = (data: CreateTeamData) => { const createTeam = (data: CreateTeamData) => {
onCreateTeam(data.teamName); onCreateTeam(data.teamName);
}; };
@ -186,6 +192,7 @@ const SectionActions = styled.div`
const SectionAction = styled(Button)` const SectionAction = styled(Button)`
padding: 6px 12px; padding: 6px 12px;
`; `;
const SectionActionLink = styled(Link)` const SectionActionLink = styled(Link)`
margin-right: 8px; margin-right: 8px;
`; `;
@ -201,12 +208,14 @@ const ProjectsContainer = styled.div`
max-width: 825px; max-width: 825px;
min-width: 288px; min-width: 288px;
`; `;
const ProjectGrid = styled.div` const ProjectGrid = styled.div`
max-width: 780px; max-width: 780px;
display: grid; display: grid;
grid-template-columns: 240px 240px 240px; grid-template-columns: 240px 240px 240px;
gap: 20px 10px; gap: 20px 10px;
`; `;
const AddTeamButton = styled(Button)` const AddTeamButton = styled(Button)`
padding: 6px 12px; padding: 6px 12px;
position: absolute; position: absolute;
@ -217,13 +226,12 @@ const AddTeamButton = styled(Button)`
const CreateFirstTeam = styled(Button)` const CreateFirstTeam = styled(Button)`
margin-top: 8px; margin-top: 8px;
`; `;
type ShowNewProject = { type ShowNewProject = {
open: boolean; open: boolean;
initialTeamID: null | string; initialTeamID: null | string;
}; };
const ProjectLink = styled(Link)``;
const Projects = () => { const Projects = () => {
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only' }); const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only' });
@ -241,7 +249,7 @@ const Projects = () => {
}); });
const [showNewProject, setShowNewProject] = useState<ShowNewProject>({ open: false, initialTeamID: null }); const [showNewProject, setShowNewProject] = useState<ShowNewProject>({ open: false, initialTeamID: null });
const { user, setUser } = useCurrentUser(); const { user } = useCurrentUser();
const [createTeam] = useCreateTeamMutation({ const [createTeam] = useCreateTeamMutation({
update: (client, createData) => { update: (client, createData) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache => updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
@ -267,7 +275,7 @@ const Projects = () => {
.sort((a, b) => { .sort((a, b) => {
const textA = a.name.toUpperCase(); const textA = a.name.toUpperCase();
const textB = b.name.toUpperCase(); const textB = b.name.toUpperCase();
return textA < textB ? -1 : textA > textB ? 1 : 0; return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary
}) })
.map(team => { .map(team => {
return { return {
@ -278,13 +286,13 @@ const Projects = () => {
.sort((a, b) => { .sort((a, b) => {
const textA = a.name.toUpperCase(); const textA = a.name.toUpperCase();
const textB = b.name.toUpperCase(); const textB = b.name.toUpperCase();
return textA < textB ? -1 : textA > textB ? 1 : 0; return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary
}), }),
}; };
}); });
return ( return (
<> <>
<GlobalTopNavbar onSaveProjectName={() => {}} projectID={null} name={null} /> <GlobalTopNavbar onSaveProjectName={NOOP} projectID={null} name={null} />
<Wrapper> <Wrapper>
<ProjectsContainer> <ProjectsContainer>
{user.roles.org === 'admin' && ( {user.roles.org === 'admin' && (

View File

@ -1,9 +1,9 @@
import React, { useState, useContext } from 'react'; import React, { useState } from 'react';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import produce from 'immer'; import produce from 'immer';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import UserContext, { useCurrentUser, PermissionLevel, PermissionObjectType } from 'App/context'; import { useCurrentUser, PermissionLevel, PermissionObjectType } from 'App/context';
import Select from 'shared/components/Select'; import Select from 'shared/components/Select';
import { import {
useGetTeamQuery, useGetTeamQuery,
@ -13,8 +13,6 @@ import {
useUpdateTeamMemberRoleMutation, useUpdateTeamMemberRoleMutation,
GetTeamQuery, GetTeamQuery,
GetTeamDocument, GetTeamDocument,
MeDocument,
MeQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import { UserPlus, Checkmark } from 'shared/icons'; import { UserPlus, Checkmark } from 'shared/icons';
import styled, { css } from 'styled-components/macro'; import styled, { css } from 'styled-components/macro';
@ -22,6 +20,7 @@ import { usePopup, Popup } from 'shared/components/PopupMenu';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
import Member from 'shared/components/Member'; import Member from 'shared/components/Member';
import ControlledInput from 'shared/components/ControlledInput'; import ControlledInput from 'shared/components/ControlledInput';
import NOOP from 'shared/utils/noop';
const MemberListWrapper = styled.div` const MemberListWrapper = styled.div`
flex: 1 1; flex: 1 1;
@ -129,6 +128,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
} }
`} `}
`; `;
export const Content = styled.div` export const Content = styled.div`
padding: 0 12px 12px; padding: 0 12px 12px;
`; `;
@ -160,6 +160,7 @@ export const RemoveMemberButton = styled(Button)`
padding: 6px 12px; padding: 6px 12px;
width: 100%; width: 100%;
`; `;
type TeamRoleManagerPopupProps = { type TeamRoleManagerPopupProps = {
currentUserID: string; currentUserID: string;
subject: User; subject: User;
@ -253,7 +254,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
{subject.role && subject.role.code === 'owner' && ( {subject.role && subject.role.code === 'owner' && (
<> <>
<Separator /> <Separator />
<WarningText>You can't change roles because there must be an owner.</WarningText> <WarningText>You can not change roles because there must be an owner.</WarningText>
</> </>
)} )}
</MiniProfileActions> </MiniProfileActions>
@ -510,7 +511,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
<MemberList> <MemberList>
{data.findTeam.members.map(member => ( {data.findTeam.members.map(member => (
<MemberListItem> <MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} /> <MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
<MemberListItemDetails> <MemberListItemDetails>
<MemberItemName>{member.fullName}</MemberItemName> <MemberItemName>{member.fullName}</MemberItemName>
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername> <MemberItemUsername>{`@${member.username}`}</MemberItemUsername>

View File

@ -1,24 +1,22 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import styled, { css } from 'styled-components/macro'; import styled from 'styled-components/macro';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
import { Route, Switch, useRouteMatch, Redirect } from 'react-router'; import { Route, Switch, useRouteMatch, Redirect, useParams, useHistory } from 'react-router';
import Members from './Members';
import Projects from './Projects';
import { import {
useGetTeamQuery, useGetTeamQuery,
useDeleteTeamMutation, useDeleteTeamMutation,
GetProjectsDocument, GetProjectsDocument,
GetProjectsQuery, GetProjectsQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import { useParams, useHistory, useLocation } from 'react-router';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import { History } from 'history'; import { History } from 'history';
import produce from 'immer'; import produce from 'immer';
import { TeamSettings, DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings'; import { TeamSettings, DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import UserContext, { PermissionObjectType, PermissionLevel, useCurrentUser } from 'App/context'; import { PermissionObjectType, PermissionLevel, useCurrentUser } from 'App/context';
import NOOP from 'shared/utils/noop';
import Members from './Members';
import Projects from './Projects';
const OuterWrapper = styled.div` const OuterWrapper = styled.div`
display: flex; display: flex;
@ -117,7 +115,7 @@ const Teams = () => {
setCurrentTab(tab); setCurrentTab(tab);
}} }}
popupContent={<TeamPopup history={history} name={data.findTeam.name} teamID={teamID} />} popupContent={<TeamPopup history={history} name={data.findTeam.name} teamID={teamID} />}
onSaveProjectName={() => {}} onSaveProjectName={NOOP}
projectID={null} projectID={null}
name={data.findTeam.name} name={data.findTeam.name}
/> />

View File

@ -81,7 +81,7 @@ const errorLink = onError(({ graphQLErrors, networkError, operation, forward })
} }
} }
if (networkError) { if (networkError) {
console.log(`[Network error]: ${networkError}`); console.log(`[Network error]: ${networkError}`); // eslint-disable-line no-console
} }
return undefined; return undefined;
}); });
@ -122,12 +122,13 @@ const client = new ApolloClient({
link: ApolloLink.from([ link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => { onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) { if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => graphQLErrors.forEach(
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), ({ message, locations, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), // eslint-disable-line no-console
); );
} }
if (networkError) { if (networkError) {
console.log(`[Network error]: ${networkError}`); console.log(`[Network error]: ${networkError}`); // eslint-disable-line no-console
} }
}), }),
errorLink, errorLink,

View File

@ -1,10 +1,10 @@
import React, { useRef } from 'react'; import React from 'react';
import Admin from '.';
import { theme } from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import theme from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import Admin from '.';
export default { export default {
component: Admin, component: Admin,

View File

@ -1,15 +1,13 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import { UserPlus, Checkmark } from 'shared/icons';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
import Select from 'shared/components/Select'; import Select from 'shared/components/Select';
import { User, Plus, Lock, Pencil, Trash } from 'shared/icons'; import { User, UserPlus, Checkmark } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import { RoleCode, useUpdateUserRoleMutation } from 'shared/generated/graphql'; import { RoleCode, useUpdateUserRoleMutation } from 'shared/generated/graphql';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import NOOP from 'shared/utils/noop';
export const RoleCheckmark = styled(Checkmark)` export const RoleCheckmark = styled(Checkmark)`
padding-left: 4px; padding-left: 4px;
@ -69,6 +67,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
} }
`} `}
`; `;
export const Content = styled.div` export const Content = styled.div`
padding: 0 12px 12px; padding: 0 12px 12px;
`; `;
@ -100,6 +99,7 @@ export const RemoveMemberButton = styled(Button)`
padding: 6px 12px; padding: 6px 12px;
width: 100%; width: 100%;
`; `;
type TeamRoleManagerPopupProps = { type TeamRoleManagerPopupProps = {
user: User; user: User;
users: Array<User>; users: Array<User>;
@ -120,7 +120,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
onChangeRole, onChangeRole,
}) => { }) => {
const { hidePopup, setTab } = usePopup(); const { hidePopup, setTab } = usePopup();
const [userPass, setUserPass] = useState({ pass: '', passConfirm: '' }); const [userPass] = useState({ pass: '', passConfirm: '' });
const [deleteUser, setDeleteUser] = useState<{ label: string; value: string } | null>(null); const [deleteUser, setDeleteUser] = useState<{ label: string; value: string } | null>(null);
const hasOwned = user.owned.projects.length !== 0 || user.owned.teams.length !== 0; const hasOwned = user.owned.projects.length !== 0 || user.owned.teams.length !== 0;
return ( return (
@ -195,7 +195,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
{user.role && user.role.code === 'owner' && ( {user.role && user.role.code === 'owner' && (
<> <>
<Separator /> <Separator />
<WarningText>You can't change roles because there must be an owner.</WarningText> <WarningText>You can not change roles because there must be an owner.</WarningText>
</> </>
)} )}
</MiniProfileActions> </MiniProfileActions>
@ -209,7 +209,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<> <>
<DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription> <DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription>
<DeleteDescription> <DeleteDescription>
Choose a new user to take over ownership of this user's teams & projects. Choose a new user to take over ownership of the users teams & projects.
</DeleteDescription> </DeleteDescription>
<UserSelect <UserSelect
onChange={v => setDeleteUser(v)} onChange={v => setDeleteUser(v)}
@ -239,7 +239,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
Removing this user from the organzation will remove them from assigned tasks, projects, and teams. Removing this user from the organzation will remove them from assigned tasks, projects, and teams.
</DeleteDescription> </DeleteDescription>
<DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription> <DeleteDescription>{`The user is the owner of ${user.owned.projects.length} projects & ${user.owned.teams.length} teams.`}</DeleteDescription>
<UserSelect onChange={() => {}} value={null} options={users.map(u => ({ label: u.fullName, value: u.id }))} /> <UserSelect onChange={NOOP} value={null} options={users.map(u => ({ label: u.fullName, value: u.id }))} />
<UserPassConfirmButton <UserPassConfirmButton
onClick={() => { onClick={() => {
// onDeleteUser(); // onDeleteUser();
@ -253,7 +253,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}> <Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
<Content> <Content>
<DeleteDescription> <DeleteDescription>
You can either set the user's new password directly or send the user an email allowing them to reset their You can either set the users new password directly or send the user an email allowing them to reset their
own password. own password.
</DeleteDescription> </DeleteDescription>
<UserPassBar> <UserPassBar>
@ -291,6 +291,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
</> </>
); );
}; };
const UserSelect = styled(Select)` const UserSelect = styled(Select)`
margin: 8px 0; margin: 8px 0;
padding: 8px 0; padding: 8px 0;
@ -299,6 +300,7 @@ const UserSelect = styled(Select)`
const NewUserPassInput = styled(Input)` const NewUserPassInput = styled(Input)`
margin: 8px 0; margin: 8px 0;
`; `;
const InviteMemberButton = styled(Button)` const InviteMemberButton = styled(Button)`
padding: 7px 12px; padding: 7px 12px;
`; `;
@ -307,6 +309,7 @@ const UserPassBar = styled.div`
display: flex; display: flex;
padding-top: 8px; padding-top: 8px;
`; `;
const UserPassConfirmButton = styled(Button)` const UserPassConfirmButton = styled(Button)`
width: 100%; width: 100%;
padding: 7px 12px; padding: 7px 12px;
@ -397,104 +400,6 @@ const MemberListWrapper = styled.div`
flex: 1 1; flex: 1 1;
`; `;
const Root = styled.div`
.ag-theme-material {
--ag-foreground-color: #c2c6dc;
--ag-secondary-foreground-color: #c2c6dc;
--ag-background-color: transparent;
--ag-header-background-color: transparent;
--ag-header-foreground-color: #c2c6dc;
--ag-border-color: #414561;
--ag-row-hover-color: #262c49;
--ag-header-cell-hover-background-color: #262c49;
--ag-checkbox-unchecked-color: #c2c6dc;
--ag-checkbox-indeterminate-color: rgba(115, 103, 240);
--ag-selected-row-background-color: #262c49;
--ag-material-primary-color: rgba(115, 103, 240);
--ag-material-accent-color: rgba(115, 103, 240);
}
.ag-theme-material ::-webkit-scrollbar {
width: 12px;
}
.ag-theme-material ::-webkit-scrollbar-track {
background: #262c49;
border-radius: 20px;
}
.ag-theme-material ::-webkit-scrollbar-thumb {
background: #7367f0;
border-radius: 20px;
}
.ag-header-cell-text {
color: #fff;
font-weight: 700;
}
`;
const Header = styled.div`
border-bottom: 1px solid #e2e2e2;
flex-direction: row;
box-sizing: border-box;
display: flex;
white-space: nowrap;
width: 100%;
overflow: hidden;
background: transparent;
border-bottom-color: #414561;
color: #fff;
height: 112px;
min-height: 112px;
`;
const EditUserIcon = styled(Pencil)``;
const LockUserIcon = styled(Lock)``;
const DeleteUserIcon = styled(Trash)``;
type ActionButtonProps = {
onClick: ($target: React.RefObject<HTMLElement>) => void;
};
const ActionButtonWrapper = styled.div`
margin-right: 8px;
cursor: pointer;
display: inline-flex;
`;
const ActionButton: React.FC<ActionButtonProps> = ({ onClick, children }) => {
const $wrapper = useRef<HTMLDivElement>(null);
return (
<ActionButtonWrapper onClick={() => onClick($wrapper)} ref={$wrapper}>
{children}
</ActionButtonWrapper>
);
};
const ActionButtons = (params: any) => {
return (
<>
<ActionButton onClick={() => {}}>
<EditUserIcon width={16} height={16} />
</ActionButton>
<ActionButton onClick={$target => params.onDeleteUser($target, params.value)}>
<DeleteUserIcon width={16} height={16} />
</ActionButton>
</>
);
};
const Wrapper = styled.div`
background: #eff2f7;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
`;
const Container = styled.div` const Container = styled.div`
padding: 2.2rem; padding: 2.2rem;
display: flex; display: flex;
@ -637,7 +542,7 @@ const Admin: React.FC<AdminProps> = ({
'You cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.'; 'You cant leave because you are the only admin. To make another user an admin, click their avatar, select “Change permissions…”, and select “Admin”.';
const [currentTop, setTop] = useState(initialTab * 48); const [currentTop, setTop] = useState(initialTab * 48);
const [currentTab, setTab] = useState(initialTab); const [currentTab, setTab] = useState(initialTab);
const { showPopup, hidePopup } = usePopup(); const { showPopup } = usePopup();
const $tabNav = useRef<HTMLDivElement>(null); const $tabNav = useRef<HTMLDivElement>(null);
const [updateUserRole] = useUpdateUserRoleMutation(); const [updateUserRole] = useUpdateUserRoleMutation();
@ -690,7 +595,7 @@ const Admin: React.FC<AdminProps> = ({
const projectTotal = member.owned.projects.length + member.member.projects.length; const projectTotal = member.owned.projects.length + member.member.projects.length;
return ( return (
<MemberListItem> <MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} /> <MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
<MemberListItemDetails> <MemberListItemDetails>
<MemberItemName>{member.fullName}</MemberItemName> <MemberItemName>{member.fullName}</MemberItemName>
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername> <MemberItemUsername>{`@${member.username}`}</MemberItemUsername>

View File

@ -2,7 +2,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import Button from '.'; import Button from '.';

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons'; import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons'; import { faClock, faEye } from '@fortawesome/free-regular-svg-icons';
import { import {
EditorTextarea, EditorTextarea,
CardMember, CardMember,
@ -24,7 +24,6 @@ import {
CardTitle, CardTitle,
CardMembers, CardMembers,
} from './Styles'; } from './Styles';
import { CheckSquare } from 'shared/icons';
type DueDate = { type DueDate = {
isPastDue: boolean; isPastDue: boolean;
@ -209,7 +208,7 @@ const Card = React.forwardRef(
) : ( ) : (
<CardTitle> <CardTitle>
{complete && <CompleteIcon width={16} height={16} />} {complete && <CompleteIcon width={16} height={16} />}
{`${title}${position ? ' - ' + position : ''}`} {`${title}${position ? ` - ${position}` : ''}`}
</CardTitle> </CardTitle>
)} )}
<ListCardBadges> <ListCardBadges>
@ -236,9 +235,9 @@ const Card = React.forwardRef(
width={8} width={8}
height={8} height={8}
/> />
<ListCardBadgeText <ListCardBadgeText color={checklists.complete === checklists.total ? 'success' : 'normal'}>
color={checklists.complete === checklists.total ? 'success' : 'normal'} {`${checklists.complete}/${checklists.total}`}
>{`${checklists.complete}/${checklists.total}`}</ListCardBadgeText> </ListCardBadgeText>
</ListCardBadge> </ListCardBadge>
)} )}
</ListCardBadges> </ListCardBadges>

View File

@ -2,9 +2,10 @@ import React, { useState } from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import produce from 'immer'; import produce from 'immer';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import NOOP from 'shared/utils/noop';
import Checklist, { ChecklistItem } from '.'; import Checklist, { ChecklistItem } from '.';
export default { export default {
@ -132,7 +133,7 @@ export const Default = () => {
}} }}
onToggleItem={onToggleItem} onToggleItem={onToggleItem}
> >
{items.map((item, idx) => ( {items.map(item => (
<ChecklistItem <ChecklistItem
key={item.id} key={item.id}
wrapperProps={{}} wrapperProps={{}}
@ -141,9 +142,9 @@ export const Default = () => {
itemID={item.id} itemID={item.id}
name={item.name} name={item.name}
complete={item.complete} complete={item.complete}
onDeleteItem={() => {}} onDeleteItem={NOOP}
onChangeName={() => {}} onChangeName={NOOP}
onToggleItem={() => {}} onToggleItem={NOOP}
/> />
))} ))}
</Checklist> </Checklist>

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import { User } from 'shared/icons';
import Input from '.'; import Input from '.';
import { User } from 'shared/icons';
export default { export default {
component: Input, component: Input,

View File

@ -1,8 +1,8 @@
import React, { useRef } from 'react'; import React from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import { Popup } from '../PopupMenu'; import { Popup } from '../PopupMenu';
import DueDateManager from '.'; import DueDateManager from '.';

View File

@ -2,22 +2,13 @@ import React, { useState, useEffect, forwardRef } from 'react';
import moment from 'moment'; import moment from 'moment';
import styled from 'styled-components'; import styled from 'styled-components';
import DatePicker from 'react-datepicker'; import DatePicker from 'react-datepicker';
import { Cross } from 'shared/icons';
import _ from 'lodash'; import _ from 'lodash';
import {
Wrapper,
ActionWrapper,
RemoveDueDate,
DueDateInput,
DueDatePickerWrapper,
ConfirmAddDueDate,
CancelDueDate,
} from './Styles';
import 'react-datepicker/dist/react-datepicker.css'; import 'react-datepicker/dist/react-datepicker.css';
import { getYear, getMonth } from 'date-fns'; import { getYear, getMonth } from 'date-fns';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import NOOP from 'shared/utils/noop';
import { Wrapper, ActionWrapper, RemoveDueDate, DueDateInput, DueDatePickerWrapper, ConfirmAddDueDate } from './Styles';
type DueDateManagerProps = { type DueDateManagerProps = {
task: Task; task: Task;
@ -220,7 +211,10 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
</HeaderButton> </HeaderButton>
<HeaderSelectLabel> <HeaderSelectLabel>
{months[date.getMonth()]} {months[date.getMonth()]}
<HeaderSelect value={getYear(date)} onChange={({ target: { value } }) => changeYear(parseInt(value))}> <HeaderSelect
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}
>
{years.map(option => ( {years.map(option => (
<option key={option} value={option}> <option key={option} value={option}>
{option} {option}
@ -255,7 +249,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
/> />
</DueDatePickerWrapper> </DueDatePickerWrapper>
<ActionWrapper> <ActionWrapper>
<ConfirmAddDueDate type="submit" onClick={() => {}}> <ConfirmAddDueDate type="submit" onClick={NOOP}>
Save Save
</ConfirmAddDueDate> </ConfirmAddDueDate>
<RemoveDueDate <RemoveDueDate

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import { User } from 'shared/icons';
import Input from '.'; import Input from '.';
import { User } from 'shared/icons';
export default { export default {
component: Input, component: Input,

View File

@ -3,6 +3,7 @@ import { action } from '@storybook/addon-actions';
import Card from 'shared/components/Card'; import Card from 'shared/components/Card';
import CardComposer from 'shared/components/CardComposer'; import CardComposer from 'shared/components/CardComposer';
import LabelColors from 'shared/constants/labelColors'; import LabelColors from 'shared/constants/labelColors';
import NOOP from 'shared/utils/noop';
import List, { ListCards } from '.'; import List, { ListCards } from '.';
export default { export default {
@ -60,7 +61,7 @@ export const Default = () => {
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<CardComposer onClose={() => {}} onCreateCard={name => {}} isOpen={false} /> <CardComposer onClose={NOOP} onCreateCard={NOOP} isOpen={false} />
</ListCards> </ListCards>
</List> </List>
); );
@ -77,7 +78,7 @@ export const WithCardComposer = () => {
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
<CardComposer onClose={() => {}} onCreateCard={name => {}} isOpen /> <CardComposer onClose={NOOP} onCreateCard={NOOP} isOpen />
</ListCards> </ListCards>
</List> </List>
); );
@ -108,7 +109,7 @@ export const WithCard = () => {
checklists={{ complete: 1, total: 4 }} checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }} dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
/> />
<CardComposer onClose={() => {}} onCreateCard={name => {}} isOpen={false} /> <CardComposer onClose={NOOP} onCreateCard={NOOP} isOpen={false} />
</ListCards> </ListCards>
</List> </List>
); );
@ -138,7 +139,7 @@ export const WithCardAndComposer = () => {
checklists={{ complete: 1, total: 4 }} checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }} dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
/> />
<CardComposer onClose={() => {}} onCreateCard={name => {}} isOpen /> <CardComposer onClose={NOOP} onCreateCard={NOOP} isOpen />
</ListCards> </ListCards>
</List> </List>
); );

View File

@ -81,7 +81,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
position: newPosition, position: newPosition,
}); });
} else { } else {
throw { error: 'task group can not be found' }; throw new Error('task group can not be found');
} }
} else { } else {
const curTaskGroup = taskGroups.findIndex( const curTaskGroup = taskGroups.findIndex(

View File

@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
import { Checkmark } from 'shared/icons'; import { Checkmark } from 'shared/icons';
import NOOP from 'shared/utils/noop';
const CardCheckmark = styled(Checkmark)` const CardCheckmark = styled(Checkmark)`
position: absolute; position: absolute;
@ -76,7 +77,7 @@ const Member: React.FC<MemberProps> = ({
} }
}} }}
> >
<TaskAssignee onMemberProfile={() => {}} size={28} member={member} /> <TaskAssignee onMemberProfile={NOOP} size={28} member={member} />
{showName && <CardMemberName>{member.fullName}</CardMemberName>} {showName && <CardMemberName>{member.fullName}</CardMemberName>}
{showCheckmark && <CardCheckmark width={12} height={12} />} {showCheckmark && <CardCheckmark width={12} height={12} />}
</CardMemberWrapper> </CardMemberWrapper>

View File

@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Checkmark } from 'shared/icons';
import Member from 'shared/components/Member';
import { import {
MemberName, MemberName,
ProfileIcon, ProfileIcon,
@ -12,8 +13,6 @@ import {
BoardMemberListItemContent, BoardMemberListItemContent,
ActiveIconWrapper, ActiveIconWrapper,
} from './Styles'; } from './Styles';
import { Checkmark } from 'shared/icons';
import Member from 'shared/components/Member';
type MemberManagerProps = { type MemberManagerProps = {
availableMembers: Array<TaskUser>; availableMembers: Array<TaskUser>;

View File

@ -160,7 +160,7 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
{user.role && user.role.code === 'owner' && ( {user.role && user.role.code === 'owner' && (
<> <>
<Separator /> <Separator />
<WarningText>You can't change roles because there must be an owner.</WarningText> <WarningText>You can not change roles because there must be an owner.</WarningText>
</> </>
)} )}
</MiniProfileActions> </MiniProfileActions>

View File

@ -1,9 +1,8 @@
import React, { useState, useRef, createRef } from 'react'; import React from 'react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import NOOP from 'shared/utils/noop';
import NewProject from '.'; import NewProject from '.';
export default { export default {
@ -26,7 +25,7 @@ export const Default = () => {
initialTeamID={null} initialTeamID={null}
onCreateProject={action('create project')} onCreateProject={action('create project')}
teams={[{ name: 'General', id: 'general', createdAt: '' }]} teams={[{ name: 'General', id: 'general', createdAt: '' }]}
onClose={() => {}} onClose={NOOP}
/> />
</> </>
); );

View File

@ -4,6 +4,19 @@ import { mixin } from 'shared/utils/styles';
import Select from 'react-select'; import Select from 'react-select';
import { ArrowLeft, Cross } from 'shared/icons'; import { ArrowLeft, Cross } from 'shared/icons';
function getBackgroundColor(isDisabled: boolean, isSelected: boolean, isFocused: boolean) {
if (isDisabled) {
return null;
}
if (isSelected) {
return mixin.darken('#262c49', 0.25);
}
if (isFocused) {
return mixin.darken('#262c49', 0.15);
}
return null;
}
const Overlay = styled.div` const Overlay = styled.div`
z-index: 10000; z-index: 10000;
background: #262c49; background: #262c49;
@ -149,14 +162,8 @@ const colourStyles = {
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => { option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
return { return {
...styles, ...styles,
backgroundColor: isDisabled backgroundColor: getBackgroundColor(isDisabled, isSelected, isFocused),
? null color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc', // eslint-disable-line
: isSelected
? mixin.darken('#262c49', 0.25)
: isFocused
? mixin.darken('#262c49', 0.15)
: null,
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc',
cursor: isDisabled ? 'not-allowed' : 'default', cursor: isDisabled ? 'not-allowed' : 'default',
':active': { ':active': {
...styles[':active'], ...styles[':active'],

View File

@ -1,381 +0,0 @@
import React, { useState, useRef, createRef } from 'react';
import { action } from '@storybook/addon-actions';
import LabelColors from 'shared/constants/labelColors';
import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager';
import MiniProfile from 'shared/components/MiniProfile';
import styled from 'styled-components';
import produce from 'immer';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
export default {
component: PopupMenu,
title: 'PopupMenu',
parameters: {
backgrounds: [
{ name: 'white', value: '#ffffff', default: true },
{ name: 'gray', value: '#f8f8f8' },
],
},
};
const labelData: Array<ProjectLabel> = [
{
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
colorHex: LabelColors.BLUE,
name: 'blue',
position: 1,
},
},
];
const OpenLabelBtn = styled.span``;
type TabProps = {
tab: number;
};
const LabelManagerEditor = () => {
const [labels, setLabels] = useState(labelData);
const [currentLabel, setCurrentLabel] = useState('');
const { setTab } = usePopup();
return (
<>
<Popup title="Labels" tab={0} onClose={action('on close')}>
<LabelManager
labels={labels}
onLabelCreate={() => {
setTab(2);
}}
onLabelEdit={labelId => {
setCurrentLabel(labelId);
setTab(1);
}}
onLabelToggle={labelId => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.id === labelId);
if (idx !== -1) {
draftState[idx] = { ...draftState[idx] };
}
}),
);
}}
/>
</Popup>
<Popup onClose={action('on close')} title="Edit label" tab={1}>
<LabelEditor
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
label={labels.find(label => label.id === currentLabel) ?? null}
onLabelEdit={(_labelId, name, color) => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.id === currentLabel);
if (idx !== -1) {
draftState[idx] = {
...draftState[idx],
name,
labelColor: {
...draftState[idx].labelColor,
name: color.name ?? '',
colorHex: color.colorHex,
},
};
}
}),
);
setTab(0);
}}
/>
</Popup>
<Popup onClose={action('on close')} title="Create new label" tab={2}>
<LabelEditor
label={null}
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
onLabelEdit={(_labelId, name, color) => {
setLabels([
...labels,
{
id: name,
name,
createdDate: new Date().toString(),
labelColor: {
id: color.id,
colorHex: color.colorHex,
name: color.name ?? '',
position: 1,
},
},
]);
setTab(0);
}}
/>
</Popup>
</>
);
};
const OpenLabelsButton = () => {
const $buttonRef = createRef<HTMLButtonElement>();
const [currentLabel, setCurrentLabel] = useState('');
const [labels, setLabels] = useState(labelData);
const { showPopup, setTab } = usePopup();
return (
<OpenLabelBtn
ref={$buttonRef}
onClick={() => {
showPopup($buttonRef, <LabelManagerEditor />);
}}
>
Open
</OpenLabelBtn>
);
};
export const LabelsPopup = () => {
const [isPopupOpen, setPopupOpen] = useState(false);
return (
<PopupProvider>
<OpenLabelsButton />
</PopupProvider>
);
};
export const LabelsLabelEditor = () => {
const [isPopupOpen, setPopupOpen] = useState(false);
return (
<>
{isPopupOpen && (
<PopupMenu
onPrevious={action('on previous')}
title="Change Label"
top={10}
onClose={() => setPopupOpen(false)}
left={10}
>
<LabelEditor
label={labelData[0]}
onLabelEdit={action('label edit')}
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
/>
</PopupMenu>
)}
<button type="submit" onClick={() => setPopupOpen(true)}>
Open
</button>
</>
);
};
const initalState = { left: 0, top: 0, isOpen: false };
export const ListActionsPopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
{popupData.isOpen && (
<PopupMenu
title="List Actions"
top={popupData.top}
onClose={() => setPopupData(initalState)}
left={popupData.left}
>
<ListActions taskGroupID="1" onArchiveTaskGroup={action('archive task group')} />
</PopupMenu>
)}
<button
ref={$buttonRef}
type="submit"
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</button>
</>
);
};
export const MemberManagerPopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
<NormalizeStyles />
<BaseStyles />
{popupData.isOpen && (
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager
availableMembers={[
{
id: '1',
fullName: 'Jordan Knott',
profileIcon: { bgColor: null, url: null, initials: null },
},
]}
activeMembers={[]}
onMemberChange={action('member change')}
/>
</PopupMenu>
)}
<span
ref={$buttonRef}
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</span>
</>
);
};
export const DueDateManagerPopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
<NormalizeStyles />
<BaseStyles />
{popupData.isOpen && (
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<DueDateManager
onRemoveDueDate={action('remove due date')}
task={{
id: '1',
taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world',
position: 1,
labels: [
{
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
},
},
],
description: 'hello!',
assigned: [
{
id: '1',
profileIcon: { bgColor: null, url: null, initials: null },
fullName: 'Jordan Knott',
},
],
}}
onCancel={action('cancel')}
onDueDateChange={action('due date change')}
/>
</PopupMenu>
)}
<span
style={{
width: '60px',
textAlign: 'center',
margin: '25px auto',
cursor: 'pointer',
}}
ref={$buttonRef}
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</span>
</>
);
};
export const MiniProfilePopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
<NormalizeStyles />
<BaseStyles />
{popupData.isOpen && (
<PopupMenu
noHeader
title="Due Date"
top={popupData.top}
onClose={() => setPopupData(initalState)}
left={popupData.left}
>
<MiniProfile
user={{
id: '1',
fullName: 'Jordan Knott',
username: 'jordanthedev',
profileIcon: { url: null, bgColor: '#000', initials: 'JK' },
}}
bio="Stuff and things"
onRemoveFromTask={action('mini profile')}
/>
</PopupMenu>
)}
<span
style={{
width: '60px',
textAlign: 'center',
margin: '25px auto',
cursor: 'pointer',
color: '#fff',
background: '#f00',
}}
ref={$buttonRef}
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</span>
</>
);
};

View File

@ -1,6 +1,5 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import Input from '../Input';
import ControlledInput from 'shared/components/ControlledInput'; import ControlledInput from 'shared/components/ControlledInput';
export const Container = styled.div<{ export const Container = styled.div<{

View File

@ -2,6 +2,7 @@ import React, { useRef, createContext, RefObject, useState, useContext, useEffec
import { Cross, AngleLeft } from 'shared/icons'; import { Cross, AngleLeft } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import NOOP from 'shared/utils/noop';
import produce from 'immer'; import produce from 'immer';
import { import {
Container, Container,
@ -52,10 +53,10 @@ PopupContainer.defaultProps = {
}; };
const PopupContext = createContext<PopupContextState>({ const PopupContext = createContext<PopupContextState>({
show: () => {}, show: NOOP,
setTab: () => {}, setTab: NOOP,
getCurrentTab: () => 0, getCurrentTab: () => 0,
hide: () => {}, hide: NOOP,
}); });
export const usePopup = () => { export const usePopup = () => {

View File

@ -5,6 +5,7 @@ import CardComposer from 'shared/components/CardComposer';
import LabelColors from 'shared/constants/labelColors'; import LabelColors from 'shared/constants/labelColors';
import List, { ListCards } from 'shared/components/List'; import List, { ListCards } from 'shared/components/List';
import QuickCardEditor from 'shared/components/QuickCardEditor'; import QuickCardEditor from 'shared/components/QuickCardEditor';
import NOOP from 'shared/utils/noop';
export default { export default {
component: QuickCardEditor, component: QuickCardEditor,
@ -71,7 +72,7 @@ export const Default = () => {
isComposerOpen={false} isComposerOpen={false}
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
onExtraMenuOpen={(taskGroupID, $targetRef) => {}} onExtraMenuOpen={NOOP}
> >
<ListCards> <ListCards>
<Card <Card
@ -81,7 +82,7 @@ export const Default = () => {
ref={$cardRef} ref={$cardRef}
title={task.name} title={task.name}
onClick={action('on click')} onClick={action('on click')}
onContextMenu={e => { onContextMenu={() => {
setTarget($cardRef); setTarget($cardRef);
setEditorOpen(true); setEditorOpen(true);
}} }}
@ -90,7 +91,7 @@ export const Default = () => {
checklists={{ complete: 1, total: 4 }} checklists={{ complete: 1, total: 4 }}
dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }} dueDate={{ isPastDue: false, formattedDate: 'Oct 26, 2020' }}
/> />
<CardComposer onClose={() => {}} onCreateCard={name => {}} isOpen={false} /> <CardComposer onClose={NOOP} onCreateCard={NOOP} isOpen={false} />
</ListCards> </ListCards>
</List> </List>
</> </>

View File

@ -3,6 +3,19 @@ import Select from 'react-select';
import styled from 'styled-components'; import styled from 'styled-components';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
function getBackgroundColor(isDisabled: boolean, isSelected: boolean, isFocused: boolean) {
if (isDisabled) {
return null;
}
if (isSelected) {
return mixin.darken('#262c49', 0.25);
}
if (isFocused) {
return mixin.darken('#262c49', 0.15);
}
return null;
}
const colourStyles = { const colourStyles = {
control: (styles: any, data: any) => { control: (styles: any, data: any) => {
return { return {
@ -43,14 +56,8 @@ const colourStyles = {
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => { option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
return { return {
...styles, ...styles,
backgroundColor: isDisabled backgroundColor: getBackgroundColor(isDisabled, isSelected, isFocused),
? null color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc', // eslint-disable-line
: isSelected
? mixin.darken('#262c49', 0.25)
: isFocused
? mixin.darken('#262c49', 0.15)
: null,
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc',
cursor: isDisabled ? 'not-allowed' : 'default', cursor: isDisabled ? 'not-allowed' : 'default',
':active': { ':active': {
...styles[':active'], ...styles[':active'],

View File

@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
import { Bin, Cross, Plus } from 'shared/icons'; import { Bin, Cross, Plus } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';
import { import {
isPositionChanged, isPositionChanged,
@ -56,7 +57,6 @@ import {
MetaDetailContent, MetaDetailContent,
} from './Styles'; } from './Styles';
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist'; import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
import styled from 'styled-components';
const ChecklistContainer = styled.div``; const ChecklistContainer = styled.div``;
@ -254,7 +254,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index); const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
onChecklistDrop({ ...droppedGroup, position: newPosition }); onChecklistDrop({ ...droppedGroup, position: newPosition });
} else { } else {
throw { error: 'task group can not be found' }; throw new Error('task group can not be found');
} }
} else { } else {
const targetChecklist = task.checklists.findIndex( const targetChecklist = task.checklists.findIndex(
@ -314,7 +314,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskMeta> <TaskMeta>
{task.taskGroup.name && ( {task.taskGroup.name && (
<TaskGroupLabel> <TaskGroupLabel>
in list <TaskGroupLabelName>{task.taskGroup.name}</TaskGroupLabelName> {`in list ${(<TaskGroupLabelName>{task.taskGroup.name}</TaskGroupLabelName>)}`}
</TaskGroupLabel> </TaskGroupLabel>
)} )}
</TaskMeta> </TaskMeta>
@ -442,9 +442,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
complete={item.complete} complete={item.complete}
onDeleteItem={onDeleteItem} onDeleteItem={onDeleteItem}
onChangeName={onChangeItemName} onChangeName={onChangeItemName}
onToggleItem={(itemID, complete) => onToggleItem={(itemID, complete) => {
onToggleChecklistItem(item.id, complete) onToggleChecklistItem(item.id, complete);
} }}
/> />
)} )}
</Draggable> </Draggable>

View File

@ -5,6 +5,7 @@ import Button from 'shared/components/Button';
import { Taskcafe } from 'shared/icons'; import { Taskcafe } from 'shared/icons';
import { NavLink, Link } from 'react-router-dom'; import { NavLink, Link } from 'react-router-dom';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
export const ProjectMember = styled(TaskAssignee)<{ zIndex: number }>` export const ProjectMember = styled(TaskAssignee)<{ zIndex: number }>`
z-index: ${props => props.zIndex}; z-index: ${props => props.zIndex};
position: relative; position: relative;

View File

@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle } from 'shared/icons'; import { Home, Star, Bell, AngleDown, BarChart, CheckCircle } from 'shared/icons';
import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import ProfileIcon from 'shared/components/ProfileIcon'; import ProfileIcon from 'shared/components/ProfileIcon';
import TaskAssignee from 'shared/components/TaskAssignee'; import TaskAssignee from 'shared/components/TaskAssignee';
@ -31,7 +32,6 @@ import {
ProjectMember, ProjectMember,
ProjectMembers, ProjectMembers,
} from './Styles'; } from './Styles';
import { Link } from 'react-router-dom';
const HomeDashboard = styled(Home)``; const HomeDashboard = styled(Home)``;

View File

@ -18,9 +18,9 @@ const AccessAccount = ({ width, height }: Props) => {
gradientTransform="translate(-3.62 1.57)" gradientTransform="translate(-3.62 1.57)"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop offset="0" stop-color="gray" stop-opacity=".25" /> <stop offset="0" stopColor="gray" stopOpacity=".25" />
<stop offset=".54" stop-color="gray" stop-opacity=".12" /> <stop offset=".54" stopColor="gray" stopOpacity=".12" />
<stop offset="1" stop-color="gray" stop-opacity=".1" /> <stop offset="1" stopColor="gray" stopOpacity=".1" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="b" id="b"

View File

@ -0,0 +1 @@
export default function NOOP() {} // eslint-disable-line @typescript-eslint/no-empty-function

View File

@ -1,8 +0,0 @@
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}

View File

@ -28,6 +28,21 @@ func (Frontend) Install() error {
return sh.RunV("yarn", "--cwd", "frontend", "install") return sh.RunV("yarn", "--cwd", "frontend", "install")
} }
// Eslint runs eslint on the frontend source
func (Frontend) Eslint() error {
return sh.RunV("yarn", "--cwd", "frontend", "lint")
}
// Tsc runs tsc on the frontend source
func (Frontend) Tsc() error {
return sh.RunV("yarn", "--cwd", "frontend", "tsc")
}
// Lint the frontend source
func (Frontend) Lint() {
mg.SerialDeps(Frontend.Eslint, Frontend.Tsc)
}
// Build the React frontend // Build the React frontend
func (Frontend) Build() error { func (Frontend) Build() error {
return sh.RunV("yarn", "--cwd", "frontend", "build") return sh.RunV("yarn", "--cwd", "frontend", "build")