deps: upgrade all dependencies

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

View File

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

View File

@ -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",
{

View File

@ -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"
}
}

View File

@ -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();

View File

@ -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>

View File

@ -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}

View File

@ -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) {

View File

@ -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 = () => {
/>,
);
*/
}}
/>
)}
}}
/>
);
}}
/>
</>
);

View File

@ -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();
}}

View File

@ -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}
/>,
);
}}
/>
);
}}
/>
</>
);

View File

@ -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>

View File

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

View File

@ -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();
}

View File

@ -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);

View File

@ -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

View File

@ -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>

View File

@ -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} />

View File

@ -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">

View File

@ -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>

View File

@ -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;

View File

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

File diff suppressed because it is too large Load Diff