deps: upgrade all dependencies
This commit is contained in:
parent
5a9a66effe
commit
8c6a3db0bc
@ -1 +1,2 @@
|
|||||||
REACT_APP_ENABLE_POLLING=true
|
REACT_APP_ENABLE_POLLING=true
|
||||||
|
ESLINT_NO_DEV_ERRORS=true
|
||||||
|
@ -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",
|
||||||
{
|
{
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
|
@ -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) {
|
||||||
|
@ -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 = () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
}}
|
}}
|
||||||
|
@ -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}
|
/>,
|
||||||
/>,
|
);
|
||||||
);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
)}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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} />
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
9047
frontend/yarn.lock
9047
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user