deps: upgrade all dependencies

This commit is contained in:
Jordan Knott 2021-05-02 17:31:24 -05:00
parent 5a9a66effe
commit 8c6a3db0bc
22 changed files with 5162 additions and 4850 deletions

View File

@ -1 +1,2 @@
REACT_APP_ENABLE_POLLING=true REACT_APP_ENABLE_POLLING=true
ESLINT_NO_DEV_ERRORS=true

View File

@ -24,7 +24,7 @@
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended"
], ],
"rules": { "rules": {
"prettier/prettier": "error", "prettier/prettier": "warn",
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"], "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
@ -48,6 +48,8 @@
"tsx": "never" "tsx": "never"
} }
], ],
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": ["error"],
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [
"error", "error",
{ {

View File

@ -3,26 +3,22 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.0.0-rc.8", "@apollo/client": "^3.3.16",
"@apollo/react-common": "^3.1.4", "@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.3", "@apollo/react-hooks": "^4.0.0",
"@types/axios": "^0.14.0",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/date-fns": "^2.6.0", "@types/dompurify": "^2.2.2",
"@types/dompurify": "^2.0.4",
"@types/emoji-mart": "^3.0.4", "@types/emoji-mart": "^3.0.4",
"@types/jest": "^24.0.0", "@types/jest": "^26.0.23",
"@types/jwt-decode": "^2.2.1", "@types/lodash": "^4.14.168",
"@types/lodash": "^4.14.149", "@types/node": "^15.0.1",
"@types/node": "^12.0.0", "@types/react": "^17.0.4",
"@types/query-string": "^6.3.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react": "^16.9.21", "@types/react-datepicker": "^3.1.8",
"@types/react-beautiful-dnd": "^12.1.1", "@types/react-dom": "^17.0.3",
"@types/react-datepicker": "^2.11.0", "@types/react-router": "^5.1.13",
"@types/react-dom": "^16.9.5", "@types/react-router-dom": "^5.1.7",
"@types/react-router": "^5.1.4", "@types/react-select": "^4.0.15",
"@types/react-router-dom": "^5.1.3",
"@types/react-select": "^3.0.13",
"@types/react-timeago": "^4.1.1", "@types/react-timeago": "^4.1.1",
"@types/styled-components": "^5.1.0", "@types/styled-components": "^5.1.0",
"apollo-cache-inmemory": "^1.6.5", "apollo-cache-inmemory": "^1.6.5",
@ -33,39 +29,39 @@
"apollo-link-state": "^0.4.2", "apollo-link-state": "^0.4.2",
"apollo-utilities": "^1.3.3", "apollo-utilities": "^1.3.3",
"axios": "^0.21.1", "axios": "^0.21.1",
"axios-auth-refresh": "^2.2.7", "axios-auth-refresh": "^3.1.0",
"color": "^3.1.2", "color": "^3.1.2",
"date-fns": "^2.14.0", "date-fns": "^2.21.1",
"dayjs": "^1.9.1", "dayjs": "^1.10.4",
"dompurify": "^2.2.6", "dompurify": "^2.2.8",
"emoji-mart": "^3.0.0", "emoji-mart": "^3.0.1",
"emoticon": "^4.0.0", "emoticon": "^4.0.0",
"graphql": "^15.0.0", "graphql": "^15.5.0",
"graphql-tag": "^2.10.3", "graphql-tag": "^2.12.4",
"history": "^4.10.1", "history": "^5.0.0",
"immer": "^8.0.1", "immer": "^9.0.2",
"jwt-decode": "^2.2.0", "jwt-decode": "^3.1.2",
"lodash": "^4.17.20", "lodash": "^4.17.21",
"node-emoji": "^1.10.0", "node-emoji": "^1.10.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"query-string": "^6.13.7", "query-string": "^7.0.0",
"react": "^16.12.0", "react": "^17.0.2",
"react-autosize-textarea": "^7.0.0", "react-autosize-textarea": "^7.0.0",
"react-beautiful-dnd": "^13.0.0", "react-beautiful-dnd": "^13.1.0",
"react-datepicker": "^2.14.1", "react-datepicker": "^3.8.0",
"react-dom": "^16.12.0", "react-dom": "^17.0.2",
"react-emoji-render": "^1.2.4", "react-emoji-render": "^1.2.4",
"react-hook-form": "^6.0.6", "react-hook-form": "^7.3.6",
"react-markdown": "^4.3.1", "react-markdown": "^6.0.1",
"react-router": "^5.1.2", "react-router": "^5.1.2",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.4.0", "react-scripts": "4.0.3",
"react-select": "^3.1.0", "react-select": "^4.3.0",
"react-timeago": "^4.4.0", "react-timeago": "^5.2.0",
"react-toastify": "^6.0.8", "react-toastify": "^7.0.4",
"rich-markdown-editor": "^10.6.5", "rich-markdown-editor": "^11.0.10",
"styled-components": "^5.1.0", "styled-components": "^5.2.3",
"typescript": "~3.7.2" "typescript": "~4.2.4"
}, },
"proxy": "http://localhost:3333", "proxy": "http://localhost:3333",
"scripts": { "scripts": {
@ -97,16 +93,16 @@
"@graphql-codegen/typescript": "^1.22.0", "@graphql-codegen/typescript": "^1.22.0",
"@graphql-codegen/typescript-operations": "^1.17.16", "@graphql-codegen/typescript-operations": "^1.17.16",
"@graphql-codegen/typescript-react-apollo": "^2.2.4", "@graphql-codegen/typescript-react-apollo": "^2.2.4",
"@typescript-eslint/eslint-plugin": "^2.20.0", "@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^2.20.0", "@typescript-eslint/parser": "^4.22.0",
"eslint": "^6.8.0", "eslint": "^7.25.0",
"eslint-config-airbnb": "^18.0.1", "eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.20.1", "eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.18.3", "eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^1.7.0", "eslint-plugin-react-hooks": "^4.2.0",
"prettier": "^1.19.1" "prettier": "^2.2.1"
} }
} }

View File

