Compare commits

...

2 Commits

Author SHA1 Message Date
Jordan Knott
9214508ca2 feat: add pre commit hook to lint frontend & fix warnings 2020-08-23 17:27:32 -05:00
Jordan Knott
b6194d552f refactor: update project name in tmuxinator 2020-08-23 15:38:20 -05:00
47 changed files with 260 additions and 736 deletions

View File

@ -1,4 +1,13 @@
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:
- id: check-yaml
- id: end-of-file-fixer

View File

@ -1,14 +1,12 @@
name: citadel
name: taskcafe
root: .
on_project_start: docker start test-db
windows:
- services:
root: ./
panes:
- api:
- go run cmd/citadel/main.go web
- go run cmd/taskcafe/main.go web
- yarn:
- cd frontend
- yarn start
@ -19,8 +17,8 @@ windows:
- api/editor:
root: ./
panes:
- vim cmd/citadel/main.go
- vim cmd/taskcafe/main.go
- database:
root: ./
panes:
- pgcli postgres://postgres:test@localhost:5432/citadel
- pgcli postgres://taskcafe:taskcafe_test@localhost:5432/taskcafe

View File

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

View File

@ -1,5 +1,5 @@
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 Dashboard from 'Dashboard';
@ -20,11 +20,12 @@ const MainContent = styled.div`
flex-direction: column;
flex-grow: 1;
`;
type RoutesProps = {
history: H.History;
};
const Routes = ({history}: RoutesProps) => (
const Routes: React.FC<RoutesProps> = () => (
<Switch>
<Route exact path="/login" component={Login} />
<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 = {
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 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 { useHistory } from 'react-router';
import { UserContext, PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context';
import { PermissionLevel, PermissionObjectType, useCurrentUser } from 'App/context';
import {
RoleCode,
useMeQuery,
@ -18,6 +18,7 @@ import produce from 'immer';
import { Link } from 'react-router-dom';
import MiniProfile from 'shared/components/MiniProfile';
import cache from 'App/cache';
import NOOP from 'shared/utils/noop';
const TeamContainer = styled.div`
display: flex;
@ -130,7 +131,7 @@ const ProjectFinder = () => {
return <span>loading</span>;
}
if (data) {
const { projects, teams, organizations } = data;
const { projects, teams } = data;
const projectTeams = teams.map(team => {
return {
id: team.id,
@ -238,7 +239,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab,
onSetTab,
menuType,
projectID,
teamID,
onChangeProjectOwner,
onChangeRole,
@ -248,19 +248,18 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onInviteUser,
onSaveProjectName,
onRemoveFromBoard,
nameOnly,
}) => {
const { user, setUserRoles, setUser } = useCurrentUser();
const { loading, data } = useMeQuery({
onCompleted: data => {
const { data } = useMeQuery({
onCompleted: response => {
if (user && user.roles) {
setUserRoles({
org: user.roles.org,
teams: data.me.teamRoles.reduce((map, obj) => {
teams: response.me.teamRoles.reduce((map, obj) => {
map.set(obj.teamID, obj.roleCode);
return map;
}, new Map<string, string>()),
projects: data.me.projectRoles.reduce((map, obj) => {
projects: response.me.projectRoles.reduce((map, obj) => {
map.set(obj.projectID, obj.roleCode);
return map;
}, 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 onLogout = () => {
fetch('/auth/logout', {
@ -367,7 +366,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onInviteUser={onInviteUser}
onChangeRole={onChangeRole}
onChangeProjectOwner={onChangeProjectOwner}
onNotificationClick={() => {}}
onNotificationClick={NOOP}
onSetTab={onSetTab}
onRemoveFromBoard={onRemoveFromBoard}
onDashboardClick={() => {

View File

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

View File

@ -1,13 +1,10 @@
import React, { useState, useEffect, useContext } from 'react';
import { useForm } from 'react-hook-form';
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 { setAccessToken } from 'shared/utils/accessToken';
import Login from 'shared/components/Login';
import UserContext from 'App/context';
import { Container, LoginWrapper } from './Styles';
const Auth = () => {
const [invalidLoginAttempt, setInvalidLoginAttempt] = useState(0);

View File

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

View File

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

View File

@ -1,39 +1,26 @@
import React, { useState, useRef, useContext, useEffect } from 'react';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import React, { useState, useRef } from 'react';
import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import LabelManagerEditor from '../LabelManagerEditor';
import styled, { css } from 'styled-components/macro';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
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 {
useUpdateProjectMemberRoleMutation,
useCreateProjectMemberMutation,
useDeleteProjectMemberMutation,
useSetTaskCompleteMutation,
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery,
useUpdateTaskGroupNameMutation,
useUpdateTaskNameMutation,
useUpdateProjectLabelMutation,
useCreateTaskMutation,
useDeleteProjectLabelMutation,
useDeleteTaskMutation,
useUpdateTaskLocationMutation,
useUpdateTaskGroupLocationMutation,
useCreateTaskGroupMutation,
useDeleteTaskGroupMutation,
useUpdateTaskDescriptionMutation,
useAssignTaskMutation,
DeleteTaskDocument,
FindProjectDocument,
useCreateProjectLabelMutation,
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
FindProjectQuery,
useUsersQuery,
} from 'shared/generated/graphql';
import QuickCardEditor from 'shared/components/QuickCardEditor';
@ -43,10 +30,9 @@ import SimpleLists from 'shared/components/Lists';
import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile';
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 NOOP from 'shared/utils/noop';
import LabelManagerEditor from 'Projects/Project/LabelManagerEditor';
const ProjectBar = styled.div`
display: flex;
@ -155,7 +141,6 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
const { showPopup, hidePopup } = usePopup();
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const { user } = useCurrentUser();
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const history = useHistory();
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
@ -308,12 +293,6 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
if (data) {
labelsRef.current = data.findProject.labels;
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 currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === taskID) : null;
if (currentTask) {
@ -381,7 +360,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`);
}}
onCardLabelClick={onCardLabelClick ?? (() => {})}
onCardLabelClick={onCardLabelClick ?? NOOP}
cardLabelVariant={cardLabelVariant ?? 'large'}
onTaskDrop={(droppedTask, previousTaskGroupID) => {
updateTaskLocation({
@ -427,7 +406,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
taskGroups={data.findProject.taskGroups}
onCreateTask={onCreateTask}
onCreateTaskGroup={onCreateList}
onCardMemberClick={($targetRef, taskID, memberID) => {
onCardMemberClick={($targetRef, _taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
if (member) {
showPopup(
@ -486,7 +465,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
</Popup>,
);
}}
onCardMemberClick={($targetRef, taskID, memberID) => {
onCardMemberClick={($targetRef, _taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
if (member) {
showPopup(
@ -516,8 +495,8 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
/>,
);
}}
onArchiveCard={(_listId: string, cardId: string) =>
deleteTask({
onArchiveCard={(_listId: string, cardId: string) => {
return deleteTask({
variables: { taskID: cardId },
update: client => {
updateApolloCache<FindProjectQuery>(
@ -533,8 +512,8 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
{ projectID },
);
},
})
}
});
}}
onOpenDueDatePopup={($targetRef, task) => {
showPopup(
$targetRef,
@ -549,7 +528,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
onCancel={NOOP}
/>
</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 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 { useRouteMatch, useHistory } from 'react-router';
import {
@ -22,7 +22,7 @@ import {
FindTaskDocument,
FindTaskQuery,
} from 'shared/generated/graphql';
import UserContext, { useCurrentUser } from 'App/context';
import { useCurrentUser } from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager';
import produce from 'immer';
@ -31,6 +31,7 @@ import Button from 'shared/components/Button';
import Input from 'shared/components/Input';
import { useForm } from 'react-hook-form';
import updateApolloCache from 'shared/utils/cache';
import NOOP from 'shared/utils/noop';
const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => {
const total = checklists.reduce((prev: any, next: any) => {
@ -75,15 +76,12 @@ const CreateChecklistInput = styled(Input)`
margin-bottom: 8px;
`;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type CreateChecklistPopupProps = {
onCreateChecklist: (data: CreateChecklistData) => void;
};
const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => {
const { register, handleSubmit, errors } = useForm<CreateChecklistData>();
const { register, handleSubmit } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data);
};
@ -132,9 +130,6 @@ const Details: React.FC<DetailsProps> = ({
const { user } = useCurrentUser();
const { showPopup, hidePopup } = usePopup();
const history = useHistory();
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [updateTaskChecklistLocation] = useUpdateTaskChecklistLocationMutation();
const [updateTaskChecklistItemLocation] = useUpdateTaskChecklistItemLocationMutation({
update: (client, response) => {
@ -148,7 +143,7 @@ const Details: React.FC<DetailsProps> = ({
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID);
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) {
draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
i => i.id !== checklistItem.id,
@ -398,7 +393,7 @@ const Details: React.FC<DetailsProps> = ({
if (member) {
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<Popup title={null} onClose={NOOP} tab={0}>
<MiniProfile
user={member}
bio="None"
@ -412,19 +407,19 @@ const Details: React.FC<DetailsProps> = ({
);
}
}}
onOpenAddMemberPopup={(task, $targetRef) => {
onOpenAddMemberPopup={(_task, $targetRef) => {
showPopup(
$targetRef,
<Popup title="Members" tab={0} onClose={() => {}}>
<Popup title="Members" tab={0} onClose={NOOP}>
<MemberManager
availableMembers={availableMembers}
activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => {
if (user) {
if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: user.id } });
assignTask({ variables: { taskID: data.findTask.id, userID: member.id } });
} 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(
$targetRef,
<Popup
title={'Change Due Date'}
title="Change Due Date"
tab={0}
onClose={() => {
hidePopup();
@ -502,7 +497,7 @@ const Details: React.FC<DetailsProps> = ({
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
onCancel={NOOP}
/>
</Popup>,
);

View File

@ -4,7 +4,6 @@ import updateApolloCache from 'shared/utils/cache';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled from 'styled-components/macro';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import LabelManagerEditor from './LabelManagerEditor';
import {
useParams,
Route,
@ -36,9 +35,11 @@ import produce from 'immer';
import UserContext, { useCurrentUser } from 'App/context';
import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
import EmptyBoard from 'shared/components/EmptyBoard';
import NOOP from 'shared/utils/noop';
import Board, { BoardLoading } from './Board';
import Details from './Details';
import EmptyBoard from 'shared/components/EmptyBoard';
import LabelManagerEditor from './LabelManagerEditor';
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
@ -124,6 +125,7 @@ const Project = () => {
const match = useRouteMatch();
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
const [toggleTaskLabel] = useToggleTaskLabelMutation({
onCompleted: newTaskLabel => {
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
@ -190,7 +192,6 @@ const Project = () => {
const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<Array<ProjectLabel>>([]);
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
useEffect(() => {
if (data) {
document.title = `${data.findProject.name} | Taskcafé`;
@ -199,7 +200,7 @@ const Project = () => {
if (loading) {
return (
<>
<GlobalTopNavbar onSaveProjectName={projectName => {}} name="" projectID={null} />
<GlobalTopNavbar onSaveProjectName={NOOP} name="" projectID={null} />
<BoardLoading />
</>
);
@ -261,7 +262,7 @@ const Project = () => {
path={`${match.path}/board/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details
refreshCache={() => {}}
refreshCache={NOOP}
availableMembers={data.findProject.members}
projectURL={`${match.url}/board`}
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 GlobalTopNavbar from 'App/TopNavbar';
import Empty from 'shared/undraw/Empty';
@ -10,16 +10,17 @@ import {
GetProjectsQuery,
} from 'shared/generated/graphql';
import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem';
import { Link } from 'react-router-dom';
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 { usePopup, Popup } from 'shared/components/PopupMenu';
import { useForm } from 'react-hook-form';
import Input from 'shared/components/Input';
import updateApolloCache from 'shared/utils/cache';
import produce from 'immer';
import NOOP from 'shared/utils/noop';
const EmptyStateContent = styled.div`
display: flex;
justy-content: center;
@ -37,21 +38,26 @@ const EmptyStatePrompt = styled.span`
font-size: 16px;
margin-top: 8px;
`;
const EmptyState = styled(Empty)`
display: block;
margin: 0 auto;
`;
const CreateTeamButton = styled(Button)`
width: 100%;
`;
type CreateTeamData = { teamName: string };
type CreateTeamFormProps = {
onCreateTeam: (teamName: string) => void;
};
const CreateTeamFormContainer = styled.form``;
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
const { register, handleSubmit, errors } = useForm<CreateTeamData>();
const { register, handleSubmit } = useForm<CreateTeamData>();
const createTeam = (data: CreateTeamData) => {
onCreateTeam(data.teamName);
};
@ -186,6 +192,7 @@ const SectionActions = styled.div`
const SectionAction = styled(Button)`
padding: 6px 12px;
`;
const SectionActionLink = styled(Link)`
margin-right: 8px;
`;
@ -201,12 +208,14 @@ const ProjectsContainer = styled.div`
max-width: 825px;
min-width: 288px;
`;
const ProjectGrid = styled.div`
max-width: 780px;
display: grid;
grid-template-columns: 240px 240px 240px;
gap: 20px 10px;
`;
const AddTeamButton = styled(Button)`
padding: 6px 12px;
position: absolute;
@ -217,13 +226,12 @@ const AddTeamButton = styled(Button)`
const CreateFirstTeam = styled(Button)`
margin-top: 8px;
`;
type ShowNewProject = {
open: boolean;
initialTeamID: null | string;
};
const ProjectLink = styled(Link)``;
const Projects = () => {
const { showPopup, hidePopup } = usePopup();
const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only' });
@ -241,7 +249,7 @@ const Projects = () => {
});
const [showNewProject, setShowNewProject] = useState<ShowNewProject>({ open: false, initialTeamID: null });
const { user, setUser } = useCurrentUser();
const { user } = useCurrentUser();
const [createTeam] = useCreateTeamMutation({
update: (client, createData) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
@ -267,7 +275,7 @@ const Projects = () => {
.sort((a, b) => {
const textA = a.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 => {
return {
@ -278,13 +286,13 @@ const Projects = () => {
.sort((a, b) => {
const textA = a.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 (
<>
<GlobalTopNavbar onSaveProjectName={() => {}} projectID={null} name={null} />
<GlobalTopNavbar onSaveProjectName={NOOP} projectID={null} name={null} />
<Wrapper>
<ProjectsContainer>
{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 updateApolloCache from 'shared/utils/cache';
import produce from 'immer';
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 {
useGetTeamQuery,
@ -13,8 +13,6 @@ import {
useUpdateTeamMemberRoleMutation,
GetTeamQuery,
GetTeamDocument,
MeDocument,
MeQuery,
} from 'shared/generated/graphql';
import { UserPlus, Checkmark } from 'shared/icons';
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 Member from 'shared/components/Member';
import ControlledInput from 'shared/components/ControlledInput';
import NOOP from 'shared/utils/noop';
const MemberListWrapper = styled.div`
flex: 1 1;
@ -129,6 +128,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
}
`}
`;
export const Content = styled.div`
padding: 0 12px 12px;
`;
@ -160,6 +160,7 @@ export const RemoveMemberButton = styled(Button)`
padding: 6px 12px;
width: 100%;
`;
type TeamRoleManagerPopupProps = {
currentUserID: string;
subject: User;
@ -253,7 +254,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
{subject.role && subject.role.code === 'owner' && (
<>
<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>
@ -510,7 +511,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
<MemberList>
{data.findTeam.members.map(member => (
<MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} />
<MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
<MemberListItemDetails>
<MemberItemName>{member.fullName}</MemberItemName>
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername>

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import React, { useRef } from 'react';
import Admin from '.';
import { theme } from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import React from 'react';
import { ThemeProvider } from 'styled-components';
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 {
component: Admin,

View File

@ -1,15 +1,13 @@
import React, { useState, useRef } from 'react';
import { UserPlus, Checkmark } from 'shared/icons';
import styled, { css } from 'styled-components';
import TaskAssignee from 'shared/components/TaskAssignee';
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 { RoleCode, useUpdateUserRoleMutation } from 'shared/generated/graphql';
import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
import Button from 'shared/components/Button';
import NOOP from 'shared/utils/noop';
export const RoleCheckmark = styled(Checkmark)`
padding-left: 4px;
@ -69,6 +67,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
}
`}
`;
export const Content = styled.div`
padding: 0 12px 12px;
`;
@ -100,6 +99,7 @@ export const RemoveMemberButton = styled(Button)`
padding: 6px 12px;
width: 100%;
`;
type TeamRoleManagerPopupProps = {
user: User;
users: Array<User>;
@ -120,7 +120,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
onChangeRole,
}) => {
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 hasOwned = user.owned.projects.length !== 0 || user.owned.teams.length !== 0;
return (
@ -195,7 +195,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
{user.role && user.role.code === 'owner' && (
<>
<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>
@ -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>
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>
<UserSelect
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.
</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
onClick={() => {
// onDeleteUser();
@ -253,7 +253,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<Popup title="Reset password?" onClose={() => hidePopup()} tab={3}>
<Content>
<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.
</DeleteDescription>
<UserPassBar>
@ -291,6 +291,7 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
</>
);
};
const UserSelect = styled(Select)`
margin: 8px 0;
padding: 8px 0;
@ -299,6 +300,7 @@ const UserSelect = styled(Select)`
const NewUserPassInput = styled(Input)`
margin: 8px 0;
`;
const InviteMemberButton = styled(Button)`
padding: 7px 12px;
`;
@ -307,6 +309,7 @@ const UserPassBar = styled.div`
display: flex;
padding-top: 8px;
`;
const UserPassConfirmButton = styled(Button)`
width: 100%;
padding: 7px 12px;
@ -397,104 +400,6 @@ const MemberListWrapper = styled.div`
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`
padding: 2.2rem;
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”.';
const [currentTop, setTop] = useState(initialTab * 48);
const [currentTab, setTab] = useState(initialTab);
const { showPopup, hidePopup } = usePopup();
const { showPopup } = usePopup();
const $tabNav = useRef<HTMLDivElement>(null);
const [updateUserRole] = useUpdateUserRoleMutation();
@ -690,7 +595,7 @@ const Admin: React.FC<AdminProps> = ({
const projectTotal = member.owned.projects.length + member.member.projects.length;
return (
<MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} />
<MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
<MemberListItemDetails>
<MemberItemName>{member.fullName}</MemberItemName>
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import styled from 'styled-components';
import TaskAssignee from 'shared/components/TaskAssignee';
import { Checkmark } from 'shared/icons';
import NOOP from 'shared/utils/noop';
const CardCheckmark = styled(Checkmark)`
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>}
{showCheckmark && <CardCheckmark width={12} height={12} />}
</CardMemberWrapper>

View File

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

View File

@ -160,7 +160,7 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
{user.role && user.role.code === 'owner' && (
<>
<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>

View File

@ -1,9 +1,8 @@
import React, { useState, useRef, createRef } from 'react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import styled from 'styled-components';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import NOOP from 'shared/utils/noop';
import NewProject from '.';
export default {
@ -26,7 +25,7 @@ export const Default = () => {
initialTeamID={null}
onCreateProject={action('create project')}
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 { 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`
z-index: 10000;
background: #262c49;
@ -149,14 +162,8 @@ const colourStyles = {
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? mixin.darken('#262c49', 0.25)
: isFocused
? mixin.darken('#262c49', 0.15)
: null,
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc',
backgroundColor: getBackgroundColor(isDisabled, isSelected, isFocused),
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc', // eslint-disable-line
cursor: isDisabled ? 'not-allowed' : 'default',
':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 { mixin } from 'shared/utils/styles';
import Input from '../Input';
import ControlledInput from 'shared/components/ControlledInput';
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 useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { createPortal } from 'react-dom';
import NOOP from 'shared/utils/noop';
import produce from 'immer';
import {
Container,
@ -52,10 +53,10 @@ PopupContainer.defaultProps = {
};
const PopupContext = createContext<PopupContextState>({
show: () => {},
setTab: () => {},
show: NOOP,
setTab: NOOP,
getCurrentTab: () => 0,
hide: () => {},
hide: NOOP,
});
export const usePopup = () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,17 @@
import gql from 'graphql-tag';
export const UPDATE_USER_ROLE_MUTATION = gql`
mutation updateUserRole($userID: UUID!, $roleCode: RoleCode!) {
updateUserRole(input:{userID: $userID, roleCode:$roleCode}) {
user {
id
role {
code
name
mutation updateUserRole($userID: UUID!, $roleCode: RoleCode!) {
updateUserRole(input: { userID: $userID, roleCode: $roleCode }) {
user {
id
role {
code
name
}
}
}
}
}
`;
export default UPDATE_USER_ROLE_MUTATION;

View File

@ -18,9 +18,9 @@ const AccessAccount = ({ width, height }: Props) => {
gradientTransform="translate(-3.62 1.57)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="gray" stop-opacity=".25" />
<stop offset=".54" stop-color="gray" stop-opacity=".12" />
<stop offset="1" stop-color="gray" stop-opacity=".1" />
<stop offset="0" stopColor="gray" stopOpacity=".25" />
<stop offset=".54" stopColor="gray" stopOpacity=".12" />
<stop offset="1" stopColor="gray" stopOpacity=".1" />
</linearGradient>
<linearGradient
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")
}
// 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
func (Frontend) Build() error {
return sh.RunV("yarn", "--cwd", "frontend", "build")