deps: upgrade all dependencies
This commit is contained in:
parent
5a9a66effe
commit
8c6a3db0bc
@ -1 +1,2 @@
|
||||
REACT_APP_ENABLE_POLLING=true
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
|
@ -24,7 +24,7 @@
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"prettier/prettier": "warn",
|
||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
@ -48,6 +48,8 @@
|
||||
"tsx": "never"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
|
@ -3,26 +3,22 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.0.0-rc.8",
|
||||
"@apollo/client": "^3.3.16",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@apollo/react-hooks": "^4.0.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/date-fns": "^2.6.0",
|
||||
"@types/dompurify": "^2.0.4",
|
||||
"@types/dompurify": "^2.2.2",
|
||||
"@types/emoji-mart": "^3.0.4",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@types/react": "^16.9.21",
|
||||
"@types/react-beautiful-dnd": "^12.1.1",
|
||||
"@types/react-datepicker": "^2.11.0",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-router": "^5.1.4",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"@types/react-select": "^3.0.13",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^15.0.1",
|
||||
"@types/react": "^17.0.4",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-datepicker": "^3.1.8",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-router": "^5.1.13",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/react-timeago": "^4.1.1",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"apollo-cache-inmemory": "^1.6.5",
|
||||
@ -33,39 +29,39 @@
|
||||
"apollo-link-state": "^0.4.2",
|
||||
"apollo-utilities": "^1.3.3",
|
||||
"axios": "^0.21.1",
|
||||
"axios-auth-refresh": "^2.2.7",
|
||||
"axios-auth-refresh": "^3.1.0",
|
||||
"color": "^3.1.2",
|
||||
"date-fns": "^2.14.0",
|
||||
"dayjs": "^1.9.1",
|
||||
"dompurify": "^2.2.6",
|
||||
"emoji-mart": "^3.0.0",
|
||||
"date-fns": "^2.21.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"dompurify": "^2.2.8",
|
||||
"emoji-mart": "^3.0.1",
|
||||
"emoticon": "^4.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"history": "^4.10.1",
|
||||
"immer": "^8.0.1",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.20",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-tag": "^2.12.4",
|
||||
"history": "^5.0.0",
|
||||
"immer": "^9.0.2",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"node-emoji": "^1.10.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^6.13.7",
|
||||
"react": "^16.12.0",
|
||||
"query-string": "^7.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-autosize-textarea": "^7.0.0",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-datepicker": "^2.14.1",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-datepicker": "^3.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-emoji-render": "^1.2.4",
|
||||
"react-hook-form": "^6.0.6",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-hook-form": "^7.3.6",
|
||||
"react-markdown": "^6.0.1",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.0",
|
||||
"react-select": "^3.1.0",
|
||||
"react-timeago": "^4.4.0",
|
||||
"react-toastify": "^6.0.8",
|
||||
"rich-markdown-editor": "^10.6.5",
|
||||
"styled-components": "^5.1.0",
|
||||
"typescript": "~3.7.2"
|
||||
"react-scripts": "4.0.3",
|
||||
"react-select": "^4.3.0",
|
||||
"react-timeago": "^5.2.0",
|
||||
"react-toastify": "^7.0.4",
|
||||
"rich-markdown-editor": "^11.0.10",
|
||||
"styled-components": "^5.2.3",
|
||||
"typescript": "~4.2.4"
|
||||
},
|
||||
"proxy": "http://localhost:3333",
|
||||
"scripts": {
|
||||
@ -97,16 +93,16 @@
|
||||
"@graphql-codegen/typescript": "^1.22.0",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.16",
|
||||
"@graphql-codegen/typescript-react-apollo": "^2.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^2.20.0",
|
||||
"@typescript-eslint/parser": "^2.20.0",
|
||||
"eslint": "^6.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"eslint": "^7.25.0",
|
||||
"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-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.18.3",
|
||||
"eslint-plugin-react-hooks": "^1.7.0",
|
||||
"prettier": "^1.19.1"
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
UsersDocument,
|
||||
UsersQuery,
|
||||
} from 'shared/generated/graphql';
|
||||
import Input from 'shared/components/Input';
|
||||
import styled from 'styled-components';
|
||||
import Button from 'shared/components/Button';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
@ -20,6 +19,7 @@ import updateApolloCache from 'shared/utils/cache';
|
||||
import { useCurrentUser } from 'App/context';
|
||||
import { Redirect } from 'react-router';
|
||||
import NOOP from 'shared/utils/noop';
|
||||
import ControlledInput from 'shared/components/ControlledInput';
|
||||
|
||||
const DeleteUserWrapper = styled.div`
|
||||
display: flex;
|
||||
@ -77,12 +77,12 @@ const CreateUserButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const AddUserInput = styled(Input)`
|
||||
const AddUserInput = styled(ControlledInput)`
|
||||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const InputError = styled.span`
|
||||
color: ${props => props.theme.colors.danger};
|
||||
color: ${(props) => props.theme.colors.danger};
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
@ -91,7 +91,12 @@ type AddUserPopupProps = {
|
||||
};
|
||||
|
||||
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) => {
|
||||
onAddUser(data);
|
||||
@ -102,30 +107,25 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
|
||||
floatingLabel
|
||||
width="100%"
|
||||
label="Full Name"
|
||||
id="fullName"
|
||||
name="fullName"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Full name is required' })}
|
||||
{...register('fullName', { required: 'Full name is required' })}
|
||||
/>
|
||||
{errors.fullName && <InputError>{errors.fullName.message}</InputError>}
|
||||
<AddUserInput
|
||||
floatingLabel
|
||||
width="100%"
|
||||
label="Email"
|
||||
id="email"
|
||||
name="email"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Email is required' })}
|
||||
{...register('email', { required: 'Email is required' })}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="roleCode"
|
||||
rules={{ required: 'Role is required' }}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
label="Role"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={[
|
||||
{ label: 'Admin', value: 'admin' },
|
||||
{ label: 'Member', value: 'member' },
|
||||
@ -138,31 +138,25 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
|
||||
floatingLabel
|
||||
width="100%"
|
||||
label="Username"
|
||||
id="username"
|
||||
name="username"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Username is required' })}
|
||||
{...register('username', { required: 'Username is required' })}
|
||||
/>
|
||||
{errors.username && <InputError>{errors.username.message}</InputError>}
|
||||
<AddUserInput
|
||||
floatingLabel
|
||||
width="100%"
|
||||
label="Initials"
|
||||
id="initials"
|
||||
name="initials"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Initials is required' })}
|
||||
{...register('initials', { required: 'Initials is required' })}
|
||||
/>
|
||||
{errors.initials && <InputError>{errors.initials.message}</InputError>}
|
||||
<AddUserInput
|
||||
floatingLabel
|
||||
width="100%"
|
||||
label="Password"
|
||||
id="password"
|
||||
name="password"
|
||||
variant="alternate"
|
||||
type="password"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
{...register('password', { required: 'Password is required' })}
|
||||
/>
|
||||
{errors.password && <InputError>{errors.password.message}</InputError>}
|
||||
<CreateUserButton type="submit">Create</CreateUserButton>
|
||||
@ -179,10 +173,10 @@ const AdminRoute = () => {
|
||||
const { user } = useCurrentUser();
|
||||
const [deleteInvitedUser] = useDeleteInvitedUserAccountMutation({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
||||
produce(cache, draftCache => {
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, (cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
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({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
||||
produce(cache, draftCache => {
|
||||
draftCache.users = cache.users.filter(u => u.id !== response.data?.deleteUserAccount.userAccount.id);
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, (cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
draftCache.users = cache.users.filter((u) => u.id !== response.data?.deleteUserAccount.userAccount.id);
|
||||
}),
|
||||
);
|
||||
},
|
||||
@ -234,7 +228,7 @@ TODO: add permision check
|
||||
onUpdateUserPassword={() => {
|
||||
hidePopup();
|
||||
}}
|
||||
onDeleteInvitedUser={invitedUserID => {
|
||||
onDeleteInvitedUser={(invitedUserID) => {
|
||||
deleteInvitedUser({ variables: { invitedUserID } });
|
||||
hidePopup();
|
||||
}}
|
||||
@ -242,12 +236,12 @@ TODO: add permision check
|
||||
deleteUser({ variables: { userID, newOwnerID } });
|
||||
hidePopup();
|
||||
}}
|
||||
onAddUser={$target => {
|
||||
onAddUser={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<Popup tab={0} title="Add member" onClose={() => hidePopup()}>
|
||||
<AddUserPopup
|
||||
onAddUser={u => {
|
||||
onAddUser={(u) => {
|
||||
const { roleCode, ...userData } = u;
|
||||
createUser({ variables: { ...userData, roleCode: roleCode.value } });
|
||||
hidePopup();
|
||||
|
@ -32,6 +32,7 @@ type ValidateTokenResponse = {
|
||||
const UserRequiredRoute: React.FC<any> = ({ children }) => {
|
||||
const { user } = useCurrentUser();
|
||||
const location = useLocation();
|
||||
console.log('user required', user);
|
||||
if (user) {
|
||||
return children;
|
||||
}
|
||||
@ -45,18 +46,14 @@ const UserRequiredRoute: React.FC<any> = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
type RoutesProps = {
|
||||
history: H.History;
|
||||
};
|
||||
|
||||
const Routes: React.FC<RoutesProps> = () => {
|
||||
const Routes: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setUser } = useCurrentUser();
|
||||
useEffect(() => {
|
||||
fetch('/auth/validate', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(async x => {
|
||||
}).then(async (x) => {
|
||||
const response: ValidateTokenResponse = await x.json();
|
||||
const { valid, userID } = response;
|
||||
if (valid) {
|
||||
@ -65,6 +62,7 @@ const Routes: React.FC<RoutesProps> = () => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
console.log('loading', loading);
|
||||
if (loading) return null;
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { Router } from 'react-router';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { PopupProvider } from 'shared/components/PopupMenu';
|
||||
import styled, { ThemeProvider } from 'styled-components';
|
||||
import NormalizeStyles from './NormalizeStyles';
|
||||
@ -13,8 +12,6 @@ import { UserContext } from './context';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import './fonts.css';
|
||||
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const App = () => {
|
||||
const [user, setUser] = useState<string | null>(null);
|
||||
|
||||
@ -24,11 +21,11 @@ const App = () => {
|
||||
<ThemeProvider theme={theme}>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<Router history={history}>
|
||||
<BrowserRouter>
|
||||
<PopupProvider>
|
||||
<Routes history={history} />
|
||||
<Routes />
|
||||
</PopupProvider>
|
||||
</Router>
|
||||
</BrowserRouter>
|
||||
<ToastedContainer
|
||||
position="bottom-right"
|
||||
autoClose={5000}
|
||||
|
@ -9,6 +9,7 @@ const Auth = () => {
|
||||
const history = useHistory();
|
||||
const location = useLocation<{ redirect: string } | undefined>();
|
||||
const { setUser } = useContext(UserContext);
|
||||
console.log('auth');
|
||||
const login = (
|
||||
data: LoginFormData,
|
||||
setComplete: (val: boolean) => void,
|
||||
@ -21,7 +22,7 @@ const Auth = () => {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
}),
|
||||
}).then(async x => {
|
||||
}).then(async (x) => {
|
||||
if (x.status === 401) {
|
||||
setInvalidLoginAttempt(invalidLoginAttempt + 1);
|
||||
setError('username', { type: 'error', message: 'Invalid username' });
|
||||
@ -44,7 +45,7 @@ const Auth = () => {
|
||||
fetch('/auth/validate', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(async x => {
|
||||
}).then(async (x) => {
|
||||
const response = await x.json();
|
||||
const { valid, userID } = response;
|
||||
if (valid) {
|
||||
|
@ -29,6 +29,7 @@ import useStickyState from 'shared/hooks/useStickyState';
|
||||
import MyTasksSortPopup from './MyTasksSort';
|
||||
import MyTasksStatusPopup from './MyTasksStatus';
|
||||
import TaskEntry from './TaskEntry';
|
||||
import { StaticContext } from 'react-router';
|
||||
|
||||
type TaskRouteProps = {
|
||||
taskID: string;
|
||||
@ -61,11 +62,7 @@ function prettySort(sort: MyTasksSort) {
|
||||
if (sort === MyTasksSort.None) {
|
||||
return 'Sort';
|
||||
}
|
||||
return `Sort: ${sort.charAt(0) +
|
||||
sort
|
||||
.slice(1)
|
||||
.toLowerCase()
|
||||
.replace(/_/gi, ' ')}`;
|
||||
return `Sort: ${sort.charAt(0) + sort.slice(1).toLowerCase().replace(/_/gi, ' ')}`;
|
||||
}
|
||||
|
||||
type Group = {
|
||||
@ -75,7 +72,7 @@ type Group = {
|
||||
};
|
||||
const DueDateEditorLabel = styled.div`
|
||||
align-items: center;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
|
||||
font-size: 11px;
|
||||
padding: 0 8px;
|
||||
@ -107,16 +104,16 @@ const ProjectActionWrapper = styled.div<{ disabled?: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
}
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.disabled &&
|
||||
css`
|
||||
opacity: 0.5;
|
||||
@ -150,7 +147,7 @@ const ProjectAction: React.FC<ProjectActionProps> = ({ onClick, disabled = false
|
||||
|
||||
const EditorPositioner = styled.div<{ top: number; left: number }>`
|
||||
position: absolute;
|
||||
top: ${p => p.top}px;
|
||||
top: ${(p) => p.top}px;
|
||||
justify-content: flex-end;
|
||||
margin-left: -100vw;
|
||||
z-index: 10000;
|
||||
@ -160,7 +157,7 @@ const EditorPositioner = styled.div<{ top: number; left: number }>`
|
||||
height: 0;
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
left: ${p => p.left}px;
|
||||
left: ${(p) => p.left}px;
|
||||
`;
|
||||
|
||||
const EditorPositionerContents = styled.div`
|
||||
@ -168,15 +165,15 @@ const EditorPositionerContents = styled.div`
|
||||
`;
|
||||
|
||||
const EditorContainer = styled.div<{ width: number }>`
|
||||
border: 1px solid ${props => props.theme.colors.primary};
|
||||
background: ${props => props.theme.colors.bg.secondary};
|
||||
border: 1px solid ${(props) => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.bg.secondary};
|
||||
position: relative;
|
||||
width: ${p => p.width}px;
|
||||
width: ${(p) => p.width}px;
|
||||
`;
|
||||
|
||||
const EditorCell = styled.div<{ width: number }>`
|
||||
display: flex;
|
||||
width: ${p => p.width}px;
|
||||
width: ${(p) => p.width}px;
|
||||
`;
|
||||
|
||||
// TABLE
|
||||
@ -224,7 +221,7 @@ const TaskGroupItems = 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;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
@ -250,7 +247,7 @@ const ProjectPillName = styled.span`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
`;
|
||||
|
||||
const ProjectPillColor = styled.svg`
|
||||
@ -299,7 +296,7 @@ const OptionTitle = styled.div`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
const OptionSubTitle = styled.div`
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
font-size: 11px;
|
||||
margin-left: 8px;
|
||||
min-width: 50px;
|
||||
@ -319,7 +316,7 @@ const Option = ({ innerProps, data }: any) => {
|
||||
};
|
||||
|
||||
const TaskGroupHeaderContents = styled.div<{ width: number }>`
|
||||
width: ${p => p.width}px;
|
||||
width: ${(p) => p.width}px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
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;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
fill: ${props => props.theme.colors.text.primary};
|
||||
fill: ${(props) => props.theme.colors.text.primary};
|
||||
transition-duration: 0.2s;
|
||||
transition-property: background, border, box-shadow, fill;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
fill: ${props => props.theme.colors.text.secondary};
|
||||
fill: ${(props) => props.theme.colors.text.secondary};
|
||||
}
|
||||
`;
|
||||
const TaskGroupName = styled.div`
|
||||
@ -371,7 +368,7 @@ const TaskGroupName = styled.div`
|
||||
display: flex;
|
||||
height: 50px;
|
||||
min-width: 1px;
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
@ -393,7 +390,7 @@ const Row = styled.div`
|
||||
`;
|
||||
|
||||
const RowHeaderLeft = styled.div<{ width: number }>`
|
||||
width: ${p => p.width}px;
|
||||
width: ${(p) => p.width}px;
|
||||
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
@ -405,7 +402,7 @@ const RowHeaderLeft = styled.div<{ width: number }>`
|
||||
`;
|
||||
const RowHeaderLeftInner = styled.div`
|
||||
align-items: stretch;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
font-size: 12px;
|
||||
@ -429,7 +426,7 @@ const RowHeaderLeftNameText = styled.div`
|
||||
`;
|
||||
|
||||
const RowHeaderRight = styled.div<{ left: number }>`
|
||||
left: ${p => p.left}px;
|
||||
left: ${(p) => p.left}px;
|
||||
right: 0px;
|
||||
height: 37px;
|
||||
position: absolute;
|
||||
@ -461,7 +458,7 @@ const RowHeaderRightContainer = styled.div`
|
||||
`;
|
||||
|
||||
const ItemWrapper = styled.div<{ width: number }>`
|
||||
width: ${p => p.width}px;
|
||||
width: ${(p) => p.width}px;
|
||||
align-items: center;
|
||||
border: 1px solid #414561;
|
||||
border-bottom: 0;
|
||||
@ -474,11 +471,11 @@ const ItemWrapper = styled.div<{ width: number }>`
|
||||
margin-right: -1px;
|
||||
padding: 0 8px;
|
||||
position: relative;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
border-bottom: 1px solid #414561;
|
||||
&:hover {
|
||||
background: ${props => props.theme.colors.primary};
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
}
|
||||
`;
|
||||
const ItemsContainer = styled.div`
|
||||
@ -566,13 +563,13 @@ const Projects = () => {
|
||||
onDueDateChange={(task, dueDate, hasTime) => {
|
||||
if (dateEditor.task) {
|
||||
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) {
|
||||
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>(
|
||||
client,
|
||||
MyTasksDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (newTaskData.data) {
|
||||
draftCache.myTasks.tasks.unshift(newTaskData.data.createTask);
|
||||
}
|
||||
@ -618,7 +615,7 @@ const Projects = () => {
|
||||
groups.push({
|
||||
id: 'recently-assigned',
|
||||
name: 'Recently Assigned',
|
||||
tasks: data.myTasks.tasks.map(task => ({
|
||||
tasks: data.myTasks.tasks.map((task) => ({
|
||||
...task,
|
||||
labels: [],
|
||||
position: 0,
|
||||
@ -628,27 +625,27 @@ const Projects = () => {
|
||||
let { tasks } = data.myTasks;
|
||||
if (filters.sort === MyTasksSort.DueDate) {
|
||||
const group: Group = { id: 'due_date', name: null, tasks: [] };
|
||||
data.myTasks.tasks.forEach(task => {
|
||||
data.myTasks.tasks.forEach((task) => {
|
||||
if (task.dueDate) {
|
||||
group.tasks.push({ ...task, labels: [], position: 0 });
|
||||
}
|
||||
});
|
||||
groups.push(group);
|
||||
tasks = tasks.filter(t => t.dueDate === null);
|
||||
tasks = tasks.filter((t) => t.dueDate === null);
|
||||
}
|
||||
const projects = new Map<string, Array<Task>>();
|
||||
data.myTasks.projects.forEach(p => {
|
||||
data.myTasks.projects.forEach((p) => {
|
||||
if (!projects.has(p.projectID)) {
|
||||
projects.set(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) {
|
||||
projects.set(p.projectID, [...prev, { ...task, labels: [], position: 0 }]);
|
||||
}
|
||||
});
|
||||
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 (project) {
|
||||
groups.push({
|
||||
@ -681,13 +678,13 @@ const Projects = () => {
|
||||
<ProjectActions />
|
||||
<ProjectActions>
|
||||
<ProjectAction
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<MyTasksStatusPopup
|
||||
status={filters.status}
|
||||
onChangeStatus={status => {
|
||||
setFilters(prev => ({ ...prev, status }));
|
||||
onChangeStatus={(status) => {
|
||||
setFilters((prev) => ({ ...prev, status }));
|
||||
hidePopup();
|
||||
}}
|
||||
/>,
|
||||
@ -699,13 +696,13 @@ const Projects = () => {
|
||||
<ProjectActionText>{prettyStatus(filters.status)}</ProjectActionText>
|
||||
</ProjectAction>
|
||||
<ProjectAction
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<MyTasksSortPopup
|
||||
sort={filters.sort}
|
||||
onChangeSort={sort => {
|
||||
setFilters(prev => ({ ...prev, sort }));
|
||||
onChangeSort={(sort) => {
|
||||
setFilters((prev) => ({ ...prev, sort }));
|
||||
hidePopup();
|
||||
}}
|
||||
/>,
|
||||
@ -752,8 +749,8 @@ const Projects = () => {
|
||||
<VerticalScoller>
|
||||
<VerticalScollerInner>
|
||||
<TableContents>
|
||||
{groups.map(group => {
|
||||
const isMinified = minified.find(m => m === group.id) ?? false;
|
||||
{groups.map((group) => {
|
||||
const isMinified = minified.find((m) => m === group.id) ?? false;
|
||||
return (
|
||||
<TaskGroupContainer key={group.id}>
|
||||
{group.name && (
|
||||
@ -761,9 +758,9 @@ const Projects = () => {
|
||||
<TaskGroupHeaderContents width={leftRow}>
|
||||
<TaskGroupMinify
|
||||
onClick={() => {
|
||||
setMinified(prev => {
|
||||
setMinified((prev) => {
|
||||
if (isMinified) {
|
||||
return prev.filter(c => c !== group.id);
|
||||
return prev.filter((c) => c !== group.id);
|
||||
}
|
||||
return [...prev, group.id];
|
||||
});
|
||||
@ -781,14 +778,14 @@ const Projects = () => {
|
||||
)}
|
||||
<TaskGroupItems>
|
||||
{!isMinified &&
|
||||
group.tasks.map(task => {
|
||||
const projectID = data.myTasks.projects.find(t => t.taskID === task.id)?.projectID;
|
||||
const projectName = data.projects.find(p => p.id === projectID)?.name;
|
||||
group.tasks.map((task) => {
|
||||
const projectID = data.myTasks.projects.find((t) => t.taskID === task.id)?.projectID;
|
||||
const projectName = data.projects.find((p) => p.id === projectID)?.name;
|
||||
return (
|
||||
<TaskEntry
|
||||
key={task.id}
|
||||
complete={task.complete ?? false}
|
||||
onToggleComplete={complete => {
|
||||
onToggleComplete={(complete) => {
|
||||
setTaskComplete({ variables: { taskID: task.id, complete } });
|
||||
}}
|
||||
onTaskDetails={() => {
|
||||
@ -801,9 +798,11 @@ const Projects = () => {
|
||||
dueDate={task.dueDate}
|
||||
hasTime={task.hasTime ?? false}
|
||||
name={task.name}
|
||||
onEditName={name => updateTaskName({ variables: { taskID: task.id, name } })}
|
||||
onEditName={(name) => updateTaskName({ variables: { taskID: task.id, name } })}
|
||||
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
|
||||
path={`${match.path}/c/:taskID`}
|
||||
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
|
||||
<Details
|
||||
refreshCache={NOOP}
|
||||
availableMembers={[]}
|
||||
projectURL={`${match.url}`}
|
||||
taskID={routeProps.match.params.taskID}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
/*
|
||||
render={() => {
|
||||
return (
|
||||
<Details
|
||||
refreshCache={NOOP}
|
||||
availableMembers={[]}
|
||||
projectURL={`${match.url}`}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
/*
|
||||
updateTaskDescription({
|
||||
variables: { taskID: updatedTask.id, description: newDescription },
|
||||
optimisticResponse: {
|
||||
@ -879,13 +878,13 @@ const Projects = () => {
|
||||
},
|
||||
});
|
||||
*/
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
// deleteTask({ variables: { taskID: deletedTask.id } });
|
||||
history.push(`${match.url}`);
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, $targetRef) => {
|
||||
/*
|
||||
}}
|
||||
onDeleteTask={(deletedTask) => {
|
||||
// deleteTask({ variables: { taskID: deletedTask.id } });
|
||||
history.push(`${match.url}`);
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, $targetRef) => {
|
||||
/*
|
||||
taskLabelsRef.current = task.labels;
|
||||
showPopup(
|
||||
$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 { Popup, usePopup } from 'shared/components/PopupMenu';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import { useRouteMatch, useHistory } from 'react-router';
|
||||
import { useRouteMatch, useHistory, useParams } from 'react-router';
|
||||
import {
|
||||
useDeleteTaskChecklistMutation,
|
||||
useUpdateTaskChecklistNameMutation,
|
||||
@ -56,7 +56,7 @@ export const ActionItem = styled.li`
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
&: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"
|
||||
width="100%"
|
||||
label="Name"
|
||||
id="name"
|
||||
name="name"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Checklist name is required' })}
|
||||
{...register('name', { required: 'Checklist name is required' })}
|
||||
/>
|
||||
<CreateChecklistButton type="submit">Create</CreateChecklistButton>
|
||||
</CreateChecklistForm>
|
||||
@ -177,7 +175,6 @@ const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChe
|
||||
};
|
||||
|
||||
type DetailsProps = {
|
||||
taskID: string;
|
||||
projectURL: string;
|
||||
onTaskNameChange: (task: Task, newName: 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> = ({
|
||||
projectURL,
|
||||
taskID,
|
||||
onTaskNameChange,
|
||||
onTaskDescriptionChange,
|
||||
onDeleteTask,
|
||||
@ -200,6 +196,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
refreshCache,
|
||||
}) => {
|
||||
const { user } = useCurrentUser();
|
||||
const { taskID } = useParams<{ taskID: string }>();
|
||||
const { showPopup, hidePopup } = usePopup();
|
||||
const history = useHistory();
|
||||
const [deleteTaskComment] = useDeleteTaskCommentMutation({
|
||||
@ -207,11 +204,11 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (response.data) {
|
||||
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>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (response.data) {
|
||||
draftCache.findTask.comments.push({
|
||||
...response.data.createTaskComment.comment,
|
||||
@ -242,18 +239,18 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (response.data) {
|
||||
const { prevChecklistID, taskChecklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
|
||||
if (taskChecklistID !== prevChecklistID) {
|
||||
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
|
||||
const newIdx = cache.findTask.checklists.findIndex(c => c.id === taskChecklistID);
|
||||
const oldIdx = cache.findTask.checklists.findIndex((c) => c.id === prevChecklistID);
|
||||
const newIdx = cache.findTask.checklists.findIndex((c) => c.id === taskChecklistID);
|
||||
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) {
|
||||
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({
|
||||
...item,
|
||||
@ -270,12 +267,12 @@ const Details: React.FC<DetailsProps> = ({
|
||||
},
|
||||
});
|
||||
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
|
||||
update: client => {
|
||||
update: (client) => {
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
|
||||
draftCache.findTask.badges.checklist = {
|
||||
__typename: 'ChecklistBadge',
|
||||
@ -292,11 +289,11 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
const { checklists } = cache.findTask;
|
||||
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);
|
||||
draftCache.findTask.badges.checklist = {
|
||||
@ -318,8 +315,8 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (createData.data) {
|
||||
const item = createData.data.createTaskChecklist;
|
||||
draftCache.findTask.checklists.push({ ...item });
|
||||
@ -335,14 +332,14 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (deleteData.data) {
|
||||
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) {
|
||||
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);
|
||||
@ -362,12 +359,12 @@ const Details: React.FC<DetailsProps> = ({
|
||||
updateApolloCache<FindTaskQuery>(
|
||||
client,
|
||||
FindTaskDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (newTaskItem.data) {
|
||||
const item = newTaskItem.data.createTaskChecklistItem;
|
||||
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) {
|
||||
draftCache.findTask.checklists[idx].items.push({ ...item });
|
||||
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
|
||||
@ -445,7 +442,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
onCreateComment={(task, message) => {
|
||||
createTaskComment({ variables: { taskID: task.id, message } });
|
||||
}}
|
||||
onChecklistDrop={checklist => {
|
||||
onChecklistDrop={(checklist) => {
|
||||
updateTaskChecklistLocation({
|
||||
variables: { taskChecklistID: checklist.id, position: checklist.position },
|
||||
|
||||
@ -487,7 +484,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
}}
|
||||
onTaskNameChange={onTaskNameChange}
|
||||
onTaskDescriptionChange={onTaskDescriptionChange}
|
||||
onToggleTaskComplete={task => {
|
||||
onToggleTaskComplete={(task) => {
|
||||
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
|
||||
}}
|
||||
onDeleteTask={onDeleteTask}
|
||||
@ -532,7 +529,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
|
||||
}}
|
||||
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) {
|
||||
showPopup(
|
||||
$targetRef,
|
||||
@ -582,7 +579,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
}}
|
||||
>
|
||||
<CreateChecklistPopup
|
||||
onCreateChecklist={checklistData => {
|
||||
onCreateChecklist={(checklistData) => {
|
||||
let position = 65535;
|
||||
if (data.findTask.checklists) {
|
||||
const [lastChecklist] = data.findTask.checklists.slice(-1);
|
||||
@ -632,7 +629,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
>
|
||||
<DueDateManager
|
||||
task={task}
|
||||
onRemoveDueDate={t => {
|
||||
onRemoveDueDate={(t) => {
|
||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
||||
// hidePopup();
|
||||
}}
|
||||
|
@ -64,7 +64,7 @@ const Project = () => {
|
||||
pollInterval: polling.PROJECT,
|
||||
});
|
||||
const [toggleTaskLabel] = useToggleTaskLabelMutation({
|
||||
onCompleted: newTaskLabel => {
|
||||
onCompleted: (newTaskLabel) => {
|
||||
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
|
||||
},
|
||||
});
|
||||
@ -73,17 +73,17 @@ const Project = () => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (resp.data) {
|
||||
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) {
|
||||
draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[
|
||||
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>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
draftCache.findProject.name = newName.data?.updateProjectName.name ?? '';
|
||||
}),
|
||||
{ projectID },
|
||||
@ -110,8 +110,8 @@ const Project = () => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (response.data) {
|
||||
draftCache.findProject.members = [
|
||||
...cache.findProject.members,
|
||||
@ -132,10 +132,10 @@ const Project = () => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter(
|
||||
m => m.email !== response.data?.deleteInvitedProjectMember.invitedMember.email ?? '',
|
||||
(m) => m.email !== response.data?.deleteInvitedProjectMember.invitedMember.email ?? '',
|
||||
);
|
||||
}),
|
||||
{ projectID },
|
||||
@ -147,10 +147,10 @@ const Project = () => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
draftCache.findProject.members = cache.findProject.members.filter(
|
||||
m => m.id !== response.data?.deleteProjectMember.member.id,
|
||||
(m) => m.id !== response.data?.deleteProjectMember.member.id,
|
||||
);
|
||||
}),
|
||||
{ projectID },
|
||||
@ -176,23 +176,23 @@ const Project = () => {
|
||||
onChangeProjectOwner={() => {
|
||||
hidePopup();
|
||||
}}
|
||||
onRemoveFromBoard={userID => {
|
||||
onRemoveFromBoard={(userID) => {
|
||||
deleteProjectMember({ variables: { userID, projectID } });
|
||||
hidePopup();
|
||||
}}
|
||||
onRemoveInvitedFromBoard={email => {
|
||||
onRemoveInvitedFromBoard={(email) => {
|
||||
deleteInvitedProjectMember({ variables: { projectID, email } });
|
||||
hidePopup();
|
||||
}}
|
||||
onSaveProjectName={projectName => {
|
||||
onSaveProjectName={(projectName) => {
|
||||
updateProjectName({ variables: { projectID, name: projectName } });
|
||||
}}
|
||||
onInviteUser={$target => {
|
||||
onInviteUser={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<UserManagementPopup
|
||||
projectID={projectID}
|
||||
onInviteProjectMembers={members => {
|
||||
onInviteProjectMembers={(members) => {
|
||||
inviteProjectMembers({ variables: { projectID, members } });
|
||||
hidePopup();
|
||||
}}
|
||||
@ -233,50 +233,51 @@ const Project = () => {
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}/board/c/:taskID`}
|
||||
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
|
||||
<Details
|
||||
refreshCache={NOOP}
|
||||
availableMembers={data.findProject.members}
|
||||
projectURL={`${match.url}/board`}
|
||||
taskID={routeProps.match.params.taskID}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
updateTaskDescription({
|
||||
variables: { taskID: updatedTask.id, description: newDescription },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateTaskDescription: {
|
||||
__typename: 'Task',
|
||||
id: updatedTask.id,
|
||||
description: newDescription,
|
||||
render={() => {
|
||||
return (
|
||||
<Details
|
||||
refreshCache={NOOP}
|
||||
availableMembers={data.findProject.members}
|
||||
projectURL={`${match.url}/board`}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
updateTaskDescription({
|
||||
variables: { taskID: updatedTask.id, description: newDescription },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateTaskDescription: {
|
||||
__typename: 'Task',
|
||||
id: updatedTask.id,
|
||||
description: newDescription,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
deleteTask({ variables: { taskID: deletedTask.id } });
|
||||
history.push(`${match.url}/board`);
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, $targetRef) => {
|
||||
taskLabelsRef.current = task.labels;
|
||||
showPopup(
|
||||
$targetRef,
|
||||
<LabelManagerEditor
|
||||
onLabelToggle={labelID => {
|
||||
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
|
||||
}}
|
||||
taskID={task.id}
|
||||
labelColors={data.labelColors}
|
||||
labels={labelsRef}
|
||||
taskLabels={taskLabelsRef}
|
||||
projectID={projectID}
|
||||
/>,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
});
|
||||
}}
|
||||
onDeleteTask={(deletedTask) => {
|
||||
deleteTask({ variables: { taskID: deletedTask.id } });
|
||||
history.push(`${match.url}/board`);
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, $targetRef) => {
|
||||
taskLabelsRef.current = task.labels;
|
||||
showPopup(
|
||||
$targetRef,
|
||||
<LabelManagerEditor
|
||||
onLabelToggle={(labelID) => {
|
||||
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
|
||||
}}
|
||||
taskID={task.id}
|
||||
labelColors={data.labelColors}
|
||||
labels={labelsRef}
|
||||
taskLabels={taskLabelsRef}
|
||||
projectID={projectID}
|
||||
/>,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -16,15 +16,15 @@ import { useCurrentUser } from 'App/context';
|
||||
import Button from 'shared/components/Button';
|
||||
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import Input from 'shared/components/Input';
|
||||
import ControlledInput from 'shared/components/ControlledInput';
|
||||
import updateApolloCache from 'shared/utils/cache';
|
||||
import produce from 'immer';
|
||||
import NOOP from 'shared/utils/noop';
|
||||
import theme from 'App/ThemeStyles';
|
||||
import { mixin } from '../shared/utils/styles';
|
||||
import polling from 'shared/utils/polling';
|
||||
import { mixin } from '../shared/utils/styles';
|
||||
|
||||
type CreateTeamData = { teamName: string };
|
||||
type CreateTeamData = { name: string };
|
||||
|
||||
type CreateTeamFormProps = {
|
||||
onCreateTeam: (teamName: string) => void;
|
||||
@ -36,28 +36,30 @@ const CreateTeamButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ErrorText = styled.span`
|
||||
font-size: 14px;
|
||||
color: ${(props) => props.theme.colors.danger};
|
||||
`;
|
||||
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
|
||||
const { register, handleSubmit } = useForm<CreateTeamData>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<CreateTeamData>();
|
||||
const createTeam = (data: CreateTeamData) => {
|
||||
onCreateTeam(data.teamName);
|
||||
onCreateTeam(data.name);
|
||||
};
|
||||
return (
|
||||
<CreateTeamFormContainer onSubmit={handleSubmit(createTeam)}>
|
||||
<Input
|
||||
width="100%"
|
||||
label="Team name"
|
||||
id="teamName"
|
||||
name="teamName"
|
||||
variant="alternate"
|
||||
ref={register({ required: 'Team name is required' })}
|
||||
/>
|
||||
{errors.name && <ErrorText>{errors.name.message}</ErrorText>}
|
||||
<ControlledInput width="100%" label="Team name" variant="alternate" {...register('name')} />
|
||||
<CreateTeamButton type="submit">Create</CreateTeamButton>
|
||||
</CreateTeamFormContainer>
|
||||
);
|
||||
};
|
||||
|
||||
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-position: 50%;
|
||||
color: #fff;
|
||||
@ -71,7 +73,7 @@ const ProjectAddTile = styled.div`
|
||||
`;
|
||||
|
||||
const ProjectTile = styled(Link)<{ color: string }>`
|
||||
background-color: ${props => props.color};
|
||||
background-color: ${(props) => props.color};
|
||||
background-size: cover;
|
||||
background-position: 50%;
|
||||
color: #fff;
|
||||
@ -142,7 +144,7 @@ const ProjectTileName = styled.div<{ centered?: boolean }>`
|
||||
max-height: 40px;
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
${props => props.centered && 'text-align: center;'}
|
||||
${(props) => props.centered && 'text-align: center;'}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -180,7 +182,7 @@ const SectionActionLink = styled(Link)`
|
||||
|
||||
const ProjectSectionTitle = styled.h3`
|
||||
font-size: 16px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
`;
|
||||
|
||||
const ProjectsContainer = styled.div`
|
||||
@ -210,8 +212,8 @@ const Projects = () => {
|
||||
}, []);
|
||||
const [createProject] = useCreateProjectMutation({
|
||||
update: (client, newProject) => {
|
||||
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
|
||||
produce(cache, draftCache => {
|
||||
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, (cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (newProject.data) {
|
||||
draftCache.projects.push({ ...newProject.data.createProject });
|
||||
}
|
||||
@ -224,8 +226,8 @@ const Projects = () => {
|
||||
const { user } = useCurrentUser();
|
||||
const [createTeam] = useCreateTeamMutation({
|
||||
update: (client, createData) => {
|
||||
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
|
||||
produce(cache, draftCache => {
|
||||
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, (cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (createData.data) {
|
||||
draftCache.teams.push({ ...createData.data?.createTeam });
|
||||
}
|
||||
@ -239,7 +241,7 @@ const Projects = () => {
|
||||
const { projects, teams, organizations } = data;
|
||||
const organizationID = organizations[0].id ?? null;
|
||||
const personalProjects = projects
|
||||
.filter(p => p.team === null)
|
||||
.filter((p) => p.team === null)
|
||||
.sort((a, b) => {
|
||||
const textA = a.name.toUpperCase();
|
||||
const textB = b.name.toUpperCase();
|
||||
@ -251,12 +253,12 @@ const Projects = () => {
|
||||
const textB = b.name.toUpperCase();
|
||||
return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary
|
||||
})
|
||||
.map(team => {
|
||||
.map((team) => {
|
||||
return {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
projects: projects
|
||||
.filter(project => project.team && project.team.id === team.id)
|
||||
.filter((project) => project.team && project.team.id === team.id)
|
||||
.sort((a, b) => {
|
||||
const textA = a.name.toUpperCase();
|
||||
const textB = b.name.toUpperCase();
|
||||
@ -272,7 +274,7 @@ const Projects = () => {
|
||||
{true && ( // TODO: add permision check
|
||||
<AddTeamButton
|
||||
variant="outline"
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<Popup
|
||||
@ -283,7 +285,7 @@ const Projects = () => {
|
||||
}}
|
||||
>
|
||||
<CreateTeamForm
|
||||
onCreateTeam={teamName => {
|
||||
onCreateTeam={(teamName) => {
|
||||
if (organizationID) {
|
||||
createTeam({ variables: { name: teamName, organizationID } });
|
||||
hidePopup();
|
||||
@ -326,7 +328,7 @@ const Projects = () => {
|
||||
</ProjectListItem>
|
||||
</ProjectList>
|
||||
</div>
|
||||
{projectTeams.map(team => {
|
||||
{projectTeams.map((team) => {
|
||||
return (
|
||||
<div key={team.id}>
|
||||
<ProjectSectionTitleWrapper>
|
||||
|
@ -35,7 +35,7 @@ const UsersRegister = () => {
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then(async x => {
|
||||
.then(async (x) => {
|
||||
const response = await x.json();
|
||||
const { setup } = response;
|
||||
console.log(response);
|
||||
|
@ -36,7 +36,7 @@ const UserMember = styled(Member)`
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
&: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;
|
||||
`;
|
||||
@ -57,8 +57,8 @@ const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, teamMe
|
||||
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
|
||||
<TeamMemberList>
|
||||
{users
|
||||
.filter(u => u.id !== teamMembers.find(p => p.id === u.id)?.id)
|
||||
.map(user => (
|
||||
.filter((u) => u.id !== teamMembers.find((p) => p.id === u.id)?.id)
|
||||
.map((user) => (
|
||||
<UserMember
|
||||
key={user.id}
|
||||
onCardMemberClick={() => onAddTeamMember(user.id)}
|
||||
@ -116,7 +116,7 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.disabled
|
||||
? css`
|
||||
user-select: none;
|
||||
@ -137,7 +137,7 @@ export const Content = styled.div`
|
||||
|
||||
export const CurrentPermission = styled.span`
|
||||
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`
|
||||
@ -148,13 +148,13 @@ export const Separator = styled.div`
|
||||
|
||||
export const WarningText = styled.span`
|
||||
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;
|
||||
`;
|
||||
|
||||
export const DeleteDescription = styled.div`
|
||||
font-size: 14px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
`;
|
||||
|
||||
export const RemoveMemberButton = styled(Button)`
|
||||
@ -221,13 +221,13 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
<MiniProfileActions>
|
||||
<MiniProfileActionWrapper>
|
||||
{permissions
|
||||
.filter(p => (subject.role && subject.role.code === 'owner') || p.code !== 'owner')
|
||||
.map(perm => (
|
||||
.filter((p) => (subject.role && subject.role.code === 'owner') || p.code !== 'owner')
|
||||
.map((perm) => (
|
||||
<MiniProfileActionItem
|
||||
disabled={subject.role && perm.code !== subject.role.code && !canChangeRole}
|
||||
key={perm.code}
|
||||
onClick={() => {
|
||||
if (onChangeRole && subject.role && perm.code !== subject.role.code) {
|
||||
if (subject.role && perm.code !== subject.role.code) {
|
||||
switch (perm.code) {
|
||||
case 'owner':
|
||||
onChangeRole(RoleCode.Owner);
|
||||
@ -276,8 +276,8 @@ const TeamRoleManagerPopup: React.FC<TeamRoleManagerPopupProps> = ({
|
||||
<Select
|
||||
label="New projects owner"
|
||||
value={orphanedProjectOwner}
|
||||
onChange={value => setOrphanedProjectOwner(value)}
|
||||
options={members.filter(m => m.id !== subject.id).map(m => ({ label: m.fullName, value: m.id }))}
|
||||
onChange={(value) => setOrphanedProjectOwner(value)}
|
||||
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`
|
||||
border-top: 1px solid ${props => props.theme.colors.border};
|
||||
border-top: 1px solid ${(props) => props.theme.colors.border};
|
||||
`;
|
||||
|
||||
const MemberListItem = styled.div`
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
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;
|
||||
padding: 12px 0 12px 40px;
|
||||
position: relative;
|
||||
@ -338,11 +338,11 @@ const MemberProfile = styled(TaskAssignee)`
|
||||
`;
|
||||
|
||||
const MemberItemName = styled.p`
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
`;
|
||||
|
||||
const MemberItemUsername = styled.p`
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
`;
|
||||
|
||||
const MemberListHeader = styled.div`
|
||||
@ -351,12 +351,12 @@ const MemberListHeader = styled.div`
|
||||
`;
|
||||
const ListTitle = styled.h3`
|
||||
font-size: 18px;
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
const ListDesc = styled.span`
|
||||
font-size: 16px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
`;
|
||||
const FilterSearch = styled(Input)`
|
||||
margin: 0;
|
||||
@ -388,11 +388,11 @@ const FilterTabItem = styled.li`
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
padding: 6px 8px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
&:hover {
|
||||
border-radius: 6px;
|
||||
background: ${props => props.theme.colors.primary};
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -433,8 +433,8 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
updateApolloCache<GetTeamQuery>(
|
||||
client,
|
||||
GetTeamDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
if (response.data) {
|
||||
draftCache.findTeam.members.push({
|
||||
...response.data.createTeamMember.teamMember,
|
||||
@ -453,10 +453,10 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
updateApolloCache<GetTeamQuery>(
|
||||
client,
|
||||
GetTeamDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
(cache) =>
|
||||
produce(cache, (draftCache) => {
|
||||
draftCache.findTeam.members = cache.findTeam.members.filter(
|
||||
member => member.id !== response.data?.deleteTeamMember.userID,
|
||||
(member) => member.id !== response.data?.deleteTeamMember.userID,
|
||||
);
|
||||
}),
|
||||
{ teamID },
|
||||
@ -484,13 +484,13 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
<FilterSearch width="250px" variant="alternate" placeholder="Filter by name" />
|
||||
{true && ( // TODO: add permission check
|
||||
<InviteMemberButton
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<UserManagementPopup
|
||||
users={data.users}
|
||||
teamMembers={data.findTeam.members}
|
||||
onAddTeamMember={userID => {
|
||||
onAddTeamMember={(userID) => {
|
||||
createTeamMember({ variables: { userID, teamID } });
|
||||
}}
|
||||
/>,
|
||||
@ -504,7 +504,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
</ListActions>
|
||||
</MemberListHeader>
|
||||
<MemberList>
|
||||
{data.findTeam.members.map(member => (
|
||||
{data.findTeam.members.map((member) => (
|
||||
<MemberListItem>
|
||||
<MemberProfile showRoleIcons size={32} onMemberProfile={NOOP} member={member} />
|
||||
<MemberListItemDetails>
|
||||
@ -515,7 +515,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
<MemberItemOption variant="flat">On 2 projects</MemberItemOption>
|
||||
<MemberItemOption
|
||||
variant="outline"
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
showPopup(
|
||||
$target,
|
||||
<TeamRoleManagerPopup
|
||||
@ -525,13 +525,13 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
|
||||
warning={member.role && member.role.code === 'owner' ? warning : null}
|
||||
// canChangeRole={user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID)} TODO: add permission check
|
||||
canChangeRole={true}
|
||||
onChangeRole={roleCode => {
|
||||
onChangeRole={(roleCode) => {
|
||||
updateTeamMemberRole({ variables: { userID: member.id, teamID, roleCode } });
|
||||
}}
|
||||
onRemoveFromTeam={
|
||||
member.role && member.role.code === 'owner'
|
||||
? undefined
|
||||
: newOwnerID => {
|
||||
: (newOwnerID) => {
|
||||
deleteTeamMember({ variables: { teamID, newOwnerID, userID: member.id } });
|
||||
hidePopup();
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ const InputWrapper = styled.div<{ width: string }>`
|
||||
`;
|
||||
|
||||
const InputLabel = styled.span<{ width: string }>`
|
||||
width: ${props => props.width};
|
||||
width: ${(props) => props.width};
|
||||
padding: 0.7rem !important;
|
||||
color: #c2c6dc;
|
||||
left: 0;
|
||||
@ -40,13 +40,13 @@ const InputInput = styled.input<{
|
||||
focusBg: string;
|
||||
borderColor: string;
|
||||
}>`
|
||||
width: ${props => props.width};
|
||||
width: ${(props) => props.width};
|
||||
font-size: 14px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-color: ${props => props.borderColor};
|
||||
border-color: ${(props) => props.borderColor};
|
||||
background: #262c49;
|
||||
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;
|
||||
color: #c2c6dc;
|
||||
position: relative;
|
||||
@ -55,13 +55,13 @@ const InputInput = styled.input<{
|
||||
&:focus {
|
||||
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid rgba(115, 103, 240);
|
||||
background: ${props => props.focusBg};
|
||||
background: ${(props) => props.focusBg};
|
||||
}
|
||||
&:focus ~ ${InputLabel} {
|
||||
color: ${props => props.theme.colors.primary};
|
||||
color: ${(props) => props.theme.colors.primary};
|
||||
transform: translate(-3px, -90%);
|
||||
}
|
||||
${props =>
|
||||
${(props) =>
|
||||
props.hasValue &&
|
||||
css`
|
||||
& ~ ${InputLabel} {
|
||||
@ -94,11 +94,13 @@ type ControlledInputProps = {
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const ControlledInput = ({
|
||||
width = 'auto',
|
||||
variant = 'normal',
|
||||
disabled = false,
|
||||
type = 'text',
|
||||
autocomplete,
|
||||
autoFocus = false,
|
||||
@ -126,8 +128,9 @@ const ControlledInput = ({
|
||||
return (
|
||||
<InputWrapper className={className} width={width}>
|
||||
<InputInput
|
||||
disabled={disabled}
|
||||
hasValue={hasValue}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
if (onChange) {
|
||||
setHasValue(e.currentTarget.value !== '' || floatingLabel);
|
||||
onChange(e);
|
||||
|
@ -59,7 +59,7 @@ const HeaderSelectLabel = styled.div`
|
||||
color: #c2c6dc;
|
||||
|
||||
&:hover {
|
||||
background: ${props => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: #c2c6dc;
|
||||
}
|
||||
`;
|
||||
@ -78,12 +78,12 @@ const HeaderSelect = styled.select`
|
||||
|
||||
& option {
|
||||
color: #c2c6dc;
|
||||
background: ${props => props.theme.colors.bg.primary};
|
||||
background: ${(props) => props.theme.colors.bg.primary};
|
||||
}
|
||||
|
||||
& option:hover {
|
||||
background: ${props => props.theme.colors.bg.secondary};
|
||||
border: 1px solid ${props => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.bg.secondary};
|
||||
border: 1px solid ${(props) => props.theme.colors.primary};
|
||||
outline: none !important;
|
||||
box-shadow: none;
|
||||
color: #c2c6dc;
|
||||
@ -115,7 +115,7 @@ const HeaderButton = styled.button`
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background: ${props => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
@ -133,7 +133,14 @@ const HeaderActions = styled.div`
|
||||
|
||||
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
||||
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 [endDate, setEndDate] = useState<Date | null>(currentDueDate);
|
||||
@ -203,7 +210,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
<DateRangeInputs>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => setStartDate(date)}
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
setStartDate(date);
|
||||
}
|
||||
}}
|
||||
popperClassName="picker-hidden"
|
||||
dateFormat="yyyy-MM-dd"
|
||||
disabledKeyboardNavigation
|
||||
@ -214,7 +225,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
isClearable
|
||||
onChange={date => setStartDate(date)}
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
setStartDate(date);
|
||||
}
|
||||
}}
|
||||
popperClassName="picker-hidden"
|
||||
dateFormat="yyyy-MM-dd"
|
||||
placeholderText="Select from date"
|
||||
@ -225,7 +240,11 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
</DateRangeInputs>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => setStartDate(date)}
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
setStartDate(date);
|
||||
}
|
||||
}}
|
||||
startDate={startDate}
|
||||
useWeekdaysShort
|
||||
renderCustomHeader={({
|
||||
@ -247,7 +266,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
|
||||
>
|
||||
{months.map(option => (
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
@ -257,7 +276,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
<HeaderSelectLabel>
|
||||
{date.getFullYear()}
|
||||
<HeaderSelect value={getYear(date)} onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}>
|
||||
{years.map(option => (
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
@ -279,8 +298,10 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
<ActionLabel>Due Time</ActionLabel>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={date => {
|
||||
setStartDate(date);
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
setStartDate(date);
|
||||
}
|
||||
}}
|
||||
showTimeSelect
|
||||
showTimeSelectOnly
|
||||
|
@ -25,7 +25,12 @@ import {
|
||||
|
||||
const Login = ({ onSubmit }: LoginProps) => {
|
||||
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) => {
|
||||
setComplete(false);
|
||||
onSubmit(data, setComplete, setError);
|
||||
@ -47,12 +52,7 @@ const Login = ({ onSubmit }: LoginProps) => {
|
||||
<Form onSubmit={handleSubmit(loginSubmit)}>
|
||||
<FormLabel htmlFor="username">
|
||||
Username
|
||||
<FormTextInput
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
ref={register({ required: 'Username is required' })}
|
||||
/>
|
||||
<FormTextInput type="text" {...register('username', { required: 'Username is required' })} />
|
||||
<FormIcon>
|
||||
<User width={20} height={20} />
|
||||
</FormIcon>
|
||||
@ -60,12 +60,7 @@ const Login = ({ onSubmit }: LoginProps) => {
|
||||
{errors.username && <FormError>{errors.username.message}</FormError>}
|
||||
<FormLabel htmlFor="password">
|
||||
Password
|
||||
<FormTextInput
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
/>
|
||||
<FormTextInput type="password" {...register('password', { required: 'Password is required' })} />
|
||||
<FormIcon>
|
||||
<Lock width={20} height={20} />
|
||||
</FormIcon>
|
||||
|
@ -26,7 +26,12 @@ const INITIALS_PATTERN = /[a-zA-Z]{2,3}/i;
|
||||
|
||||
const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
const [isComplete, setComplete] = useState(true);
|
||||
const { register, handleSubmit, errors, setError } = useForm<RegisterFormData>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setError,
|
||||
} = useForm<RegisterFormData>();
|
||||
const loginSubmit = (data: RegisterFormData) => {
|
||||
setComplete(false);
|
||||
onSubmit(data, setComplete, setError);
|
||||
@ -55,12 +60,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
<Form onSubmit={handleSubmit(loginSubmit)}>
|
||||
<FormLabel htmlFor="fullname">
|
||||
Full name
|
||||
<FormTextInput
|
||||
type="text"
|
||||
id="fullname"
|
||||
name="fullname"
|
||||
ref={register({ required: 'Full name is required' })}
|
||||
/>
|
||||
<FormTextInput type="text" {...register('fullname', { required: 'Full name is required' })} />
|
||||
<FormIcon>
|
||||
<User width={20} height={20} />
|
||||
</FormIcon>
|
||||
@ -68,12 +68,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
{errors.username && <FormError>{errors.username.message}</FormError>}
|
||||
<FormLabel htmlFor="username">
|
||||
Username
|
||||
<FormTextInput
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
ref={register({ required: 'Username is required' })}
|
||||
/>
|
||||
<FormTextInput type="text" {...register('username', { required: 'Username is required' })} />
|
||||
<FormIcon>
|
||||
<User width={20} height={20} />
|
||||
</FormIcon>
|
||||
@ -83,9 +78,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
Email
|
||||
<FormTextInput
|
||||
type="text"
|
||||
id="email"
|
||||
name="email"
|
||||
ref={register({
|
||||
{...register('email', {
|
||||
required: 'Email is required',
|
||||
pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
|
||||
})}
|
||||
@ -99,9 +92,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
Initials
|
||||
<FormTextInput
|
||||
type="text"
|
||||
id="initials"
|
||||
name="initials"
|
||||
ref={register({
|
||||
{...register('initials', {
|
||||
required: 'Initials is required',
|
||||
pattern: {
|
||||
value: INITIALS_PATTERN,
|
||||
@ -116,12 +107,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
{errors.initials && <FormError>{errors.initials.message}</FormError>}
|
||||
<FormLabel htmlFor="password">
|
||||
Password
|
||||
<FormTextInput
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
/>
|
||||
<FormTextInput type="password" {...register('password', { required: 'Password is required' })} />
|
||||
<FormIcon>
|
||||
<Lock width={20} height={20} />
|
||||
</FormIcon>
|
||||
@ -131,9 +117,7 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
|
||||
Password (Confirm)
|
||||
<FormTextInput
|
||||
type="password"
|
||||
id="password_confirm"
|
||||
name="password_confirm"
|
||||
ref={register({ required: 'Password (confirm) is required' })}
|
||||
{...register('password_confirm', { required: 'Password (confirm) is required' })}
|
||||
/>
|
||||
<FormIcon>
|
||||
<Lock width={20} height={20} />
|
||||
|
@ -1,23 +1,23 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { User } from 'shared/icons';
|
||||
import Input from 'shared/components/Input';
|
||||
import Button from 'shared/components/Button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import ControlledInput from 'shared/components/ControlledInput';
|
||||
|
||||
const PasswordInput = styled(Input)`
|
||||
const PasswordInput = styled(ControlledInput)`
|
||||
margin-top: 30px;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
const UserInfoInput = styled(Input)`
|
||||
const UserInfoInput = styled(ControlledInput)`
|
||||
margin-top: 30px;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
const FormError = styled.span`
|
||||
font-size: 12px;
|
||||
color: ${props => props.theme.colors.warning};
|
||||
color: ${(props) => props.theme.colors.warning};
|
||||
`;
|
||||
|
||||
const ProfileContainer = styled.div`
|
||||
@ -42,7 +42,7 @@ const AvatarMask = styled.div<{ background: string }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: ${props => props.background};
|
||||
background: ${(props) => props.background};
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -152,12 +152,12 @@ const TabNavItemButton = styled.button<{ active: boolean }>`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
color: ${props => (props.active ? `${props.theme.colors.primary}` : '#c2c6dc')};
|
||||
color: ${(props) => (props.active ? `${props.theme.colors.primary}` : '#c2c6dc')};
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.primary};
|
||||
color: ${(props) => props.theme.colors.primary};
|
||||
}
|
||||
&: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;
|
||||
height: 48px;
|
||||
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});
|
||||
box-shadow: 0 0 8px 0 ${props => props.theme.colors.primary};
|
||||
background: linear-gradient(
|
||||
30deg,
|
||||
${(props) => props.theme.colors.primary},
|
||||
${(props) => props.theme.colors.primary}
|
||||
);
|
||||
box-shadow: 0 0 8px 0 ${(props) => props.theme.colors.primary};
|
||||
display: block;
|
||||
position: absolute;
|
||||
transition: all 0.2s ease;
|
||||
@ -267,36 +271,36 @@ type ResetPasswordTabProps = {
|
||||
};
|
||||
const ResetPasswordTab: React.FC<ResetPasswordTabProps> = ({ onResetPassword }) => {
|
||||
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 = () => {
|
||||
reset();
|
||||
setActive(true);
|
||||
};
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(data => {
|
||||
if (data.password !== data.password_confirm) {
|
||||
onSubmit={handleSubmit((data) => {
|
||||
if (data.password !== data.passwordConfirm) {
|
||||
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 {
|
||||
onResetPassword(data.password, done);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<PasswordInput
|
||||
width="100%"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
label="Password"
|
||||
name="password"
|
||||
/>
|
||||
<PasswordInput width="100%" {...register('password', { required: 'Password is required' })} label="Password" />
|
||||
{errors.password && <FormError>{errors.password.message}</FormError>}
|
||||
<PasswordInput
|
||||
width="100%"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
{...register('passwordConfirm', { required: 'Password is required' })}
|
||||
label="Password (confirm)"
|
||||
name="password_confirm"
|
||||
/>
|
||||
{errors.password_confirm && <FormError>{errors.password_confirm.message}</FormError>}
|
||||
{errors.passwordConfirm && <FormError>{errors.passwordConfirm.message}</FormError>}
|
||||
<SettingActions>
|
||||
<SaveButton disabled={!active} type="submit">
|
||||
Save Change
|
||||
@ -329,7 +333,11 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
|
||||
onChangeUserInfo,
|
||||
}) => {
|
||||
const [active, setActive] = useState(true);
|
||||
const { register, handleSubmit, errors } = useForm<UserInfoData>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<UserInfoData>();
|
||||
const done = () => {
|
||||
setActive(true);
|
||||
};
|
||||
@ -341,14 +349,13 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
|
||||
profile={profile.profileIcon}
|
||||
/>
|
||||
<form
|
||||
onSubmit={handleSubmit(data => {
|
||||
onSubmit={handleSubmit((data) => {
|
||||
setActive(false);
|
||||
onChangeUserInfo(data, done);
|
||||
})}
|
||||
>
|
||||
<UserInfoInput
|
||||
ref={register({ required: 'Full name is required' })}
|
||||
name="full_name"
|
||||
{...register('full_name', { required: 'Full name is required' })}
|
||||
defaultValue={profile.fullName}
|
||||
width="100%"
|
||||
label="Name"
|
||||
@ -356,11 +363,10 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
|
||||
{errors.full_name && <FormError>{errors.full_name.message}</FormError>}
|
||||
<UserInfoInput
|
||||
defaultValue={profile.profileIcon && profile.profileIcon.initials ? profile.profileIcon.initials : ''}
|
||||
ref={register({
|
||||
{...register('initials', {
|
||||
required: 'Initials is required',
|
||||
pattern: { value: INITIALS_PATTERN, message: 'Intials must be between two to four characters' },
|
||||
})}
|
||||
name="initials"
|
||||
width="100%"
|
||||
label="Initials "
|
||||
/>
|
||||
@ -368,8 +374,7 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
|
||||
<UserInfoInput disabled defaultValue={profile.username ?? ''} width="100%" label="Username " />
|
||||
<UserInfoInput
|
||||
width="100%"
|
||||
name="email"
|
||||
ref={register({
|
||||
{...register('email', {
|
||||
required: 'Email is required',
|
||||
pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
|
||||
})}
|
||||
@ -377,7 +382,7 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
|
||||
label="Email"
|
||||
/>
|
||||
{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>}
|
||||
<SettingActions>
|
||||
<SaveButton disabled={!active} type="submit">
|
||||
|
@ -136,7 +136,7 @@ const StreamComment: React.FC<StreamCommentProps> = ({
|
||||
onCreateComment={onUpdateComment}
|
||||
/>
|
||||
) : (
|
||||
<ReactMarkdown escapeHtml={false} plugins={[em]}>
|
||||
<ReactMarkdown skipHtml plugins={[em]}>
|
||||
{DOMPurify.sanitize(comment.message, { FORBID_TAGS: ['style', 'img'] })}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
@ -300,7 +300,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
const activityStream: Array<{ id: string; data: { time: string; type: 'comment' | 'activity' } }> = [];
|
||||
|
||||
if (task.activity) {
|
||||
task.activity.forEach(activity => {
|
||||
task.activity.forEach((activity) => {
|
||||
activityStream.push({
|
||||
id: activity.id,
|
||||
data: {
|
||||
@ -312,7 +312,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
}
|
||||
|
||||
if (task.comments) {
|
||||
task.comments.forEach(comment => {
|
||||
task.comments.forEach((comment) => {
|
||||
activityStream.push({
|
||||
id: comment.id,
|
||||
data: {
|
||||
@ -358,12 +358,12 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<DueDateTitle>MEMBERS</DueDateTitle>
|
||||
{task.assigned && task.assigned.length !== 0 ? (
|
||||
<MemberList>
|
||||
{task.assigned.map(m => (
|
||||
{task.assigned.map((m) => (
|
||||
<TaskMember
|
||||
key={m.id}
|
||||
member={m}
|
||||
size={32}
|
||||
onMemberProfile={$target => {
|
||||
onMemberProfile={($target) => {
|
||||
if (user) {
|
||||
onMemberProfile($target, m.id);
|
||||
}
|
||||
@ -401,7 +401,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<ExtraActionsSection>
|
||||
<DueDateTitle>ACTIONS</DueDateTitle>
|
||||
<ActionButton
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
onOpenAddLabelPopup(task, $target);
|
||||
}}
|
||||
icon={<Tags width={12} height={12} />}
|
||||
@ -409,7 +409,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
Labels
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
onOpenAddChecklistPopup(task, $target);
|
||||
}}
|
||||
icon={<CheckSquareOutline width={12} height={12} />}
|
||||
@ -460,7 +460,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
value={taskName}
|
||||
ref={$detailsTitle}
|
||||
disabled={user === null}
|
||||
onKeyDown={e => {
|
||||
onKeyDown={(e) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
if ($detailsTitle && $detailsTitle.current) {
|
||||
@ -468,7 +468,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setTaskName(e.currentTarget.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
@ -481,12 +481,12 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<Labels>
|
||||
{task.labels.length !== 0 && (
|
||||
<MetaDetailContent>
|
||||
{task.labels.map(label => {
|
||||
{task.labels.map((label) => {
|
||||
return (
|
||||
<TaskLabelItem
|
||||
key={label.projectLabel.id}
|
||||
label={label}
|
||||
onClick={$target => {
|
||||
onClick={($target) => {
|
||||
onOpenAddLabelPopup(task, $target);
|
||||
}}
|
||||
/>
|
||||
@ -505,7 +505,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<TaskDetailsEditor value={taskDescriptionRef.current} />
|
||||
) : (
|
||||
<EditorContainer
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
if (!editTaskDescription) {
|
||||
setEditTaskDescription(true);
|
||||
}
|
||||
@ -513,10 +513,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
>
|
||||
<Editor
|
||||
defaultValue={task.description ?? ''}
|
||||
theme={dark}
|
||||
readOnly={user === null || !editTaskDescription}
|
||||
autoFocus
|
||||
onChange={value => {
|
||||
theme={dark}
|
||||
onChange={(value) => {
|
||||
setSaveTimeout(() => {
|
||||
clearTimeout(saveTimeout);
|
||||
return setTimeout(saveDescription, 2000);
|
||||
@ -531,9 +531,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton>
|
||||
</DescriptionContainer>
|
||||
<ChecklistSection>
|
||||
<DragDropContext onDragEnd={result => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>
|
||||
<DragDropContext onDragEnd={(result) => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>
|
||||
<Droppable direction="vertical" type="checklist" droppableId="root">
|
||||
{dropProvided => (
|
||||
{(dropProvided) => (
|
||||
<ChecklistContainer {...dropProvided.droppableProps} ref={dropProvided.innerRef}>
|
||||
{task.checklists &&
|
||||
task.checklists
|
||||
@ -541,7 +541,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((checklist, idx) => (
|
||||
<Draggable key={checklist.id} draggableId={checklist.id} index={idx}>
|
||||
{provided => (
|
||||
{(provided) => (
|
||||
<Checklist
|
||||
ref={provided.innerRef}
|
||||
wrapperProps={provided.draggableProps}
|
||||
@ -551,10 +551,10 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
checklistID={checklist.id}
|
||||
items={checklist.items}
|
||||
onDeleteChecklist={onDeleteChecklist}
|
||||
onChangeName={newName => onChangeChecklistName(checklist.id, newName)}
|
||||
onChangeName={(newName) => onChangeChecklistName(checklist.id, newName)}
|
||||
onToggleItem={onToggleChecklistItem}
|
||||
onDeleteItem={onDeleteItem}
|
||||
onAddItem={n => {
|
||||
onAddItem={(n) => {
|
||||
if (task.checklists) {
|
||||
let position = 65535;
|
||||
const [lastItem] = checklist.items
|
||||
@ -569,7 +569,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
onChangeItemName={onChangeItemName}
|
||||
>
|
||||
<Droppable direction="vertical" type="checklistItem" droppableId={checklist.id}>
|
||||
{checklistDrop => (
|
||||
{(checklistDrop) => (
|
||||
<>
|
||||
<ChecklistItems ref={checklistDrop.innerRef} {...checklistDrop.droppableProps}>
|
||||
{checklist.items
|
||||
@ -577,7 +577,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((item, itemIdx) => (
|
||||
<Draggable key={item.id} draggableId={item.id} index={itemIdx}>
|
||||
{itemDrop => (
|
||||
{(itemDrop) => (
|
||||
<ChecklistItem
|
||||
key={item.id}
|
||||
itemID={item.id}
|
||||
@ -615,17 +615,19 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<TabBarItem>Activity</TabBarItem>
|
||||
</TabBarSection>
|
||||
<ActivitySection>
|
||||
{activityStream.map(stream =>
|
||||
{activityStream.map((stream) =>
|
||||
stream.data.type === 'comment' ? (
|
||||
<StreamComment
|
||||
onExtraActions={onCommentShowActions}
|
||||
onCancelCommentEdit={onCancelCommentEdit}
|
||||
onUpdateComment={message => onUpdateComment(stream.id, message)}
|
||||
onUpdateComment={(message) => onUpdateComment(stream.id, message)}
|
||||
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>
|
||||
@ -634,7 +636,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<CommentContainer>
|
||||
<CommentCreator
|
||||
me={me}
|
||||
onCreateComment={message => onCreateComment(task, message)}
|
||||
onCreateComment={(message) => onCreateComment(task, message)}
|
||||
onMemberProfile={onMemberProfile}
|
||||
/>
|
||||
</CommentContainer>
|
||||
|
@ -1,14 +1,15 @@
|
||||
import theme from 'App/ThemeStyles';
|
||||
|
||||
const colors = {
|
||||
almostBlack: '#181A1B',
|
||||
lightBlack: '#2F3336',
|
||||
almostWhite: '#E6E6E6',
|
||||
almostBlack: 'rgb(38, 44, 73)',
|
||||
lightBlack: 'rgb(16, 22, 58)',
|
||||
bgPrimary: 'rgb(16, 22, 58)',
|
||||
almostWhite: 'rgb(194, 198, 220)',
|
||||
white: '#FFF',
|
||||
white10: 'rgba(255, 255, 255, 0.1)',
|
||||
white10: 'rgb(194, 198, 220)',
|
||||
black: '#000',
|
||||
black10: 'rgba(0, 0, 0, 0.1)',
|
||||
primary: '#1AB6FF',
|
||||
primary: 'rgb(115, 103, 240)',
|
||||
greyLight: '#F4F7FA',
|
||||
grey: '#E8EBED',
|
||||
greyMid: '#C5CCD3',
|
||||
@ -17,15 +18,16 @@ const colors = {
|
||||
|
||||
export const base = {
|
||||
...colors,
|
||||
fontFamily: "'Open Sans', sans-serif",
|
||||
fontFamily: 'Open Sans',
|
||||
fontFamilyMono: "'SFMono-Regular',Consolas,'Liberation Mono', Menlo, Courier,monospace",
|
||||
fontWeight: 400,
|
||||
zIndex: 10000,
|
||||
zIndex: 1000000,
|
||||
link: colors.primary,
|
||||
placeholder: '#B1BECC',
|
||||
textSecondary: '#4E5C6E',
|
||||
textSecondary: '#fff',
|
||||
textLight: colors.white,
|
||||
textHighlight: '#b3e7ff',
|
||||
textHighlightForeground: colors.white,
|
||||
selected: colors.primary,
|
||||
codeComment: '#6a737d',
|
||||
codePunctuation: '#5e6687',
|
||||
@ -43,13 +45,13 @@ export const base = {
|
||||
codeInserted: '#202746',
|
||||
codeImportant: '#c94922',
|
||||
|
||||
blockToolbarBackground: colors.white,
|
||||
blockToolbarTrigger: colors.greyMid,
|
||||
blockToolbarBackground: colors.bgPrimary,
|
||||
blockToolbarTrigger: colors.white,
|
||||
blockToolbarTriggerIcon: colors.white,
|
||||
blockToolbarItem: colors.almostBlack,
|
||||
blockToolbarText: colors.almostBlack,
|
||||
blockToolbarHoverBackground: colors.greyLight,
|
||||
blockToolbarDivider: colors.greyMid,
|
||||
blockToolbarItem: colors.white,
|
||||
blockToolbarText: colors.white,
|
||||
blockToolbarHoverBackground: colors.primary,
|
||||
blockToolbarDivider: colors.almostWhite,
|
||||
|
||||
noticeInfoBackground: '#F5BE31',
|
||||
noticeInfoText: colors.almostBlack,
|
||||
@ -58,18 +60,20 @@ export const base = {
|
||||
noticeWarningBackground: '#FF5C80',
|
||||
noticeWarningText: colors.white,
|
||||
};
|
||||
|
||||
export const dark = {
|
||||
...base,
|
||||
background: 'transparent',
|
||||
text: `${theme.colors.text.primary}`,
|
||||
code: `${theme.colors.text.primary}`,
|
||||
cursor: `${theme.colors.text.primary}`,
|
||||
background: colors.almostBlack,
|
||||
text: colors.almostWhite,
|
||||
code: colors.almostWhite,
|
||||
cursor: colors.white,
|
||||
divider: '#4E5C6E',
|
||||
placeholder: '#52657A',
|
||||
|
||||
toolbarBackground: colors.white,
|
||||
toolbarInput: colors.black10,
|
||||
toolbarItem: colors.lightBlack,
|
||||
toolbarBackground: colors.bgPrimary,
|
||||
toolbarHoverBackground: colors.primary,
|
||||
toolbarInput: colors.almostWhite,
|
||||
toolbarItem: colors.white,
|
||||
|
||||
tableDivider: colors.lightBlack,
|
||||
tableSelected: colors.primary,
|
||||
@ -81,6 +85,9 @@ export const dark = {
|
||||
codeString: '#3d8fd1',
|
||||
horizontalRule: colors.lightBlack,
|
||||
imageErrorBackground: 'rgba(0, 0, 0, 0.5)',
|
||||
|
||||
scrollbarBackground: colors.black,
|
||||
scrollbarThumb: colors.lightBlack,
|
||||
};
|
||||
|
||||
export default dark;
|
||||
|
@ -18,8 +18,9 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react",
|
||||
"baseUrl": "src"
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "src",
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"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