@ -10,7 +10,6 @@ import {
UsersDocument, UsersDocument,
UsersQuery, UsersQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import Input from 'shared/components/Input';
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
@ -20,6 +19,7 @@ import updateApolloCache from 'shared/utils/cache';
import { 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'; import NOOP from 'shared/utils/noop';
import ControlledInput from 'shared/components/ControlledInput';
const DeleteUserWrapper = styled.div` const DeleteUserWrapper = styled.div`
display: flex; display: flex;
@ -77,12 +77,12 @@ const CreateUserButton = styled(Button)`
width: 100%; width: 100%;
`; `;
const AddUserInput = styled(Input)` const AddUserInput = styled(ControlledInput)`
margin-bottom: 8px; margin-bottom: 8px;
`; `;
const InputError = styled.span` const InputError = styled.span`
color: ${props => props.theme.colors.danger}; color: ${(props) => props.theme.colors.danger};
font-size: 12px; font-size: 12px;
`; `;
@ -91,7 +91,12 @@ type AddUserPopupProps = {
}; };
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => { const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors, control } = useForm<CreateUserData>(); const {
register,
handleSubmit,
formState: { errors },
control,
} = useForm<CreateUserData>();
const createUser = (data: CreateUserData) => { const createUser = (data: CreateUserData) => {
onAddUser(data); onAddUser(data);
@ -102,30 +107,25 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
floatingLabel floatingLabel
width="100%" width="100%"
label="Full Name" label="Full Name"
id="fullName"
name="fullName"
variant="alternate" variant="alternate"
ref={register({ required: 'Full name is required' })} {...register('fullName', { required: 'Full name is required' })}
/> />
{errors.fullName && <InputError>{errors.fullName.message}</InputError>} {errors.fullName && <InputError>{errors.fullName.message}</InputError>}
<AddUserInput <AddUserInput
floatingLabel floatingLabel
width="100%" width="100%"
label="Email" label="Email"
id="email"
name="email"
variant="alternate" variant="alternate"
ref={register({ required: 'Email is required' })} {...register('email', { required: 'Email is required' })}
/> />
<Controller <Controller
control={control} control={control}
name="roleCode" name="roleCode"
rules={{ required: 'Role is required' }} rules={{ required: 'Role is required' }}
render={({ onChange, value }) => ( render={({ field }) => (
<Select <Select
{...field}
label="Role" label="Role"
value={value}
onChange={onChange}
options={[ options={[
{ label: 'Admin', value: 'admin' }, { label: 'Admin', value: 'admin' },
{ label: 'Member', value: 'member' }, { label: 'Member', value: 'member' },
@ -138,31 +138,25 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
floatingLabel floatingLabel
width="100%" width="100%"
label="Username" label="Username"
id="username"
name="username"
variant="alternate" variant="alternate"
ref={register({ required: 'Username is required' })} {...register('username', { required: 'Username is required' })}
/> />
{errors.username && <InputError>{errors.username.message}</InputError>} {errors.username && <InputError>{errors.username.message}</InputError>}
<AddUserInput <AddUserInput
floatingLabel floatingLabel
width="100%" width="100%"
label="Initials" label="Initials"
id="initials"
name="initials"
variant="alternate" variant="alternate"
ref={register({ required: 'Initials is required' })} {...register('initials', { required: 'Initials is required' })}
/> />
{errors.initials && <InputError>{errors.initials.message}</InputError>} {errors.initials && <InputError>{errors.initials.message}</InputError>}
<AddUserInput <AddUserInput
floatingLabel floatingLabel
width="100%" width="100%"
label="Password" label="Password"
id="password"
name="password"
variant="alternate" variant="alternate"
type="password" type="password"
ref={register({ required: 'Password is required' })} {...register('password', { required: 'Password is required' })}
/> />
{errors.password && <InputError>{errors.password.message}</InputError>} {errors.password && <InputError>{errors.password.message}</InputError>}
<CreateUserButton type="submit">Create</CreateUserButton> <CreateUserButton type="submit">Create</CreateUserButton>
@ -179,10 +173,10 @@ const AdminRoute = () => {
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const [deleteInvitedUser] = useDeleteInvitedUserAccountMutation({ const [deleteInvitedUser] = useDeleteInvitedUserAccountMutation({
update: (client, response) => { update: (client, response) => {
updateApolloCache<UsersQuery>(client, UsersDocument, cache => updateApolloCache<UsersQuery>(client, UsersDocument, (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.invitedUsers = cache.invitedUsers.filter( draftCache.invitedUsers = cache.invitedUsers.filter(
u => u.id !== response.data?.deleteInvitedUserAccount.invitedUser.id, (u) => u.id !== response.data?.deleteInvitedUserAccount.invitedUser.id,
); );
}), }),
); );
@ -190,9 +184,9 @@ const AdminRoute = () => {
}); });
const [deleteUser] = useDeleteUserAccountMutation({ const [deleteUser] = useDeleteUserAccountMutation({
update: (client, response) => { update: (client, response) => {
updateApolloCache<UsersQuery>(client, UsersDocument, cache => updateApolloCache<UsersQuery>(client, UsersDocument, (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.users = cache.users.filter(u => u.id !== response.data?.deleteUserAccount.userAccount.id); draftCache.users = cache.users.filter((u) => u.id !== response.data?.deleteUserAccount.userAccount.id);
}), }),
); );
}, },
@ -234,7 +228,7 @@ TODO: add permision check
onUpdateUserPassword={() => { onUpdateUserPassword={() => {
hidePopup(); hidePopup();
}} }}
onDeleteInvitedUser={invitedUserID => { onDeleteInvitedUser={(invitedUserID) => {
deleteInvitedUser({ variables: { invitedUserID } }); deleteInvitedUser({ variables: { invitedUserID } });
hidePopup(); hidePopup();
}} }}
@ -242,12 +236,12 @@ TODO: add permision check
deleteUser({ variables: { userID, newOwnerID } }); deleteUser({ variables: { userID, newOwnerID } });
hidePopup(); hidePopup();
}} }}
onAddUser={$target => { onAddUser={($target) => {
showPopup( showPopup(
$target, $target,
<Popup tab={0} title="Add member" onClose={() => hidePopup()}> <Popup tab={0} title="Add member" onClose={() => hidePopup()}>
<AddUserPopup <AddUserPopup
onAddUser={u => { onAddUser={(u) => {
const { roleCode, ...userData } = u; const { roleCode, ...userData } = u;
createUser({ variables: { ...userData, roleCode: roleCode.value } }); createUser({ variables: { ...userData, roleCode: roleCode.value } });
hidePopup(); hidePopup();

View File

@ -32,6 +32,7 @@ type ValidateTokenResponse = {
const UserRequiredRoute: React.FC<any> = ({ children }) => { const UserRequiredRoute: React.FC<any> = ({ children }) => {
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const location = useLocation(); const location = useLocation();
console.log('user required', user);
if (user) { if (user) {
return children; return children;
} }
@ -45,18 +46,14 @@ const UserRequiredRoute: React.FC<any> = ({ children }) => {
); );
}; };
type RoutesProps = { const Routes: React.FC = () => {
history: H.History;
};
const Routes: React.FC<RoutesProps> = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { setUser } = useCurrentUser(); const { setUser } = useCurrentUser();
useEffect(() => { useEffect(() => {
fetch('/auth/validate', { fetch('/auth/validate', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}).then(async x => { }).then(async (x) => {
const response: ValidateTokenResponse = await x.json(); const response: ValidateTokenResponse = await x.json();
const { valid, userID } = response; const { valid, userID } = response;
if (valid) { if (valid) {
@ -65,6 +62,7 @@ const Routes: React.FC<RoutesProps> = () => {
setLoading(false); setLoading(false);
}); });
}, []); }, []);
console.log('loading', loading);
if (loading) return null; if (loading) return null;
return ( return (
<Switch> <Switch>

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { createBrowserHistory } from 'history'; import { BrowserRouter } from 'react-router-dom';
import { Router } from 'react-router';
import { PopupProvider } from 'shared/components/PopupMenu'; import { PopupProvider } from 'shared/components/PopupMenu';
import styled, { ThemeProvider } from 'styled-components'; import styled, { ThemeProvider } from 'styled-components';
import NormalizeStyles from './NormalizeStyles'; import NormalizeStyles from './NormalizeStyles';
@ -13,8 +12,6 @@ import { UserContext } from './context';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import './fonts.css'; import './fonts.css';
const history = createBrowserHistory();
const App = () => { const App = () => {
const [user, setUser] = useState<string | null>(null); const [user, setUser] = useState<string | null>(null);
@ -24,11 +21,11 @@ const App = () => {
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<NormalizeStyles /> <NormalizeStyles />
<BaseStyles /> <BaseStyles />
<Router history={history}> <BrowserRouter>
<PopupProvider> <PopupProvider>
<Routes history={history} /> <Routes />
</PopupProvider> </PopupProvider>
</Router> </BrowserRouter>
<ToastedContainer <ToastedContainer
position="bottom-right" position="bottom-right"
autoClose={5000} autoClose={5000}

View File

@ -9,6 +9,7 @@ const Auth = () => {
const history = useHistory(); const history = useHistory();
const location = useLocation<{ redirect: string } | undefined>(); const location = useLocation<{ redirect: string } | undefined>();
const { setUser } = useContext(UserContext); const { setUser } = useContext(UserContext);
console.log('auth');
const login = ( const login = (
data: LoginFormData, data: LoginFormData,
setComplete: (val: boolean) => void, setComplete: (val: boolean) => void,
@ -21,7 +22,7 @@ const Auth = () => {
username: data.username, username: data.username,
password: data.password, password: data.password,
}), }),
}).then(async x => { }).then(async (x) => {
if (x.status === 401) { if (x.status === 401) {
setInvalidLoginAttempt(invalidLoginAttempt + 1); setInvalidLoginAttempt(invalidLoginAttempt + 1);
setError('username', { type: 'error', message: 'Invalid username' }); setError('username', { type: 'error', message: 'Invalid username' });
@ -44,7 +45,7 @@ const Auth = () => {
fetch('/auth/validate', { fetch('/auth/validate', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}).then(async x => { }).then(async (x) => {
const response = await x.json(); const response = await x.json();
const { valid, userID } = response; const { valid, userID } = response;
if (valid) { if (valid) {

View File

@ -29,6 +29,7 @@ import useStickyState from 'shared/hooks/useStickyState';
import MyTasksSortPopup from './MyTasksSort'; import MyTasksSortPopup from './MyTasksSort';
import MyTasksStatusPopup from './MyTasksStatus'; import MyTasksStatusPopup from './MyTasksStatus';
import TaskEntry from './TaskEntry'; import TaskEntry from './TaskEntry';
import { StaticContext } from 'react-router';
type TaskRouteProps = { type TaskRouteProps = {
taskID: string; taskID: string;
@ -61,11 +62,7 @@ function prettySort(sort: MyTasksSort) {
if (sort === MyTasksSort.None) { if (sort === MyTasksSort.None) {
return 'Sort'; return 'Sort';
} }
return `Sort: ${sort.charAt(0) + return `Sort: ${sort.charAt(0) + sort.slice(1).toLowerCase().replace(/_/gi, ' ')}`;
sort
.slice(1)
.toLowerCase()
.replace(/_/gi, ' ')}`;
} }
type Group = { type Group = {
@ -75,7 +72,7 @@ type Group = {
}; };
const DueDateEditorLabel = styled.div` const DueDateEditorLabel = styled.div`
align-items: center; align-items: center;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
font-size: 11px; font-size: 11px;
padding: 0 8px; padding: 0 8px;
@ -107,16 +104,16 @@ const ProjectActionWrapper = styled.div<{ disabled?: boolean }>`
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 15px; font-size: 15px;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
&:not(:last-of-type) { &:not(:last-of-type) {
margin-right: 16px; margin-right: 16px;
} }
&:hover { &:hover {
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
} }
${props => ${(props) =>
props.disabled && props.disabled &&
css` css`
opacity: 0.5; opacity: 0.5;
@ -150,7 +147,7 @@ const ProjectAction: React.FC<ProjectActionProps> = ({ onClick, disabled = false
const EditorPositioner = styled.div<{ top: number; left: number }>` const EditorPositioner = styled.div<{ top: number; left: number }>`
position: absolute; position: absolute;
top: ${p => p.top}px; top: ${(p) => p.top}px;
justify-content: flex-end; justify-content: flex-end;
margin-left: -100vw; margin-left: -100vw;
z-index: 10000; z-index: 10000;
@ -160,7 +157,7 @@ const EditorPositioner = styled.div<{ top: number; left: number }>`
height: 0; height: 0;
position: fixed; position: fixed;
width: 100vw; width: 100vw;
left: ${p => p.left}px; left: ${(p) => p.left}px;
`; `;
const EditorPositionerContents = styled.div` const EditorPositionerContents = styled.div`
@ -168,15 +165,15 @@ const EditorPositionerContents = styled.div`
`; `;
const EditorContainer = styled.div<{ width: number }>` const EditorContainer = styled.div<{ width: number }>`
border: 1px solid ${props => props.theme.colors.primary}; border: 1px solid ${(props) => props.theme.colors.primary};
background: ${props => props.theme.colors.bg.secondary}; background: ${(props) => props.theme.colors.bg.secondary};
position: relative; position: relative;
width: ${p => p.width}px; width: ${(p) => p.width}px;
`; `;
const EditorCell = styled.div<{ width: number }>` const EditorCell = styled.div<{ width: number }>`
display: flex; display: flex;
width: ${p => p.width}px; width: ${(p) => p.width}px;
`; `;
// TABLE // TABLE
@ -224,7 +221,7 @@ const TaskGroupItems = styled.div`
`; `;
const ProjectPill = styled.div` const ProjectPill = styled.div`
background-color: ${props => props.theme.colors.bg.primary}; background-color: ${(props) => props.theme.colors.bg.primary};
text-overflow: ellipsis; text-overflow: ellipsis;
border-radius: 10px; border-radius: 10px;
box-sizing: border-box; box-sizing: border-box;
@ -250,7 +247,7 @@ const ProjectPillName = styled.span`
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
`; `;
const ProjectPillColor = styled.svg` const ProjectPillColor = styled.svg`
@ -299,7 +296,7 @@ const OptionTitle = styled.div`
white-space: nowrap; white-space: nowrap;
`; `;
const OptionSubTitle = styled.div` const OptionSubTitle = styled.div`
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
font-size: 11px; font-size: 11px;
margin-left: 8px; margin-left: 8px;
min-width: 50px; min-width: 50px;
@ -319,7 +316,7 @@ const Option = ({ innerProps, data }: any) => {
}; };
const TaskGroupHeaderContents = styled.div<{ width: number }>` const TaskGroupHeaderContents = styled.div<{ width: number }>`
width: ${p => p.width}px; width: ${(p) => p.width}px;
left: 0; left: 0;
position: absolute; position: absolute;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
@ -356,13 +353,13 @@ const TaskGroupMinify = styled.div`
transition-property: background, border, box-shadow, fill; transition-property: background, border, box-shadow, fill;
cursor: pointer; cursor: pointer;
svg { svg {
fill: ${props => props.theme.colors.text.primary}; fill: ${(props) => props.theme.colors.text.primary};
transition-duration: 0.2s; transition-duration: 0.2s;
transition-property: background, border, box-shadow, fill; transition-property: background, border, box-shadow, fill;
} }
&:hover svg { &:hover svg {
fill: ${props => props.theme.colors.text.secondary}; fill: ${(props) => props.theme.colors.text.secondary};
} }
`; `;
const TaskGroupName = styled.div` const TaskGroupName = styled.div`
@ -371,7 +368,7 @@ const TaskGroupName = styled.div`
display: flex; display: flex;
height: 50px; height: 50px;
min-width: 1px; min-width: 1px;
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
font-weight: 400; font-weight: 400;
`; `;
@ -393,7 +390,7 @@ const Row = styled.div`
`; `;
const RowHeaderLeft = styled.div<{ width: number }>` const RowHeaderLeft = styled.div<{ width: number }>`
width: ${p => p.width}px; width: ${(p) => p.width}px;
align-items: stretch; align-items: stretch;
display: flex; display: flex;
@ -405,7 +402,7 @@ const RowHeaderLeft = styled.div<{ width: number }>`
`; `;
const RowHeaderLeftInner = styled.div` const RowHeaderLeftInner = styled.div`
align-items: stretch; align-items: stretch;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
display: flex; display: flex;
flex: 1 0 auto; flex: 1 0 auto;
font-size: 12px; font-size: 12px;
@ -429,7 +426,7 @@ const RowHeaderLeftNameText = styled.div`
`; `;
const RowHeaderRight = styled.div<{ left: number }>` const RowHeaderRight = styled.div<{ left: number }>`
left: ${p => p.left}px; left: ${(p) => p.left}px;
right: 0px; right: 0px;
height: 37px; height: 37px;
position: absolute; position: absolute;
@ -461,7 +458,7 @@ const RowHeaderRightContainer = styled.div`
`; `;
const ItemWrapper = styled.div<{ width: number }>` const ItemWrapper = styled.div<{ width: number }>`
width: ${p => p.width}px; width: ${(p) => p.width}px;
align-items: center; align-items: center;
border: 1px solid #414561; border: 1px solid #414561;
border-bottom: 0; border-bottom: 0;
@ -474,11 +471,11 @@ const ItemWrapper = styled.div<{ width: number }>`
margin-right: -1px; margin-right: -1px;
padding: 0 8px; padding: 0 8px;
position: relative; position: relative;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
border-bottom: 1px solid #414561; border-bottom: 1px solid #414561;
&:hover { &:hover {
background: ${props => props.theme.colors.primary}; background: ${(props) => props.theme.colors.primary};
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
} }
`; `;
const ItemsContainer = styled.div` const ItemsContainer = styled.div`
@ -566,13 +563,13 @@ const Projects = () => {
onDueDateChange={(task, dueDate, hasTime) => { onDueDateChange={(task, dueDate, hasTime) => {
if (dateEditor.task) { if (dateEditor.task) {
updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate, hasTime } }); updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate, hasTime } });
setDateEditor(prev => ({ ...prev, task: { ...task, dueDate: dueDate.toISOString(), hasTime } })); setDateEditor((prev) => ({ ...prev, task: { ...task, dueDate: dueDate.toISOString(), hasTime } }));
} }
}} }}
onRemoveDueDate={task => { onRemoveDueDate={(task) => {
if (dateEditor.task) { if (dateEditor.task) {
updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate: null, hasTime: false } }); updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate: null, hasTime: false } });
setDateEditor(prev => ({ ...prev, task: { ...task, hasTime: false } })); setDateEditor((prev) => ({ ...prev, task: { ...task, hasTime: false } }));
} }
}} }}
/> />
@ -587,8 +584,8 @@ const Projects = () => {
updateApolloCache<MyTasksQuery>( updateApolloCache<MyTasksQuery>(
client, client,
MyTasksDocument, MyTasksDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (newTaskData.data) { if (newTaskData.data) {
draftCache.myTasks.tasks.unshift(newTaskData.data.createTask); draftCache.myTasks.tasks.unshift(newTaskData.data.createTask);
} }
@ -618,7 +615,7 @@ const Projects = () => {
groups.push({ groups.push({
id: 'recently-assigned', id: 'recently-assigned',
name: 'Recently Assigned', name: 'Recently Assigned',
tasks: data.myTasks.tasks.map(task => ({ tasks: data.myTasks.tasks.map((task) => ({
...task, ...task,
labels: [], labels: [],
position: 0, position: 0,
@ -628,27 +625,27 @@ const Projects = () => {
let { tasks } = data.myTasks; let { tasks } = data.myTasks;
if (filters.sort === MyTasksSort.DueDate) { if (filters.sort === MyTasksSort.DueDate) {
const group: Group = { id: 'due_date', name: null, tasks: [] }; const group: Group = { id: 'due_date', name: null, tasks: [] };
data.myTasks.tasks.forEach(task => { data.myTasks.tasks.forEach((task) => {
if (task.dueDate) { if (task.dueDate) {
group.tasks.push({ ...task, labels: [], position: 0 }); group.tasks.push({ ...task, labels: [], position: 0 });
} }
}); });
groups.push(group); groups.push(group);
tasks = tasks.filter(t => t.dueDate === null); tasks = tasks.filter((t) => t.dueDate === null);
} }
const projects = new Map<string, Array<Task>>(); const projects = new Map<string, Array<Task>>();
data.myTasks.projects.forEach(p => { data.myTasks.projects.forEach((p) => {
if (!projects.has(p.projectID)) { if (!projects.has(p.projectID)) {
projects.set(p.projectID, []); projects.set(p.projectID, []);
} }
const prev = projects.get(p.projectID); const prev = projects.get(p.projectID);
const task = tasks.find(t => t.id === p.taskID); const task = tasks.find((t) => t.id === p.taskID);
if (prev && task) { if (prev && task) {
projects.set(p.projectID, [...prev, { ...task, labels: [], position: 0 }]); projects.set(p.projectID, [...prev, { ...task, labels: [], position: 0 }]);
} }
}); });
for (const [id, pTasks] of projects) { for (const [id, pTasks] of projects) {
const project = data.projects.find(c => c.id === id); const project = data.projects.find((c) => c.id === id);
if (pTasks.length === 0) continue; if (pTasks.length === 0) continue;
if (project) { if (project) {
groups.push({ groups.push({
@ -681,13 +678,13 @@ const Projects = () => {
<ProjectActions /> <ProjectActions />
<ProjectActions> <ProjectActions>
<ProjectAction <ProjectAction
onClick={$target => { onClick={($target) => {
showPopup( showPopup(
$target, $target,
<MyTasksStatusPopup <MyTasksStatusPopup
status={filters.status} status={filters.status}
onChangeStatus={status => { onChangeStatus={(status) => {
setFilters(prev => ({ ...prev, status })); setFilters((prev) => ({ ...prev, status }));
hidePopup(); hidePopup();
}} }}
/>, />,
@ -699,13 +696,13 @@ const Projects = () => {
<ProjectActionText>{prettyStatus(filters.status)}</ProjectActionText> <ProjectActionText>{prettyStatus(filters.status)}</ProjectActionText>
</ProjectAction> </ProjectAction>
<ProjectAction <ProjectAction
onClick={$target => { onClick={($target) => {
showPopup( showPopup(
$target, $target,
<MyTasksSortPopup <MyTasksSortPopup
sort={filters.sort} sort={filters.sort}
onChangeSort={sort => { onChangeSort={(sort) => {
setFilters(prev => ({ ...prev, sort })); setFilters((prev) => ({ ...prev, sort }));
hidePopup(); hidePopup();
}} }}
/>, />,
@ -752,8 +749,8 @@ const Projects = () => {
<VerticalScoller> <VerticalScoller>
<VerticalScollerInner> <VerticalScollerInner>
<TableContents> <TableContents>
{groups.map(group => { {groups.map((group) => {
const isMinified = minified.find(m => m === group.id) ?? false; const isMinified = minified.find((m) => m === group.id) ?? false;
return ( return (
<TaskGroupContainer key={group.id}> <TaskGroupContainer key={group.id}>
{group.name && ( {group.name && (
@ -761,9 +758,9 @@ const Projects = () => {
<TaskGroupHeaderContents width={leftRow}> <TaskGroupHeaderContents width={leftRow}>
<TaskGroupMinify <TaskGroupMinify
onClick={() => { onClick={() => {
setMinified(prev => { setMinified((prev) => {
if (isMinified) { if (isMinified) {
return prev.filter(c => c !== group.id); return prev.filter((c) => c !== group.id);
} }
return [...prev, group.id]; return [...prev, group.id];
}); });
@ -781,14 +778,14 @@ const Projects = () => {
)} )}
<TaskGroupItems> <TaskGroupItems>
{!isMinified && {!isMinified &&
group.tasks.map(task => { group.tasks.map((task) => {
const projectID = data.myTasks.projects.find(t => t.taskID === task.id)?.projectID; const projectID = data.myTasks.projects.find((t) => t.taskID === task.id)?.projectID;
const projectName = data.projects.find(p => p.id === projectID)?.name; const projectName = data.projects.find((p) => p.id === projectID)?.name;
return ( return (
<TaskEntry <TaskEntry
key={task.id} key={task.id}
complete={task.complete ?? false} complete={task.complete ?? false}
onToggleComplete={complete => { onToggleComplete={(complete) => {
setTaskComplete({ variables: { taskID: task.id, complete } }); setTaskComplete({ variables: { taskID: task.id, complete } });
}} }}
onTaskDetails={() => { onTaskDetails={() => {
@ -801,9 +798,11 @@ const Projects = () => {
dueDate={task.dueDate} dueDate={task.dueDate}
hasTime={task.hasTime ?? false} hasTime={task.hasTime ?? false}
name={task.name} name={task.name}
onEditName={name => updateTaskName({ variables: { taskID: task.id, name } })} onEditName={(name) => updateTaskName({ variables: { taskID: task.id, name } })}
onEditProject={onEditProject} onEditProject={onEditProject}
onEditDueDate={$target => onEditDueDate({ ...task, position: 0, labels: [] }, $target)} onEditDueDate={($target) =>
onEditDueDate({ ...task, position: 0, labels: [] }, $target)
}
/> />
); );
})} })}
@ -856,17 +855,17 @@ const Projects = () => {
)} )}
<Route <Route
path={`${match.path}/c/:taskID`} path={`${match.path}/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => ( render={() => {
<Details return (
refreshCache={NOOP} <Details
availableMembers={[]} refreshCache={NOOP}
projectURL={`${match.url}`} availableMembers={[]}
taskID={routeProps.match.params.taskID} projectURL={`${match.url}`}
onTaskNameChange={(updatedTask, newName) => { onTaskNameChange={(updatedTask, newName) => {
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } }); updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
}} }}
onTaskDescriptionChange={(updatedTask, newDescription) => { onTaskDescriptionChange={(updatedTask, newDescription) => {
/* /*
updateTaskDescription({ updateTaskDescription({
variables: { taskID: updatedTask.id, description: newDescription }, variables: { taskID: updatedTask.id, description: newDescription },
optimisticResponse: { optimisticResponse: {
@ -879,13 +878,13 @@ const Projects = () => {
}, },
}); });
*/ */
}} }}
onDeleteTask={deletedTask => { onDeleteTask={(deletedTask) => {
// deleteTask({ variables: { taskID: deletedTask.id } }); // deleteTask({ variables: { taskID: deletedTask.id } });
history.push(`${match.url}`); history.push(`${match.url}`);
}} }}
onOpenAddLabelPopup={(task, $targetRef) => { onOpenAddLabelPopup={(task, $targetRef) => {
/* /*
taskLabelsRef.current = task.labels; taskLabelsRef.current = task.labels;
showPopup( showPopup(
$targetRef, $targetRef,
@ -900,9 +899,10 @@ const Projects = () => {
/>, />,
); );
*/ */
}} }}
/> />
)} );
}}
/> />
</> </>
); );

View File

@ -4,7 +4,7 @@ import TaskDetails from 'shared/components/TaskDetails';
import TaskDetailsLoading from 'shared/components/TaskDetails/Loading'; import TaskDetailsLoading from 'shared/components/TaskDetails/Loading';
import { 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, useParams } from 'react-router';
import { import {
useDeleteTaskChecklistMutation, useDeleteTaskChecklistMutation,
useUpdateTaskChecklistNameMutation, useUpdateTaskChecklistNameMutation,
@ -56,7 +56,7 @@ export const ActionItem = styled.li`
align-items: center; align-items: center;
font-size: 14px; font-size: 14px;
&:hover { &:hover {
background: ${props => props.theme.colors.primary}; background: ${(props) => props.theme.colors.primary};
} }
`; `;
@ -166,10 +166,8 @@ const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChe
defaultValue="Checklist" defaultValue="Checklist"
width="100%" width="100%"
label="Name" label="Name"
id="name"
name="name"
variant="alternate" variant="alternate"
ref={register({ required: 'Checklist name is required' })} {...register('name', { required: 'Checklist name is required' })}
/> />
<CreateChecklistButton type="submit">Create</CreateChecklistButton> <CreateChecklistButton type="submit">Create</CreateChecklistButton>
</CreateChecklistForm> </CreateChecklistForm>
@ -177,7 +175,6 @@ const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChe
}; };
type DetailsProps = { type DetailsProps = {
taskID: string;
projectURL: string; projectURL: string;
onTaskNameChange: (task: Task, newName: string) => void; onTaskNameChange: (task: Task, newName: string) => void;
onTaskDescriptionChange: (task: Task, newDescription: string) => void; onTaskDescriptionChange: (task: Task, newDescription: string) => void;
@ -191,7 +188,6 @@ const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
const Details: React.FC<DetailsProps> = ({ const Details: React.FC<DetailsProps> = ({
projectURL, projectURL,
taskID,
onTaskNameChange, onTaskNameChange,
onTaskDescriptionChange, onTaskDescriptionChange,
onDeleteTask, onDeleteTask,
@ -200,6 +196,7 @@ const Details: React.FC<DetailsProps> = ({
refreshCache, refreshCache,
}) => { }) => {
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const { taskID } = useParams<{ taskID: string }>();
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const history = useHistory(); const history = useHistory();
const [deleteTaskComment] = useDeleteTaskCommentMutation({ const [deleteTaskComment] = useDeleteTaskCommentMutation({
@ -207,11 +204,11 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (response.data) { if (response.data) {
draftCache.findTask.comments = cache.findTask.comments.filter( draftCache.findTask.comments = cache.findTask.comments.filter(
c => c.id !== response.data?.deleteTaskComment.commentID, (c) => c.id !== response.data?.deleteTaskComment.commentID,
); );
} }
}), }),
@ -224,8 +221,8 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (response.data) { if (response.data) {
draftCache.findTask.comments.push({ draftCache.findTask.comments.push({
...response.data.createTaskComment.comment, ...response.data.createTaskComment.comment,
@ -242,18 +239,18 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (response.data) { if (response.data) {
const { prevChecklistID, taskChecklistID, checklistItem } = response.data.updateTaskChecklistItemLocation; const { prevChecklistID, taskChecklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
if (taskChecklistID !== prevChecklistID) { if (taskChecklistID !== prevChecklistID) {
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 === taskChecklistID); const newIdx = cache.findTask.checklists.findIndex((c) => c.id === taskChecklistID);
if (oldIdx > -1 && newIdx > -1) { if (oldIdx > -1 && newIdx > -1) {
const item = cache.findTask.checklists[oldIdx].items.find(i => i.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,
); );
draftCache.findTask.checklists[newIdx].items.push({ draftCache.findTask.checklists[newIdx].items.push({
...item, ...item,
@ -270,12 +267,12 @@ const Details: React.FC<DetailsProps> = ({
}, },
}); });
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({ const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
update: client => { update: (client) => {
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = { draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge', __typename: 'ChecklistBadge',
@ -292,11 +289,11 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
const { checklists } = cache.findTask; const { checklists } = cache.findTask;
draftCache.findTask.checklists = checklists.filter( draftCache.findTask.checklists = checklists.filter(
c => c.id !== deleteData.data?.deleteTaskChecklist.taskChecklist.id, (c) => c.id !== deleteData.data?.deleteTaskChecklist.taskChecklist.id,
); );
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = { draftCache.findTask.badges.checklist = {
@ -318,8 +315,8 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (createData.data) { if (createData.data) {
const item = createData.data.createTaskChecklist; const item = createData.data.createTaskChecklist;
draftCache.findTask.checklists.push({ ...item }); draftCache.findTask.checklists.push({ ...item });
@ -335,14 +332,14 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (deleteData.data) { if (deleteData.data) {
const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem; const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
const targetIdx = cache.findTask.checklists.findIndex(c => c.id === item.taskChecklistID); const targetIdx = cache.findTask.checklists.findIndex((c) => c.id === item.taskChecklistID);
if (targetIdx > -1) { if (targetIdx > -1) {
draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter( draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter(
c => item.id !== c.id, (c) => item.id !== c.id,
); );
} }
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
@ -362,12 +359,12 @@ const Details: React.FC<DetailsProps> = ({
updateApolloCache<FindTaskQuery>( updateApolloCache<FindTaskQuery>(
client, client,
FindTaskDocument, FindTaskDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (newTaskItem.data) { if (newTaskItem.data) {
const item = newTaskItem.data.createTaskChecklistItem; const item = newTaskItem.data.createTaskChecklistItem;
const { checklists } = cache.findTask; const { checklists } = cache.findTask;
const idx = checklists.findIndex(c => c.id === item.taskChecklistID); const idx = checklists.findIndex((c) => c.id === item.taskChecklistID);
if (idx !== -1) { if (idx !== -1) {
draftCache.findTask.checklists[idx].items.push({ ...item }); draftCache.findTask.checklists[idx].items.push({ ...item });
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
@ -445,7 +442,7 @@ const Details: React.FC<DetailsProps> = ({
onCreateComment={(task, message) => { onCreateComment={(task, message) => {
createTaskComment({ variables: { taskID: task.id, message } }); createTaskComment({ variables: { taskID: task.id, message } });
}} }}
onChecklistDrop={checklist => { onChecklistDrop={(checklist) => {
updateTaskChecklistLocation({ updateTaskChecklistLocation({
variables: { taskChecklistID: checklist.id, position: checklist.position }, variables: { taskChecklistID: checklist.id, position: checklist.position },
@ -487,7 +484,7 @@ const Details: React.FC<DetailsProps> = ({
}} }}
onTaskNameChange={onTaskNameChange} onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange} onTaskDescriptionChange={onTaskDescriptionChange}
onToggleTaskComplete={task => { onToggleTaskComplete={(task) => {
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } }); setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
}} }}
onDeleteTask={onDeleteTask} onDeleteTask={onDeleteTask}
@ -532,7 +529,7 @@ const Details: React.FC<DetailsProps> = ({
createTaskChecklistItem({ variables: { taskChecklistID, name, position } }); createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
}} }}
onMemberProfile={($targetRef, memberID) => { onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID); const member = data.findTask.assigned.find((m) => m.id === memberID);
if (member) { if (member) {
showPopup( showPopup(
$targetRef, $targetRef,
@ -582,7 +579,7 @@ const Details: React.FC<DetailsProps> = ({
}} }}
> >
<CreateChecklistPopup <CreateChecklistPopup
onCreateChecklist={checklistData => { onCreateChecklist={(checklistData) => {
let position = 65535; let position = 65535;
if (data.findTask.checklists) { if (data.findTask.checklists) {
const [lastChecklist] = data.findTask.checklists.slice(-1); const [lastChecklist] = data.findTask.checklists.slice(-1);
@ -632,7 +629,7 @@ const Details: React.FC<DetailsProps> = ({
> >
<DueDateManager <DueDateManager
task={task} task={task}
onRemoveDueDate={t => { onRemoveDueDate={(t) => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } }); updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
// hidePopup(); // hidePopup();
}} }}

View File

@ -64,7 +64,7 @@ const Project = () => {
pollInterval: polling.PROJECT, pollInterval: polling.PROJECT,
}); });
const [toggleTaskLabel] = useToggleTaskLabelMutation({ const [toggleTaskLabel] = useToggleTaskLabelMutation({
onCompleted: newTaskLabel => { onCompleted: (newTaskLabel) => {
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels; taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
}, },
}); });
@ -73,17 +73,17 @@ const Project = () => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (resp.data) { if (resp.data) {
const taskGroupIdx = draftCache.findProject.taskGroups.findIndex( const taskGroupIdx = draftCache.findProject.taskGroups.findIndex(
tg => tg.tasks.findIndex(t => t.id === resp.data?.deleteTask.taskID) !== -1, (tg) => tg.tasks.findIndex((t) => t.id === resp.data?.deleteTask.taskID) !== -1,
); );
if (taskGroupIdx !== -1) { if (taskGroupIdx !== -1) {
draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[ draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[
taskGroupIdx taskGroupIdx
].tasks.filter(t => t.id !== resp.data?.deleteTask.taskID); ].tasks.filter((t) => t.id !== resp.data?.deleteTask.taskID);
} }
} }
}), }),
@ -96,8 +96,8 @@ const Project = () => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.findProject.name = newName.data?.updateProjectName.name ?? ''; draftCache.findProject.name = newName.data?.updateProjectName.name ?? '';
}), }),
{ projectID }, { projectID },
@ -110,8 +110,8 @@ const Project = () => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (response.data) { if (response.data) {
draftCache.findProject.members = [ draftCache.findProject.members = [
...cache.findProject.members, ...cache.findProject.members,
@ -132,10 +132,10 @@ const Project = () => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter( draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter(
m => m.email !== response.data?.deleteInvitedProjectMember.invitedMember.email ?? '', (m) => m.email !== response.data?.deleteInvitedProjectMember.invitedMember.email ?? '',
); );
}), }),
{ projectID }, { projectID },
@ -147,10 +147,10 @@ const Project = () => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.findProject.members = cache.findProject.members.filter( draftCache.findProject.members = cache.findProject.members.filter(
m => m.id !== response.data?.deleteProjectMember.member.id, (m) => m.id !== response.data?.deleteProjectMember.member.id,
); );
}), }),
{ projectID }, { projectID },
@ -176,23 +176,23 @@ const Project = () => {
onChangeProjectOwner={() => { onChangeProjectOwner={() => {
hidePopup(); hidePopup();
}} }}
onRemoveFromBoard={userID => { onRemoveFromBoard={(userID) => {
deleteProjectMember({ variables: { userID, projectID } }); deleteProjectMember({ variables: { userID, projectID } });
hidePopup(); hidePopup();
}} }}
onRemoveInvitedFromBoard={email => { onRemoveInvitedFromBoard={(email) => {
deleteInvitedProjectMember({ variables: { projectID, email } }); deleteInvitedProjectMember({ variables: { projectID, email } });
hidePopup(); hidePopup();
}} }}
onSaveProjectName={projectName => { onSaveProjectName={(projectName) => {
updateProjectName({ variables: { projectID, name: projectName } }); updateProjectName({ variables: { projectID, name: projectName } });
}} }}
onInviteUser={$target => { onInviteUser={($target) => {
showPopup( showPopup(
$target, $target,
<UserManagementPopup <UserManagementPopup
projectID={projectID} projectID={projectID}
onInviteProjectMembers={members => { onInviteProjectMembers={(members) => {
inviteProjectMembers({ variables: { projectID, members } }); inviteProjectMembers({ variables: { projectID, members } });
hidePopup(); hidePopup();
}} }}
@ -233,50 +233,51 @@ const Project = () => {
/> />
<Route <Route
path={`${match.path}/board/c/:taskID`} path={`${match.path}/board/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => ( render={() => {
<Details return (
refreshCache={NOOP} <Details
availableMembers={data.findProject.members} refreshCache={NOOP}
projectURL={`${match.url}/board`} availableMembers={data.findProject.members}
taskID={routeProps.match.params.taskID} projectURL={`${match.url}/board`}
onTaskNameChange={(updatedTask, newName) => { onTaskNameChange={(updatedTask, newName) => {
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } }); updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
}} }}
onTaskDescriptionChange={(updatedTask, newDescription) => { onTaskDescriptionChange={(updatedTask, newDescription) => {
updateTaskDescription({ updateTaskDescription({
variables: { taskID: updatedTask.id, description: newDescription }, variables: { taskID: updatedTask.id, description: newDescription },
optimisticResponse: { optimisticResponse: {
__typename: 'Mutation', __typename: 'Mutation',
updateTaskDescription: { updateTaskDescription: {
__typename: 'Task', __typename: 'Task',
id: updatedTask.id, id: updatedTask.id,
description: newDescription, description: newDescription,
},
}, },
}, });
}); }}
}} onDeleteTask={(deletedTask) => {
onDeleteTask={deletedTask => { deleteTask({ variables: { taskID: deletedTask.id } });
deleteTask({ variables: { taskID: deletedTask.id } }); history.push(`${match.url}/board`);
history.push(`${match.url}/board`); }}
}} onOpenAddLabelPopup={(task, $targetRef) => {
onOpenAddLabelPopup={(task, $targetRef) => { taskLabelsRef.current = task.labels;
taskLabelsRef.current = task.labels; showPopup(
showPopup( $targetRef,
$targetRef, <LabelManagerEditor
<LabelManagerEditor onLabelToggle={(labelID) => {
onLabelToggle={labelID => { toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } }); }}
}} taskID={task.id}
taskID={task.id} labelColors={data.labelColors}
labelColors={data.labelColors} labels={labelsRef}
labels={labelsRef} taskLabels={taskLabelsRef}
taskLabels={taskLabelsRef} projectID={projectID}
projectID={projectID} />,
/>, );
); }}
}} />
/> );
)} }}
/> />
</> </>
); );

View File

@ -16,15 +16,15 @@ import { 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 ControlledInput from 'shared/components/ControlledInput';
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'; import NOOP from 'shared/utils/noop';
import theme from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
import { mixin } from '../shared/utils/styles';
import polling from 'shared/utils/polling'; import polling from 'shared/utils/polling';
import { mixin } from '../shared/utils/styles';
type CreateTeamData = { teamName: string }; type CreateTeamData = { name: string };
type CreateTeamFormProps = { type CreateTeamFormProps = {
onCreateTeam: (teamName: string) => void; onCreateTeam: (teamName: string) => void;
@ -36,28 +36,30 @@ const CreateTeamButton = styled(Button)`
width: 100%; width: 100%;
`; `;
const ErrorText = styled.span`
font-size: 14px;
color: ${(props) => props.theme.colors.danger};
`;
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => { const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
const { register, handleSubmit } = useForm<CreateTeamData>(); const {
register,
handleSubmit,
formState: { errors },
} = useForm<CreateTeamData>();
const createTeam = (data: CreateTeamData) => { const createTeam = (data: CreateTeamData) => {
onCreateTeam(data.teamName); onCreateTeam(data.name);
}; };
return ( return (
<CreateTeamFormContainer onSubmit={handleSubmit(createTeam)}> <CreateTeamFormContainer onSubmit={handleSubmit(createTeam)}>
<Input {errors.name && <ErrorText>{errors.name.message}</ErrorText>}
width="100%" <ControlledInput width="100%" label="Team name" variant="alternate" {...register('name')} />
label="Team name"
id="teamName"
name="teamName"
variant="alternate"
ref={register({ required: 'Team name is required' })}
/>
<CreateTeamButton type="submit">Create</CreateTeamButton> <CreateTeamButton type="submit">Create</CreateTeamButton>
</CreateTeamFormContainer> </CreateTeamFormContainer>
); );
}; };
const ProjectAddTile = styled.div` const ProjectAddTile = styled.div`
background-color: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)}; background-color: ${(props) => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
background-size: cover; background-size: cover;
background-position: 50%; background-position: 50%;
color: #fff; color: #fff;
@ -71,7 +73,7 @@ const ProjectAddTile = styled.div`
`; `;
const ProjectTile = styled(Link)<{ color: string }>` const ProjectTile = styled(Link)<{ color: string }>`
background-color: ${props => props.color}; background-color: ${(props) => props.color};
background-size: cover; background-size: cover;
background-position: 50%; background-position: 50%;
color: #fff; color: #fff;
@ -142,7 +144,7 @@ const ProjectTileName = styled.div<{ centered?: boolean }>`
max-height: 40px; max-height: 40px;
width: 100%; width: 100%;
word-wrap: break-word; word-wrap: break-word;
${props => props.centered && 'text-align: center;'} ${(props) => props.centered && 'text-align: center;'}
`; `;
const Wrapper = styled.div` const Wrapper = styled.div`
@ -180,7 +182,7 @@ const SectionActionLink = styled(Link)`
const ProjectSectionTitle = styled.h3` const ProjectSectionTitle = styled.h3`
font-size: 16px; font-size: 16px;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
`; `;
const ProjectsContainer = styled.div` const ProjectsContainer = styled.div`
@ -210,8 +212,8 @@ const Projects = () => {
}, []); }, []);
const [createProject] = useCreateProjectMutation({ const [createProject] = useCreateProjectMutation({
update: (client, newProject) => { update: (client, newProject) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache => updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (newProject.data) { if (newProject.data) {
draftCache.projects.push({ ...newProject.data.createProject }); draftCache.projects.push({ ...newProject.data.createProject });
} }
@ -224,8 +226,8 @@ const Projects = () => {
const { user } = 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) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (createData.data) { if (createData.data) {
draftCache.teams.push({ ...createData.data?.createTeam }); draftCache.teams.push({ ...createData.data?.createTeam });
} }
@ -239,7 +241,7 @@ const Projects = () => {
const { projects, teams, organizations } = data; const { projects, teams, organizations } = data;
const organizationID = organizations[0].id ?? null; const organizationID = organizations[0].id ?? null;
const personalProjects = projects const personalProjects = projects
.filter(p => p.team === null) .filter((p) => p.team === null)
.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();
@ -251,12 +253,12 @@ const Projects = () => {
const textB = b.name.toUpperCase(); const textB = b.name.toUpperCase();
return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary
}) })
.map(team => { .map((team) => {
return { return {
id: team.id, id: team.id,
name: team.name, name: team.name,
projects: projects projects: projects
.filter(project => project.team && project.team.id === team.id) .filter((project) => project.team && project.team.id === team.id)
.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();
@ -272,7 +274,7 @@ const Projects = () => {
{true && ( // TODO: add permision check {true && ( // TODO: add permision check
<AddTeamButton <AddTeamButton
variant="outline" variant="outline"
onClick={$target => { onClick={($target) => {
showPopup( showPopup(
$target, $target,
<Popup <Popup
@ -283,7 +285,7 @@ const Projects = () => {
}} }}
> >
<CreateTeamForm <CreateTeamForm
onCreateTeam={teamName => { onCreateTeam={(teamName) => {
if (organizationID) { if (organizationID) {
createTeam({ variables: { name: teamName, organizationID } }); createTeam({ variables: { name: teamName, organizationID } });
hidePopup(); hidePopup();
@ -326,7 +328,7 @@ const Projects = () => {
</ProjectListItem> </ProjectListItem>
</ProjectList> </ProjectList>
</div> </div>
{projectTeams.map(team => { {projectTeams.map((team) => {
return ( return (
<div key={team.id}> <div key={team.id}>
<ProjectSectionTitleWrapper> <ProjectSectionTitleWrapper>

View File

@ -35,7 +35,7 @@ const UsersRegister = () => {
}, },
}), }),
}) })
.then(async x => { .then(async (x) => {
const response = await x.json(); const response = await x.json();
const { setup } = response; const { setup } = response;
console.log(response); console.log(response);

View File

@ -36,7 +36,7 @@ const UserMember = styled(Member)`
padding: 4px 0; padding: 4px 0;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)}; background: ${(props) => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
} }
border-radius: 6px; border-radius: 6px;
`; `;
@ -57,8 +57,8 @@ const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, teamMe
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" /> <SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
<TeamMemberList> <TeamMemberList>
{users {users
.filter(u => u.id !== teamMembers.find(p => p.id === u.id)?.id) .filter((u) => u.id !== teamMembers.find((p) => p.id === u.id)?.id)
.map(user => ( .map((user) => (
<UserMember <UserMember
key={user.id} key={user.id}
onCardMemberClick={() => onAddTeamMember(user.id)} onCardMemberClick={() => onAddTeamMember(user.id)}
@ -116,7 +116,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
position: relative; position: relative;
text-decoration: none; text-decoration: none;
${props => ${(props) =>
props.disabled props.disabled
? css` ? css`
user-select: none; user-select: none;
@ -137,7 +137,7 @@ export const Content = styled.div`
export const CurrentPermission = styled.span` export const CurrentPermission = styled.span`
margin-left: 4px; margin-left: 4px;
color: ${props => mixin.rgba(props.theme.colors.text.secondary, 0.4)}; color: ${(props) => mixin.rgba(props.theme.colors.text.secondary, 0.4)};
`; `;
export const Separator = styled.div` export const Separator = styled.div`
@ -148,13 +148,13 @@ export const Separator = styled.div`
export const WarningText = styled.span` export const WarningText = styled.span`
display: flex; display: flex;
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.4)}; color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.4)};
padding: 6px; padding: 6px;
`; `;
export const DeleteDescription = styled.div` export const DeleteDescription = styled.div`
font-size: 14px; font-size: 14px;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
`; `;
export const RemoveMemberButton = styled(Button)` export const RemoveMemberButton = styled(Button)`
@ -221,13 +221,13 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<MiniProfileActions> <MiniProfileActions>
<MiniProfileActionWrapper> <MiniProfileActionWrapper>
{permissions {permissions
.filter(p => (subject.role && subject.role.code === 'owner') || p.code !== 'owner') .filter((p) => (subject.role && subject.role.code === 'owner') || p.code !== 'owner')
.map(perm => ( .map((perm) => (
<MiniProfileActionItem <MiniProfileActionItem
disabled={subject.role && perm.code !== subject.role.code && !canChangeRole} disabled={subject.role && perm.code !== subject.role.code && !canChangeRole}
key={perm.code} key={perm.code}
onClick={() => { onClick={() => {
if (onChangeRole && subject.role && perm.code !== subject.role.code) { if (subject.role && perm.code !== subject.role.code) {
switch (perm.code) { switch (perm.code) {
case 'owner': case 'owner':
onChangeRole(RoleCode.Owner); onChangeRole(RoleCode.Owner);
@ -276,8 +276,8 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
<Select <Select
label="New projects owner" label="New projects owner"
value={orphanedProjectOwner} value={orphanedProjectOwner}
onChange={value => setOrphanedProjectOwner(value)} onChange={(value) => setOrphanedProjectOwner(value)}
options={members.filter(m => m.id !== subject.id).map(m => ({ label: m.fullName, value: m.id }))} options={members.filter((m) => m.id !== subject.id).map((m) => ({ label: m.fullName, value: m.id }))}
/> />
</> </>
)} )}
@ -307,14 +307,14 @@ const MemberItemOption = styled(Button)`
`; `;
const MemberList = styled.div` const MemberList = styled.div`
border-top: 1px solid ${props => props.theme.colors.border}; border-top: 1px solid ${(props) => props.theme.colors.border};
`; `;
const MemberListItem = styled.div` const MemberListItem = styled.div`
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid ${props => props.theme.colors.border}; border-bottom: 1px solid ${(props) => props.theme.colors.border};
min-height: 40px; min-height: 40px;
padding: 12px 0 12px 40px; padding: 12px 0 12px 40px;
position: relative; position: relative;
@ -338,11 +338,11 @@ const MemberProfile = styled(TaskAssignee)`
`; `;
const MemberItemName = styled.p` const MemberItemName = styled.p`
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
`; `;
const MemberItemUsername = styled.p` const MemberItemUsername = styled.p`
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
`; `;
const MemberListHeader = styled.div` const MemberListHeader = styled.div`
@ -351,12 +351,12 @@ const MemberListHeader = styled.div`
`; `;
const ListTitle = styled.h3` const ListTitle = styled.h3`
font-size: 18px; font-size: 18px;
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
margin-bottom: 12px; margin-bottom: 12px;
`; `;
const ListDesc = styled.span` const ListDesc = styled.span`
font-size: 16px; font-size: 16px;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
`; `;
const FilterSearch = styled(Input)` const FilterSearch = styled(Input)`
margin: 0; margin: 0;
@ -388,11 +388,11 @@ const FilterTabItem = styled.li`
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
padding: 6px 8px; padding: 6px 8px;
color: ${props => props.theme.colors.text.primary}; color: ${(props) => props.theme.colors.text.primary};
&:hover { &:hover {
border-radius: 6px; border-radius: 6px;
background: ${props => props.theme.colors.primary}; background: ${(props) => props.theme.colors.primary};
color: ${props => props.theme.colors.text.secondary}; color: ${(props) => props.theme.colors.text.secondary};
} }
`; `;
@ -433,8 +433,8 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
updateApolloCache<GetTeamQuery>( updateApolloCache<GetTeamQuery>(
client, client,
GetTeamDocument, GetTeamDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
if (response.data) { if (response.data) {
draftCache.findTeam.members.push({ draftCache.findTeam.members.push({
...response.data.createTeamMember.teamMember, ...response.data.createTeamMember.teamMember,
@ -453,10 +453,10 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
updateApolloCache<GetTeamQuery>( updateApolloCache<GetTeamQuery>(
client, client,
GetTeamDocument, GetTeamDocument,
cache => (cache) =>
produce(cache, draftCache => { produce(cache, (draftCache) => {
draftCache.findTeam.members = cache.findTeam.members.filter( draftCache.findTeam.members = cache.findTeam.members.filter(
member => member.id !== response.data?.deleteTeamMember.userID, (member) => member.id !== response.data?.deleteTeamMember.userID,
); );
}), }),
{ teamID }, { teamID },
@ -484,13 +484,13 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" /> <FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
{true && ( // TODO: add permission check {true && ( // TODO: add permission check
<InviteMemberButton <InviteMemberButton
onClick={$target => { onClick={($target) => {
showPopup( showPopup(
$target, $target,
<UserManagementPopup <UserManagementPopup
users={data.users} users={data.users}
teamMembers={data.findTeam.members} teamMembers={data.findTeam.members}
onAddTeamMember={userID => { onAddTeamMember={(userID) => {
createTeamMember({ variables: { userID, teamID } }); createTeamMember({ variables: { userID, teamID } });
}} }}
/>, />,
@ -504,7 +504,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
</ListActions> </ListActions>
</MemberListHeader> </MemberListHeader>
<MemberList> <MemberList>
{data.findTeam.members.map(member => ( {data.findTeam.members.map((member) => (
<MemberListItem> <MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} /> <MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
<MemberListItemDetails> <MemberListItemDetails>
@ -515,7 +515,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
<MemberItemOption variant="flat">On 2 projects</MemberItemOption> <MemberItemOption variant="flat">On 2 projects</MemberItemOption>
<MemberItemOption <MemberItemOption
variant="outline" variant="outline"
onClick={$target => { onClick={($target) => {
showPopup( showPopup(
$target, $target,
<TeamRoleManagerPopup <TeamRoleManagerPopup
@ -525,13 +525,13 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
warning={member.role && member.role.code === 'owner' ? warning : null} warning={member.role && member.role.code === 'owner' ? warning : null}
// canChangeRole={user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID)} TODO: add permission check // canChangeRole={user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID)} TODO: add permission check
canChangeRole={true} canChangeRole={true}
onChangeRole={roleCode => { onChangeRole={(roleCode) => {
updateTeamMemberRole({ variables: { userID: member.id, teamID, roleCode } }); updateTeamMemberRole({ variables: { userID: member.id, teamID, roleCode } });
}} }}
onRemoveFromTeam={ onRemoveFromTeam={
member.role && member.role.code === 'owner' member.role && member.role.code === 'owner'
? undefined ? undefined
: newOwnerID => { : (newOwnerID) => {
deleteTeamMember({ variables: { teamID, newOwnerID, userID: member.id } }); deleteTeamMember({ variables: { teamID, newOwnerID, userID: member.id } });
hidePopup(); hidePopup();
} }

View File

@ -16,7 +16,7 @@ const InputWrapper = styled.div<{ width: string }>`
`; `;
const InputLabel = styled.span<{ width: string }>` const InputLabel = styled.span<{ width: string }>`
width: ${props => props.width}; width: ${(props) => props.width};
padding: 0.7rem !important; padding: 0.7rem !important;
color: #c2c6dc; color: #c2c6dc;
left: 0; left: 0;
@ -40,13 +40,13 @@ const InputInput = styled.input<{
focusBg: string; focusBg: string;
borderColor: string; borderColor: string;
}>` }>`
width: ${props => props.width}; width: ${(props) => props.width};
font-size: 14px; font-size: 14px;
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
border-color: ${props => props.borderColor}; border-color: ${(props) => props.borderColor};
background: #262c49; background: #262c49;
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
${props => (props.hasIcon ? 'padding: 0.7rem 1rem 0.7rem 3rem;' : 'padding: 0.7rem;')} ${(props) => (props.hasIcon ? 'padding: 0.7rem 1rem 0.7rem 3rem;' : 'padding: 0.7rem;')}
line-height: 16px; line-height: 16px;
color: #c2c6dc; color: #c2c6dc;
position: relative; position: relative;
@ -55,13 +55,13 @@ const InputInput = styled.input<{
&:focus { &:focus {
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15); box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
border: 1px solid rgba(115, 103, 240); border: 1px solid rgba(115, 103, 240);
background: ${props => props.focusBg}; background: ${(props) => props.focusBg};
} }
&:focus ~ ${InputLabel} { &:focus ~ ${InputLabel} {
color: ${props => props.theme.colors.primary}; color: ${(props) => props.theme.colors.primary};
transform: translate(-3px, -90%); transform: translate(-3px, -90%);
} }
${props => ${(props) =>
props.hasValue && props.hasValue &&
css` css`
& ~ ${InputLabel} { & ~ ${InputLabel} {
@ -94,11 +94,13 @@ type ControlledInputProps = {
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
value?: string; value?: string;
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void; onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;
disabled?: boolean;
}; };
const ControlledInput = ({ const ControlledInput = ({
width = 'auto', width = 'auto',
variant = 'normal', variant = 'normal',
disabled = false,
type = 'text', type = 'text',
autocomplete, autocomplete,
autoFocus = false, autoFocus = false,
@ -126,8 +128,9 @@ const ControlledInput = ({
return ( return (
<InputWrapper className={className} width={width}> <InputWrapper className={className} width={width}>
<InputInput <InputInput
disabled={disabled}
hasValue={hasValue} hasValue={hasValue}
onChange={e => { onChange={(e) => {
if (onChange) { if (onChange) {
setHasValue(e.currentTarget.value !== '' || floatingLabel); setHasValue(e.currentTarget.value !== '' || floatingLabel);
onChange(e); onChange(e);

View File

@ -59,7 +59,7 @@ const HeaderSelectLabel = styled.div`
color: #c2c6dc; color: #c2c6dc;
&:hover { &:hover {
background: ${props => props.theme.colors.primary}; background: ${(props) => props.theme.colors.primary};
color: #c2c6dc; color: #c2c6dc;
} }
`; `;
@ -78,12 +78,12 @@ const HeaderSelect = styled.select`
& option { & option {
color: #c2c6dc; color: #c2c6dc;
background: ${props => props.theme.colors.bg.primary}; background: ${(props) => props.theme.colors.bg.primary};
} }
& option:hover { & option:hover {
background: ${props => props.theme.colors.bg.secondary}; background: ${(props) => props.theme.colors.bg.secondary};
border: 1px solid ${props => props.theme.colors.primary}; border: 1px solid ${(props) => props.theme.colors.primary};
outline: none !important; outline: none !important;
box-shadow: none; box-shadow: none;
color: #c2c6dc; color: #c2c6dc;
@ -115,7 +115,7 @@ const HeaderButton = styled.button`
border: none; border: none;
border-radius: 3px; border-radius: 3px;
&:hover { &:hover {
background: ${props => props.theme.colors.primary}; background: ${(props) => props.theme.colors.primary};
color: #fff; color: #fff;
} }
`; `;
@ -133,7 +133,14 @@ const HeaderActions = styled.div`
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => { const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
const currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null; const currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null;
const { register, handleSubmit, errors, setValue, setError, formState, control } = useForm<DueDateFormData>(); const {
register,
handleSubmit,
setValue,
setError,
formState: { errors },
control,
} = useForm<DueDateFormData>();
const [startDate, setStartDate] = useState<Date | null>(currentDueDate); const [startDate, setStartDate] = useState<Date | null>(currentDueDate);
const [endDate, setEndDate] = useState<Date | null>(currentDueDate); const [endDate, setEndDate] = useState<Date | null>(currentDueDate);
@ -203,7 +210,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
<DateRangeInputs> <DateRangeInputs>
<DatePicker <DatePicker
selected={startDate} selected={startDate}
onChange={date => setStartDate(date)} onChange={(date) => {
if (!Array.isArray(date)) {
setStartDate(date);
}
}}
popperClassName="picker-hidden" popperClassName="picker-hidden"
dateFormat="yyyy-MM-dd" dateFormat="yyyy-MM-dd"
disabledKeyboardNavigation disabledKeyboardNavigation
@ -214,7 +225,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
<DatePicker <DatePicker
selected={startDate} selected={startDate}
isClearable isClearable
onChange={date => setStartDate(date)} onChange={(date) => {
if (!Array.isArray(date)) {
setStartDate(date);
}
}}
popperClassName="picker-hidden" popperClassName="picker-hidden"
dateFormat="yyyy-MM-dd" dateFormat="yyyy-MM-dd"
placeholderText="Select from date" placeholderText="Select from date"
@ -225,7 +240,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
</DateRangeInputs> </DateRangeInputs>
<DatePicker <DatePicker
selected={startDate} selected={startDate}
onChange={date => setStartDate(date)} onChange={(date) => {
if (!Array.isArray(date)) {
setStartDate(date);
}
}}
startDate={startDate} startDate={startDate}
useWeekdaysShort useWeekdaysShort
renderCustomHeader={({ renderCustomHeader={({
@ -247,7 +266,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
value={months[getMonth(date)]} value={months[getMonth(date)]}
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
> >
{months.map(option => ( {months.map((option) => (
<option key={option} value={option}> <option key={option} value={option}>
{option} {option}
</option> </option>
@ -257,7 +276,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
<HeaderSelectLabel> <HeaderSelectLabel>
{date.getFullYear()} {date.getFullYear()}
<HeaderSelect value={getYear(date)} onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}> <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}
</option> </option>
@ -279,8 +298,10 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
<ActionLabel>Due Time</ActionLabel> <ActionLabel>Due Time</ActionLabel>
<DatePicker <DatePicker
selected={startDate} selected={startDate}
onChange={date => { onChange={(date) => {
setStartDate(date); if (!Array.isArray(date)) {
setStartDate(date);
}
}} }}
showTimeSelect showTimeSelect
showTimeSelectOnly showTimeSelectOnly

View File

@ -25,7 +25,12 @@ import {
const Login = ({ onSubmit }: LoginProps) => { const Login = ({ onSubmit }: LoginProps) => {
const [isComplete, setComplete] = useState(true); const [isComplete, setComplete] = useState(true);
const { register, handleSubmit, errors, setError, formState } = useForm<LoginFormData>(); const {
register,
handleSubmit,
setError,
formState: { errors },
} = useForm<LoginFormData>();
const loginSubmit = (data: LoginFormData) => { const loginSubmit = (data: LoginFormData) => {
setComplete(false); setComplete(false);
onSubmit(data, setComplete, setError); onSubmit(data, setComplete, setError);
@ -47,12 +52,7 @@ const Login = ({ onSubmit }: LoginProps) => {
<Form onSubmit={handleSubmit(loginSubmit)}> <Form onSubmit={handleSubmit(loginSubmit)}>
<FormLabel htmlFor="username"> <FormLabel htmlFor="username">
Username Username
<FormTextInput <FormTextInput type="text" {...register('username', { required: 'Username is required' })} />
type="text"
id="username"
name="username"
ref={register({ required: 'Username is required' })}
/>
<FormIcon> <FormIcon>
<User width={20} height={20} /> <User width={20} height={20} />
</FormIcon> </FormIcon>
@ -60,12 +60,7 @@ const Login = ({ onSubmit }: LoginProps) => {
{errors.username && <FormError>{errors.username.message}</FormError>} {errors.username && <FormError>{errors.username.message}</FormError>}
<FormLabel htmlFor="password"> <FormLabel htmlFor="password">
Password Password
<FormTextInput <FormTextInput type="password" {...register('password', { required: 'Password is required' })} />
type="password"
id="password"
name="password"
ref={register({ required: 'Password is required' })}
/>
<FormIcon> <FormIcon>
<Lock width={20} height={20} /> <Lock width={20} height={20} />
</FormIcon> </FormIcon>

View File

@ -26,7 +26,12 @@ const INITIALS_PATTERN = /[a-zA-Z]{2,3}/i;
const Register = ({ onSubmit, registered = false }: RegisterProps) => { const Register = ({ onSubmit, registered = false }: RegisterProps) => {
const [isComplete, setComplete] = useState(true); const [isComplete, setComplete] = useState(true);
const { register, handleSubmit, errors, setError } = useForm<RegisterFormData>(); const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm<RegisterFormData>();
const loginSubmit = (data: RegisterFormData) => { const loginSubmit = (data: RegisterFormData) => {
setComplete(false); setComplete(false);
onSubmit(data, setComplete, setError); onSubmit(data, setComplete, setError);
@ -55,12 +60,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
<Form onSubmit={handleSubmit(loginSubmit)}> <Form onSubmit={handleSubmit(loginSubmit)}>
<FormLabel htmlFor="fullname"> <FormLabel htmlFor="fullname">
Full name Full name
<FormTextInput <FormTextInput type="text" {...register('fullname', { required: 'Full name is required' })} />
type="text"
id="fullname"
name="fullname"
ref={register({ required: 'Full name is required' })}
/>
<FormIcon> <FormIcon>
<User width={20} height={20} /> <User width={20} height={20} />
</FormIcon> </FormIcon>
@ -68,12 +68,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
{errors.username && <FormError>{errors.username.message}</FormError>} {errors.username && <FormError>{errors.username.message}</FormError>}
<FormLabel htmlFor="username"> <FormLabel htmlFor="username">
Username Username
<FormTextInput <FormTextInput type="text" {...register('username', { required: 'Username is required' })} />
type="text"
id="username"
name="username"
ref={register({ required: 'Username is required' })}
/>
<FormIcon> <FormIcon>
<User width={20} height={20} /> <User width={20} height={20} />
</FormIcon> </FormIcon>
@ -83,9 +78,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
Email Email
<FormTextInput <FormTextInput
type="text" type="text"
id="email" {...register('email', {
name="email"
ref={register({
required: 'Email is required', required: 'Email is required',
pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' }, pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
})} })}
@ -99,9 +92,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
Initials Initials
<FormTextInput <FormTextInput
type="text" type="text"
id="initials" {...register('initials', {
name="initials"
ref={register({
required: 'Initials is required', required: 'Initials is required',
pattern: { pattern: {
value: INITIALS_PATTERN, value: INITIALS_PATTERN,
@ -116,12 +107,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
{errors.initials && <FormError>{errors.initials.message}</FormError>} {errors.initials && <FormError>{errors.initials.message}</FormError>}
<FormLabel htmlFor="password"> <FormLabel htmlFor="password">
Password Password
<FormTextInput <FormTextInput type="password" {...register('password', { required: 'Password is required' })} />
type="password"
id="password"
name="password"
ref={register({ required: 'Password is required' })}
/>
<FormIcon> <FormIcon>
<Lock width={20} height={20} /> <Lock width={20} height={20} />
</FormIcon> </FormIcon>
@ -131,9 +117,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
Password (Confirm) Password (Confirm)
<FormTextInput <FormTextInput
type="password" type="password"
id="password_confirm" {...register('password_confirm', { required: 'Password (confirm) is required' })}
name="password_confirm"
ref={register({ required: 'Password (confirm) is required' })}
/> />
<FormIcon> <FormIcon>
<Lock width={20} height={20} /> <Lock width={20} height={20} />

View File

@ -1,23 +1,23 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { User } from 'shared/icons'; import { User } from 'shared/icons';
import Input from 'shared/components/Input';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import ControlledInput from 'shared/components/ControlledInput';
const PasswordInput = styled(Input)` const PasswordInput = styled(ControlledInput)`
margin-top: 30px; margin-top: 30px;
margin-bottom: 0; margin-bottom: 0;
`; `;
const UserInfoInput = styled(Input)` const UserInfoInput = styled(ControlledInput)`
margin-top: 30px; margin-top: 30px;
margin-bottom: 0; margin-bottom: 0;
`; `;
const FormError = styled.span` const FormError = styled.span`
font-size: 12px; font-size: 12px;
color: ${props => props.theme.colors.warning}; color: ${(props) => props.theme.colors.warning};
`; `;
const ProfileContainer = styled.div` const ProfileContainer = styled.div`
@ -42,7 +42,7 @@ const AvatarMask = styled.div<{ background: string }>`
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background: ${props => props.background}; background: ${(props) => props.background};
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
@ -152,12 +152,12 @@ const TabNavItemButton = styled.button<{ active: boolean }>`
width: 100%; width: 100%;
position: relative; position: relative;
color: ${props => (props.active ? `${props.theme.colors.primary}` : '#c2c6dc')}; color: ${(props) => (props.active ? `${props.theme.colors.primary}` : '#c2c6dc')};
&:hover { &:hover {
color: ${props => props.theme.colors.primary}; color: ${(props) => props.theme.colors.primary};
} }
&:hover svg { &:hover svg {
fill: ${props => props.theme.colors.primary}; fill: ${(props) => props.theme.colors.primary};
} }
`; `;
@ -173,10 +173,14 @@ const TabNavLine = styled.span<{ top: number }>`
width: 2px; width: 2px;
height: 48px; height: 48px;
transform: scaleX(1); transform: scaleX(1);
top: ${props => props.top}px; top: ${(props) => props.top}px;
background: linear-gradient(30deg, ${props => props.theme.colors.primary}, ${props => props.theme.colors.primary}); background: linear-gradient(
box-shadow: 0 0 8px 0 ${props => props.theme.colors.primary}; 30deg,
${(props) => props.theme.colors.primary},
${(props) => props.theme.colors.primary}
);
box-shadow: 0 0 8px 0 ${(props) => props.theme.colors.primary};
display: block; display: block;
position: absolute; position: absolute;
transition: all 0.2s ease; transition: all 0.2s ease;
@ -267,36 +271,36 @@ type ResetPasswordTabProps = {
}; };
const ResetPasswordTab: React.FC<ResetPasswordTabProps> = ({ onResetPassword }) => { const ResetPasswordTab: React.FC<ResetPasswordTabProps> = ({ onResetPassword }) => {
const [active, setActive] = useState(true); const [active, setActive] = useState(true);
const { register, handleSubmit, errors, setError, reset } = useForm<{ password: string; password_confirm: string }>(); const {
register,
handleSubmit,
formState: { errors },
setError,
reset,
} = useForm<{ password: string; passwordConfirm: string }>();
const done = () => { const done = () => {
reset(); reset();
setActive(true); setActive(true);
}; };
return ( return (
<form <form
onSubmit={handleSubmit(data => { onSubmit={handleSubmit((data) => {
if (data.password !== data.password_confirm) { if (data.password !== data.passwordConfirm) {
setError('password', { message: 'Passwords must match!', type: 'error' }); setError('password', { message: 'Passwords must match!', type: 'error' });
setError('password_confirm', { message: 'Passwords must match!', type: 'error' }); setError('passwordConfirm', { message: 'Passwords must match!', type: 'error' });
} else { } else {
onResetPassword(data.password, done); onResetPassword(data.password, done);
} }
})} })}
> >
<PasswordInput <PasswordInput width="100%" {...register('password', { required: 'Password is required' })} label="Password" />
width="100%"
ref={register({ required: 'Password is required' })}
label="Password"
name="password"
/>
{errors.password && <FormError>{errors.password.message}</FormError>} {errors.password && <FormError>{errors.password.message}</FormError>}
<PasswordInput <PasswordInput
width="100%" width="100%"
ref={register({ required: 'Password is required' })} {...register('passwordConfirm', { required: 'Password is required' })}
label="Password (confirm)" label="Password (confirm)"
name="password_confirm"
/> />
{errors.password_confirm && <FormError>{errors.password_confirm.message}</FormError>} {errors.passwordConfirm && <FormError>{errors.passwordConfirm.message}</FormError>}
<SettingActions> <SettingActions>
<SaveButton disabled={!active} type="submit"> <SaveButton disabled={!active} type="submit">
Save Change Save Change
@ -329,7 +333,11 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
onChangeUserInfo, onChangeUserInfo,
}) => { }) => {
const [active, setActive] = useState(true); const [active, setActive] = useState(true);
const { register, handleSubmit, errors } = useForm<UserInfoData>(); const {
register,
handleSubmit,
formState: { errors },
} = useForm<UserInfoData>();
const done = () => { const done = () => {
setActive(true); setActive(true);
}; };
@ -341,14 +349,13 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
profile={profile.profileIcon} profile={profile.profileIcon}
/> />
<form <form
onSubmit={handleSubmit(data => { onSubmit={handleSubmit((data) => {
setActive(false); setActive(false);
onChangeUserInfo(data, done); onChangeUserInfo(data, done);
})} })}
> >
<UserInfoInput <UserInfoInput
ref={register({ required: 'Full name is required' })} {...register('full_name', { required: 'Full name is required' })}
name="full_name"
defaultValue={profile.fullName} defaultValue={profile.fullName}
width="100%" width="100%"
label="Name" label="Name"
@ -356,11 +363,10 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
{errors.full_name && <FormError>{errors.full_name.message}</FormError>} {errors.full_name && <FormError>{errors.full_name.message}</FormError>}
<UserInfoInput <UserInfoInput
defaultValue={profile.profileIcon && profile.profileIcon.initials ? profile.profileIcon.initials : ''} defaultValue={profile.profileIcon && profile.profileIcon.initials ? profile.profileIcon.initials : ''}
ref={register({ {...register('initials', {
required: 'Initials is required', required: 'Initials is required',
pattern: { value: INITIALS_PATTERN, message: 'Intials must be between two to four characters' }, pattern: { value: INITIALS_PATTERN, message: 'Intials must be between two to four characters' },
})} })}
name="initials"
width="100%" width="100%"
label="Initials " label="Initials "
/> />
@ -368,8 +374,7 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
<UserInfoInput disabled defaultValue={profile.username ?? ''} width="100%" label="Username " /> <UserInfoInput disabled defaultValue={profile.username ?? ''} width="100%" label="Username " />
<UserInfoInput <UserInfoInput
width="100%" width="100%"
name="email" {...register('email', {
ref={register({
required: 'Email is required', required: 'Email is required',
pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' }, pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
})} })}
@ -377,7 +382,7 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
label="Email" label="Email"
/> />
{errors.email && <FormError>{errors.email.message}</FormError>} {errors.email && <FormError>{errors.email.message}</FormError>}
<UserInfoInput width="100%" name="bio" ref={register()} defaultValue={profile.bio ?? ''} label="Bio" /> <UserInfoInput width="100%" {...register('bio')} defaultValue={profile.bio ?? ''} label="Bio" />
{errors.bio && <FormError>{errors.bio.message}</FormError>} {errors.bio && <FormError>{errors.bio.message}</FormError>}
<SettingActions> <SettingActions>
<SaveButton disabled={!active} type="submit"> <SaveButton disabled={!active} type="submit">

View File

@ -136,7 +136,7 @@ const StreamComment: React.FC<StreamCommentProps> = ({
onCreateComment={onUpdateComment} onCreateComment={onUpdateComment}
/> />
) : ( ) : (
<ReactMarkdown escapeHtml={false} plugins={[em]}> <ReactMarkdown skipHtml plugins={[em]}>
{DOMPurify.sanitize(comment.message, { FORBID_TAGS: ['style', 'img'] })} {DOMPurify.sanitize(comment.message, { FORBID_TAGS: ['style', 'img'] })}
</ReactMarkdown> </ReactMarkdown>
)} )}
@ -300,7 +300,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const activityStream: Array<{ id: string; data: { time: string; type: 'comment' | 'activity' } }> = []; const activityStream: Array<{ id: string; data: { time: string; type: 'comment' | 'activity' } }> = [];
if (task.activity) { if (task.activity) {
task.activity.forEach(activity => { task.activity.forEach((activity) => {
activityStream.push({ activityStream.push({
id: activity.id, id: activity.id,
data: { data: {
@ -312,7 +312,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
} }
if (task.comments) { if (task.comments) {
task.comments.forEach(comment => { task.comments.forEach((comment) => {
activityStream.push({ activityStream.push({
id: comment.id, id: comment.id,
data: { data: {
@ -358,12 +358,12 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<DueDateTitle>MEMBERS</DueDateTitle> <DueDateTitle>MEMBERS</DueDateTitle>
{task.assigned && task.assigned.length !== 0 ? ( {task.assigned && task.assigned.length !== 0 ? (
<MemberList> <MemberList>
{task.assigned.map(m => ( {task.assigned.map((m) => (
<TaskMember <TaskMember
key={m.id} key={m.id}
member={m} member={m}
size={32} size={32}
onMemberProfile={$target => { onMemberProfile={($target) => {
if (user) { if (user) {
onMemberProfile($target, m.id); onMemberProfile($target, m.id);
} }
@ -401,7 +401,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<ExtraActionsSection> <ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle> <DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton <ActionButton
onClick={$target => { onClick={($target) => {
onOpenAddLabelPopup(task, $target); onOpenAddLabelPopup(task, $target);
}} }}
icon={<Tags width={12} height={12} />} icon={<Tags width={12} height={12} />}
@ -409,7 +409,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
Labels Labels
</ActionButton> </ActionButton>
<ActionButton <ActionButton
onClick={$target => { onClick={($target) => {
onOpenAddChecklistPopup(task, $target); onOpenAddChecklistPopup(task, $target);
}} }}
icon={<CheckSquareOutline width={12} height={12} />} icon={<CheckSquareOutline width={12} height={12} />}
@ -460,7 +460,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
value={taskName} value={taskName}
ref={$detailsTitle} ref={$detailsTitle}
disabled={user === null} disabled={user === null}
onKeyDown={e => { onKeyDown={(e) => {
if (e.keyCode === 13) { if (e.keyCode === 13) {
e.preventDefault(); e.preventDefault();
if ($detailsTitle && $detailsTitle.current) { if ($detailsTitle && $detailsTitle.current) {
@ -468,7 +468,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
} }
} }
}} }}
onChange={e => { onChange={(e) => {
setTaskName(e.currentTarget.value); setTaskName(e.currentTarget.value);
}} }}
onBlur={() => { onBlur={() => {
@ -481,12 +481,12 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<Labels> <Labels>
{task.labels.length !== 0 && ( {task.labels.length !== 0 && (
<MetaDetailContent> <MetaDetailContent>
{task.labels.map(label => { {task.labels.map((label) => {
return ( return (
<TaskLabelItem <TaskLabelItem
key={label.projectLabel.id} key={label.projectLabel.id}
label={label} label={label}
onClick={$target => { onClick={($target) => {
onOpenAddLabelPopup(task, $target); onOpenAddLabelPopup(task, $target);
}} }}
/> />
@ -505,7 +505,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailsEditor value={taskDescriptionRef.current} /> <TaskDetailsEditor value={taskDescriptionRef.current} />
) : ( ) : (
<EditorContainer <EditorContainer
onClick={e => { onClick={(e) => {
if (!editTaskDescription) { if (!editTaskDescription) {
setEditTaskDescription(true); setEditTaskDescription(true);
} }
@ -513,10 +513,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
> >
<Editor <Editor
defaultValue={task.description ?? ''} defaultValue={task.description ?? ''}
theme={dark}
readOnly={user === null || !editTaskDescription} readOnly={user === null || !editTaskDescription}
autoFocus autoFocus
onChange={value => { theme={dark}
onChange={(value) => {
setSaveTimeout(() => { setSaveTimeout(() => {
clearTimeout(saveTimeout); clearTimeout(saveTimeout);
return setTimeout(saveDescription, 2000); return setTimeout(saveDescription, 2000);
@ -531,9 +531,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton> <ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton>
</DescriptionContainer> </DescriptionContainer>
<ChecklistSection> <ChecklistSection>
<DragDropContext onDragEnd={result => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}> <DragDropContext onDragEnd={(result) => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>
<Droppable direction="vertical" type="checklist" droppableId="root"> <Droppable direction="vertical" type="checklist" droppableId="root">
{dropProvided => ( {(dropProvided) => (
<ChecklistContainer {...dropProvided.droppableProps} ref={dropProvided.innerRef}> <ChecklistContainer {...dropProvided.droppableProps} ref={dropProvided.innerRef}>
{task.checklists && {task.checklists &&
task.checklists task.checklists
@ -541,7 +541,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
.sort((a, b) => a.position - b.position) .sort((a, b) => a.position - b.position)
.map((checklist, idx) => ( .map((checklist, idx) => (
<Draggable key={checklist.id} draggableId={checklist.id} index={idx}> <Draggable key={checklist.id} draggableId={checklist.id} index={idx}>
{provided => ( {(provided) => (
<Checklist <Checklist
ref={provided.innerRef} ref={provided.innerRef}
wrapperProps={provided.draggableProps} wrapperProps={provided.draggableProps}
@ -551,10 +551,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
checklistID={checklist.id} checklistID={checklist.id}
items={checklist.items} items={checklist.items}
onDeleteChecklist={onDeleteChecklist} onDeleteChecklist={onDeleteChecklist}
onChangeName={newName => onChangeChecklistName(checklist.id, newName)} onChangeName={(newName) => onChangeChecklistName(checklist.id, newName)}
onToggleItem={onToggleChecklistItem} onToggleItem={onToggleChecklistItem}
onDeleteItem={onDeleteItem} onDeleteItem={onDeleteItem}
onAddItem={n => { onAddItem={(n) => {
if (task.checklists) { if (task.checklists) {
let position = 65535; let position = 65535;
const [lastItem] = checklist.items const [lastItem] = checklist.items
@ -569,7 +569,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onChangeItemName={onChangeItemName} onChangeItemName={onChangeItemName}
> >
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}> <Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
{checklistDrop => ( {(checklistDrop) => (
<> <>
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}> <ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
{checklist.items {checklist.items
@ -577,7 +577,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
.sort((a, b) => a.position - b.position) .sort((a, b) => a.position - b.position)
.map((item, itemIdx) => ( .map((item, itemIdx) => (
<Draggable key={item.id} draggableId={item.id} index={itemIdx}> <Draggable key={item.id} draggableId={item.id} index={itemIdx}>
{itemDrop => ( {(itemDrop) => (
<ChecklistItem <ChecklistItem
key={item.id} key={item.id}
itemID={item.id} itemID={item.id}
@ -615,17 +615,19 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TabBarItem>Activity</TabBarItem> <TabBarItem>Activity</TabBarItem>
</TabBarSection> </TabBarSection>
<ActivitySection> <ActivitySection>
{activityStream.map(stream => {activityStream.map((stream) =>
stream.data.type === 'comment' ? ( stream.data.type === 'comment' ? (
<StreamComment <StreamComment
onExtraActions={onCommentShowActions} onExtraActions={onCommentShowActions}
onCancelCommentEdit={onCancelCommentEdit} onCancelCommentEdit={onCancelCommentEdit}
onUpdateComment={message => onUpdateComment(stream.id, message)} onUpdateComment={(message) => onUpdateComment(stream.id, message)}
editable={stream.id === editableComment} editable={stream.id === editableComment}
comment={task.comments && task.comments.find(comment => comment.id === stream.id)} comment={task.comments && task.comments.find((comment) => comment.id === stream.id)}
/> />
) : ( ) : (
<StreamActivity activity={task.activity && task.activity.find(activity => activity.id === stream.id)} /> <StreamActivity
activity={task.activity && task.activity.find((activity) => activity.id === stream.id)}
/>
), ),
)} )}
</ActivitySection> </ActivitySection>
@ -634,7 +636,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<CommentContainer> <CommentContainer>
<CommentCreator <CommentCreator
me={me} me={me}
onCreateComment={message => onCreateComment(task, message)} onCreateComment={(message) => onCreateComment(task, message)}
onMemberProfile={onMemberProfile} onMemberProfile={onMemberProfile}
/> />
</CommentContainer> </CommentContainer>

View File

@ -1,14 +1,15 @@
import theme from 'App/ThemeStyles'; import theme from 'App/ThemeStyles';
const colors = { const colors = {
almostBlack: '#181A1B', almostBlack: 'rgb(38, 44, 73)',
lightBlack: '#2F3336', lightBlack: 'rgb(16, 22, 58)',
almostWhite: '#E6E6E6', bgPrimary: 'rgb(16, 22, 58)',
almostWhite: 'rgb(194, 198, 220)',
white: '#FFF', white: '#FFF',
white10: 'rgba(255, 255, 255, 0.1)', white10: 'rgb(194, 198, 220)',
black: '#000', black: '#000',
black10: 'rgba(0, 0, 0, 0.1)', black10: 'rgba(0, 0, 0, 0.1)',
primary: '#1AB6FF', primary: 'rgb(115, 103, 240)',
greyLight: '#F4F7FA', greyLight: '#F4F7FA',
grey: '#E8EBED', grey: '#E8EBED',
greyMid: '#C5CCD3', greyMid: '#C5CCD3',
@ -17,15 +18,16 @@ const colors = {
export const base = { export const base = {
...colors, ...colors,
fontFamily: "'Open Sans', sans-serif", fontFamily: 'Open Sans',
fontFamilyMono: "'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace", fontFamilyMono: "'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
fontWeight: 400, fontWeight: 400,
zIndex: 10000, zIndex: 1000000,
link: colors.primary, link: colors.primary,
placeholder: '#B1BECC', placeholder: '#B1BECC',
textSecondary: '#4E5C6E', textSecondary: '#fff',
textLight: colors.white, textLight: colors.white,
textHighlight: '#b3e7ff', textHighlight: '#b3e7ff',
textHighlightForeground: colors.white,
selected: colors.primary, selected: colors.primary,
codeComment: '#6a737d', codeComment: '#6a737d',
codePunctuation: '#5e6687', codePunctuation: '#5e6687',
@ -43,13 +45,13 @@ export const base = {
codeInserted: '#202746', codeInserted: '#202746',
codeImportant: '#c94922', codeImportant: '#c94922',
blockToolbarBackground: colors.white, blockToolbarBackground: colors.bgPrimary,
blockToolbarTrigger: colors.greyMid, blockToolbarTrigger: colors.white,
blockToolbarTriggerIcon: colors.white, blockToolbarTriggerIcon: colors.white,
blockToolbarItem: colors.almostBlack, blockToolbarItem: colors.white,
blockToolbarText: colors.almostBlack, blockToolbarText: colors.white,
blockToolbarHoverBackground: colors.greyLight, blockToolbarHoverBackground: colors.primary,
blockToolbarDivider: colors.greyMid, blockToolbarDivider: colors.almostWhite,
noticeInfoBackground: '#F5BE31', noticeInfoBackground: '#F5BE31',
noticeInfoText: colors.almostBlack, noticeInfoText: colors.almostBlack,
@ -58,18 +60,20 @@ export const base = {
noticeWarningBackground: '#FF5C80', noticeWarningBackground: '#FF5C80',
noticeWarningText: colors.white, noticeWarningText: colors.white,
}; };
export const dark = { export const dark = {
...base, ...base,
background: 'transparent', background: colors.almostBlack,
text: `${theme.colors.text.primary}`, text: colors.almostWhite,
code: `${theme.colors.text.primary}`, code: colors.almostWhite,
cursor: `${theme.colors.text.primary}`, cursor: colors.white,
divider: '#4E5C6E', divider: '#4E5C6E',
placeholder: '#52657A', placeholder: '#52657A',
toolbarBackground: colors.white, toolbarBackground: colors.bgPrimary,
toolbarInput: colors.black10, toolbarHoverBackground: colors.primary,
toolbarItem: colors.lightBlack, toolbarInput: colors.almostWhite,
toolbarItem: colors.white,
tableDivider: colors.lightBlack, tableDivider: colors.lightBlack,
tableSelected: colors.primary, tableSelected: colors.primary,
@ -81,6 +85,9 @@ export const dark = {
codeString: '#3d8fd1', codeString: '#3d8fd1',
horizontalRule: colors.lightBlack, horizontalRule: colors.lightBlack,
imageErrorBackground: 'rgba(0, 0, 0, 0.5)', imageErrorBackground: 'rgba(0, 0, 0, 0.5)',
scrollbarBackground: colors.black,
scrollbarThumb: colors.lightBlack,
}; };
export default dark; export default dark;

View File

@ -18,8 +18,9 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react", "jsx": "react-jsx",
"baseUrl": "src" "baseUrl": "src",
"noFallthroughCasesInSwitch": true
}, },
"include": [ "include": [
"src" "src"

File diff suppressed because it is too large Load Diff