Compare commits
	
		
			8 Commits
		
	
	
		
			feat/outli
			...
			feat/publi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					33f06c1035 | ||
| 
						 | 
					eff2044a6b | ||
| 
						 | 
					451581e934 | ||
| 
						 | 
					0a48b578fd | ||
| 
						 | 
					262f9cbdda | ||
| 
						 | 
					737d2b640f | ||
| 
						 | 
					36f25391b4 | ||
| 
						 | 
					696a9aeee7 | 
@@ -21,4 +21,4 @@ windows:
 | 
			
		||||
  - database:
 | 
			
		||||
      root: ./
 | 
			
		||||
      panes:
 | 
			
		||||
        - pgcli postgres://taskcafe:taskcafe_test@localhost:8855/taskcafe
 | 
			
		||||
        - pgcli postgres://taskcafe:taskcafe_test@localhost:5432/taskcafe
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,7 @@
 | 
			
		||||
    "@types/jest": "^24.0.0",
 | 
			
		||||
    "@types/jwt-decode": "^2.2.1",
 | 
			
		||||
    "@types/lodash": "^4.14.149",
 | 
			
		||||
    "@types/marked": "^1.2.2",
 | 
			
		||||
    "@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",
 | 
			
		||||
@@ -23,7 +21,6 @@
 | 
			
		||||
    "@types/react-router-dom": "^5.1.3",
 | 
			
		||||
    "@types/react-select": "^3.0.13",
 | 
			
		||||
    "@types/react-timeago": "^4.1.1",
 | 
			
		||||
    "@types/react-window": "^1.8.2",
 | 
			
		||||
    "@types/styled-components": "^5.0.0",
 | 
			
		||||
    "apollo-cache-inmemory": "^1.6.5",
 | 
			
		||||
    "apollo-client": "^2.6.8",
 | 
			
		||||
@@ -43,9 +40,7 @@
 | 
			
		||||
    "immer": "^6.0.3",
 | 
			
		||||
    "jwt-decode": "^2.2.0",
 | 
			
		||||
    "lodash": "^4.17.20",
 | 
			
		||||
    "marked": "^2.0.0",
 | 
			
		||||
    "prop-types": "^15.7.2",
 | 
			
		||||
    "query-string": "^6.13.7",
 | 
			
		||||
    "react": "^16.12.0",
 | 
			
		||||
    "react-autosize-textarea": "^7.0.0",
 | 
			
		||||
    "react-beautiful-dnd": "^13.0.0",
 | 
			
		||||
@@ -59,8 +54,6 @@
 | 
			
		||||
    "react-select": "^3.1.0",
 | 
			
		||||
    "react-timeago": "^4.4.0",
 | 
			
		||||
    "react-toastify": "^6.0.8",
 | 
			
		||||
    "react-visibility-sensor": "^5.1.1",
 | 
			
		||||
    "react-window": "^1.8.6",
 | 
			
		||||
    "rich-markdown-editor": "^10.6.5",
 | 
			
		||||
    "styled-components": "^5.0.1",
 | 
			
		||||
    "typescript": "~3.7.2"
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ const AddUserInput = styled(Input)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const InputError = styled.span`
 | 
			
		||||
  color: ${props => props.theme.colors.danger};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.danger});
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -182,7 +182,7 @@ const AdminRoute = () => {
 | 
			
		||||
      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,
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
@@ -192,7 +192,7 @@ const AdminRoute = () => {
 | 
			
		||||
    update: (client, response) => {
 | 
			
		||||
      updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
 | 
			
		||||
        produce(cache, draftCache => {
 | 
			
		||||
          draftCache.users = cache.users.filter(u => u.id !== response.data?.deleteUserAccount.userAccount.id);
 | 
			
		||||
          draftCache.users = cache.users.filter(u => u.id !== response.data.deleteUserAccount.userAccount.id);
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
@@ -203,7 +203,7 @@ const AdminRoute = () => {
 | 
			
		||||
        query: UsersDocument,
 | 
			
		||||
      });
 | 
			
		||||
      const newData = produce(cacheData, (draftState: any) => {
 | 
			
		||||
        draftState.users = [...draftState.users, { ...createData.data?.createUserAccount }];
 | 
			
		||||
        draftState.users = [...draftState.users, { ...createData.data.createUserAccount }];
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      client.writeQuery({
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,17 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Switch, Route, useHistory } from 'react-router-dom';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Switch, Route } from 'react-router-dom';
 | 
			
		||||
import * as H from 'history';
 | 
			
		||||
 | 
			
		||||
import Dashboard from 'Dashboard';
 | 
			
		||||
import Admin from 'Admin';
 | 
			
		||||
import Confirm from 'Confirm';
 | 
			
		||||
import Projects from 'Projects';
 | 
			
		||||
import Outline from 'Outline';
 | 
			
		||||
import Project from 'Projects/Project';
 | 
			
		||||
import Teams from 'Teams';
 | 
			
		||||
import Login from 'Auth';
 | 
			
		||||
import Register from 'Register';
 | 
			
		||||
import Install from 'Install';
 | 
			
		||||
import Profile from 'Profile';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import JwtDecode from 'jwt-decode';
 | 
			
		||||
import { setAccessToken } from 'shared/utils/accessToken';
 | 
			
		||||
import { useCurrentUser } from 'App/context';
 | 
			
		||||
import Outline from 'Outline';
 | 
			
		||||
 | 
			
		||||
const MainContent = styled.div`
 | 
			
		||||
  padding: 0 0 0 0;
 | 
			
		||||
@@ -26,62 +22,6 @@ const MainContent = styled.div`
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type RefreshTokenResponse = {
 | 
			
		||||
  accessToken: string;
 | 
			
		||||
  setup?: null | { confirmToken: string };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AuthorizedRoutes = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const [loading, setLoading] = useState(true);
 | 
			
		||||
  const { setUser } = useCurrentUser();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const abortController = new AbortController();
 | 
			
		||||
 | 
			
		||||
    fetch('/auth/refresh_token', {
 | 
			
		||||
      signal: abortController.signal,
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      credentials: 'include',
 | 
			
		||||
    }).then(async x => {
 | 
			
		||||
      const { status } = x;
 | 
			
		||||
      if (status === 400) {
 | 
			
		||||
        history.replace('/login');
 | 
			
		||||
      } else {
 | 
			
		||||
        const response: RefreshTokenResponse = await x.json();
 | 
			
		||||
        const { accessToken, setup } = response;
 | 
			
		||||
        if (setup) {
 | 
			
		||||
          history.replace(`/register?confirmToken=${setup.confirmToken}`);
 | 
			
		||||
        } else {
 | 
			
		||||
          const claims: JWTToken = JwtDecode(accessToken);
 | 
			
		||||
          const currentUser = {
 | 
			
		||||
            id: claims.userId,
 | 
			
		||||
            roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
 | 
			
		||||
          };
 | 
			
		||||
          setUser(currentUser);
 | 
			
		||||
          setAccessToken(accessToken);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    });
 | 
			
		||||
    return () => {
 | 
			
		||||
      abortController.abort();
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
  return loading ? null : (
 | 
			
		||||
    <Switch>
 | 
			
		||||
      <MainContent>
 | 
			
		||||
        <Route exact path="/" component={Dashboard} />
 | 
			
		||||
        <Route exact path="/projects" component={Projects} />
 | 
			
		||||
        <Route path="/projects/:projectID" component={Project} />
 | 
			
		||||
        <Route path="/teams/:teamID" component={Teams} />
 | 
			
		||||
        <Route path="/outline" component={Outline} />
 | 
			
		||||
        <Route path="/profile" component={Profile} />
 | 
			
		||||
        <Route path="/admin" component={Admin} />
 | 
			
		||||
      </MainContent>
 | 
			
		||||
    </Switch>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type RoutesProps = {
 | 
			
		||||
  history: H.History;
 | 
			
		||||
};
 | 
			
		||||
@@ -89,9 +29,16 @@ type RoutesProps = {
 | 
			
		||||
const Routes: React.FC<RoutesProps> = () => (
 | 
			
		||||
  <Switch>
 | 
			
		||||
    <Route exact path="/login" component={Login} />
 | 
			
		||||
    <Route exact path="/register" component={Register} />
 | 
			
		||||
    <Route exact path="/confirm" component={Confirm} />
 | 
			
		||||
    <AuthorizedRoutes />
 | 
			
		||||
    <Route exact path="/install" component={Install} />
 | 
			
		||||
    <MainContent>
 | 
			
		||||
      <Route exact path="/" component={Dashboard} />
 | 
			
		||||
      <Route exact path="/projects" component={Projects} />
 | 
			
		||||
      <Route path="/projects/:projectID" component={Project} />
 | 
			
		||||
      <Route path="/teams/:teamID" component={Teams} />
 | 
			
		||||
      <Route path="/profile" component={Profile} />
 | 
			
		||||
      <Route path="/admin" component={Admin} />
 | 
			
		||||
      <Route path="/outline" component={Outline} />
 | 
			
		||||
    </MainContent>
 | 
			
		||||
  </Switch>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,26 @@
 | 
			
		||||
import { DefaultTheme } from 'styled-components';
 | 
			
		||||
import Color from 'color';
 | 
			
		||||
 | 
			
		||||
const theme: DefaultTheme = {
 | 
			
		||||
  borderRadius: {
 | 
			
		||||
    primary: '3x',
 | 
			
		||||
    primary: '3px',
 | 
			
		||||
    alternate: '6px',
 | 
			
		||||
  },
 | 
			
		||||
  colors: {
 | 
			
		||||
    multiColors: ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'],
 | 
			
		||||
    primary: 'rgb(115, 103, 240)',
 | 
			
		||||
    secondary: 'rgb(216, 93, 216)',
 | 
			
		||||
    alternate: 'rgb(65, 69, 97)',
 | 
			
		||||
    success: 'rgb(40, 199, 111)',
 | 
			
		||||
    danger: 'rgb(234, 84, 85)',
 | 
			
		||||
    warning: 'rgb(255, 159, 67)',
 | 
			
		||||
    dark: 'rgb(30, 30, 30)',
 | 
			
		||||
    primary: '115, 103, 240',
 | 
			
		||||
    secondary: '216, 93, 216',
 | 
			
		||||
    alternate: '65, 69, 97',
 | 
			
		||||
    success: '40, 199, 111',
 | 
			
		||||
    danger: '234, 84, 85',
 | 
			
		||||
    warning: '255, 159, 67',
 | 
			
		||||
    dark: '30, 30, 30',
 | 
			
		||||
    text: {
 | 
			
		||||
      primary: 'rgb(194, 198, 220)',
 | 
			
		||||
      secondary: 'rgb(255, 255, 255)',
 | 
			
		||||
      primary: '194, 198, 220',
 | 
			
		||||
      secondary: '255, 255, 255',
 | 
			
		||||
    },
 | 
			
		||||
    border: 'rgb(65, 69, 97)',
 | 
			
		||||
    border: '65, 69, 97',
 | 
			
		||||
    bg: {
 | 
			
		||||
      primary: 'rgb(16, 22, 58)',
 | 
			
		||||
      secondary: 'rgb(38, 44, 73)',
 | 
			
		||||
      primary: '16, 22, 58',
 | 
			
		||||
      secondary: '38, 44, 73',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import MiniProfile from 'shared/components/MiniProfile';
 | 
			
		||||
import cache from 'App/cache';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import NotificationPopup, { NotificationItem } from 'shared/components/NotifcationPopup';
 | 
			
		||||
import theme from './ThemeStyles';
 | 
			
		||||
 | 
			
		||||
const TeamContainer = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
@@ -63,7 +62,7 @@ const TeamProjectBackground = styled.div<{ color: string }>`
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  &:before {
 | 
			
		||||
    background: ${props => props.theme.colors.bg.secondary};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.secondary});
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    content: '';
 | 
			
		||||
    left: 0;
 | 
			
		||||
@@ -115,7 +114,7 @@ const TeamProjectContainer = styled.div`
 | 
			
		||||
  margin: 0 4px 4px 0;
 | 
			
		||||
  min-width: 0;
 | 
			
		||||
  &:hover ${TeamProjectTitle} {
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
  &:hover ${TeamProjectAvatar} {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
@@ -125,7 +124,7 @@ const TeamProjectContainer = styled.div`
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const colors = [theme.colors.primary, theme.colors.secondary];
 | 
			
		||||
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
 | 
			
		||||
 | 
			
		||||
const ProjectFinder = () => {
 | 
			
		||||
  const { loading, data } = useGetProjectsQuery();
 | 
			
		||||
@@ -167,7 +166,7 @@ const ProjectFinder = () => {
 | 
			
		||||
  return <span>error</span>;
 | 
			
		||||
};
 | 
			
		||||
type ProjectPopupProps = {
 | 
			
		||||
  history: any;
 | 
			
		||||
  history: History<History.PoorMansUnknown>;
 | 
			
		||||
  name: string;
 | 
			
		||||
  projectID: string;
 | 
			
		||||
};
 | 
			
		||||
@@ -182,7 +181,7 @@ export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, proje
 | 
			
		||||
 | 
			
		||||
      const newData = produce(cacheData, (draftState: any) => {
 | 
			
		||||
        draftState.projects = draftState.projects.filter(
 | 
			
		||||
          (project: any) => project.id !== deleteData.data?.deleteProject.project.id,
 | 
			
		||||
          (project: any) => project.id !== deleteData.data.deleteProject.project.id,
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@@ -329,7 +328,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
 | 
			
		||||
            />
 | 
			
		||||
          ))}
 | 
			
		||||
        </NotificationPopup>,
 | 
			
		||||
        { width: 415, borders: false, diamondColor: theme.colors.primary },
 | 
			
		||||
        { width: 415, borders: false, diamondColor: '#7367f0' },
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -28,13 +28,13 @@ const StyledContainer = styled(ToastContainer).attrs({
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
  .Toastify__toast--error {
 | 
			
		||||
    background: ${props => props.theme.colors.danger};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.danger});
 | 
			
		||||
  }
 | 
			
		||||
  .Toastify__toast--warning {
 | 
			
		||||
    background: ${props => props.theme.colors.warning};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.warning});
 | 
			
		||||
  }
 | 
			
		||||
  .Toastify__toast--success {
 | 
			
		||||
    background: ${props => props.theme.colors.success};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.success});
 | 
			
		||||
  }
 | 
			
		||||
  .Toastify__toast-body {
 | 
			
		||||
  }
 | 
			
		||||
@@ -46,8 +46,13 @@ const StyledContainer = styled(ToastContainer).attrs({
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const history = createBrowserHistory();
 | 
			
		||||
type RefreshTokenResponse = {
 | 
			
		||||
  accessToken: string;
 | 
			
		||||
  isInstalled: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
  const [loading, setLoading] = useState(true);
 | 
			
		||||
  const [user, setUser] = useState<CurrentUserRaw | null>(null);
 | 
			
		||||
  const setUserRoles = (roles: CurrentUserRoles) => {
 | 
			
		||||
    if (user) {
 | 
			
		||||
@@ -58,6 +63,32 @@ const App = () => {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetch('/auth/refresh_token', {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      credentials: 'include',
 | 
			
		||||
    }).then(async x => {
 | 
			
		||||
      const { status } = x;
 | 
			
		||||
      if (status === 400) {
 | 
			
		||||
        history.replace('/login');
 | 
			
		||||
      } else {
 | 
			
		||||
        const response: RefreshTokenResponse = await x.json();
 | 
			
		||||
        const { accessToken, isInstalled } = response;
 | 
			
		||||
        const claims: JWTToken = jwtDecode(accessToken);
 | 
			
		||||
        const currentUser = {
 | 
			
		||||
          id: claims.userId,
 | 
			
		||||
          roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
 | 
			
		||||
        };
 | 
			
		||||
        setUser(currentUser);
 | 
			
		||||
        setAccessToken(accessToken);
 | 
			
		||||
        if (!isInstalled) {
 | 
			
		||||
          history.replace('/install');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <UserContext.Provider value={{ user, setUser, setUserRoles }}>
 | 
			
		||||
@@ -66,7 +97,13 @@ const App = () => {
 | 
			
		||||
          <BaseStyles />
 | 
			
		||||
          <Router history={history}>
 | 
			
		||||
            <PopupProvider>
 | 
			
		||||
              <Routes history={history} />
 | 
			
		||||
              {loading ? (
 | 
			
		||||
                <div>loading</div>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <>
 | 
			
		||||
                  <Routes history={history} />
 | 
			
		||||
                </>
 | 
			
		||||
              )}
 | 
			
		||||
            </PopupProvider>
 | 
			
		||||
          </Router>
 | 
			
		||||
          <StyledContainer
 | 
			
		||||
 
 | 
			
		||||
@@ -52,20 +52,7 @@ const Auth = () => {
 | 
			
		||||
    }).then(async x => {
 | 
			
		||||
      const { status } = x;
 | 
			
		||||
      if (status === 200) {
 | 
			
		||||
        const response: RefreshTokenResponse = await x.json();
 | 
			
		||||
        const { accessToken, setup } = response;
 | 
			
		||||
        if (setup) {
 | 
			
		||||
          history.replace(`/register?confirmToken=${setup.confirmToken}`);
 | 
			
		||||
        } else {
 | 
			
		||||
          const claims: JWTToken = JwtDecode(accessToken);
 | 
			
		||||
          const currentUser = {
 | 
			
		||||
            id: claims.userId,
 | 
			
		||||
            roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
 | 
			
		||||
          };
 | 
			
		||||
          setUser(currentUser);
 | 
			
		||||
          setAccessToken(accessToken);
 | 
			
		||||
          history.replace('/projects');
 | 
			
		||||
        }
 | 
			
		||||
        history.replace('/projects');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import Confirm from 'shared/components/Confirm';
 | 
			
		||||
import { useHistory, useLocation } from 'react-router';
 | 
			
		||||
import * as QueryString from 'query-string';
 | 
			
		||||
import { toast } from 'react-toastify';
 | 
			
		||||
import { Container, LoginWrapper } from './Styles';
 | 
			
		||||
import JwtDecode from 'jwt-decode';
 | 
			
		||||
import { setAccessToken } from 'shared/utils/accessToken';
 | 
			
		||||
import { useCurrentUser } from 'App/context';
 | 
			
		||||
 | 
			
		||||
const UsersConfirm = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const [registered, setRegistered] = useState(false);
 | 
			
		||||
  const params = QueryString.parse(location.search);
 | 
			
		||||
  const { setUser } = useCurrentUser();
 | 
			
		||||
  return (
 | 
			
		||||
    <Container>
 | 
			
		||||
      <LoginWrapper>
 | 
			
		||||
        <Confirm
 | 
			
		||||
          hasConfirmToken={params.confirmToken !== undefined}
 | 
			
		||||
          onConfirmUser={setFailed => {
 | 
			
		||||
            fetch('/auth/confirm', {
 | 
			
		||||
              method: 'POST',
 | 
			
		||||
              body: JSON.stringify({
 | 
			
		||||
                confirmToken: params.confirmToken,
 | 
			
		||||
              }),
 | 
			
		||||
            })
 | 
			
		||||
              .then(async x => {
 | 
			
		||||
                const { status } = x;
 | 
			
		||||
                if (status === 200) {
 | 
			
		||||
                  const response = await x.json();
 | 
			
		||||
                  const { accessToken } = response;
 | 
			
		||||
                  const claims: JWTToken = JwtDecode(accessToken);
 | 
			
		||||
                  const currentUser = {
 | 
			
		||||
                    id: claims.userId,
 | 
			
		||||
                    roles: {
 | 
			
		||||
                      org: claims.orgRole,
 | 
			
		||||
                      teams: new Map<string, string>(),
 | 
			
		||||
                      projects: new Map<string, string>(),
 | 
			
		||||
                    },
 | 
			
		||||
                  };
 | 
			
		||||
                  setUser(currentUser);
 | 
			
		||||
                  setAccessToken(accessToken);
 | 
			
		||||
                  history.push('/');
 | 
			
		||||
                } else {
 | 
			
		||||
                  setFailed();
 | 
			
		||||
                }
 | 
			
		||||
              })
 | 
			
		||||
              .catch(() => {
 | 
			
		||||
                setFailed();
 | 
			
		||||
              });
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </LoginWrapper>
 | 
			
		||||
    </Container>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default UsersConfirm;
 | 
			
		||||
							
								
								
									
										88
									
								
								frontend/src/Install/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								frontend/src/Install/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
import React, { useEffect, useContext } from 'react';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import Register from 'shared/components/Register';
 | 
			
		||||
import { useHistory } from 'react-router';
 | 
			
		||||
import { getAccessToken, setAccessToken } from 'shared/utils/accessToken';
 | 
			
		||||
import UserContext from 'App/context';
 | 
			
		||||
import jwtDecode from 'jwt-decode';
 | 
			
		||||
import { Container, LoginWrapper } from './Styles';
 | 
			
		||||
 | 
			
		||||
const Install = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const { setUser } = useContext(UserContext);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetch('/auth/refresh_token', {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      credentials: 'include',
 | 
			
		||||
    }).then(async x => {
 | 
			
		||||
      const { status } = x;
 | 
			
		||||
      const response: RefreshTokenResponse = await x.json();
 | 
			
		||||
      const { isInstalled } = response;
 | 
			
		||||
      if (status === 200 && isInstalled) {
 | 
			
		||||
        history.replace('/projects');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }, []);
 | 
			
		||||
  return (
 | 
			
		||||
    <Container>
 | 
			
		||||
      <LoginWrapper>
 | 
			
		||||
        <Register
 | 
			
		||||
          onSubmit={(data, setComplete, setError) => {
 | 
			
		||||
            const accessToken = getAccessToken();
 | 
			
		||||
            if (data.password !== data.password_confirm) {
 | 
			
		||||
              setError('password', { type: 'error', message: 'Passwords must match' });
 | 
			
		||||
              setError('password_confirm', { type: 'error', message: 'Passwords must match' });
 | 
			
		||||
            } else {
 | 
			
		||||
              axios
 | 
			
		||||
                .post(
 | 
			
		||||
                  '/auth/install',
 | 
			
		||||
                  {
 | 
			
		||||
                    user: {
 | 
			
		||||
                      username: data.username,
 | 
			
		||||
                      roleCode: 'admin',
 | 
			
		||||
                      email: data.email,
 | 
			
		||||
                      password: data.password,
 | 
			
		||||
                      initials: data.initials,
 | 
			
		||||
                      fullname: data.fullname,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    headers: {
 | 
			
		||||
                      Authorization: `Bearer ${accessToken}`,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                )
 | 
			
		||||
                .then(async x => {
 | 
			
		||||
                  const { status } = x;
 | 
			
		||||
                  if (status === 400) {
 | 
			
		||||
                    history.replace('/login');
 | 
			
		||||
                  } else {
 | 
			
		||||
                    const response: RefreshTokenResponse = await x.data;
 | 
			
		||||
                    const { accessToken: newToken, isInstalled } = response;
 | 
			
		||||
                    const claims: JWTToken = jwtDecode(newToken);
 | 
			
		||||
                    const currentUser = {
 | 
			
		||||
                      id: claims.userId,
 | 
			
		||||
                      roles: {
 | 
			
		||||
                        org: claims.orgRole,
 | 
			
		||||
                        teams: new Map<string, string>(),
 | 
			
		||||
                        projects: new Map<string, string>(),
 | 
			
		||||
                      },
 | 
			
		||||
                    };
 | 
			
		||||
                    setUser(currentUser);
 | 
			
		||||
                    setAccessToken(newToken);
 | 
			
		||||
                    if (!isInstalled) {
 | 
			
		||||
                      history.replace('/install');
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                  history.push('/projects');
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setComplete(true);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </LoginWrapper>
 | 
			
		||||
    </Container>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Install;
 | 
			
		||||
@@ -34,93 +34,9 @@ type DraggerProps = {
 | 
			
		||||
  isDragging: boolean;
 | 
			
		||||
  onDragEnd: (zone: ImpactZone) => void;
 | 
			
		||||
  initialPos: { x: number; y: number };
 | 
			
		||||
  pageRef: React.RefObject<HTMLDivElement>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let timer: any = null;
 | 
			
		||||
 | 
			
		||||
type windowScrollOptions = {
 | 
			
		||||
  maxScrollX: number;
 | 
			
		||||
  maxScrollY: number;
 | 
			
		||||
  isInTopEdge: boolean;
 | 
			
		||||
  isInBottomEdge: boolean;
 | 
			
		||||
  edgeTop: number;
 | 
			
		||||
  edgeBottom: number;
 | 
			
		||||
  edgeSize: number;
 | 
			
		||||
  viewportY: number;
 | 
			
		||||
  $page: React.RefObject<HTMLDivElement>;
 | 
			
		||||
};
 | 
			
		||||
function adjustWindowScroll({
 | 
			
		||||
  maxScrollY,
 | 
			
		||||
  maxScrollX,
 | 
			
		||||
  $page,
 | 
			
		||||
  isInTopEdge,
 | 
			
		||||
  isInBottomEdge,
 | 
			
		||||
  edgeTop,
 | 
			
		||||
  edgeBottom,
 | 
			
		||||
  edgeSize,
 | 
			
		||||
  viewportY,
 | 
			
		||||
}: windowScrollOptions) {
 | 
			
		||||
  // Get the current scroll position of the document.
 | 
			
		||||
  if ($page.current) {
 | 
			
		||||
    var currentScrollX = $page.current.scrollLeft;
 | 
			
		||||
    var currentScrollY = $page.current.scrollTop;
 | 
			
		||||
 | 
			
		||||
    // Determine if the window can be scrolled in any particular direction.
 | 
			
		||||
    var canScrollUp = currentScrollY > 0;
 | 
			
		||||
    var canScrollDown = currentScrollY < maxScrollY;
 | 
			
		||||
 | 
			
		||||
    // Since we can potentially scroll in two directions at the same time,
 | 
			
		||||
    // let's keep track of the next scroll, starting with the current scroll.
 | 
			
		||||
    // Each of these values can then be adjusted independently in the logic
 | 
			
		||||
    // below.
 | 
			
		||||
    var nextScrollX = currentScrollX;
 | 
			
		||||
    var nextScrollY = currentScrollY;
 | 
			
		||||
 | 
			
		||||
    // As we examine the mouse position within the edge, we want to make the
 | 
			
		||||
    // incremental scroll changes more "intense" the closer that the user
 | 
			
		||||
    // gets the viewport edge. As such, we'll calculate the percentage that
 | 
			
		||||
    // the user has made it "through the edge" when calculating the delta.
 | 
			
		||||
    // Then, that use that percentage to back-off from the "max" step value.
 | 
			
		||||
    var maxStep = 50;
 | 
			
		||||
 | 
			
		||||
    // Should we scroll up?
 | 
			
		||||
    if (isInTopEdge && canScrollUp) {
 | 
			
		||||
      var intensity = (edgeTop - viewportY) / edgeSize;
 | 
			
		||||
 | 
			
		||||
      nextScrollY = nextScrollY - maxStep * intensity;
 | 
			
		||||
 | 
			
		||||
      // Should we scroll down?
 | 
			
		||||
    } else if (isInBottomEdge && canScrollDown) {
 | 
			
		||||
      var intensity = (viewportY - edgeBottom) / edgeSize;
 | 
			
		||||
 | 
			
		||||
      nextScrollY = nextScrollY + maxStep * intensity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sanitize invalid maximums. An invalid scroll offset won't break the
 | 
			
		||||
    // subsequent .scrollTo() call; however, it will make it harder to
 | 
			
		||||
    // determine if the .scrollTo() method should have been called in the
 | 
			
		||||
    // first place.
 | 
			
		||||
    nextScrollX = Math.max(0, Math.min(maxScrollX, nextScrollX));
 | 
			
		||||
    nextScrollY = Math.max(0, Math.min(maxScrollY, nextScrollY));
 | 
			
		||||
 | 
			
		||||
    if (nextScrollX !== currentScrollX || nextScrollY !== currentScrollY) {
 | 
			
		||||
      $page.current.scrollTo(nextScrollX, nextScrollY);
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Dragger: React.FC<DraggerProps> = ({
 | 
			
		||||
  draggedNodes,
 | 
			
		||||
  container,
 | 
			
		||||
  onDragEnd,
 | 
			
		||||
  isDragging,
 | 
			
		||||
  initialPos,
 | 
			
		||||
  pageRef: $page,
 | 
			
		||||
}) => {
 | 
			
		||||
const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, isDragging, initialPos }) => {
 | 
			
		||||
  const [pos, setPos] = useState<{ x: number; y: number }>(initialPos);
 | 
			
		||||
  const { outline, impact, setImpact } = useDrag();
 | 
			
		||||
  const $handle = useRef<HTMLDivElement>(null);
 | 
			
		||||
@@ -129,8 +45,6 @@ const Dragger: React.FC<DraggerProps> = ({
 | 
			
		||||
  }, [impact]);
 | 
			
		||||
  const handleMouseMove = useCallback(
 | 
			
		||||
    e => {
 | 
			
		||||
      var t0 = performance.now();
 | 
			
		||||
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      const { clientX, clientY, pageX, pageY } = e;
 | 
			
		||||
      setPos({ x: clientX, y: clientY });
 | 
			
		||||
@@ -139,61 +53,6 @@ const Dragger: React.FC<DraggerProps> = ({
 | 
			
		||||
      let aboveNode: null | OutlineNode = null;
 | 
			
		||||
      let belowNode: null | OutlineNode = null;
 | 
			
		||||
 | 
			
		||||
      const edgeSize = 50;
 | 
			
		||||
 | 
			
		||||
      const viewportWidth = document.documentElement.clientWidth;
 | 
			
		||||
      const viewportHeight = document.documentElement.clientHeight;
 | 
			
		||||
 | 
			
		||||
      var edgeTop = edgeSize + 80;
 | 
			
		||||
      var edgeBottom = viewportHeight - edgeSize;
 | 
			
		||||
 | 
			
		||||
      var isInTopEdge = clientY < edgeTop;
 | 
			
		||||
      var isInBottomEdge = clientY > edgeBottom;
 | 
			
		||||
 | 
			
		||||
      if ((isInBottomEdge || isInTopEdge) && $page.current) {
 | 
			
		||||
        var documentWidth = Math.max(
 | 
			
		||||
          $page.current.scrollWidth,
 | 
			
		||||
          $page.current.offsetWidth,
 | 
			
		||||
          $page.current.clientWidth,
 | 
			
		||||
          $page.current.scrollWidth,
 | 
			
		||||
          $page.current.offsetWidth,
 | 
			
		||||
          $page.current.clientWidth,
 | 
			
		||||
        );
 | 
			
		||||
        var documentHeight = Math.max(
 | 
			
		||||
          $page.current.scrollHeight,
 | 
			
		||||
          $page.current.offsetHeight,
 | 
			
		||||
          $page.current.clientHeight,
 | 
			
		||||
          $page.current.scrollHeight,
 | 
			
		||||
          $page.current.offsetHeight,
 | 
			
		||||
          $page.current.clientHeight,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        var maxScrollX = documentWidth - viewportWidth;
 | 
			
		||||
        var maxScrollY = documentHeight - viewportHeight;
 | 
			
		||||
 | 
			
		||||
        (function checkForWindowScroll() {
 | 
			
		||||
          clearTimeout(timer);
 | 
			
		||||
 | 
			
		||||
          if (
 | 
			
		||||
            adjustWindowScroll({
 | 
			
		||||
              maxScrollX,
 | 
			
		||||
              maxScrollY,
 | 
			
		||||
              edgeBottom,
 | 
			
		||||
              $page,
 | 
			
		||||
              edgeTop,
 | 
			
		||||
              edgeSize,
 | 
			
		||||
              isInBottomEdge,
 | 
			
		||||
              isInTopEdge,
 | 
			
		||||
              viewportY: clientY,
 | 
			
		||||
            })
 | 
			
		||||
          ) {
 | 
			
		||||
            timer = setTimeout(checkForWindowScroll, 30);
 | 
			
		||||
          }
 | 
			
		||||
        })();
 | 
			
		||||
      } else {
 | 
			
		||||
        clearTimeout(timer);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (curPosition === 'before') {
 | 
			
		||||
        belowNode = curDraggable;
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -272,23 +131,21 @@ const Dragger: React.FC<DraggerProps> = ({
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (aboveNode) {
 | 
			
		||||
        const foundDepth = findNodeDepth(outline.current.published, aboveNode.id);
 | 
			
		||||
        if (foundDepth === null) return;
 | 
			
		||||
        const { ancestors } = findNodeDepth(outline.current.published, aboveNode.id);
 | 
			
		||||
        for (let i = 0; i < draggedNodes.nodes.length; i++) {
 | 
			
		||||
          const nodeID = draggedNodes.nodes[i];
 | 
			
		||||
          if (foundDepth.ancestors.find(c => c === nodeID)) {
 | 
			
		||||
          if (ancestors.find(c => c === nodeID)) {
 | 
			
		||||
            if (draggedNodes.first) {
 | 
			
		||||
              belowNode = draggedNodes.first;
 | 
			
		||||
              aboveNode = findNodeAbove(outline.current, aboveNode ? aboveNode.depth : 1, draggedNodes.first);
 | 
			
		||||
            } else {
 | 
			
		||||
              const foundDepth = findNodeDepth(outline.current.published, nodeID);
 | 
			
		||||
              if (foundDepth === null) return;
 | 
			
		||||
              const nodeDepth = outline.current.nodes.get(foundDepth.depth);
 | 
			
		||||
              const { depth } = findNodeDepth(outline.current.published, nodeID);
 | 
			
		||||
              const nodeDepth = outline.current.nodes.get(depth);
 | 
			
		||||
              const targetNode = nodeDepth ? nodeDepth.get(nodeID) : null;
 | 
			
		||||
              if (targetNode) {
 | 
			
		||||
                belowNode = targetNode;
 | 
			
		||||
 | 
			
		||||
                aboveNode = findNodeAbove(outline.current, foundDepth.depth, targetNode);
 | 
			
		||||
                aboveNode = findNodeAbove(outline.current, depth, targetNode);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
@@ -361,7 +218,7 @@ const Dragger: React.FC<DraggerProps> = ({
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
  const styles = useMemo(() => {
 | 
			
		||||
    const position: 'fixed' | 'relative' = isDragging ? 'fixed' : 'relative';
 | 
			
		||||
    const position: 'absolute' | 'relative' = isDragging ? 'absolute' : 'relative';
 | 
			
		||||
    return {
 | 
			
		||||
      cursor: isDragging ? '-webkit-grabbing' : '-webkit-grab',
 | 
			
		||||
      transform: `translate(${pos.x - 10}px, ${pos.y - 4}px)`,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,101 +1,24 @@
 | 
			
		||||
import React, { useRef, useEffect, useCallback, useState } from 'react';
 | 
			
		||||
import React, { useRef, useEffect } from 'react';
 | 
			
		||||
import { Dot, CaretDown, CaretRight } from 'shared/icons';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import marked from 'marked';
 | 
			
		||||
import VisibilitySensor from 'react-visibility-sensor';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  EntryChildren,
 | 
			
		||||
  EntryWrapper,
 | 
			
		||||
  EntryContent,
 | 
			
		||||
  EntryInnerContent,
 | 
			
		||||
  EntryHandle,
 | 
			
		||||
  ExpandButton,
 | 
			
		||||
  EntryContentEditor,
 | 
			
		||||
  EntryContentDisplay,
 | 
			
		||||
} from './Styles';
 | 
			
		||||
import { EntryChildren, EntryWrapper, EntryContent, EntryInnerContent, EntryHandle, ExpandButton } from './Styles';
 | 
			
		||||
import { useDrag } from './useDrag';
 | 
			
		||||
import { getCaretPosition, setCurrentCursorPosition } from './utils';
 | 
			
		||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
 | 
			
		||||
 | 
			
		||||
type EditorProps = {
 | 
			
		||||
  text: string;
 | 
			
		||||
  initFocus: null | { caret: null | number };
 | 
			
		||||
  autoFocus: number | null;
 | 
			
		||||
  onChangeCurrentText: (text: string) => void;
 | 
			
		||||
  onDeleteEntry: (caret: number) => void;
 | 
			
		||||
  onBlur: () => void;
 | 
			
		||||
  handleChangeText: (caret: number) => void;
 | 
			
		||||
  onDepthChange: (delta: number) => void;
 | 
			
		||||
  onCreateEntry: () => void;
 | 
			
		||||
  onNodeFocused: () => void;
 | 
			
		||||
};
 | 
			
		||||
const Editor: React.FC<EditorProps> = ({
 | 
			
		||||
  text,
 | 
			
		||||
  onCreateEntry,
 | 
			
		||||
  initFocus,
 | 
			
		||||
  autoFocus,
 | 
			
		||||
  onChangeCurrentText,
 | 
			
		||||
  onDepthChange,
 | 
			
		||||
  onDeleteEntry,
 | 
			
		||||
  onNodeFocused,
 | 
			
		||||
  handleChangeText,
 | 
			
		||||
  onBlur,
 | 
			
		||||
}) => {
 | 
			
		||||
  const $editor = useRef<HTMLInputElement>(null);
 | 
			
		||||
  useOnOutsideClick($editor, true, () => onBlur(), null);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (autoFocus && $editor.current) {
 | 
			
		||||
      $editor.current.focus();
 | 
			
		||||
      $editor.current.setSelectionRange(autoFocus, autoFocus);
 | 
			
		||||
      onNodeFocused();
 | 
			
		||||
    }
 | 
			
		||||
  }, [autoFocus]);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (initFocus && $editor.current) {
 | 
			
		||||
      $editor.current.focus();
 | 
			
		||||
      if (initFocus.caret) {
 | 
			
		||||
        $editor.current.setSelectionRange(initFocus.caret ?? 0, initFocus.caret ?? 0);
 | 
			
		||||
function getCaretPosition(editableDiv: any) {
 | 
			
		||||
  let caretPos = 0;
 | 
			
		||||
  let sel: any = null;
 | 
			
		||||
  let range: any = null;
 | 
			
		||||
  if (window.getSelection) {
 | 
			
		||||
    sel = window.getSelection();
 | 
			
		||||
    if (sel && sel.rangeCount) {
 | 
			
		||||
      range = sel.getRangeAt(0);
 | 
			
		||||
      if (range.commonAncestorContainer.parentNode === editableDiv) {
 | 
			
		||||
        caretPos = range.endOffset;
 | 
			
		||||
      }
 | 
			
		||||
      onNodeFocused();
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
  return (
 | 
			
		||||
    <EntryContentEditor
 | 
			
		||||
      value={text}
 | 
			
		||||
      ref={$editor}
 | 
			
		||||
      onChange={e => {
 | 
			
		||||
        onChangeCurrentText(e.currentTarget.value);
 | 
			
		||||
      }}
 | 
			
		||||
      onKeyDown={e => {
 | 
			
		||||
        if (e.keyCode === 13) {
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          // onCreateEntry(parentID, position * 2);
 | 
			
		||||
          onCreateEntry();
 | 
			
		||||
          return;
 | 
			
		||||
        } else if (e.keyCode === 9) {
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          onDepthChange(e.shiftKey ? -1 : 1);
 | 
			
		||||
        } else if (e.keyCode === 8) {
 | 
			
		||||
          const caretPos = e.currentTarget.selectionEnd;
 | 
			
		||||
          if (caretPos === 0) {
 | 
			
		||||
            // handleChangeText.flush();
 | 
			
		||||
            // onDeleteEntry(depth, id, currentText, caretPos);
 | 
			
		||||
            onDeleteEntry(caretPos);
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
        } else if (e.key === 'z' && e.ctrlKey) {
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        handleChangeText(e.currentTarget.selectionEnd ?? 0);
 | 
			
		||||
        // setCaretPos(e.currentTarget.selectionEnd ?? 0);
 | 
			
		||||
        // handleChangeText();
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
  }
 | 
			
		||||
  return caretPos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EntryProps = {
 | 
			
		||||
  id: string;
 | 
			
		||||
@@ -107,37 +30,21 @@ type EntryProps = {
 | 
			
		||||
  isRoot?: boolean;
 | 
			
		||||
  selection: null | Array<{ id: string }>;
 | 
			
		||||
  draggedNodes: null | Array<string>;
 | 
			
		||||
  onNodeFocused: (id: string) => void;
 | 
			
		||||
  text: string;
 | 
			
		||||
  entries: Array<ItemElement>;
 | 
			
		||||
  onTextChange: (id: string, prex: string, next: string, caret: number) => void;
 | 
			
		||||
  onCancelDrag: () => void;
 | 
			
		||||
  autoFocus: null | { caret: null | number };
 | 
			
		||||
  onCreateEntry: (parent: string, nextPositon: number) => void;
 | 
			
		||||
  position: number;
 | 
			
		||||
  chain?: Array<string>;
 | 
			
		||||
  onHandleClick: (id: string) => void;
 | 
			
		||||
  onDepthChange: (id: string, parent: string, position: number, depth: number, depthDelta: number) => void;
 | 
			
		||||
  onDeleteEntry: (depth: number, id: string, text: string, caretPos: number) => void;
 | 
			
		||||
  depth?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Entry: React.FC<EntryProps> = ({
 | 
			
		||||
  id,
 | 
			
		||||
  text,
 | 
			
		||||
  parentID,
 | 
			
		||||
  isRoot = false,
 | 
			
		||||
  selection,
 | 
			
		||||
  onToggleCollapse,
 | 
			
		||||
  autoFocus,
 | 
			
		||||
  onStartSelect,
 | 
			
		||||
  onHandleClick,
 | 
			
		||||
  onTextChange,
 | 
			
		||||
  position,
 | 
			
		||||
  onNodeFocused,
 | 
			
		||||
  onDepthChange,
 | 
			
		||||
  onCreateEntry,
 | 
			
		||||
  onDeleteEntry,
 | 
			
		||||
  onCancelDrag,
 | 
			
		||||
  onStartDrag,
 | 
			
		||||
  collapsed = false,
 | 
			
		||||
@@ -149,46 +56,8 @@ const Entry: React.FC<EntryProps> = ({
 | 
			
		||||
  const $entry = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const $children = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const { setNodeDimensions, clearNodeDimensions } = useDrag();
 | 
			
		||||
  if (autoFocus) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const $snapshot = useRef<{ now: string; prev: string }>({ now: text, prev: text });
 | 
			
		||||
  const [currentText, setCurrentText] = useState(text);
 | 
			
		||||
  const [caretPos, setCaretPos] = useState(0);
 | 
			
		||||
  const $firstRun = useRef<boolean>(true);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if ($firstRun.current) {
 | 
			
		||||
      $firstRun.current = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    console.log('updating text');
 | 
			
		||||
    setCurrentText(text);
 | 
			
		||||
  }, [text]);
 | 
			
		||||
 | 
			
		||||
  const [editor, setEditor] = useState<{ open: boolean; caret: null | number }>({
 | 
			
		||||
    open: false,
 | 
			
		||||
    caret: null,
 | 
			
		||||
  });
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (autoFocus) setEditor({ open: true, caret: null });
 | 
			
		||||
  }, [autoFocus]);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    $snapshot.current.now = currentText;
 | 
			
		||||
  }, [currentText]);
 | 
			
		||||
  const handleChangeText = useCallback(
 | 
			
		||||
    _.debounce(() => {
 | 
			
		||||
      onTextChange(id, $snapshot.current.prev, $snapshot.current.now, caretPos);
 | 
			
		||||
      $snapshot.current.prev = $snapshot.current.now;
 | 
			
		||||
    }, 500),
 | 
			
		||||
    [],
 | 
			
		||||
  );
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isRoot) return;
 | 
			
		||||
    if (!visible) {
 | 
			
		||||
      clearNodeDimensions(id);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($entry && $entry.current) {
 | 
			
		||||
      setNodeDimensions(id, {
 | 
			
		||||
        entry: $entry,
 | 
			
		||||
@@ -198,7 +67,7 @@ const Entry: React.FC<EntryProps> = ({
 | 
			
		||||
    return () => {
 | 
			
		||||
      clearNodeDimensions(id);
 | 
			
		||||
    };
 | 
			
		||||
  }, [position, depth, entries, visible]);
 | 
			
		||||
  }, [position, depth, entries]);
 | 
			
		||||
  let showHandle = true;
 | 
			
		||||
  if (draggedNodes && draggedNodes.length === 1 && draggedNodes.find(c => c === id)) {
 | 
			
		||||
    showHandle = false;
 | 
			
		||||
@@ -207,170 +76,79 @@ const Entry: React.FC<EntryProps> = ({
 | 
			
		||||
  if (selection && selection.find(c => c.id === id)) {
 | 
			
		||||
    isSelected = true;
 | 
			
		||||
  }
 | 
			
		||||
  const renderMap: Array<number> = [];
 | 
			
		||||
  const renderer = {
 | 
			
		||||
    text(text: any) {
 | 
			
		||||
      const localId = renderMap.length;
 | 
			
		||||
      renderMap.push(text.length);
 | 
			
		||||
      return `<span id="${id}_${localId}">${text}</span>`;
 | 
			
		||||
    },
 | 
			
		||||
    codespan(text: any) {
 | 
			
		||||
      const localId = renderMap.length;
 | 
			
		||||
      renderMap.push(text.length + 2);
 | 
			
		||||
      return `<span class="markdown-code" id="${id}_${localId}">${text}</span>`;
 | 
			
		||||
    },
 | 
			
		||||
    strong(text: string) {
 | 
			
		||||
      const idx = parseInt(text.split('"')[1].split('_')[1]);
 | 
			
		||||
      renderMap[idx] += 4;
 | 
			
		||||
      return text.replace('<span', '<span class="markdown-strong"');
 | 
			
		||||
    },
 | 
			
		||||
    em(text: string) {
 | 
			
		||||
      const idx = parseInt(text.split('"')[1].split('_')[1]);
 | 
			
		||||
      renderMap[idx] += 2;
 | 
			
		||||
      return text.replace('<span', '<span class="markdown-em"');
 | 
			
		||||
    },
 | 
			
		||||
    del(text: string) {
 | 
			
		||||
      const idx = parseInt(text.split('"')[1].split('_')[1]);
 | 
			
		||||
      renderMap[idx] += 2;
 | 
			
		||||
      return text.replace('<span', '<span class="markdown-del"');
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  marked.use({ renderer });
 | 
			
		||||
 | 
			
		||||
  const handleMouseDown = useCallback(
 | 
			
		||||
    _.debounce((e: any) => {
 | 
			
		||||
      onStartDrag({ id, clientX: e.clientX, clientY: e.clientY });
 | 
			
		||||
    }, 100),
 | 
			
		||||
    [],
 | 
			
		||||
  );
 | 
			
		||||
  let onSaveTimer: any = null;
 | 
			
		||||
  const onSaveTimeout = 300;
 | 
			
		||||
  return (
 | 
			
		||||
    <VisibilitySensor
 | 
			
		||||
      onChange={v => {
 | 
			
		||||
        if (v) {
 | 
			
		||||
          setVisible(v);
 | 
			
		||||
        }
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <EntryWrapper isSelected={isSelected} isDragging={!showHandle}>
 | 
			
		||||
        {!isRoot && (
 | 
			
		||||
          <EntryContent>
 | 
			
		||||
            {entries.length !== 0 && (
 | 
			
		||||
              <ExpandButton onClick={() => onToggleCollapse(id, !collapsed)}>
 | 
			
		||||
                {collapsed ? <CaretRight width={20} height={20} /> : <CaretDown width={20} height={20} />}
 | 
			
		||||
              </ExpandButton>
 | 
			
		||||
            )}
 | 
			
		||||
            {showHandle && (
 | 
			
		||||
              <EntryHandle
 | 
			
		||||
                onMouseUp={() => {
 | 
			
		||||
                  handleMouseDown.cancel();
 | 
			
		||||
                  onHandleClick(id);
 | 
			
		||||
                }}
 | 
			
		||||
                onMouseDown={e => {
 | 
			
		||||
                  handleMouseDown(e);
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Dot width={18} height={18} />
 | 
			
		||||
              </EntryHandle>
 | 
			
		||||
            )}
 | 
			
		||||
            <EntryInnerContent
 | 
			
		||||
              onMouseDown={() => {
 | 
			
		||||
                onStartSelect({ id, depth });
 | 
			
		||||
    <EntryWrapper isSelected={isSelected} isDragging={!showHandle}>
 | 
			
		||||
      {!isRoot && (
 | 
			
		||||
        <EntryContent>
 | 
			
		||||
          {entries.length !== 0 && (
 | 
			
		||||
            <ExpandButton onClick={() => onToggleCollapse(id, !collapsed)}>
 | 
			
		||||
              {collapsed ? <CaretRight width={20} height={20} /> : <CaretDown width={20} height={20} />}
 | 
			
		||||
            </ExpandButton>
 | 
			
		||||
          )}
 | 
			
		||||
          {showHandle && (
 | 
			
		||||
            <EntryHandle
 | 
			
		||||
              onMouseUp={() => onCancelDrag()}
 | 
			
		||||
              onMouseDown={e => {
 | 
			
		||||
                onStartDrag({ id, clientX: e.clientX, clientY: e.clientY });
 | 
			
		||||
              }}
 | 
			
		||||
              ref={$entry}
 | 
			
		||||
            >
 | 
			
		||||
              {editor.open ? (
 | 
			
		||||
                <Editor
 | 
			
		||||
                  onDepthChange={delta => onDepthChange(id, parentID, depth, position, delta)}
 | 
			
		||||
                  onBlur={() => setEditor({ open: false, caret: null })}
 | 
			
		||||
                  onNodeFocused={() => onNodeFocused(id)}
 | 
			
		||||
                  autoFocus={autoFocus ? (autoFocus.caret ? autoFocus.caret : 0) : null}
 | 
			
		||||
                  initFocus={editor.open ? { caret: editor.caret } : null}
 | 
			
		||||
                  text={currentText}
 | 
			
		||||
                  onDeleteEntry={caret => {
 | 
			
		||||
                    handleChangeText.flush();
 | 
			
		||||
                    onDeleteEntry(depth, id, currentText, caret);
 | 
			
		||||
                  }}
 | 
			
		||||
                  onCreateEntry={() => {
 | 
			
		||||
                    onCreateEntry(parentID, position * 2);
 | 
			
		||||
                  }}
 | 
			
		||||
                  onChangeCurrentText={text => setCurrentText(text)}
 | 
			
		||||
                  handleChangeText={caret => {
 | 
			
		||||
                    setCaretPos(caret);
 | 
			
		||||
                    handleChangeText();
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <EntryContentDisplay
 | 
			
		||||
                  onClick={e => {
 | 
			
		||||
                    let offset = 0;
 | 
			
		||||
                    let textNode: any;
 | 
			
		||||
                    if (document.caretPositionFromPoint) {
 | 
			
		||||
                      // standard
 | 
			
		||||
                      const range = document.caretPositionFromPoint(e.pageX, e.pageY);
 | 
			
		||||
                      console.dir(range);
 | 
			
		||||
                      if (range) {
 | 
			
		||||
                        textNode = range.offsetNode;
 | 
			
		||||
                        offset = range.offset;
 | 
			
		||||
                      }
 | 
			
		||||
                    } else if (document.caretRangeFromPoint) {
 | 
			
		||||
                      // WebKit
 | 
			
		||||
                      const range = document.caretRangeFromPoint(e.pageX, e.pageY);
 | 
			
		||||
                      if (range) {
 | 
			
		||||
                        textNode = range.startContainer;
 | 
			
		||||
                        offset = range.startOffset;
 | 
			
		||||
                      }
 | 
			
		||||
              <Dot width={18} height={18} />
 | 
			
		||||
            </EntryHandle>
 | 
			
		||||
          )}
 | 
			
		||||
          <EntryInnerContent
 | 
			
		||||
            onMouseDown={() => {
 | 
			
		||||
              onStartSelect({ id, depth });
 | 
			
		||||
            }}
 | 
			
		||||
            onKeyDown={e => {
 | 
			
		||||
              if (e.key === 'z' && e.ctrlKey) {
 | 
			
		||||
                if ($entry && $entry.current) {
 | 
			
		||||
                  console.log(getCaretPosition($entry.current));
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                clearTimeout(onSaveTimer);
 | 
			
		||||
                if ($entry && $entry.current) {
 | 
			
		||||
                  onSaveTimer = setTimeout(() => {
 | 
			
		||||
                    if ($entry && $entry.current) {
 | 
			
		||||
                      console.log($entry.current.textContent);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    const id = textNode.parentNode.id.split('_');
 | 
			
		||||
                    const index = parseInt(id[1]);
 | 
			
		||||
                    let caret = offset;
 | 
			
		||||
                    for (let i = 0; i < index; i++) {
 | 
			
		||||
                      caret += renderMap[i];
 | 
			
		||||
                    }
 | 
			
		||||
                    setEditor({ open: true, caret });
 | 
			
		||||
                  }}
 | 
			
		||||
                  dangerouslySetInnerHTML={{ __html: marked.parseInline(text) }}
 | 
			
		||||
                />
 | 
			
		||||
              )}
 | 
			
		||||
            </EntryInnerContent>
 | 
			
		||||
          </EntryContent>
 | 
			
		||||
        )}
 | 
			
		||||
        {entries.length !== 0 && !collapsed && (
 | 
			
		||||
          <EntryChildren ref={$children} isRoot={isRoot}>
 | 
			
		||||
            {entries
 | 
			
		||||
              .sort((a, b) => a.position - b.position)
 | 
			
		||||
              .map(entry => (
 | 
			
		||||
                <Entry
 | 
			
		||||
                  onDeleteEntry={onDeleteEntry}
 | 
			
		||||
                  onHandleClick={onHandleClick}
 | 
			
		||||
                  onDepthChange={onDepthChange}
 | 
			
		||||
                  parentID={id}
 | 
			
		||||
                  key={entry.id}
 | 
			
		||||
                  onTextChange={onTextChange}
 | 
			
		||||
                  position={entry.position}
 | 
			
		||||
                  text={entry.text}
 | 
			
		||||
                  depth={depth + 1}
 | 
			
		||||
                  draggedNodes={draggedNodes}
 | 
			
		||||
                  collapsed={entry.collapsed}
 | 
			
		||||
                  id={entry.id}
 | 
			
		||||
                  autoFocus={entry.focus}
 | 
			
		||||
                  onNodeFocused={onNodeFocused}
 | 
			
		||||
                  onStartSelect={onStartSelect}
 | 
			
		||||
                  onStartDrag={onStartDrag}
 | 
			
		||||
                  onCancelDrag={onCancelDrag}
 | 
			
		||||
                  entries={entry.children ?? []}
 | 
			
		||||
                  chain={[...chain, id]}
 | 
			
		||||
                  selection={selection}
 | 
			
		||||
                  onToggleCollapse={onToggleCollapse}
 | 
			
		||||
                  onCreateEntry={onCreateEntry}
 | 
			
		||||
                />
 | 
			
		||||
              ))}
 | 
			
		||||
          </EntryChildren>
 | 
			
		||||
        )}
 | 
			
		||||
      </EntryWrapper>
 | 
			
		||||
    </VisibilitySensor>
 | 
			
		||||
                  }, onSaveTimeout);
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
            contentEditable
 | 
			
		||||
            ref={$entry}
 | 
			
		||||
          >
 | 
			
		||||
            {`${id.toString()} - ${position}`}
 | 
			
		||||
          </EntryInnerContent>
 | 
			
		||||
        </EntryContent>
 | 
			
		||||
      )}
 | 
			
		||||
      {entries.length !== 0 && !collapsed && (
 | 
			
		||||
        <EntryChildren ref={$children} isRoot={isRoot}>
 | 
			
		||||
          {entries
 | 
			
		||||
            .sort((a, b) => a.position - b.position)
 | 
			
		||||
            .map(entry => (
 | 
			
		||||
              <Entry
 | 
			
		||||
                parentID={id}
 | 
			
		||||
                key={entry.id}
 | 
			
		||||
                position={entry.position}
 | 
			
		||||
                depth={depth + 1}
 | 
			
		||||
                draggedNodes={draggedNodes}
 | 
			
		||||
                collapsed={entry.collapsed}
 | 
			
		||||
                id={entry.id}
 | 
			
		||||
                onStartSelect={onStartSelect}
 | 
			
		||||
                onStartDrag={onStartDrag}
 | 
			
		||||
                onCancelDrag={onCancelDrag}
 | 
			
		||||
                entries={entry.children ?? []}
 | 
			
		||||
                chain={[...chain, id]}
 | 
			
		||||
                selection={selection}
 | 
			
		||||
                onToggleCollapse={onToggleCollapse}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
        </EntryChildren>
 | 
			
		||||
      )}
 | 
			
		||||
    </EntryWrapper>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ export const EntryWrapper = styled.div<{ isDragging: boolean; isSelected: boolea
 | 
			
		||||
        right: -5px;
 | 
			
		||||
        bottom: -2px;
 | 
			
		||||
        left: -5px;
 | 
			
		||||
        background-color: ${mixin.rgba(props.theme.colors.primary, 0.75)};
 | 
			
		||||
        background-color: rgba(${props.theme.colors.primary}, 0.75);
 | 
			
		||||
      }
 | 
			
		||||
    `}
 | 
			
		||||
`;
 | 
			
		||||
@@ -40,7 +40,7 @@ export const EntryChildren = styled.div<{ isRoot: boolean }>`
 | 
			
		||||
    css`
 | 
			
		||||
      margin-left: 10px;
 | 
			
		||||
      padding-left: 25px;
 | 
			
		||||
      border-left: 1px solid ${mixin.rgba(props.theme.colors.text.primary, 0.6)};
 | 
			
		||||
      border-left: 1px solid rgba(${props.theme.colors.text.primary}, 0.6);
 | 
			
		||||
    `}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -87,84 +87,14 @@ export const EntryHandle = styled.div`
 | 
			
		||||
  top: 7px;
 | 
			
		||||
  width: 18px;
 | 
			
		||||
  height: 18px;
 | 
			
		||||
  color: ${p => p.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${p => p.theme.colors.text.primary});
 | 
			
		||||
  border-radius: 9px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${p => p.theme.colors.primary};
 | 
			
		||||
    background: rgba(${p => p.theme.colors.primary});
 | 
			
		||||
  }
 | 
			
		||||
  svg {
 | 
			
		||||
    fill: ${p => p.theme.colors.text.primary};
 | 
			
		||||
    stroke: ${p => p.theme.colors.text.primary};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
export const EntryContentDisplay = styled.div`
 | 
			
		||||
  display: inline-flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  white-space: pre-wrap;
 | 
			
		||||
  background: none;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  min-height: 24px;
 | 
			
		||||
  overflow-wrap: break-word;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: ${p => p.theme.colors.text.primary};
 | 
			
		||||
  user-select: none;
 | 
			
		||||
 | 
			
		||||
  cursor: text;
 | 
			
		||||
  .markdown-del {
 | 
			
		||||
    text-decoration: line-through;
 | 
			
		||||
  }
 | 
			
		||||
  .markdown-code {
 | 
			
		||||
    margin-top: -4px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    line-height: 19px;
 | 
			
		||||
    color: ${props => props.theme.colors.primary};
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
    padding: 4px 5px 0;
 | 
			
		||||
    font-family: 'Consolas', Courier, monospace;
 | 
			
		||||
    background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
  }
 | 
			
		||||
  .markdown-em {
 | 
			
		||||
    margin-top: -4px;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
  }
 | 
			
		||||
  .markdown-strong {
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
  &:focus {
 | 
			
		||||
    outline: 0;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const EntryContentEditor = styled.input`
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  white-space: pre-wrap;
 | 
			
		||||
  background: none;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  min-height: 24px;
 | 
			
		||||
  overflow-wrap: break-word;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  user-select: text;
 | 
			
		||||
  color: ${p => p.theme.colors.text.primary};
 | 
			
		||||
  &::selection {
 | 
			
		||||
    background: #a49de8;
 | 
			
		||||
  }
 | 
			
		||||
  &:focus {
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    fill: rgba(${p => p.theme.colors.text.primary});
 | 
			
		||||
    stroke: rgba(${p => p.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -172,15 +102,12 @@ export const EntryInnerContent = styled.div`
 | 
			
		||||
  padding-top: 4px;
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  white-space: pre-wrap;
 | 
			
		||||
  background: none;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  min-height: 24px;
 | 
			
		||||
  overflow-wrap: break-word;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  user-select: text;
 | 
			
		||||
  color: ${p => p.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${p => p.theme.colors.text.primary});
 | 
			
		||||
  &::selection {
 | 
			
		||||
    background: #a49de8;
 | 
			
		||||
  }
 | 
			
		||||
@@ -197,7 +124,7 @@ export const DragDebugWrapper = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const DragIndicatorBar = styled.div<{ left: number; top: number; width: number }>`
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: ${props => props.width}px;
 | 
			
		||||
  top: ${props => props.top}px;
 | 
			
		||||
  left: ${props => props.left}px;
 | 
			
		||||
@@ -228,33 +155,10 @@ export const EntryContent = styled.div`
 | 
			
		||||
  padding-left: 524px;
 | 
			
		||||
 | 
			
		||||
  &:hover ${ExpandButton} svg {
 | 
			
		||||
    fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
    fill: rgb(${props => props.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const PageContainer = styled.div`
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
  overflow: scroll;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const PageName = styled.div`
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin-left: -100px;
 | 
			
		||||
  padding-left: 100px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  border-color: rgb(170, 170, 170);
 | 
			
		||||
  font-size: 26px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const PageNameContent = styled.div`
 | 
			
		||||
  white-space: pre-wrap;
 | 
			
		||||
  line-height: 34px;
 | 
			
		||||
  min-height: 34px;
 | 
			
		||||
  overflow-wrap: break-word;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  user-select: text;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const PageNameText = styled.span``;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,6 @@ import {
 | 
			
		||||
  EntryContent,
 | 
			
		||||
  RootWrapper,
 | 
			
		||||
  EntryHandle,
 | 
			
		||||
  PageNameContent,
 | 
			
		||||
  PageNameText,
 | 
			
		||||
  PageName,
 | 
			
		||||
} from './Styles';
 | 
			
		||||
import {
 | 
			
		||||
  transformToTree,
 | 
			
		||||
@@ -36,49 +33,14 @@ import {
 | 
			
		||||
  getNodeOver,
 | 
			
		||||
  getCorrectNode,
 | 
			
		||||
  findCommonParent,
 | 
			
		||||
  getNodeAbove,
 | 
			
		||||
  findNodeAbove,
 | 
			
		||||
} from './utils';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
 | 
			
		||||
enum CommandType {
 | 
			
		||||
  MOVE,
 | 
			
		||||
  MERGE,
 | 
			
		||||
  CHANGE_TEXT,
 | 
			
		||||
  DELETE,
 | 
			
		||||
  CREATE,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MoveData = {
 | 
			
		||||
  prev: { position: number; parent: string | null };
 | 
			
		||||
  next: { position: number; parent: string | null };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ChangeTextData = {
 | 
			
		||||
  node: {
 | 
			
		||||
    id: string;
 | 
			
		||||
    parentID: string;
 | 
			
		||||
    position: number;
 | 
			
		||||
  };
 | 
			
		||||
  caret: number;
 | 
			
		||||
  prev: string;
 | 
			
		||||
  next: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type DeleteData = {
 | 
			
		||||
  node: {
 | 
			
		||||
    id: string;
 | 
			
		||||
    parentID: string;
 | 
			
		||||
    position: number;
 | 
			
		||||
    text: string;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type OutlineCommand = {
 | 
			
		||||
  nodes: Array<{
 | 
			
		||||
    id: string;
 | 
			
		||||
    type: CommandType;
 | 
			
		||||
    data: MoveData | DeleteData | ChangeTextData;
 | 
			
		||||
    prev: { position: number; parent: string | null };
 | 
			
		||||
    next: { position: number; parent: string | null };
 | 
			
		||||
  }>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -87,49 +49,19 @@ type ItemCollapsed = {
 | 
			
		||||
  collapsed: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function generateItems(c: number) {
 | 
			
		||||
  const items: Array<ItemElement> = [];
 | 
			
		||||
  for (let i = 0; i < c; i++) {
 | 
			
		||||
    items.push({
 | 
			
		||||
      collapsed: false,
 | 
			
		||||
      focus: null,
 | 
			
		||||
      id: `entry-gen-${i}`,
 | 
			
		||||
      text: `entry-gen-${i}`,
 | 
			
		||||
      parent: 'root',
 | 
			
		||||
      position: 4096 * (6 + i),
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  return items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const listItems: Array<ItemElement> = [
 | 
			
		||||
  { id: 'root', text: '', position: 4096, parent: null, collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-1', text: 'entry-1', position: 4096, parent: 'root', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-1-3', text: 'entry-1-3', position: 4096 * 3, parent: 'entry-1', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-1-3-1', text: 'entry-1-3-1', position: 4096, parent: 'entry-1-3', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-1-3-2', text: 'entry-1-3-2', position: 4096 * 2, parent: 'entry-1-3', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-1-3-3', text: 'entry-1-3-3', position: 4096 * 3, parent: 'entry-1-3', collapsed: false, focus: null },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'entry-1-3-3-1',
 | 
			
		||||
    text: '*Hello!* I am `doing super` well ~how~ are **you**?',
 | 
			
		||||
    position: 4096 * 1,
 | 
			
		||||
    parent: 'entry-1-3-3',
 | 
			
		||||
    collapsed: false,
 | 
			
		||||
    focus: null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: 'entry-1-3-3-1-1',
 | 
			
		||||
    text: 'entry-1-3-3-1-1',
 | 
			
		||||
    position: 4096 * 1,
 | 
			
		||||
    parent: 'entry-1-3-3-1',
 | 
			
		||||
    collapsed: false,
 | 
			
		||||
    focus: null,
 | 
			
		||||
  },
 | 
			
		||||
  { id: 'entry-2', text: 'entry-2', position: 4096 * 2, parent: 'root', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-3', text: 'entry-3', position: 4096 * 3, parent: 'root', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-4', text: 'entry-4', position: 4096 * 4, parent: 'root', collapsed: false, focus: null },
 | 
			
		||||
  { id: 'entry-5', text: 'entry-5', position: 4096 * 5, parent: 'root', collapsed: false, focus: null },
 | 
			
		||||
  ...generateItems(100),
 | 
			
		||||
  { id: 'root', position: 4096, parent: null, collapsed: false },
 | 
			
		||||
  { id: 'entry-1', position: 4096, parent: 'root', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3', position: 4096 * 3, parent: 'entry-1', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3_1', position: 4096, parent: 'entry-1_3', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3_2', position: 4096 * 2, parent: 'entry-1_3', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3_3', position: 4096 * 3, parent: 'entry-1_3', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3_3_1', position: 4096 * 1, parent: 'entry-1_3_3', collapsed: false },
 | 
			
		||||
  { id: 'entry-1_3_3_1_1', position: 4096 * 1, parent: 'entry-1_3_3_1', collapsed: false },
 | 
			
		||||
  { id: 'entry-2', position: 4096 * 2, parent: 'root', collapsed: false },
 | 
			
		||||
  { id: 'entry-3', position: 4096 * 3, parent: 'root', collapsed: false },
 | 
			
		||||
  { id: 'entry-4', position: 4096 * 4, parent: 'root', collapsed: false },
 | 
			
		||||
  { id: 'entry-5', position: 4096 * 5, parent: 'root', collapsed: false },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const Outline: React.FC = () => {
 | 
			
		||||
@@ -201,11 +133,7 @@ const Outline: React.FC = () => {
 | 
			
		||||
      }
 | 
			
		||||
      const parent = curParent ?? 'root';
 | 
			
		||||
      outline.current.published.set(id, parent ?? 'root');
 | 
			
		||||
      const foundDepth = findNodeDepth(outline.current.published, id);
 | 
			
		||||
      if (foundDepth === null) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const { depth, ancestors } = foundDepth;
 | 
			
		||||
      const { depth, ancestors } = findNodeDepth(outline.current.published, id);
 | 
			
		||||
      const collapsedParent = ancestors.slice(0, -1).find(a => collapsedMap.get(a));
 | 
			
		||||
      if (collapsedParent) {
 | 
			
		||||
        continue;
 | 
			
		||||
@@ -256,29 +184,9 @@ const Outline: React.FC = () => {
 | 
			
		||||
          produce(prevItems, draftItems => {
 | 
			
		||||
            currentCommand.nodes.forEach(node => {
 | 
			
		||||
              const idx = prevItems.findIndex(c => c.id === node.id);
 | 
			
		||||
              if (node.type === CommandType.MOVE) {
 | 
			
		||||
                if (idx === -1) return;
 | 
			
		||||
                const data = node.data as MoveData;
 | 
			
		||||
                draftItems[idx].parent = data.prev.parent;
 | 
			
		||||
                draftItems[idx].position = data.prev.position;
 | 
			
		||||
              } else if (node.type === CommandType.CHANGE_TEXT) {
 | 
			
		||||
                if (idx === -1) return;
 | 
			
		||||
                const data = node.data as ChangeTextData;
 | 
			
		||||
                draftItems[idx] = produce(prevItems[idx], draftItem => {
 | 
			
		||||
                  draftItem.text = data.prev;
 | 
			
		||||
                  draftItem.focus = { caret: data.caret };
 | 
			
		||||
                });
 | 
			
		||||
              } else if (node.type === CommandType.DELETE) {
 | 
			
		||||
                const data = node.data as DeleteData;
 | 
			
		||||
                draftItems.push({
 | 
			
		||||
                  id: data.node.id,
 | 
			
		||||
                  position: data.node.position,
 | 
			
		||||
                  parent: data.node.parentID,
 | 
			
		||||
                  text: '',
 | 
			
		||||
                  focus: { caret: null },
 | 
			
		||||
                  children: [],
 | 
			
		||||
                  collapsed: false,
 | 
			
		||||
                });
 | 
			
		||||
              if (idx !== -1) {
 | 
			
		||||
                draftItems[idx].parent = node.prev.parent;
 | 
			
		||||
                draftItems[idx].position = node.prev.position;
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
            outlineHistory.current.current--;
 | 
			
		||||
@@ -293,11 +201,8 @@ const Outline: React.FC = () => {
 | 
			
		||||
            currentCommand.nodes.forEach(node => {
 | 
			
		||||
              const idx = prevItems.findIndex(c => c.id === node.id);
 | 
			
		||||
              if (idx !== -1) {
 | 
			
		||||
                if (node.type === CommandType.MOVE) {
 | 
			
		||||
                  const data = node.data as MoveData;
 | 
			
		||||
                  draftItems[idx].parent = data.next.parent;
 | 
			
		||||
                  draftItems[idx].position = data.next.position;
 | 
			
		||||
                }
 | 
			
		||||
                draftItems[idx].parent = node.next.parent;
 | 
			
		||||
                draftItems[idx].position = node.next.position;
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
            outlineHistory.current.current++;
 | 
			
		||||
@@ -403,8 +308,6 @@ const Outline: React.FC = () => {
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const $page = useRef<HTMLDivElement>(null);
 | 
			
		||||
  const $pageName = useRef<HTMLDivElement>(null);
 | 
			
		||||
  if (!root) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
@@ -428,9 +331,13 @@ const Outline: React.FC = () => {
 | 
			
		||||
                  listPosition = lastChild.position * 2.0;
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                console.log(zone.above);
 | 
			
		||||
                console.log(zone.below);
 | 
			
		||||
                const correctNode = getCorrectNode(outline.current, zone.above ? zone.above.node : null, depth);
 | 
			
		||||
                console.log(correctNode);
 | 
			
		||||
                const listAbove = validateDepth(correctNode, depth);
 | 
			
		||||
                const listBelow = validateDepth(zone.below ? zone.below.node : null, depth);
 | 
			
		||||
                console.log(listAbove, listBelow);
 | 
			
		||||
                if (listAbove && listBelow) {
 | 
			
		||||
                  listPosition = (listAbove.position + listBelow.position) / 2.0;
 | 
			
		||||
                } else if (listAbove && !listBelow) {
 | 
			
		||||
@@ -471,192 +378,10 @@ const Outline: React.FC = () => {
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <>
 | 
			
		||||
          <PageContainer ref={$page}>
 | 
			
		||||
          <PageContainer>
 | 
			
		||||
            <PageContent>
 | 
			
		||||
              <RootWrapper ref={$content}>
 | 
			
		||||
                <PageName>
 | 
			
		||||
                  <PageNameContent ref={$pageName}>
 | 
			
		||||
                    <PageNameText>entry-1-3-1</PageNameText>
 | 
			
		||||
                  </PageNameContent>
 | 
			
		||||
                </PageName>
 | 
			
		||||
                <Entry
 | 
			
		||||
                  onDepthChange={(id, parentID, position, depth, depthDelta) => {
 | 
			
		||||
                    if (depthDelta === -1) {
 | 
			
		||||
                      const parentRelation = outline.current.relationships.get(parentID);
 | 
			
		||||
                      if (parentRelation) {
 | 
			
		||||
                        const nodeIdx = parentRelation.children
 | 
			
		||||
                          .sort((a, b) => a.position - b.position)
 | 
			
		||||
                          .findIndex(c => c.id === id);
 | 
			
		||||
                        if (parentRelation.children.length !== 0) {
 | 
			
		||||
                          const grandparent = outline.current.published.get(parentID);
 | 
			
		||||
                          if (grandparent) {
 | 
			
		||||
                            const grandparentNode = outline.current.relationships.get(grandparent);
 | 
			
		||||
                            if (grandparentNode) {
 | 
			
		||||
                              const parents = grandparentNode.children.sort((a, b) => a.position - b.position);
 | 
			
		||||
                              const parentIdx = parents.findIndex(c => c.id === parentID);
 | 
			
		||||
                              if (parentIdx === -1) return;
 | 
			
		||||
                              let position = parents[parentIdx].position * 2;
 | 
			
		||||
                              const nextParent = parents[parentIdx + 1];
 | 
			
		||||
                              if (nextParent) {
 | 
			
		||||
                                position = (parents[parentIdx].position + nextParent.position) / 2.0;
 | 
			
		||||
                              }
 | 
			
		||||
                              setItems(prevItems =>
 | 
			
		||||
                                produce(prevItems, draftItems => {
 | 
			
		||||
                                  const idx = prevItems.findIndex(c => c.id === id);
 | 
			
		||||
                                  draftItems[idx] = produce(prevItems[idx], draftItem => {
 | 
			
		||||
                                    draftItem.parent = grandparent;
 | 
			
		||||
                                    draftItem.position = position;
 | 
			
		||||
                                    draftItem.focus = { caret: 0 };
 | 
			
		||||
                                  });
 | 
			
		||||
                                }),
 | 
			
		||||
                              );
 | 
			
		||||
                            }
 | 
			
		||||
                          }
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
                    } else {
 | 
			
		||||
                      const parent = outline.current.relationships.get(parentID);
 | 
			
		||||
                      if (parent) {
 | 
			
		||||
                        const nodeIdx = parent.children
 | 
			
		||||
                          .sort((a, b) => a.position - b.position)
 | 
			
		||||
                          .findIndex(c => c.id === id);
 | 
			
		||||
                        const aboveNode = parent.children[nodeIdx - 1];
 | 
			
		||||
                        if (aboveNode) {
 | 
			
		||||
                          const aboveNodeRelations = outline.current.relationships.get(aboveNode.id);
 | 
			
		||||
                          let position = 65535;
 | 
			
		||||
                          if (aboveNodeRelations) {
 | 
			
		||||
                            const children = aboveNodeRelations.children.sort((a, b) => a.position - b.position);
 | 
			
		||||
                            if (children.length !== 0) {
 | 
			
		||||
                              position = children[children.length - 1].position * 2;
 | 
			
		||||
                            }
 | 
			
		||||
                          }
 | 
			
		||||
                          setItems(prevItems =>
 | 
			
		||||
                            produce(prevItems, draftItems => {
 | 
			
		||||
                              const idx = prevItems.findIndex(c => c.id === id);
 | 
			
		||||
                              draftItems[idx] = produce(prevItems[idx], draftItem => {
 | 
			
		||||
                                draftItem.parent = aboveNode.id;
 | 
			
		||||
                                draftItem.position = position;
 | 
			
		||||
                                draftItem.focus = { caret: 0 };
 | 
			
		||||
                              });
 | 
			
		||||
                            }),
 | 
			
		||||
                          );
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  }}
 | 
			
		||||
                  onTextChange={(id, prev, next, caret) => {
 | 
			
		||||
                    outlineHistory.current.current += 1;
 | 
			
		||||
                    const data: ChangeTextData = {
 | 
			
		||||
                      node: {
 | 
			
		||||
                        id,
 | 
			
		||||
                        position: 0,
 | 
			
		||||
                        parentID: '',
 | 
			
		||||
                      },
 | 
			
		||||
                      caret,
 | 
			
		||||
                      prev,
 | 
			
		||||
                      next,
 | 
			
		||||
                    };
 | 
			
		||||
                    const command: OutlineCommand = {
 | 
			
		||||
                      nodes: [
 | 
			
		||||
                        {
 | 
			
		||||
                          id,
 | 
			
		||||
                          type: CommandType.CHANGE_TEXT,
 | 
			
		||||
                          data,
 | 
			
		||||
                        },
 | 
			
		||||
                      ],
 | 
			
		||||
                    };
 | 
			
		||||
                    outlineHistory.current.commands[outlineHistory.current.current] = command;
 | 
			
		||||
                    if (outlineHistory.current.commands[outlineHistory.current.current + 1]) {
 | 
			
		||||
                      outlineHistory.current.commands.splice(outlineHistory.current.current + 1);
 | 
			
		||||
                    }
 | 
			
		||||
                    setItems(prevItems =>
 | 
			
		||||
                      produce(prevItems, draftItems => {
 | 
			
		||||
                        const idx = prevItems.findIndex(c => c.id === id);
 | 
			
		||||
                        if (idx !== -1) {
 | 
			
		||||
                          draftItems[idx] = produce(prevItems[idx], draftItem => {
 | 
			
		||||
                            draftItem.text = next;
 | 
			
		||||
                          });
 | 
			
		||||
                        }
 | 
			
		||||
                      }),
 | 
			
		||||
                    );
 | 
			
		||||
                  }}
 | 
			
		||||
                  text=""
 | 
			
		||||
                  autoFocus={null}
 | 
			
		||||
                  onDeleteEntry={(depth, id, text, caretPos) => {
 | 
			
		||||
                    const nodeDepth = outline.current.nodes.get(depth);
 | 
			
		||||
                    if (nodeDepth) {
 | 
			
		||||
                      const node = nodeDepth.get(id);
 | 
			
		||||
                      if (node) {
 | 
			
		||||
                        const nodeAbove = findNodeAbove(outline.current, depth, node);
 | 
			
		||||
                        setItems(prevItems => {
 | 
			
		||||
                          return produce(prevItems, draftItems => {
 | 
			
		||||
                            draftItems = prevItems.filter(c => c.id !== id);
 | 
			
		||||
                            const idx = prevItems.findIndex(c => c.id === nodeAbove?.id);
 | 
			
		||||
                            if (idx !== -1) {
 | 
			
		||||
                              draftItems[idx] = produce(prevItems[idx], draftItem => {
 | 
			
		||||
                                draftItem.focus = { caret: draftItem.text.length };
 | 
			
		||||
                                const cType = CommandType.DELETE;
 | 
			
		||||
                                const data: DeleteData = {
 | 
			
		||||
                                  node: {
 | 
			
		||||
                                    id,
 | 
			
		||||
                                    position: node.position,
 | 
			
		||||
                                    parentID: node.parent,
 | 
			
		||||
                                    text: '',
 | 
			
		||||
                                  },
 | 
			
		||||
                                };
 | 
			
		||||
                                if (text !== '') {
 | 
			
		||||
                                  draftItem.text += text;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                const command: OutlineCommand = {
 | 
			
		||||
                                  nodes: [
 | 
			
		||||
                                    {
 | 
			
		||||
                                      id,
 | 
			
		||||
                                      type: cType,
 | 
			
		||||
                                      data,
 | 
			
		||||
                                    },
 | 
			
		||||
                                  ],
 | 
			
		||||
                                };
 | 
			
		||||
                                outlineHistory.current.current += 1;
 | 
			
		||||
                                outlineHistory.current.commands[outlineHistory.current.current] = command;
 | 
			
		||||
                                if (outlineHistory.current.commands[outlineHistory.current.current + 1]) {
 | 
			
		||||
                                  outlineHistory.current.commands.splice(outlineHistory.current.current + 1);
 | 
			
		||||
                                }
 | 
			
		||||
                              });
 | 
			
		||||
                            }
 | 
			
		||||
                            return draftItems;
 | 
			
		||||
                          });
 | 
			
		||||
                        });
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  }}
 | 
			
		||||
                  onCreateEntry={(parent, position) => {
 | 
			
		||||
                    setItems(prevItems =>
 | 
			
		||||
                      produce(prevItems, draftItems => {
 | 
			
		||||
                        draftItems.push({
 | 
			
		||||
                          id: '' + Math.random(),
 | 
			
		||||
                          collapsed: false,
 | 
			
		||||
                          position,
 | 
			
		||||
                          text: '',
 | 
			
		||||
                          focus: {
 | 
			
		||||
                            caret: null,
 | 
			
		||||
                          },
 | 
			
		||||
                          parent,
 | 
			
		||||
                          children: [],
 | 
			
		||||
                        });
 | 
			
		||||
                      }),
 | 
			
		||||
                    );
 | 
			
		||||
                  }}
 | 
			
		||||
                  onNodeFocused={id => {
 | 
			
		||||
                    setItems(prevItems =>
 | 
			
		||||
                      produce(prevItems, draftItems => {
 | 
			
		||||
                        const idx = draftItems.findIndex(c => c.id === id);
 | 
			
		||||
                        draftItems[idx] = produce(draftItems[idx], draftItem => {
 | 
			
		||||
                          draftItem.focus = null;
 | 
			
		||||
                        });
 | 
			
		||||
                      }),
 | 
			
		||||
                    );
 | 
			
		||||
                  }}
 | 
			
		||||
                  onStartSelect={({ id, depth }) => {
 | 
			
		||||
                    setSelection(null);
 | 
			
		||||
                    setSelecting({ isSelecting: true, node: { id, depth } });
 | 
			
		||||
@@ -682,7 +407,6 @@ const Outline: React.FC = () => {
 | 
			
		||||
                    setImpact(null);
 | 
			
		||||
                    setDragging({ show: false, draggedNodes: null, initialPos: { x: 0, y: 0 } });
 | 
			
		||||
                  }}
 | 
			
		||||
                  onHandleClick={id => {}}
 | 
			
		||||
                  onStartDrag={e => {
 | 
			
		||||
                    if (e.id !== 'root') {
 | 
			
		||||
                      if (selectRef.current.hasSelection && selection && selection.nodes.find(c => c.id === e.id)) {
 | 
			
		||||
@@ -706,7 +430,6 @@ const Outline: React.FC = () => {
 | 
			
		||||
            <Dragger
 | 
			
		||||
              container={$content}
 | 
			
		||||
              initialPos={dragging.initialPos}
 | 
			
		||||
              pageRef={$page}
 | 
			
		||||
              draggedNodes={{ nodes: dragging.draggedNodes, first: selection ? selection.first : null }}
 | 
			
		||||
              isDragging={dragging.show}
 | 
			
		||||
              onDragEnd={() => {
 | 
			
		||||
@@ -741,16 +464,13 @@ const Outline: React.FC = () => {
 | 
			
		||||
                              const curDragging = itemsPrev.findIndex(i => i.id === n);
 | 
			
		||||
                              command.nodes.push({
 | 
			
		||||
                                id: n,
 | 
			
		||||
                                type: CommandType.MOVE,
 | 
			
		||||
                                data: {
 | 
			
		||||
                                  prev: {
 | 
			
		||||
                                    parent: draftItems[curDragging].parent,
 | 
			
		||||
                                    position: draftItems[curDragging].position,
 | 
			
		||||
                                  },
 | 
			
		||||
                                  next: {
 | 
			
		||||
                                    parent: parentID,
 | 
			
		||||
                                    position: listPosition,
 | 
			
		||||
                                  },
 | 
			
		||||
                                prev: {
 | 
			
		||||
                                  parent: draftItems[curDragging].parent,
 | 
			
		||||
                                  position: draftItems[curDragging].position,
 | 
			
		||||
                                },
 | 
			
		||||
                                next: {
 | 
			
		||||
                                  parent: parentID,
 | 
			
		||||
                                  position: listPosition,
 | 
			
		||||
                                },
 | 
			
		||||
                              });
 | 
			
		||||
                              draftItems[curDragging].parent = parentID;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,12 @@ import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
export function getCorrectNode(data: OutlineData, node: OutlineNode | null, depth: number) {
 | 
			
		||||
  if (node) {
 | 
			
		||||
    console.log(depth, node);
 | 
			
		||||
    if (depth === node.depth) {
 | 
			
		||||
      return node;
 | 
			
		||||
    }
 | 
			
		||||
    const parent = node.ancestors[depth];
 | 
			
		||||
    console.log('parent', parent);
 | 
			
		||||
    if (parent) {
 | 
			
		||||
      const parentNode = data.relationships.get(parent);
 | 
			
		||||
      if (parentNode) {
 | 
			
		||||
@@ -41,6 +43,7 @@ export function getNodeAbove(node: OutlineNode, startingParent: RelationshipChil
 | 
			
		||||
            position: parentNode.position,
 | 
			
		||||
            children: parentNode.children,
 | 
			
		||||
          };
 | 
			
		||||
          console.log('node above', nodeAbove);
 | 
			
		||||
        }
 | 
			
		||||
        hasChildren = false;
 | 
			
		||||
        continue;
 | 
			
		||||
@@ -62,6 +65,7 @@ export function getNodeAbove(node: OutlineNode, startingParent: RelationshipChil
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  console.log('final node above', nodeAbove);
 | 
			
		||||
  return nodeAbove;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -111,6 +115,7 @@ export function getTargetDepth(mouseX: number, handleLeft: number, availableDept
 | 
			
		||||
  let curDepth = availableDepths.max - 1;
 | 
			
		||||
  for (let x = availableDepths.min; x < availableDepths.max; x++) {
 | 
			
		||||
    const breakpoint = handleLeft - x * 35;
 | 
			
		||||
    // console.log(`mouseX=${mouseX} breakpoint=${breakpoint} x=${x} curDepth=${curDepth}`);
 | 
			
		||||
    if (mouseX > breakpoint) {
 | 
			
		||||
      return curDepth;
 | 
			
		||||
    }
 | 
			
		||||
@@ -132,6 +137,10 @@ export function findNextDraggable(pos: { x: number; y: number }, outline: Outlin
 | 
			
		||||
    const target = dimensions ? getDimensions(dimensions.entry) : null;
 | 
			
		||||
    const children = dimensions ? getDimensions(dimensions.children) : null;
 | 
			
		||||
    if (target) {
 | 
			
		||||
      console.log(
 | 
			
		||||
        `[${id}] ${pos.y} <= ${target.bottom} = ${pos.y <= target.bottom} / ${pos.y} >= ${target.top} = ${pos.y >=
 | 
			
		||||
          target.top}`,
 | 
			
		||||
      );
 | 
			
		||||
      if (pos.y <= target.bottom && pos.y >= target.top) {
 | 
			
		||||
        const middlePoint = target.top + target.height / 2;
 | 
			
		||||
        const position: ImpactPosition = pos.y > middlePoint ? 'after' : 'before';
 | 
			
		||||
@@ -143,6 +152,10 @@ export function findNextDraggable(pos: { x: number; y: number }, outline: Outlin
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (children) {
 | 
			
		||||
      console.log(
 | 
			
		||||
        `[${id}] ${pos.y} <= ${children.bottom} = ${pos.y <= children.bottom} / ${pos.y} >= ${children.top} = ${pos.y >=
 | 
			
		||||
          children.top}`,
 | 
			
		||||
      );
 | 
			
		||||
      if (pos.y <= children.bottom && pos.y >= children.top) {
 | 
			
		||||
        const position: ImpactPosition = 'after';
 | 
			
		||||
        return { found: false, node, position };
 | 
			
		||||
@@ -194,7 +207,7 @@ export function findNodeDepth(published: Map<string, string>, id: string) {
 | 
			
		||||
        throw new Error('node depth breaker was thrown');
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      return null;
 | 
			
		||||
      throw new Error('unable to find nextID');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return { depth, ancestors };
 | 
			
		||||
@@ -346,64 +359,3 @@ export function getLastChildInBranch(outline: OutlineData, lastParentNode: Outli
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCaretPosition(editableDiv: any) {
 | 
			
		||||
  /*
 | 
			
		||||
  let caretPos = 0;
 | 
			
		||||
  let sel: any = null;
 | 
			
		||||
  let range: any = null;
 | 
			
		||||
  if (window.getSelection) {
 | 
			
		||||
    sel = window.getSelection();
 | 
			
		||||
    if (sel && sel.rangeCount) {
 | 
			
		||||
      range = sel.getRangeAt(0);
 | 
			
		||||
      if (range.commonAncestorContainer.parentNode === editableDiv.current) {
 | 
			
		||||
        caretPos = range.endOffset;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  */
 | 
			
		||||
  return editableDiv.selectionEnd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createRange(node: any, chars: any, range: any) {
 | 
			
		||||
  if (!range) {
 | 
			
		||||
    range = document.createRange();
 | 
			
		||||
    range.selectNode(node);
 | 
			
		||||
    range.setStart(node, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (chars.count === 0) {
 | 
			
		||||
    range.setEnd(node, chars.count);
 | 
			
		||||
  } else if (node && chars.count > 0) {
 | 
			
		||||
    if (node.nodeType === Node.TEXT_NODE) {
 | 
			
		||||
      if (node.textContent.length < chars.count) {
 | 
			
		||||
        chars.count -= node.textContent.length;
 | 
			
		||||
      } else {
 | 
			
		||||
        range.setEnd(node, chars.count);
 | 
			
		||||
        chars.count = 0;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      for (var lp = 0; lp < node.childNodes.length; lp++) {
 | 
			
		||||
        range = createRange(node.childNodes[lp], chars, range);
 | 
			
		||||
 | 
			
		||||
        if (chars.count === 0) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return range;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setCurrentCursorPosition(element: any, chars: any) {
 | 
			
		||||
  if (chars >= 0) {
 | 
			
		||||
    const selection = window.getSelection();
 | 
			
		||||
    const range = createRange(element, { count: chars }, false);
 | 
			
		||||
    if (range && selection) {
 | 
			
		||||
      range.collapse(false);
 | 
			
		||||
      selection.removeAllRanges();
 | 
			
		||||
      selection.addRange(range);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ const FilterMember = styled(Member)`
 | 
			
		||||
  margin: 2px 0;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -71,7 +71,7 @@ export const ActionItem = styled.li`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +80,7 @@ export const ActionTitle = styled.span`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ActionItemSeparator = styled.li`
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  padding-left: 4px;
 | 
			
		||||
  padding-right: 4px;
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ export const ActionItem = styled.li`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover ${ActionExtraMenuContainer} {
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
@@ -69,11 +69,11 @@ export const ActionExtraMenuItem = styled.li`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: rgb(${props => props.theme.colors.primary});
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
const ActionExtraMenuSeparator = styled.li`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  padding-left: 4px;
 | 
			
		||||
  padding-right: 4px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { TaskSorting, TaskSortingType, TaskSortingDirection } from 'shared/utils/sorting';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const ActionsList = styled.ul`
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -21,7 +20,7 @@ export const ActionItem = styled.li`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -30,7 +29,7 @@ export const ActionTitle = styled.span`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ActionItemSeparator = styled.li`
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  padding-left: 4px;
 | 
			
		||||
  padding-right: 4px;
 | 
			
		||||
 
 | 
			
		||||
@@ -136,14 +136,14 @@ const ProjectActionWrapper = styled.div<{ disabled?: boolean }>`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
 | 
			
		||||
  &:not(:last-of-type) {
 | 
			
		||||
    margin-right: 16px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.disabled &&
 | 
			
		||||
@@ -280,7 +280,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            draftCache.findProject.taskGroups = draftCache.findProject.taskGroups.filter(
 | 
			
		||||
              (taskGroup: TaskGroup) => taskGroup.id !== deletedTaskGroupData.data?.deleteTaskGroup.taskGroup.id,
 | 
			
		||||
              (taskGroup: TaskGroup) => taskGroup.id !== deletedTaskGroupData.data.deleteTaskGroup.taskGroup.id,
 | 
			
		||||
            );
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
@@ -296,11 +296,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            const { taskGroups } = cache.findProject;
 | 
			
		||||
            const idx = taskGroups.findIndex(taskGroup => taskGroup.id === newTaskData.data?.createTask.taskGroup.id);
 | 
			
		||||
            const idx = taskGroups.findIndex(taskGroup => taskGroup.id === newTaskData.data.createTask.taskGroup.id);
 | 
			
		||||
            if (idx !== -1) {
 | 
			
		||||
              if (newTaskData.data) {
 | 
			
		||||
                draftCache.findProject.taskGroups[idx].tasks.push({ ...newTaskData.data.createTask });
 | 
			
		||||
              }
 | 
			
		||||
              draftCache.findProject.taskGroups[idx].tasks.push({ ...newTaskData.data.createTask });
 | 
			
		||||
            }
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
@@ -315,9 +313,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (newTaskGroupData.data) {
 | 
			
		||||
              draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
 | 
			
		||||
            }
 | 
			
		||||
            draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -336,7 +332,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            const idx = cache.findProject.taskGroups.findIndex(
 | 
			
		||||
              t => t.id === resp.data?.deleteTaskGroupTasks.taskGroupID,
 | 
			
		||||
              t => t.id === resp.data.deleteTaskGroupTasks.taskGroupID,
 | 
			
		||||
            );
 | 
			
		||||
            if (idx !== -1) {
 | 
			
		||||
              draftCache.findProject.taskGroups[idx].tasks = [];
 | 
			
		||||
@@ -352,9 +348,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (resp.data) {
 | 
			
		||||
              draftCache.findProject.taskGroups.push(resp.data.duplicateTaskGroup.taskGroup);
 | 
			
		||||
            }
 | 
			
		||||
            draftCache.findProject.taskGroups.push(resp.data.duplicateTaskGroup.taskGroup);
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -370,24 +364,19 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (newTask.data) {
 | 
			
		||||
              const { previousTaskGroupID, task } = newTask.data.updateTaskLocation;
 | 
			
		||||
              if (previousTaskGroupID !== task.taskGroup.id) {
 | 
			
		||||
                const { taskGroups } = cache.findProject;
 | 
			
		||||
                const oldTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === previousTaskGroupID);
 | 
			
		||||
                const newTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === task.taskGroup.id);
 | 
			
		||||
                if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) {
 | 
			
		||||
                  const previousTask = cache.findProject.taskGroups[oldTaskGroupIdx].tasks.find(t => t.id === task.id);
 | 
			
		||||
                  draftCache.findProject.taskGroups[oldTaskGroupIdx].tasks = taskGroups[oldTaskGroupIdx].tasks.filter(
 | 
			
		||||
                    (t: Task) => t.id !== task.id,
 | 
			
		||||
                  );
 | 
			
		||||
                  if (previousTask) {
 | 
			
		||||
                    draftCache.findProject.taskGroups[newTaskGroupIdx].tasks = [
 | 
			
		||||
                      ...taskGroups[newTaskGroupIdx].tasks,
 | 
			
		||||
                      { ...previousTask },
 | 
			
		||||
                    ];
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
            const { previousTaskGroupID, task } = newTask.data.updateTaskLocation;
 | 
			
		||||
            if (previousTaskGroupID !== task.taskGroup.id) {
 | 
			
		||||
              const { taskGroups } = cache.findProject;
 | 
			
		||||
              const oldTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === previousTaskGroupID);
 | 
			
		||||
              const newTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === task.taskGroup.id);
 | 
			
		||||
              if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) {
 | 
			
		||||
                draftCache.findProject.taskGroups[oldTaskGroupIdx].tasks = taskGroups[oldTaskGroupIdx].tasks.filter(
 | 
			
		||||
                  (t: Task) => t.id !== task.id,
 | 
			
		||||
                );
 | 
			
		||||
                draftCache.findProject.taskGroups[newTaskGroupIdx].tasks = [
 | 
			
		||||
                  ...taskGroups[newTaskGroupIdx].tasks,
 | 
			
		||||
                  { ...task },
 | 
			
		||||
                ];
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }),
 | 
			
		||||
 
 | 
			
		||||
@@ -138,23 +138,21 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
        FindTaskDocument,
 | 
			
		||||
        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);
 | 
			
		||||
                if (oldIdx > -1 && newIdx > -1) {
 | 
			
		||||
                  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,
 | 
			
		||||
                    );
 | 
			
		||||
                    draftCache.findTask.checklists[newIdx].items.push({
 | 
			
		||||
                      ...item,
 | 
			
		||||
                      position: checklistItem.position,
 | 
			
		||||
                      taskChecklistID: taskChecklistID,
 | 
			
		||||
                    });
 | 
			
		||||
                  }
 | 
			
		||||
            const { prevChecklistID, checklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
 | 
			
		||||
            if (checklistID !== prevChecklistID) {
 | 
			
		||||
              const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
 | 
			
		||||
              const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID);
 | 
			
		||||
              if (oldIdx > -1 && newIdx > -1) {
 | 
			
		||||
                const item = cache.findTask.checklists[oldIdx].items.find(i => i.id === checklistItem.id);
 | 
			
		||||
                if (item) {
 | 
			
		||||
                  draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
 | 
			
		||||
                    i => i.id !== checklistItem.id,
 | 
			
		||||
                  );
 | 
			
		||||
                  draftCache.findTask.checklists[newIdx].items.push({
 | 
			
		||||
                    ...item,
 | 
			
		||||
                    position: checklistItem.position,
 | 
			
		||||
                    taskChecklistID: checklistID,
 | 
			
		||||
                  });
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
@@ -190,7 +188,7 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
          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 = {
 | 
			
		||||
@@ -214,10 +212,8 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
        FindTaskDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (createData.data) {
 | 
			
		||||
              const item = createData.data.createTaskChecklist;
 | 
			
		||||
              draftCache.findTask.checklists.push({ ...item });
 | 
			
		||||
            }
 | 
			
		||||
            const item = createData.data.createTaskChecklist;
 | 
			
		||||
            draftCache.findTask.checklists.push({ ...item });
 | 
			
		||||
          }),
 | 
			
		||||
        { taskID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -231,21 +227,19 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
        FindTaskDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (deleteData.data) {
 | 
			
		||||
              const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
 | 
			
		||||
              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,
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
              const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
 | 
			
		||||
              draftCache.findTask.badges.checklist = {
 | 
			
		||||
                __typename: 'ChecklistBadge',
 | 
			
		||||
                complete,
 | 
			
		||||
                total,
 | 
			
		||||
              };
 | 
			
		||||
            const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
 | 
			
		||||
            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,
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
 | 
			
		||||
            draftCache.findTask.badges.checklist = {
 | 
			
		||||
              __typename: 'ChecklistBadge',
 | 
			
		||||
              complete,
 | 
			
		||||
              total,
 | 
			
		||||
            };
 | 
			
		||||
          }),
 | 
			
		||||
        { taskID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -258,26 +252,24 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
        FindTaskDocument,
 | 
			
		||||
        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);
 | 
			
		||||
              if (idx !== -1) {
 | 
			
		||||
                draftCache.findTask.checklists[idx].items.push({ ...item });
 | 
			
		||||
                const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
 | 
			
		||||
                draftCache.findTask.badges.checklist = {
 | 
			
		||||
                  __typename: 'ChecklistBadge',
 | 
			
		||||
                  complete,
 | 
			
		||||
                  total,
 | 
			
		||||
                };
 | 
			
		||||
              }
 | 
			
		||||
            const item = newTaskItem.data.createTaskChecklistItem;
 | 
			
		||||
            const { checklists } = cache.findTask;
 | 
			
		||||
            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);
 | 
			
		||||
              draftCache.findTask.badges.checklist = {
 | 
			
		||||
                __typename: 'ChecklistBadge',
 | 
			
		||||
                complete,
 | 
			
		||||
                total,
 | 
			
		||||
              };
 | 
			
		||||
            }
 | 
			
		||||
          }),
 | 
			
		||||
        { taskID },
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID }, fetchPolicy: 'cache-and-network' });
 | 
			
		||||
  const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
 | 
			
		||||
  const [setTaskComplete] = useSetTaskCompleteMutation();
 | 
			
		||||
  const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
 | 
			
		||||
    onCompleted: () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,9 +36,7 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (newLabelData.data) {
 | 
			
		||||
              draftCache.findProject.labels.push({ ...newLabelData.data.createProjectLabel });
 | 
			
		||||
            }
 | 
			
		||||
            draftCache.findProject.labels.push({ ...newLabelData.data.createProjectLabel });
 | 
			
		||||
          }),
 | 
			
		||||
        {
 | 
			
		||||
          projectID,
 | 
			
		||||
@@ -55,7 +53,7 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            draftCache.findProject.labels = cache.findProject.labels.filter(
 | 
			
		||||
              label => label.id !== newLabelData.data?.deleteProjectLabel.id,
 | 
			
		||||
              label => label.id !== newLabelData.data.deleteProjectLabel.id,
 | 
			
		||||
            );
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ import {
 | 
			
		||||
  FindProjectDocument,
 | 
			
		||||
  FindProjectQuery,
 | 
			
		||||
} from 'shared/generated/graphql';
 | 
			
		||||
 | 
			
		||||
import produce from 'immer';
 | 
			
		||||
import UserContext, { useCurrentUser } from 'App/context';
 | 
			
		||||
import Input from 'shared/components/Input';
 | 
			
		||||
@@ -47,7 +48,6 @@ import { colourStyles } from 'shared/components/Select';
 | 
			
		||||
import Board, { BoardLoading } from './Board';
 | 
			
		||||
import Details from './Details';
 | 
			
		||||
import LabelManagerEditor from './LabelManagerEditor';
 | 
			
		||||
import { mixin } from '../../shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
 | 
			
		||||
 | 
			
		||||
@@ -71,7 +71,7 @@ const UserMember = styled(Member)`
 | 
			
		||||
  padding: 4px 0;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  }
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
`;
 | 
			
		||||
@@ -422,16 +422,14 @@ const Project = () => {
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        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,
 | 
			
		||||
              );
 | 
			
		||||
            const taskGroupIdx = draftCache.findProject.taskGroups.findIndex(
 | 
			
		||||
              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);
 | 
			
		||||
              }
 | 
			
		||||
            if (taskGroupIdx !== -1) {
 | 
			
		||||
              draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[
 | 
			
		||||
                taskGroupIdx
 | 
			
		||||
              ].tasks.filter(t => t.id !== resp.data.deleteTask.taskID);
 | 
			
		||||
            }
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
@@ -451,7 +449,7 @@ const Project = () => {
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            draftCache.findProject.name = newName.data?.updateProjectName.name ?? '';
 | 
			
		||||
            draftCache.findProject.name = newName.data.updateProjectName.name;
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -465,16 +463,14 @@ const Project = () => {
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (response.data) {
 | 
			
		||||
              draftCache.findProject.members = [
 | 
			
		||||
                ...cache.findProject.members,
 | 
			
		||||
                ...response.data.inviteProjectMembers.members,
 | 
			
		||||
              ];
 | 
			
		||||
              draftCache.findProject.invitedMembers = [
 | 
			
		||||
                ...cache.findProject.invitedMembers,
 | 
			
		||||
                ...response.data.inviteProjectMembers.invitedMembers,
 | 
			
		||||
              ];
 | 
			
		||||
            }
 | 
			
		||||
            draftCache.findProject.members = [
 | 
			
		||||
              ...cache.findProject.members,
 | 
			
		||||
              ...response.data.inviteProjectMembers.members,
 | 
			
		||||
            ];
 | 
			
		||||
            draftCache.findProject.invitedMembers = [
 | 
			
		||||
              ...cache.findProject.invitedMembers,
 | 
			
		||||
              ...response.data.inviteProjectMembers.invitedMembers,
 | 
			
		||||
            ];
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -488,7 +484,7 @@ const Project = () => {
 | 
			
		||||
        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 },
 | 
			
		||||
@@ -503,7 +499,7 @@ const Project = () => {
 | 
			
		||||
        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 },
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,6 @@ import Input from 'shared/components/Input';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
type CreateTeamData = { teamName: string };
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +54,7 @@ const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ProjectAddTile = styled.div`
 | 
			
		||||
  background-color: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
 | 
			
		||||
  background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  background-position: 50%;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
@@ -178,7 +176,7 @@ const SectionActionLink = styled(Link)`
 | 
			
		||||
 | 
			
		||||
const ProjectSectionTitle = styled.h3`
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ProjectsContainer = styled.div`
 | 
			
		||||
@@ -210,9 +208,7 @@ const Projects = () => {
 | 
			
		||||
    update: (client, newProject) => {
 | 
			
		||||
      updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
 | 
			
		||||
        produce(cache, draftCache => {
 | 
			
		||||
          if (newProject.data) {
 | 
			
		||||
            draftCache.projects.push({ ...newProject.data.createProject });
 | 
			
		||||
          }
 | 
			
		||||
          draftCache.projects.push({ ...newProject.data.createProject });
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
@@ -224,9 +220,7 @@ const Projects = () => {
 | 
			
		||||
    update: (client, createData) => {
 | 
			
		||||
      updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
 | 
			
		||||
        produce(cache, draftCache => {
 | 
			
		||||
          if (createData.data) {
 | 
			
		||||
            draftCache.teams.push({ ...createData.data?.createTeam });
 | 
			
		||||
          }
 | 
			
		||||
          draftCache.teams.push({ ...createData.data.createTeam });
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
@@ -235,7 +229,7 @@ const Projects = () => {
 | 
			
		||||
    return <GlobalTopNavbar onSaveProjectName={NOOP} projectID={null} name={null} />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const colors = theme.colors.multiColors;
 | 
			
		||||
  const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
 | 
			
		||||
  if (data && user) {
 | 
			
		||||
    const { projects, teams, organizations } = data;
 | 
			
		||||
    const organizationID = organizations[0].id ?? null;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
export const Container = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginWrapper = styled.div`
 | 
			
		||||
  width: 60%;
 | 
			
		||||
`;
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import Register from 'shared/components/Register';
 | 
			
		||||
import { useHistory, useLocation } from 'react-router';
 | 
			
		||||
import * as QueryString from 'query-string';
 | 
			
		||||
import { toast } from 'react-toastify';
 | 
			
		||||
import { Container, LoginWrapper } from './Styles';
 | 
			
		||||
 | 
			
		||||
const UsersRegister = () => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const [registered, setRegistered] = useState(false);
 | 
			
		||||
  const params = QueryString.parse(location.search);
 | 
			
		||||
  return (
 | 
			
		||||
    <Container>
 | 
			
		||||
      <LoginWrapper>
 | 
			
		||||
        <Register
 | 
			
		||||
          registered={registered}
 | 
			
		||||
          onSubmit={(data, setComplete, setError) => {
 | 
			
		||||
            if (data.password !== data.password_confirm) {
 | 
			
		||||
              setError('password', { type: 'error', message: 'Passwords must match' });
 | 
			
		||||
              setError('password_confirm', { type: 'error', message: 'Passwords must match' });
 | 
			
		||||
            } else {
 | 
			
		||||
              // TODO: change to fetch?
 | 
			
		||||
              fetch('/auth/register', {
 | 
			
		||||
                method: 'POST',
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                  user: {
 | 
			
		||||
                    username: data.username,
 | 
			
		||||
                    roleCode: 'admin',
 | 
			
		||||
                    email: data.email,
 | 
			
		||||
                    password: data.password,
 | 
			
		||||
                    initials: data.initials,
 | 
			
		||||
                    fullname: data.fullname,
 | 
			
		||||
                  },
 | 
			
		||||
                }),
 | 
			
		||||
              })
 | 
			
		||||
                .then(async x => {
 | 
			
		||||
                  const response = await x.json();
 | 
			
		||||
                  const { setup } = response;
 | 
			
		||||
                  console.log(response);
 | 
			
		||||
                  if (setup) {
 | 
			
		||||
                    history.replace(`/confirm?confirmToken=xxxx`);
 | 
			
		||||
                  } else if (params.confirmToken) {
 | 
			
		||||
                    history.replace(`/confirm?confirmToken=${params.confirmToken}`);
 | 
			
		||||
                  } else {
 | 
			
		||||
                    setRegistered(true);
 | 
			
		||||
                  }
 | 
			
		||||
                })
 | 
			
		||||
                .catch(() => {
 | 
			
		||||
                  toast('There was an issue trying to register');
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            setComplete(true);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </LoginWrapper>
 | 
			
		||||
    </Container>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default UsersRegister;
 | 
			
		||||
@@ -21,7 +21,6 @@ import TaskAssignee from 'shared/components/TaskAssignee';
 | 
			
		||||
import Member from 'shared/components/Member';
 | 
			
		||||
import ControlledInput from 'shared/components/ControlledInput';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
const MemberListWrapper = styled.div`
 | 
			
		||||
  flex: 1 1;
 | 
			
		||||
@@ -35,7 +34,7 @@ const UserMember = styled(Member)`
 | 
			
		||||
  padding: 4px 0;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  }
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
`;
 | 
			
		||||
@@ -120,12 +119,12 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
 | 
			
		||||
      ? css`
 | 
			
		||||
          user-select: none;
 | 
			
		||||
          pointer-events: none;
 | 
			
		||||
          color: ${mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
          color: rgba(${props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${props.theme.colors.primary};
 | 
			
		||||
            background: rgb(115, 103, 240);
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
@@ -136,7 +135,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: rgba(${props => props.theme.colors.text.secondary}, 0.4);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Separator = styled.div`
 | 
			
		||||
@@ -147,13 +146,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: rgba(${props => 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: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const RemoveMemberButton = styled(Button)`
 | 
			
		||||
@@ -306,14 +305,14 @@ const MemberItemOption = styled(Button)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberList = styled.div`
 | 
			
		||||
  border-top: 1px solid ${props => props.theme.colors.border};
 | 
			
		||||
  border-top: 1px solid rgba(${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 rgba(${props => props.theme.colors.border});
 | 
			
		||||
  min-height: 40px;
 | 
			
		||||
  padding: 12px 0 12px 40px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -337,11 +336,11 @@ const MemberProfile = styled(TaskAssignee)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberItemName = styled.p`
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberItemUsername = styled.p`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberListHeader = styled.div`
 | 
			
		||||
@@ -350,12 +349,12 @@ const MemberListHeader = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
const ListTitle = styled.h3`
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  margin-bottom: 12px;
 | 
			
		||||
`;
 | 
			
		||||
const ListDesc = styled.span`
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
const FilterSearch = styled(Input)`
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -387,11 +386,11 @@ const FilterTabItem = styled.li`
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  padding: 6px 8px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -430,13 +429,11 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
 | 
			
		||||
        GetTeamDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            if (response.data) {
 | 
			
		||||
              draftCache.findTeam.members.push({
 | 
			
		||||
                ...response.data.createTeamMember.teamMember,
 | 
			
		||||
                member: { __typename: 'MemberList', projects: [], teams: [] },
 | 
			
		||||
                owned: { __typename: 'OwnedList', projects: [], teams: [] },
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
            draftCache.findTeam.members.push({
 | 
			
		||||
              ...response.data.createTeamMember.teamMember,
 | 
			
		||||
              member: { __typename: 'MemberList', projects: [], teams: [] },
 | 
			
		||||
              owned: { __typename: 'OwnedList', projects: [], teams: [] },
 | 
			
		||||
            });
 | 
			
		||||
          }),
 | 
			
		||||
        { teamID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -461,7 +458,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
 | 
			
		||||
        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 },
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import {
 | 
			
		||||
} from 'shared/generated/graphql';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import Input from 'shared/components/Input';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
const FilterSearch = styled(Input)`
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -35,11 +34,11 @@ const FilterTabItem = styled.li`
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  padding: 6px 8px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +55,7 @@ const FilterTabTitle = styled.h2`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ProjectAddTile = styled.div`
 | 
			
		||||
  background-color: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  background-position: 50%;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
@@ -148,7 +147,7 @@ const ProjectListWrapper = styled.div`
 | 
			
		||||
  flex: 1 1;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const colors = theme.colors.multiColors;
 | 
			
		||||
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
 | 
			
		||||
 | 
			
		||||
type TeamProjectsProps = {
 | 
			
		||||
  teamID: string;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ const Wrapper = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type TeamPopupProps = {
 | 
			
		||||
  history: History<any>;
 | 
			
		||||
  history: History<History.PoorMansUnknown>;
 | 
			
		||||
  name: string;
 | 
			
		||||
  teamID: string;
 | 
			
		||||
};
 | 
			
		||||
@@ -44,9 +44,9 @@ export const TeamPopup: React.FC<TeamPopupProps> = ({ history, name, teamID }) =
 | 
			
		||||
    update: (client, deleteData) => {
 | 
			
		||||
      updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
 | 
			
		||||
        produce(cache, draftCache => {
 | 
			
		||||
          draftCache.teams = cache.teams.filter((team: any) => team.id !== deleteData.data?.deleteTeam.team.id);
 | 
			
		||||
          draftCache.teams = cache.teams.filter((team: any) => team.id !== deleteData.data.deleteTeam.team.id);
 | 
			
		||||
          draftCache.projects = cache.projects.filter(
 | 
			
		||||
            (project: any) => project.team.id !== deleteData.data?.deleteTeam.team.id,
 | 
			
		||||
            (project: any) => project.team.id !== deleteData.data.deleteTeam.team.id,
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { action } from '@storybook/addon-actions';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
import AddList from '.';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -8,7 +7,7 @@ export default {
 | 
			
		||||
  title: 'AddList',
 | 
			
		||||
  parameters: {
 | 
			
		||||
    backgrounds: [
 | 
			
		||||
      { name: 'gray', value: theme.colors.bg.secondary, default: true },
 | 
			
		||||
      { name: 'gray', value: '#262c49', default: true },
 | 
			
		||||
      { name: 'white', value: '#ffffff' },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ export const ListNameEditorWrapper = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
`;
 | 
			
		||||
export const ListNameEditor = styled(TextareaAutosize)`
 | 
			
		||||
  background-color: ${props => mixin.lighten(props.theme.colors.bg.secondary, 0.05)};
 | 
			
		||||
  background-color: ${props => mixin.lighten('#262c49', 0.05)};
 | 
			
		||||
  border: none;
 | 
			
		||||
  box-shadow: inset 0 0 0 2px #0079bf;
 | 
			
		||||
  transition: margin 85ms ease-in, background 85ms ease-in;
 | 
			
		||||
@@ -91,7 +91,7 @@ export const ListNameEditor = styled(TextareaAutosize)`
 | 
			
		||||
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  l &:focus {
 | 
			
		||||
    background-color: ${props => mixin.lighten(props.theme.colors.bg.secondary, 0.05)};
 | 
			
		||||
    background-color: ${props => mixin.lighten('#262c49', 0.05)};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import { RoleCode, useUpdateUserRoleMutation } from 'shared/generated/graphql';
 | 
			
		||||
import Input from 'shared/components/Input';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const RoleCheckmark = styled(Checkmark)`
 | 
			
		||||
  padding-left: 4px;
 | 
			
		||||
@@ -59,12 +58,12 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
 | 
			
		||||
      ? css`
 | 
			
		||||
          user-select: none;
 | 
			
		||||
          pointer-events: none;
 | 
			
		||||
          color: ${mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
          color: rgba(${props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${props.theme.colors.primary};
 | 
			
		||||
            background: rgb(115, 103, 240);
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
@@ -75,7 +74,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: rgba(${props => props.theme.colors.text.secondary}, 0.4);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Separator = styled.div`
 | 
			
		||||
@@ -86,13 +85,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: rgba(${props => 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: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const RemoveMemberButton = styled(Button)`
 | 
			
		||||
@@ -334,14 +333,14 @@ const MemberItemOption = styled(Button)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberList = styled.div`
 | 
			
		||||
  border-top: 1px solid ${props => props.theme.colors.border};
 | 
			
		||||
  border-top: 1px solid rgba(${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 rgba(${props => props.theme.colors.border});
 | 
			
		||||
  min-height: 40px;
 | 
			
		||||
  padding: 12px 0 12px 40px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -365,11 +364,11 @@ const MemberProfile = styled(TaskAssignee)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberItemName = styled.p`
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberItemUsername = styled.p`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MemberListHeader = styled.div`
 | 
			
		||||
@@ -378,12 +377,12 @@ const MemberListHeader = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
const ListTitle = styled.h3`
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  margin-bottom: 12px;
 | 
			
		||||
`;
 | 
			
		||||
const ListDesc = styled.span`
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
const FilterSearch = styled(Input)`
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -444,17 +443,17 @@ const TabNavItemButton = styled.button<{ active: boolean }>`
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  color: ${props => (props.active ? `${props.theme.colors.secondary}` : props.theme.colors.text.primary)};
 | 
			
		||||
  color: ${props => (props.active ? 'rgba(115, 103, 240)' : '#c2c6dc')};
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: ${props => `${props.theme.colors.primary}`};
 | 
			
		||||
    color: rgba(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover svg {
 | 
			
		||||
    fill: ${props => props.theme.colors.primary};
 | 
			
		||||
    fill: rgba(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
const TabItemUser = styled(User)<{ active: boolean }>`
 | 
			
		||||
fill: ${props => (props.active ? `${props.theme.colors.primary}` : props.theme.colors.text.primary)}
 | 
			
		||||
stroke: ${props => (props.active ? `${props.theme.colors.primary}` : props.theme.colors.text.primary)}
 | 
			
		||||
fill: ${props => (props.active ? 'rgba(115, 103, 240)' : '#c2c6dc')}
 | 
			
		||||
stroke: ${props => (props.active ? 'rgba(115, 103, 240)' : '#c2c6dc')}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const TabNavItemSpan = styled.span`
 | 
			
		||||
@@ -471,8 +470,8 @@ const TabNavLine = styled.span<{ top: number }>`
 | 
			
		||||
  transform: scaleX(1);
 | 
			
		||||
  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, rgba(115, 103, 240), rgba(115, 103, 240));
 | 
			
		||||
  box-shadow: 0 0 8px 0 rgba(115, 103, 240);
 | 
			
		||||
  display: block;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import React, { useRef } from 'react';
 | 
			
		||||
import styled, { css } from 'styled-components/macro';
 | 
			
		||||
import { mixin } from '../../utils/styles';
 | 
			
		||||
 | 
			
		||||
const Text = styled.span<{ fontSize: string; justifyTextContent: string; hasIcon?: boolean }>`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -9,7 +8,7 @@ const Text = styled.span<{ fontSize: string; justifyTextContent: string; hasIcon
 | 
			
		||||
  justify-content: ${props => props.justifyTextContent};
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
  font-size: ${props => props.fontSize};
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.hasIcon &&
 | 
			
		||||
    css`
 | 
			
		||||
@@ -37,36 +36,35 @@ const Base = styled.button<{ color: string; disabled: boolean }>`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Filled = styled(Base)<{ hoverVariant: HoverVariant }>`
 | 
			
		||||
  background: ${props => props.theme.colors[props.color]};
 | 
			
		||||
  background: rgba(${props => props.theme.colors[props.color]});
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.hoverVariant === 'boxShadow' &&
 | 
			
		||||
    css`
 | 
			
		||||
      &:hover {
 | 
			
		||||
        box-shadow: 0 8px 25px -8px ${props.theme.colors[props.color]};
 | 
			
		||||
        box-shadow: 0 8px 25px -8px rgba(${props.theme.colors[props.color]});
 | 
			
		||||
      }
 | 
			
		||||
    `}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Outline = styled(Base)<{ invert: boolean }>`
 | 
			
		||||
  border: 1px solid ${props => props.theme.colors[props.color]};
 | 
			
		||||
  border: 1px solid rgba(${props => props.theme.colors[props.color]});
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.invert
 | 
			
		||||
      ? css`
 | 
			
		||||
          background: ${props.theme.colors[props.color]});
 | 
			
		||||
          background: rgba(${props.theme.colors[props.color]});
 | 
			
		||||
          & ${Text} {
 | 
			
		||||
            color: ${props.theme.colors.text.secondary});
 | 
			
		||||
            color: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${mixin.rgba(props.theme.colors[props.color], 0.8)};
 | 
			
		||||
            background: rgba(${props.theme.colors[props.color]}, 0.8);
 | 
			
		||||
          }
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          & ${Text} {
 | 
			
		||||
            color: ${props.theme.colors[props.color]});
 | 
			
		||||
            color: rgba(${props.theme.colors[props.color]});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${mixin.rgba(props.theme.colors[props.color], 0.08)};
 | 
			
		||||
            background: rgba(${props.theme.colors[props.color]}, 0.08);
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
@@ -74,7 +72,7 @@ const Outline = styled(Base)<{ invert: boolean }>`
 | 
			
		||||
const Flat = styled(Base)`
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => mixin.rgba(props.theme.colors[props.color], 0.2)};
 | 
			
		||||
    background: rgba(${props => props.theme.colors[props.color]}, 0.2);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +85,7 @@ const LineX = styled.span<{ color: string }>`
 | 
			
		||||
  bottom: -2px;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  transform: translate(-50%);
 | 
			
		||||
  background: ${props => mixin.rgba(props.theme.colors[props.color], 1)};
 | 
			
		||||
  background: rgba(${props => props.theme.colors[props.color]}, 1);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const LineDown = styled(Base)`
 | 
			
		||||
@@ -96,7 +94,7 @@ const LineDown = styled(Base)`
 | 
			
		||||
  border-width: 0;
 | 
			
		||||
  border-style: solid;
 | 
			
		||||
  border-bottom-width: 2px;
 | 
			
		||||
  border-color: ${props => mixin.rgba(props.theme.colors[props.color], 0.2)};
 | 
			
		||||
  border-color: rgba(${props => props.theme.colors[props.color]}, 0.2);
 | 
			
		||||
 | 
			
		||||
  &:hover ${LineX} {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
@@ -109,8 +107,8 @@ const LineDown = styled(Base)`
 | 
			
		||||
const Gradient = styled(Base)`
 | 
			
		||||
  background: linear-gradient(
 | 
			
		||||
    30deg,
 | 
			
		||||
    ${props => mixin.rgba(props.theme.colors[props.color], 1)},
 | 
			
		||||
    ${props => mixin.rgba(props.theme.colors[props.color], 0.5)}
 | 
			
		||||
    rgba(${props => props.theme.colors[props.color]}, 1),
 | 
			
		||||
    rgba(${props => props.theme.colors[props.color]}, 0.5)
 | 
			
		||||
  );
 | 
			
		||||
  text-shadow: 1px 2px 4px rgba(0, 0, 0, 0.3);
 | 
			
		||||
  &:hover {
 | 
			
		||||
@@ -119,7 +117,7 @@ const Gradient = styled(Base)`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Relief = styled(Base)`
 | 
			
		||||
  background: ${props => mixin.rgba(props.theme.colors[props.color], 1)};
 | 
			
		||||
  background: rgba(${props => props.theme.colors[props.color]}, 1);
 | 
			
		||||
  -webkit-box-shadow: 0 -3px 0 0 rgba(0, 0, 0, 0.2) inset;
 | 
			
		||||
  box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, 0.2);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@ import { CheckCircle, CheckSquareOutline, Clock } from 'shared/icons';
 | 
			
		||||
import TaskAssignee from 'shared/components/TaskAssignee';
 | 
			
		||||
 | 
			
		||||
export const CardMember = styled(TaskAssignee)<{ zIndex: number }>`
 | 
			
		||||
  box-shadow: 0 0 0 2px ${props => props.theme.colors.bg.secondary},
 | 
			
		||||
    inset 0 0 0 1px ${props => mixin.rgba(props.theme.colors.bg.secondary, 0.07)};
 | 
			
		||||
  box-shadow: 0 0 0 2px rgba(${props => props.theme.colors.bg.secondary}),
 | 
			
		||||
    inset 0 0 0 1px rgba(${props => props.theme.colors.bg.secondary}, 0.07);
 | 
			
		||||
  z-index: ${props => props.zIndex};
 | 
			
		||||
  position: relative;
 | 
			
		||||
`;
 | 
			
		||||
@@ -14,8 +14,8 @@ export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'no
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.color === 'success' &&
 | 
			
		||||
    css`
 | 
			
		||||
      fill: ${props.theme.colors.success};
 | 
			
		||||
      stroke: ${props.theme.colors.success};
 | 
			
		||||
      fill: rgba(${props.theme.colors.success});
 | 
			
		||||
      stroke: rgba(${props.theme.colors.success});
 | 
			
		||||
    `}
 | 
			
		||||
`;
 | 
			
		||||
export const ClockIcon = styled(Clock)<{ color: string }>`
 | 
			
		||||
@@ -38,7 +38,7 @@ export const EditorTextarea = styled(TextareaAutosize)`
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  line-height: 18px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  &:focus {
 | 
			
		||||
    border: none;
 | 
			
		||||
    outline: none;
 | 
			
		||||
@@ -89,7 +89,7 @@ export const ListCardBadgeText = styled.span<{ color?: 'success' | 'normal' }>`
 | 
			
		||||
  padding: 0 4px 0 6px;
 | 
			
		||||
  vertical-align: top;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  ${props => props.color === 'success' && `color: ${props.theme.colors.success};`}
 | 
			
		||||
  ${props => props.color === 'success' && `color: rgba(${props.theme.colors.success});`}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
 | 
			
		||||
@@ -101,9 +101,7 @@ export const ListCardContainer = styled.div<{ isActive: boolean; editable: boole
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  background-color: ${props =>
 | 
			
		||||
    props.isActive && !props.editable
 | 
			
		||||
      ? mixin.darken(props.theme.colors.bg.secondary, 0.1)
 | 
			
		||||
      : `${props.theme.colors.bg.secondary}`};
 | 
			
		||||
    props.isActive && !props.editable ? mixin.darken('#262c49', 0.1) : `rgba(${props.theme.colors.bg.secondary})`};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ListCardInnerContainer = styled.div`
 | 
			
		||||
@@ -223,7 +221,7 @@ export const ListCardOperation = styled.span`
 | 
			
		||||
  top: 2px;
 | 
			
		||||
  z-index: 100;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.25)};
 | 
			
		||||
    background-color: ${props => mixin.darken('#262c49', 0.25)};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -235,7 +233,7 @@ export const CardTitle = styled.span`
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
  line-height: 18px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
@@ -248,7 +246,7 @@ export const CardMembers = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const CompleteIcon = styled(CheckCircle)`
 | 
			
		||||
  fill: ${props => props.theme.colors.success};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.success});
 | 
			
		||||
  margin-right: 4px;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ export default {
 | 
			
		||||
const Container = styled.div`
 | 
			
		||||
  width: 552px;
 | 
			
		||||
  margin: 25px;
 | 
			
		||||
  border: 1px solid ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  border: 1px solid rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const defaultItems = [
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import Button from 'shared/components/Button';
 | 
			
		||||
import TextareaAutosize from 'react-autosize-textarea';
 | 
			
		||||
import Control from 'react-select/src/components/Control';
 | 
			
		||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  margin-bottom: 24px;
 | 
			
		||||
@@ -39,7 +38,7 @@ const WindowChecklistTitle = styled.div`
 | 
			
		||||
 | 
			
		||||
const WindowTitleText = styled.h3`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  margin: 6px 0;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: auto;
 | 
			
		||||
@@ -74,7 +73,7 @@ const ChecklistProgressPercent = styled.span`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ChecklistProgressBar = styled.div`
 | 
			
		||||
  background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  clear: both;
 | 
			
		||||
  height: 8px;
 | 
			
		||||
@@ -84,7 +83,7 @@ const ChecklistProgressBar = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
const ChecklistProgressBarCurrent = styled.div<{ width: number }>`
 | 
			
		||||
  width: ${props => props.width}%;
 | 
			
		||||
  background: ${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)};
 | 
			
		||||
  background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)});
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
@@ -112,7 +111,7 @@ const ChecklistIcon = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ChecklistItemCheckedIcon = styled(CheckSquare)`
 | 
			
		||||
  fill: ${props => props.theme.colors.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ChecklistItemDetails = styled.div`
 | 
			
		||||
@@ -134,7 +133,7 @@ const ChecklistItemTextControls = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ChecklistItemText = styled.span<{ complete: boolean }>`
 | 
			
		||||
  color: ${props => (props.complete ? '#5e6c84' : `${props.theme.colors.text.primary}`)};
 | 
			
		||||
  color: ${props => (props.complete ? '#5e6c84' : `rgba(${props.theme.colors.text.primary})`)};
 | 
			
		||||
  ${props => props.complete && 'text-decoration: line-through;'}
 | 
			
		||||
  line-height: 20px;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
@@ -156,14 +155,14 @@ const ControlButton = styled.div`
 | 
			
		||||
  margin-left: 4px;
 | 
			
		||||
  padding: 4px 6px;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  background-color: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.8)};
 | 
			
		||||
  background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8);
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 32px;
 | 
			
		||||
  height: 32px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: ${props => mixin.rgba(props.theme.colors.primary, 1)};
 | 
			
		||||
    background-color: rgba(${props => props.theme.colors.primary}, 1);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -190,27 +189,27 @@ export const ChecklistNameEditor = styled(TextareaAutosize)`
 | 
			
		||||
  padding: 8px 12px;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  line-height: 20px;
 | 
			
		||||
  border: 1px solid ${props => props.theme.colors.primary};
 | 
			
		||||
  border: 1px solid rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
 | 
			
		||||
  border-color: ${props => props.theme.colors.border};
 | 
			
		||||
  background-color: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
 | 
			
		||||
  border-color: rgba(${props => props.theme.colors.border});
 | 
			
		||||
  background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  &:focus {
 | 
			
		||||
    border-color: ${props => props.theme.colors.primary};
 | 
			
		||||
    border-color: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const AssignUserButton = styled(AccountPlus)`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ClockButton = styled(Clock)`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const TrashButton = styled(Trash)`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ChecklistItemWrapper = styled.div<{ ref: any }>`
 | 
			
		||||
@@ -225,7 +224,7 @@ const ChecklistItemWrapper = styled.div<{ ref: any }>`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.4)};
 | 
			
		||||
    background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover ${ControlButton} {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
@@ -247,10 +246,10 @@ const CancelButton = styled.div`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  margin: 5px;
 | 
			
		||||
  & svg {
 | 
			
		||||
    fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
  &:hover svg {
 | 
			
		||||
    fill: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -266,7 +265,7 @@ const EditableDeleteButton = styled.button`
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => mixin.rgba(props.theme.colors.primary, 0.8)};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.primary}, 0.8);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ const LabelText = styled.span`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Container = styled.div<{ color?: string }>`
 | 
			
		||||
@@ -24,11 +24,11 @@ const Container = styled.div<{ color?: string }>`
 | 
			
		||||
      ? css`
 | 
			
		||||
          background: ${props.color};
 | 
			
		||||
          & ${LabelText} {
 | 
			
		||||
            color: ${props.theme.colors.text.secondary};
 | 
			
		||||
            color: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          background: ${props.theme.colors.bg.primary};
 | 
			
		||||
          background: rgba(${props.theme.colors.bg.primary});
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
 | 
			
		||||
export const Wrapper = styled.div`
 | 
			
		||||
  background: #eff2f7;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Column = styled.div`
 | 
			
		||||
  width: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginFormWrapper = styled.div`
 | 
			
		||||
  background: #10163a;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginFormContainer = styled.div`
 | 
			
		||||
  min-height: 505px;
 | 
			
		||||
  padding: 2rem;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Title = styled.h1`
 | 
			
		||||
  color: #ebeefd;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  margin-bottom: 14px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const SubTitle = styled.h2`
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  margin-bottom: 14px;
 | 
			
		||||
`;
 | 
			
		||||
export const Form = styled.form`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const FormLabel = styled.label`
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin-top: 14px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const FormTextInput = styled.input`
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background: #262c49;
 | 
			
		||||
  border: 1px solid rgba(0, 0, 0, 0.2);
 | 
			
		||||
  margin-top: 4px;
 | 
			
		||||
  padding: 0.7rem 1rem 0.7rem 3rem;
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const FormIcon = styled.div`
 | 
			
		||||
  top: 30px;
 | 
			
		||||
  left: 16px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const FormError = styled.span`
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
  color: rgb(234, 84, 85);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginButton = styled(Button)``;
 | 
			
		||||
 | 
			
		||||
export const ActionButtons = styled.div`
 | 
			
		||||
  margin-top: 17.5px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const RegisterButton = styled(Button)``;
 | 
			
		||||
 | 
			
		||||
export const LogoTitle = styled.div`
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  margin-left: 12px;
 | 
			
		||||
  transition: visibility, opacity, transform 0.25s ease;
 | 
			
		||||
  color: #7367f0;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LogoWrapper = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding-bottom: 16px;
 | 
			
		||||
  margin-bottom: 24px;
 | 
			
		||||
  color: rgb(222, 235, 255);
 | 
			
		||||
  border-bottom: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
`;
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
import React, { useState, useEffect } from 'react';
 | 
			
		||||
import AccessAccount from 'shared/undraw/AccessAccount';
 | 
			
		||||
import { User, Lock, Taskcafe } from 'shared/icons';
 | 
			
		||||
import { useForm } from 'react-hook-form';
 | 
			
		||||
import LoadingSpinner from 'shared/components/LoadingSpinner';
 | 
			
		||||
import {
 | 
			
		||||
  Form,
 | 
			
		||||
  LogoWrapper,
 | 
			
		||||
  LogoTitle,
 | 
			
		||||
  ActionButtons,
 | 
			
		||||
  RegisterButton,
 | 
			
		||||
  FormError,
 | 
			
		||||
  FormIcon,
 | 
			
		||||
  FormLabel,
 | 
			
		||||
  FormTextInput,
 | 
			
		||||
  Wrapper,
 | 
			
		||||
  Column,
 | 
			
		||||
  LoginFormWrapper,
 | 
			
		||||
  LoginFormContainer,
 | 
			
		||||
  Title,
 | 
			
		||||
  SubTitle,
 | 
			
		||||
} from './Styles';
 | 
			
		||||
 | 
			
		||||
const Confirm = ({ onConfirmUser, hasConfirmToken }: ConfirmProps) => {
 | 
			
		||||
  const [hasFailed, setFailed] = useState(false);
 | 
			
		||||
  const setHasFailed = () => {
 | 
			
		||||
    setFailed(true);
 | 
			
		||||
  };
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    onConfirmUser(setHasFailed);
 | 
			
		||||
  });
 | 
			
		||||
  return (
 | 
			
		||||
    <Wrapper>
 | 
			
		||||
      <Column>
 | 
			
		||||
        <AccessAccount width={275} height={250} />
 | 
			
		||||
      </Column>
 | 
			
		||||
      <Column>
 | 
			
		||||
        <LoginFormWrapper>
 | 
			
		||||
          <LoginFormContainer>
 | 
			
		||||
            <LogoWrapper>
 | 
			
		||||
              <Taskcafe width={42} height={42} />
 | 
			
		||||
              <LogoTitle>Taskcafé</LogoTitle>
 | 
			
		||||
            </LogoWrapper>
 | 
			
		||||
            {hasConfirmToken ? (
 | 
			
		||||
              <>
 | 
			
		||||
                <Title>Confirming user...</Title>
 | 
			
		||||
                {hasFailed ? <SubTitle>There was an error while confirming your user</SubTitle> : <LoadingSpinner />}
 | 
			
		||||
              </>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <>
 | 
			
		||||
                <Title>There is no confirmation token</Title>
 | 
			
		||||
                <SubTitle>There seems to have been an error.</SubTitle>
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
          </LoginFormContainer>
 | 
			
		||||
        </LoginFormWrapper>
 | 
			
		||||
      </Column>
 | 
			
		||||
    </Wrapper>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Confirm;
 | 
			
		||||
@@ -19,7 +19,7 @@ export default {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
  padding: 45px;
 | 
			
		||||
  margin: 25px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import React, { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import styled, { css } from 'styled-components/macro';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
const InputWrapper = styled.div<{ width: string }>`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -58,14 +57,14 @@ const InputInput = styled.input<{
 | 
			
		||||
    background: ${props => props.focusBg};
 | 
			
		||||
  }
 | 
			
		||||
  &:focus ~ ${InputLabel} {
 | 
			
		||||
    color: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: rgba(115, 103, 240);
 | 
			
		||||
    transform: translate(-3px, -90%);
 | 
			
		||||
  }
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.hasValue &&
 | 
			
		||||
    css`
 | 
			
		||||
      & ~ ${InputLabel} {
 | 
			
		||||
        color: ${props.theme.colors.primary};
 | 
			
		||||
        color: rgba(115, 103, 240);
 | 
			
		||||
        transform: translate(-3px, -90%);
 | 
			
		||||
      }
 | 
			
		||||
    `}
 | 
			
		||||
@@ -116,8 +115,8 @@ const ControlledInput = ({
 | 
			
		||||
}: ControlledInputProps) => {
 | 
			
		||||
  const $input = useRef<HTMLInputElement>(null);
 | 
			
		||||
  const [hasValue, setHasValue] = useState(false);
 | 
			
		||||
  const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : theme.colors.alternate;
 | 
			
		||||
  const focusBg = variant === 'normal' ? theme.colors.bg.secondary : theme.colors.bg.primary;
 | 
			
		||||
  const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561';
 | 
			
		||||
  const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)';
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (autoFocus && $input && $input.current) {
 | 
			
		||||
      $input.current.focus();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import React, { createRef, useState } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { action } from '@storybook/addon-actions';
 | 
			
		||||
import DropdownMenu from '.';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  component: DropdownMenu,
 | 
			
		||||
@@ -11,7 +10,7 @@ export default {
 | 
			
		||||
    backgrounds: [
 | 
			
		||||
      { name: 'white', value: '#ffffff' },
 | 
			
		||||
      { name: 'gray', value: '#f8f8f8' },
 | 
			
		||||
      { name: 'darkBlue', value: theme.colors.bg.secondary, default: true },
 | 
			
		||||
      { name: 'darkBlue', value: '#262c49', default: true },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ export const ActionItem = styled.li`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,23 +19,23 @@ display: flex
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & .react-datepicker-time__header {
 | 
			
		||||
    color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__time-list-item {
 | 
			
		||||
    color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__time-container .react-datepicker__time
 | 
			
		||||
  .react-datepicker__time-box ul.react-datepicker__time-list
 | 
			
		||||
  li.react-datepicker__time-list-item:hover {
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    background: ${props => props.theme.colors.bg.secondary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.secondary});
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__time-container .react-datepicker__time {
 | 
			
		||||
    background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker--time-only {
 | 
			
		||||
    background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
    border: 1px solid ${props => props.theme.colors.border};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
    border: 1px solid rgba(${props => props.theme.colors.border});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & .react-datepicker * {
 | 
			
		||||
@@ -75,12 +75,12 @@ display: flex
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__day--selected {
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgba(115, 103, 240);
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__day--selected:hover {
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgba(115, 103, 240);
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__header {
 | 
			
		||||
@@ -88,7 +88,7 @@ display: flex
 | 
			
		||||
    border: none;
 | 
			
		||||
  }
 | 
			
		||||
  & .react-datepicker__header--time {
 | 
			
		||||
    border-bottom: 1px solid ${props => props.theme.colors.border};
 | 
			
		||||
    border-bottom: 1px solid rgba(${props => props.theme.colors.border});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ const HeaderSelectLabel = styled.div`
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgba(115, 103, 240);
 | 
			
		||||
    color: #c2c6dc;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
@@ -60,8 +60,8 @@ const HeaderSelect = styled.select`
 | 
			
		||||
  appearance: none;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.bg.secondary};
 | 
			
		||||
    border: 1px solid ${props => props.theme.colors.primary};
 | 
			
		||||
    background: #262c49;
 | 
			
		||||
    border: 1px solid rgba(115, 103, 240);
 | 
			
		||||
    outline: none !important;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
    color: #c2c6dc;
 | 
			
		||||
@@ -93,7 +93,7 @@ const HeaderButton = styled.button`
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgba(115, 103, 240);
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import styled, { keyframes } from 'styled-components/macro';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
export const BoardContainer = styled.div`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -35,9 +34,9 @@ export const Container = styled.div`
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const defaultBaseColor = theme.colors.bg.primary;
 | 
			
		||||
export const defaultBaseColor = '#10163a';
 | 
			
		||||
 | 
			
		||||
export const defaultHighlightColor = mixin.lighten(theme.colors.bg.primary, 0.25);
 | 
			
		||||
export const defaultHighlightColor = mixin.lighten('#10163a', 0.25);
 | 
			
		||||
 | 
			
		||||
export const skeletonKeyframes = keyframes`
 | 
			
		||||
  0% {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ export default {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.bg.primary});
 | 
			
		||||
  padding: 45px;
 | 
			
		||||
  margin: 25px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import React, { useState, useEffect, useRef } from 'react';
 | 
			
		||||
import styled, { css } from 'styled-components/macro';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
const InputWrapper = styled.div<{ width: string }>`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -54,18 +53,18 @@ const InputInput = styled.input<{
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
  &:focus {
 | 
			
		||||
    box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
 | 
			
		||||
    border: 1px solid ${props => props.theme.colors.primary};
 | 
			
		||||
    border: 1px solid rgba(115, 103, 240);
 | 
			
		||||
    background: ${props => props.focusBg};
 | 
			
		||||
  }
 | 
			
		||||
  &:focus ~ ${InputLabel} {
 | 
			
		||||
    color: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: rgba(115, 103, 240);
 | 
			
		||||
    transform: translate(-3px, -90%);
 | 
			
		||||
  }
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.hasValue &&
 | 
			
		||||
    css`
 | 
			
		||||
      & ~ ${InputLabel} {
 | 
			
		||||
        color: ${props.theme.colors.primary};
 | 
			
		||||
        color: rgba(115, 103, 240);
 | 
			
		||||
        transform: translate(-3px, -90%);
 | 
			
		||||
      }
 | 
			
		||||
    `}
 | 
			
		||||
@@ -139,8 +138,8 @@ const Input = React.forwardRef(
 | 
			
		||||
    $ref: any,
 | 
			
		||||
  ) => {
 | 
			
		||||
    const [hasValue, setHasValue] = useState(defaultValue !== '');
 | 
			
		||||
    const borderColor = variant === 'normal' ? 'rgba(0,0,0,0.2)' : theme.colors.alternate;
 | 
			
		||||
    const focusBg = variant === 'normal' ? theme.colors.bg.secondary : theme.colors.bg.primary;
 | 
			
		||||
    const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561';
 | 
			
		||||
    const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)';
 | 
			
		||||
 | 
			
		||||
    // Merge forwarded ref and internal ref in order to be able to access the ref in the useEffect
 | 
			
		||||
    // The forwarded ref is not accessible by itself, which is what the innerRef & combined ref is for
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import styled, { css } from 'styled-components';
 | 
			
		||||
import TextareaAutosize from 'react-autosize-textarea';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const Container = styled.div`
 | 
			
		||||
  width: 272px;
 | 
			
		||||
@@ -33,7 +34,7 @@ export const AddCardButton = styled.a`
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: #c2c6dc;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
export const Wrapper = styled.div`
 | 
			
		||||
@@ -95,7 +96,7 @@ export const Header = styled.div<{ isEditing: boolean }>`
 | 
			
		||||
    props.isEditing &&
 | 
			
		||||
    css`
 | 
			
		||||
      & ${HeaderName} {
 | 
			
		||||
        box-shadow: ${props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
        box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
      }
 | 
			
		||||
    `}
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ export const ListActionItem = styled.span`
 | 
			
		||||
  margin: 0 -12px;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { action } from '@storybook/addon-actions';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
import Lists from '.';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -8,7 +7,7 @@ export default {
 | 
			
		||||
  title: 'Lists',
 | 
			
		||||
  parameters: {
 | 
			
		||||
    backgrounds: [
 | 
			
		||||
      { name: 'gray', value: theme.colors.bg.secondary, default: true },
 | 
			
		||||
      { name: 'gray', value: '#262c49', default: true },
 | 
			
		||||
      { name: 'white', value: '#ffffff' },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -22,10 +22,10 @@ export const LoadingSpinnerWrapper = styled.div<{ color: string; size: string; b
 | 
			
		||||
    width: ${props => props.size};
 | 
			
		||||
    height: ${props => props.size};
 | 
			
		||||
    margin: ${props => props.thickness};
 | 
			
		||||
    border: ${props => props.thickness} solid ${props => props.theme.colors[props.color]};
 | 
			
		||||
    border: ${props => props.thickness} solid rgba(${props => props.theme.colors[props.color]});
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    animation: 1.2s ${LoadingSpinnerKeyframes} cubic-bezier(0.5, 0, 0.5, 1) infinite;
 | 
			
		||||
    border-color: ${props => props.theme.colors[props.color]} transparent transparent transparent;
 | 
			
		||||
    border-color: rgba(${props => props.theme.colors[props.color]}) transparent transparent transparent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > div:nth-child(1) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import { LoadingSpinnerWrapper } from './Styles';
 | 
			
		||||
import { LoadingSpinnerWrapper} from './Styles';
 | 
			
		||||
 | 
			
		||||
type LoadingSpinnerProps = {
 | 
			
		||||
  color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
 | 
			
		||||
@@ -30,11 +30,11 @@ const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
 | 
			
		||||
  borderSize = '80px',
 | 
			
		||||
}) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <LoadingSpinnerWrapper color={color} size={size} thickness={thickness} borderSize={borderSize}>
 | 
			
		||||
      <div />
 | 
			
		||||
      <div />
 | 
			
		||||
      <div />
 | 
			
		||||
    </LoadingSpinnerWrapper>
 | 
			
		||||
      <LoadingSpinnerWrapper color={color} size={size} thickness={thickness} borderSize={borderSize}>
 | 
			
		||||
        <div />
 | 
			
		||||
        <div />
 | 
			
		||||
        <div />
 | 
			
		||||
      </LoadingSpinnerWrapper>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const Wrapper = styled.div`
 | 
			
		||||
  background: #eff2f7;
 | 
			
		||||
@@ -69,7 +68,7 @@ export const FormIcon = styled.div`
 | 
			
		||||
 | 
			
		||||
export const FormError = styled.span`
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
  color: ${props => props.theme.colors.danger};
 | 
			
		||||
  color: rgb(234, 84, 85);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginButton = styled(Button)``;
 | 
			
		||||
@@ -100,5 +99,5 @@ export const LogoWrapper = styled.div`
 | 
			
		||||
  padding-bottom: 16px;
 | 
			
		||||
  margin-bottom: 24px;
 | 
			
		||||
  color: rgb(222, 235, 255);
 | 
			
		||||
  border-bottom: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0.65)};
 | 
			
		||||
  border-bottom: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,14 @@ export const MemberManagerSearch = styled(TextareaAutosize)`
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
 | 
			
		||||
  background: ${props => props.theme.colors.bg.secondary};
 | 
			
		||||
  background: #262c49;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  border-color: ${props => props.theme.colors.border};
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  border-color: #414561;
 | 
			
		||||
 | 
			
		||||
  &:focus {
 | 
			
		||||
    box-shadow: ${props => props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
    background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
 | 
			
		||||
    box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
    background: ${mixin.darken('#262c49', 0.15)};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -66,8 +66,8 @@ export const BoardMemberListItemContent = styled(Member)`
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    background-color: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +80,7 @@ export const ProfileIcon = styled.div`
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  background: ${props => props.theme.colors.primary};
 | 
			
		||||
  background: rgb(115, 103, 240);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  margin-right: 6px;
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import styled, { css } from 'styled-components';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
import { Checkmark } from 'shared/icons';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const RoleCheckmark = styled(Checkmark)`
 | 
			
		||||
  padding-left: 4px;
 | 
			
		||||
@@ -81,36 +80,36 @@ export const MiniProfileActionItem = styled.span<{ disabled?: boolean }>`
 | 
			
		||||
      ? css`
 | 
			
		||||
          user-select: none;
 | 
			
		||||
          pointer-events: none;
 | 
			
		||||
          color: ${mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
          color: rgba(${props.theme.colors.text.primary}, 0.4);
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${props.theme.colors.primary};
 | 
			
		||||
            background: rgb(115, 103, 240);
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const CurrentPermission = styled.span`
 | 
			
		||||
  margin-left: 4px;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.secondary, 0.4)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary}, 0.4);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Separator = styled.div`
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  border-top: 1px solid ${props => props.theme.colors.alternate};
 | 
			
		||||
  border-top: 1px solid #414561;
 | 
			
		||||
  margin: 0.25rem !important;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const WarningText = styled.span`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.4)};
 | 
			
		||||
  color: rgba(${props => 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: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const RemoveMemberButton = styled(Button)`
 | 
			
		||||
 
 | 
			
		||||
@@ -30,9 +30,9 @@ const CloseIcon = styled(Cross)`
 | 
			
		||||
  top: 16px;
 | 
			
		||||
  right: -32px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  &:hover {
 | 
			
		||||
    fill: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import styled, { css } from 'styled-components';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const Logo = styled.div``;
 | 
			
		||||
 | 
			
		||||
@@ -10,7 +9,7 @@ export const LogoTitle = styled.div`
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  transition: visibility, opacity, transform 0.25s ease;
 | 
			
		||||
  color: #22ff00;
 | 
			
		||||
  color: #7367f0;
 | 
			
		||||
`;
 | 
			
		||||
export const ActionContainer = styled.div`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@@ -47,8 +46,8 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.active &&
 | 
			
		||||
    css`
 | 
			
		||||
      background: ${props.theme.colors.primary};
 | 
			
		||||
      box-shadow: 0 0 10px 1px ${mixin.rgba(props.theme.colors.primary, 0.7)};
 | 
			
		||||
      background: rgb(115, 103, 240);
 | 
			
		||||
      box-shadow: 0 0 10px 1px rgba(115, 103, 240, 0.7);
 | 
			
		||||
    `}
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
@@ -74,7 +73,7 @@ export const LogoWrapper = styled.div`
 | 
			
		||||
  color: rgb(222, 235, 255);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  transition: color 0.1s ease 0s, border 0.1s ease 0s;
 | 
			
		||||
  border-bottom: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0.65)};
 | 
			
		||||
  border-bottom: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const Container = styled.aside`
 | 
			
		||||
@@ -88,12 +87,12 @@ export const Container = styled.aside`
 | 
			
		||||
  transform: translateZ(0px);
 | 
			
		||||
  background: #10163a;
 | 
			
		||||
  transition: all 0.1s ease 0s;
 | 
			
		||||
  border-right: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0.65)};
 | 
			
		||||
  border-right: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    width: 260px;
 | 
			
		||||
    box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 50px 0px;
 | 
			
		||||
    border-right: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0)};
 | 
			
		||||
    border-right: 1px solid rgba(65, 69, 97, 0);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover ${LogoTitle} {
 | 
			
		||||
    bottom: -12px;
 | 
			
		||||
@@ -107,6 +106,6 @@ export const Container = styled.aside`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover ${LogoWrapper} {
 | 
			
		||||
    border-bottom: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0)};
 | 
			
		||||
    border-bottom: 1px solid rgba(65, 69, 97, 0);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,17 +3,16 @@ import styled from 'styled-components';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
import Select from 'react-select';
 | 
			
		||||
import { ArrowLeft, Cross } from 'shared/icons';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
function getBackgroundColor(isDisabled: boolean, isSelected: boolean, isFocused: boolean) {
 | 
			
		||||
  if (isDisabled) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  if (isSelected) {
 | 
			
		||||
    return mixin.darken(theme.colors.bg.secondary, 0.25);
 | 
			
		||||
    return mixin.darken('#262c49', 0.25);
 | 
			
		||||
  }
 | 
			
		||||
  if (isFocused) {
 | 
			
		||||
    return mixin.darken(theme.colors.bg.secondary, 0.15);
 | 
			
		||||
    return mixin.darken('#262c49', 0.15);
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
@@ -98,8 +97,8 @@ const ProjectName = styled.input`
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
 | 
			
		||||
  &:focus {
 | 
			
		||||
    background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
 | 
			
		||||
    box-shadow: ${props => props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
    background: ${mixin.darken('#262c49', 0.15)};
 | 
			
		||||
    box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
const ProjectNameLabel = styled.label`
 | 
			
		||||
@@ -127,35 +126,35 @@ const colourStyles = {
 | 
			
		||||
  control: (styles: any, data: any) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...styles,
 | 
			
		||||
      backgroundColor: data.isMenuOpen ? mixin.darken(theme.colors.bg.secondary, 0.15) : theme.colors.bg.secondary,
 | 
			
		||||
      boxShadow: data.menuIsOpen ? `${theme.colors.primary} 0px 0px 0px 1px` : 'none',
 | 
			
		||||
      backgroundColor: data.isMenuOpen ? mixin.darken('#262c49', 0.15) : '#262c49',
 | 
			
		||||
      boxShadow: data.menuIsOpen ? 'rgb(115, 103, 240) 0px 0px 0px 1px' : 'none',
 | 
			
		||||
      borderRadius: '3px',
 | 
			
		||||
      borderWidth: '1px',
 | 
			
		||||
      borderStyle: 'solid',
 | 
			
		||||
      borderImage: 'initial',
 | 
			
		||||
      borderColor: theme.colors.alternate,
 | 
			
		||||
      borderColor: '#414561',
 | 
			
		||||
      ':hover': {
 | 
			
		||||
        boxShadow: `${theme.colors.primary} 0px 0px 0px 1px`,
 | 
			
		||||
        boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
 | 
			
		||||
        borderRadius: '3px',
 | 
			
		||||
        borderWidth: '1px',
 | 
			
		||||
        borderStyle: 'solid',
 | 
			
		||||
        borderImage: 'initial',
 | 
			
		||||
        borderColor: theme.colors.alternate,
 | 
			
		||||
        borderColor: '#414561',
 | 
			
		||||
      },
 | 
			
		||||
      ':active': {
 | 
			
		||||
        boxShadow: `${theme.colors.primary} 0px 0px 0px 1px`,
 | 
			
		||||
        boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
 | 
			
		||||
        borderRadius: '3px',
 | 
			
		||||
        borderWidth: '1px',
 | 
			
		||||
        borderStyle: 'solid',
 | 
			
		||||
        borderImage: 'initial',
 | 
			
		||||
        borderColor: `${theme.colors.primary}`,
 | 
			
		||||
        borderColor: 'rgb(115, 103, 240)',
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  menu: (styles: any) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...styles,
 | 
			
		||||
      backgroundColor: mixin.darken(theme.colors.bg.secondary, 0.15),
 | 
			
		||||
      backgroundColor: mixin.darken('#262c49', 0.15),
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  dropdownIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
 | 
			
		||||
@@ -168,11 +167,11 @@ const colourStyles = {
 | 
			
		||||
      cursor: isDisabled ? 'not-allowed' : 'default',
 | 
			
		||||
      ':active': {
 | 
			
		||||
        ...styles[':active'],
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? mixin.darken(theme.colors.bg.secondary, 0.25) : '#fff'),
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? mixin.darken('#262c49', 0.25) : '#fff'),
 | 
			
		||||
      },
 | 
			
		||||
      ':hover': {
 | 
			
		||||
        ...styles[':hover'],
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? theme.colors.primary : theme.colors.primary),
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? 'rgb(115, 103, 240)' : 'rgb(115, 103, 240)'),
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
@@ -210,8 +209,8 @@ const CreateButton = styled.button`
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    border-color: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
    border-color: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
type NewProjectProps = {
 | 
			
		||||
@@ -263,7 +262,7 @@ const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose,
 | 
			
		||||
                  onChange={(e: any) => {
 | 
			
		||||
                    setTeam(e.value);
 | 
			
		||||
                  }}
 | 
			
		||||
                  value={options.find(d => d.value === team)}
 | 
			
		||||
                  value={options.filter(d => d.value === team)}
 | 
			
		||||
                  styles={colourStyles}
 | 
			
		||||
                  classNamePrefix="teamSelect"
 | 
			
		||||
                  options={options}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ const ItemTextContainer = styled.div`
 | 
			
		||||
const ItemTextTitle = styled.span`
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  display: block;
 | 
			
		||||
  color: ${props => props.theme.colors.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
`;
 | 
			
		||||
const ItemTextDesc = styled.span`
 | 
			
		||||
@@ -76,21 +76,21 @@ const NotificationHeader = styled.div`
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  border-top-left-radius: 6px;
 | 
			
		||||
  border-top-right-radius: 6px;
 | 
			
		||||
  background: ${props => props.theme.colors.primary};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const NotificationHeaderTitle = styled.span`
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const NotificationFooter = styled.div`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  color: ${props => props.theme.colors.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
    background: #10163a;
 | 
			
		||||
  }
 | 
			
		||||
  border-bottom-left-radius: 6px;
 | 
			
		||||
  border-bottom-right-radius: 6px;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import styled from 'styled-components';
 | 
			
		||||
import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldName } from './Styles';
 | 
			
		||||
 | 
			
		||||
const WhiteCheckmark = styled(Checkmark)`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
`;
 | 
			
		||||
type Props = {
 | 
			
		||||
  labelColors: Array<LabelColor>;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import styled, { css } from 'styled-components';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
import ControlledInput from 'shared/components/ControlledInput';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
export const Container = styled.div<{
 | 
			
		||||
  invertY: boolean;
 | 
			
		||||
@@ -177,7 +176,7 @@ export const LabelIcon = styled.div`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -234,8 +233,8 @@ export const FieldName = styled.input`
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
 | 
			
		||||
  &:focus {
 | 
			
		||||
    box-shadow: ${props => props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
    background: ${mixin.darken(theme.colors.bg.secondary, 0.15)};
 | 
			
		||||
    box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
    background: ${mixin.darken('#262c49', 0.15)};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -259,7 +258,7 @@ export const LabelBox = styled.span<{ color: string }>`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const SaveButton = styled.input`
 | 
			
		||||
  background: ${props => props.theme.colors.primary};
 | 
			
		||||
  background: rgb(115, 103, 240);
 | 
			
		||||
  box-shadow: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
@@ -297,7 +296,7 @@ export const DeleteButton = styled.input`
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
    border-color: transparent;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
@@ -318,7 +317,7 @@ export const CreateLabelButton = styled.button`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import useOnOutsideClick from 'shared/hooks/onOutsideClick';
 | 
			
		||||
import { createPortal } from 'react-dom';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import produce from 'immer';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
import {
 | 
			
		||||
  Container,
 | 
			
		||||
  ContainerDiamond,
 | 
			
		||||
@@ -19,7 +18,7 @@ import {
 | 
			
		||||
function getPopupOptions(options?: PopupOptions) {
 | 
			
		||||
  const popupOptions = {
 | 
			
		||||
    borders: true,
 | 
			
		||||
    diamondColor: theme.colors.bg.secondary,
 | 
			
		||||
    diamondColor: '#262c49',
 | 
			
		||||
    targetPadding: '10px',
 | 
			
		||||
    showDiamond: true,
 | 
			
		||||
    width: 316,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ export const ListActionItem = styled.span`
 | 
			
		||||
  margin: 0 -12px;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import styled, { keyframes, css } from 'styled-components';
 | 
			
		||||
import TextareaAutosize from 'react-autosize-textarea';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const Wrapper = styled.div<{ open: boolean }>`
 | 
			
		||||
  background: rgba(0, 0, 0, 0.55);
 | 
			
		||||
@@ -28,7 +30,7 @@ export const Container = styled.div<{ fixed: boolean; width: number; top: number
 | 
			
		||||
 | 
			
		||||
export const SaveButton = styled.button`
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background: ${props => props.theme.colors.primary};
 | 
			
		||||
  background: rgb(115, 103, 240);
 | 
			
		||||
  box-shadow: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
 | 
			
		||||
export const Wrapper = styled.div`
 | 
			
		||||
  background: #eff2f7;
 | 
			
		||||
@@ -69,7 +68,7 @@ export const FormIcon = styled.div`
 | 
			
		||||
 | 
			
		||||
export const FormError = styled.span`
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
  color: ${props => props.theme.colors.danger};
 | 
			
		||||
  color: rgb(234, 84, 85);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const LoginButton = styled(Button)``;
 | 
			
		||||
@@ -100,5 +99,5 @@ export const LogoWrapper = styled.div`
 | 
			
		||||
  padding-bottom: 16px;
 | 
			
		||||
  margin-bottom: 24px;
 | 
			
		||||
  color: rgb(222, 235, 255);
 | 
			
		||||
  border-bottom: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0.65)};
 | 
			
		||||
  border-bottom: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ import {
 | 
			
		||||
const EMAIL_PATTERN = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i;
 | 
			
		||||
const INITIALS_PATTERN = /[a-zA-Z]{2,3}/i;
 | 
			
		||||
 | 
			
		||||
const Register = ({ onSubmit, registered = false }: RegisterProps) => {
 | 
			
		||||
const Register = ({ onSubmit }: RegisterProps) => {
 | 
			
		||||
  const [isComplete, setComplete] = useState(true);
 | 
			
		||||
  const { register, handleSubmit, errors, setError } = useForm<RegisterFormData>();
 | 
			
		||||
  const loginSubmit = (data: RegisterFormData) => {
 | 
			
		||||
@@ -43,112 +43,103 @@ const Register = ({ onSubmit, registered = false }: RegisterProps) => {
 | 
			
		||||
              <Taskcafe width={42} height={42} />
 | 
			
		||||
              <LogoTitle>Taskcafé</LogoTitle>
 | 
			
		||||
            </LogoWrapper>
 | 
			
		||||
            {registered ? (
 | 
			
		||||
              <>
 | 
			
		||||
                <Title>Thanks for registering</Title>
 | 
			
		||||
                <SubTitle>Please check your inbox for a confirmation email.</SubTitle>
 | 
			
		||||
              </>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <>
 | 
			
		||||
                <Title>Register</Title>
 | 
			
		||||
                <SubTitle>Please create your user</SubTitle>
 | 
			
		||||
                <Form onSubmit={handleSubmit(loginSubmit)}>
 | 
			
		||||
                  <FormLabel htmlFor="fullname">
 | 
			
		||||
                    Full name
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      id="fullname"
 | 
			
		||||
                      name="fullname"
 | 
			
		||||
                      ref={register({ required: 'Full name is required' })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <User width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.username && <FormError>{errors.username.message}</FormError>}
 | 
			
		||||
                  <FormLabel htmlFor="username">
 | 
			
		||||
                    Username
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      id="username"
 | 
			
		||||
                      name="username"
 | 
			
		||||
                      ref={register({ required: 'Username is required' })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <User width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.username && <FormError>{errors.username.message}</FormError>}
 | 
			
		||||
                  <FormLabel htmlFor="email">
 | 
			
		||||
                    Email
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      id="email"
 | 
			
		||||
                      name="email"
 | 
			
		||||
                      ref={register({
 | 
			
		||||
                        required: 'Email is required',
 | 
			
		||||
                        pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
 | 
			
		||||
                      })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <User width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.email && <FormError>{errors.email.message}</FormError>}
 | 
			
		||||
                  <FormLabel htmlFor="initials">
 | 
			
		||||
                    Initials
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      id="initials"
 | 
			
		||||
                      name="initials"
 | 
			
		||||
                      ref={register({
 | 
			
		||||
                        required: 'Initials is required',
 | 
			
		||||
                        pattern: {
 | 
			
		||||
                          value: INITIALS_PATTERN,
 | 
			
		||||
                          message: 'Initials must be between 2 to 3 characters.',
 | 
			
		||||
                        },
 | 
			
		||||
                      })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <User width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.initials && <FormError>{errors.initials.message}</FormError>}
 | 
			
		||||
                  <FormLabel htmlFor="password">
 | 
			
		||||
                    Password
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="password"
 | 
			
		||||
                      id="password"
 | 
			
		||||
                      name="password"
 | 
			
		||||
                      ref={register({ required: 'Password is required' })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <Lock width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.password && <FormError>{errors.password.message}</FormError>}
 | 
			
		||||
                  <FormLabel htmlFor="password_confirm">
 | 
			
		||||
                    Password (Confirm)
 | 
			
		||||
                    <FormTextInput
 | 
			
		||||
                      type="password"
 | 
			
		||||
                      id="password_confirm"
 | 
			
		||||
                      name="password_confirm"
 | 
			
		||||
                      ref={register({ required: 'Password (confirm) is required' })}
 | 
			
		||||
                    />
 | 
			
		||||
                    <FormIcon>
 | 
			
		||||
                      <Lock width={20} height={20} />
 | 
			
		||||
                    </FormIcon>
 | 
			
		||||
                  </FormLabel>
 | 
			
		||||
                  {errors.password_confirm && <FormError>{errors.password_confirm.message}</FormError>}
 | 
			
		||||
            <Title>Register</Title>
 | 
			
		||||
            <SubTitle>Please create the system admin user</SubTitle>
 | 
			
		||||
            <Form onSubmit={handleSubmit(loginSubmit)}>
 | 
			
		||||
              <FormLabel htmlFor="fullname">
 | 
			
		||||
                Full name
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  id="fullname"
 | 
			
		||||
                  name="fullname"
 | 
			
		||||
                  ref={register({ required: 'Full name is required' })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <User width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.username && <FormError>{errors.username.message}</FormError>}
 | 
			
		||||
              <FormLabel htmlFor="username">
 | 
			
		||||
                Username
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  id="username"
 | 
			
		||||
                  name="username"
 | 
			
		||||
                  ref={register({ required: 'Username is required' })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <User width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.username && <FormError>{errors.username.message}</FormError>}
 | 
			
		||||
              <FormLabel htmlFor="email">
 | 
			
		||||
                Email
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  id="email"
 | 
			
		||||
                  name="email"
 | 
			
		||||
                  ref={register({
 | 
			
		||||
                    required: 'Email is required',
 | 
			
		||||
                    pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
 | 
			
		||||
                  })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <User width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.email && <FormError>{errors.email.message}</FormError>}
 | 
			
		||||
              <FormLabel htmlFor="initials">
 | 
			
		||||
                Initials
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  id="initials"
 | 
			
		||||
                  name="initials"
 | 
			
		||||
                  ref={register({
 | 
			
		||||
                    required: 'Initials is required',
 | 
			
		||||
                    pattern: {
 | 
			
		||||
                      value: INITIALS_PATTERN,
 | 
			
		||||
                      message: 'Initials must be between 2 to 3 characters.',
 | 
			
		||||
                    },
 | 
			
		||||
                  })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <User width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.initials && <FormError>{errors.initials.message}</FormError>}
 | 
			
		||||
              <FormLabel htmlFor="password">
 | 
			
		||||
                Password
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="password"
 | 
			
		||||
                  id="password"
 | 
			
		||||
                  name="password"
 | 
			
		||||
                  ref={register({ required: 'Password is required' })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <Lock width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.password && <FormError>{errors.password.message}</FormError>}
 | 
			
		||||
              <FormLabel htmlFor="password_confirm">
 | 
			
		||||
                Password (Confirm)
 | 
			
		||||
                <FormTextInput
 | 
			
		||||
                  type="password"
 | 
			
		||||
                  id="password_confirm"
 | 
			
		||||
                  name="password_confirm"
 | 
			
		||||
                  ref={register({ required: 'Password (confirm) is required' })}
 | 
			
		||||
                />
 | 
			
		||||
                <FormIcon>
 | 
			
		||||
                  <Lock width={20} height={20} />
 | 
			
		||||
                </FormIcon>
 | 
			
		||||
              </FormLabel>
 | 
			
		||||
              {errors.password_confirm && <FormError>{errors.password_confirm.message}</FormError>}
 | 
			
		||||
 | 
			
		||||
                  <ActionButtons>
 | 
			
		||||
                    <RegisterButton type="submit" disabled={!isComplete}>
 | 
			
		||||
                      Register
 | 
			
		||||
                    </RegisterButton>
 | 
			
		||||
                  </ActionButtons>
 | 
			
		||||
                </Form>
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
              <ActionButtons>
 | 
			
		||||
                <RegisterButton type="submit" disabled={!isComplete}>
 | 
			
		||||
                  Register
 | 
			
		||||
                </RegisterButton>
 | 
			
		||||
              </ActionButtons>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </LoginFormContainer>
 | 
			
		||||
        </LoginFormWrapper>
 | 
			
		||||
      </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,17 +2,16 @@ import React from 'react';
 | 
			
		||||
import Select from 'react-select';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
import theme from 'App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
function getBackgroundColor(isDisabled: boolean, isSelected: boolean, isFocused: boolean) {
 | 
			
		||||
  if (isDisabled) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  if (isSelected) {
 | 
			
		||||
    return mixin.darken(theme.colors.bg.secondary, 0.25);
 | 
			
		||||
    return mixin.darken('#262c49', 0.25);
 | 
			
		||||
  }
 | 
			
		||||
  if (isFocused) {
 | 
			
		||||
    return mixin.darken(theme.colors.bg.secondary, 0.15);
 | 
			
		||||
    return mixin.darken('#262c49', 0.15);
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
@@ -21,35 +20,35 @@ export const colourStyles = {
 | 
			
		||||
  control: (styles: any, data: any) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...styles,
 | 
			
		||||
      backgroundColor: data.isMenuOpen ? mixin.darken(theme.colors.bg.secondary, 0.15) : theme.colors.bg.secondary,
 | 
			
		||||
      boxShadow: data.menuIsOpen ? `${theme.colors.primary} 0px 0px 0px 1px` : 'none',
 | 
			
		||||
      backgroundColor: data.isMenuOpen ? mixin.darken('#262c49', 0.15) : '#262c49',
 | 
			
		||||
      boxShadow: data.menuIsOpen ? 'rgb(115, 103, 240) 0px 0px 0px 1px' : 'none',
 | 
			
		||||
      borderRadius: '3px',
 | 
			
		||||
      borderWidth: '1px',
 | 
			
		||||
      borderStyle: 'solid',
 | 
			
		||||
      borderImage: 'initial',
 | 
			
		||||
      borderColor: theme.colors.alternate,
 | 
			
		||||
      borderColor: '#414561',
 | 
			
		||||
      ':hover': {
 | 
			
		||||
        boxShadow: `${theme.colors.primary} 0px 0px 0px 1px`,
 | 
			
		||||
        boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
 | 
			
		||||
        borderRadius: '3px',
 | 
			
		||||
        borderWidth: '1px',
 | 
			
		||||
        borderStyle: 'solid',
 | 
			
		||||
        borderImage: 'initial',
 | 
			
		||||
        borderColor: theme.colors.alternate,
 | 
			
		||||
        borderColor: '#414561',
 | 
			
		||||
      },
 | 
			
		||||
      ':active': {
 | 
			
		||||
        boxShadow: `${theme.colors.primary} 0px 0px 0px 1px`,
 | 
			
		||||
        boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
 | 
			
		||||
        borderRadius: '3px',
 | 
			
		||||
        borderWidth: '1px',
 | 
			
		||||
        borderStyle: 'solid',
 | 
			
		||||
        borderImage: 'initial',
 | 
			
		||||
        borderColor: `${theme.colors.primary}`,
 | 
			
		||||
        borderColor: 'rgb(115, 103, 240)',
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  menu: (styles: any) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...styles,
 | 
			
		||||
      backgroundColor: mixin.darken(theme.colors.bg.secondary, 0.15),
 | 
			
		||||
      backgroundColor: mixin.darken('#262c49', 0.15),
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  dropdownIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
 | 
			
		||||
@@ -62,11 +61,11 @@ export const colourStyles = {
 | 
			
		||||
      cursor: isDisabled ? 'not-allowed' : 'default',
 | 
			
		||||
      ':active': {
 | 
			
		||||
        ...styles[':active'],
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? mixin.darken(theme.colors.bg.secondary, 0.25) : '#fff'),
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? mixin.darken('#262c49', 0.25) : '#fff'),
 | 
			
		||||
      },
 | 
			
		||||
      ':hover': {
 | 
			
		||||
        ...styles[':hover'],
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? theme.colors.primary : theme.colors.primary),
 | 
			
		||||
        backgroundColor: !isDisabled && (isSelected ? 'rgb(115, 103, 240)' : 'rgb(115, 103, 240)'),
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
@@ -87,7 +86,7 @@ export const colourStyles = {
 | 
			
		||||
const InputLabel = styled.span<{ width: string }>`
 | 
			
		||||
width: ${props => props.width};
 | 
			
		||||
padding-left: 0.7rem;
 | 
			
		||||
color: ${props => props.theme.colors.primary};
 | 
			
		||||
color: rgba(115, 103, 240);
 | 
			
		||||
left: 0;
 | 
			
		||||
top: 0;
 | 
			
		||||
transition: all 0.2s ease;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ const UserInfoInput = styled(Input)`
 | 
			
		||||
 | 
			
		||||
const FormError = styled.span`
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: ${props => props.theme.colors.warning};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.warning});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ProfileContainer = styled.div`
 | 
			
		||||
@@ -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 ? 'rgba(115, 103, 240)' : '#c2c6dc')};
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: ${props => props.theme.colors.primary};
 | 
			
		||||
    color: rgba(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover svg {
 | 
			
		||||
    fill: ${props => props.theme.colors.primary};
 | 
			
		||||
    fill: rgba(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -175,8 +175,8 @@ const TabNavLine = styled.span<{ top: number }>`
 | 
			
		||||
  transform: scaleX(1);
 | 
			
		||||
  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, rgba(115, 103, 240), rgba(115, 103, 240));
 | 
			
		||||
  box-shadow: 0 0 8px 0 rgba(115, 103, 240);
 | 
			
		||||
  display: block;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ export const Wrapper = styled.div<{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  color: ${props => (props.backgroundURL ? props.theme.colors.text.primary : 'rgb(0,0,0)')};
 | 
			
		||||
  color: rgba(${props => (props.backgroundURL ? props.theme.colors.text.primary : '0,0,0')});
 | 
			
		||||
  background: ${props => (props.backgroundURL ? `url(${props.backgroundURL})` : props.bgColor)};
 | 
			
		||||
  background-position: center;
 | 
			
		||||
  background-size: contain;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { TaskActivityData, ActivityType } from 'shared/generated/graphql';
 | 
			
		||||
 | 
			
		||||
type ActivityMessageProps = {
 | 
			
		||||
  type: ActivityType;
 | 
			
		||||
  data: Array<TaskActivityData>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getVariable(data: Array<TaskActivityData>, name: string) {
 | 
			
		||||
  const target = data.find(d => d.name === name);
 | 
			
		||||
  return target ? target.value : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ActivityMessage: React.FC<ActivityMessageProps> = ({ type, data }) => {
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case ActivityType.TaskAdded:
 | 
			
		||||
      return <>`added this task to ${getVariable(data, 'TaskGroup')}`</>;
 | 
			
		||||
  }
 | 
			
		||||
  return <h1>hello</h1>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ActivityMessage;
 | 
			
		||||
@@ -3,6 +3,8 @@ import TextareaAutosize from 'react-autosize-textarea';
 | 
			
		||||
import { mixin } from 'shared/utils/styles';
 | 
			
		||||
import Button from 'shared/components/Button';
 | 
			
		||||
import TaskAssignee from 'shared/components/TaskAssignee';
 | 
			
		||||
import { User, Trash, Paperclip } from 'shared/icons';
 | 
			
		||||
import Member from 'shared/components/Member';
 | 
			
		||||
 | 
			
		||||
export const Container = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
@@ -31,35 +33,35 @@ export const MarkCompleteButton = styled.button<{ invert: boolean }>`
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.invert
 | 
			
		||||
      ? css`
 | 
			
		||||
          background: ${props.theme.colors.success};
 | 
			
		||||
          background: rgba(${props.theme.colors.success});
 | 
			
		||||
          & svg {
 | 
			
		||||
            fill: ${props.theme.colors.text.secondary};
 | 
			
		||||
            fill: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
          & span {
 | 
			
		||||
            color: ${props.theme.colors.text.secondary};
 | 
			
		||||
            color: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${mixin.rgba(props.theme.colors.success, 0.8)};
 | 
			
		||||
            background: rgba(${props.theme.colors.success}, 0.8);
 | 
			
		||||
          }
 | 
			
		||||
        `
 | 
			
		||||
      : css`
 | 
			
		||||
          background: none;
 | 
			
		||||
          border: 1px solid ${props.theme.colors.text.secondary};
 | 
			
		||||
          border: 1px solid rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          & svg {
 | 
			
		||||
            fill: ${props.theme.colors.text.secondary};
 | 
			
		||||
            fill: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
          & span {
 | 
			
		||||
            color: ${props.theme.colors.text.secondary};
 | 
			
		||||
            color: rgba(${props.theme.colors.text.secondary});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover {
 | 
			
		||||
            background: ${mixin.rgba(props.theme.colors.success, 0.08)};
 | 
			
		||||
            border: 1px solid ${props.theme.colors.success};
 | 
			
		||||
            background: rgba(${props.theme.colors.success}, 0.08);
 | 
			
		||||
            border: 1px solid rgba(${props.theme.colors.success});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover svg {
 | 
			
		||||
            fill: ${props.theme.colors.success};
 | 
			
		||||
            fill: rgba(${props.theme.colors.success});
 | 
			
		||||
          }
 | 
			
		||||
          &:hover span {
 | 
			
		||||
            color: ${props.theme.colors.success};
 | 
			
		||||
            color: rgba(${props.theme.colors.success});
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
`;
 | 
			
		||||
@@ -83,7 +85,7 @@ export const SidebarTitle = styled.div`
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  min-height: 24px;
 | 
			
		||||
  margin-left: 8px;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.75);
 | 
			
		||||
  padding-top: 4px;
 | 
			
		||||
  letter-spacing: 0.5px;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
@@ -91,7 +93,7 @@ export const SidebarTitle = styled.div`
 | 
			
		||||
 | 
			
		||||
export const SidebarButton = styled.div`
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  min-height: 32px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
@@ -166,7 +168,7 @@ export const TaskDetailsTitle = styled(TextareaAutosize)`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:focus {
 | 
			
		||||
    border-color: ${props => props.theme.colors.primary};
 | 
			
		||||
    border-color: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -174,7 +176,7 @@ export const DueDateTitle = styled.div`
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  min-height: 24px;
 | 
			
		||||
  margin-left: 8px;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.75);
 | 
			
		||||
  padding-top: 8px;
 | 
			
		||||
  letter-spacing: 0.5px;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
@@ -185,7 +187,7 @@ export const AssignedUsersSection = styled.div`
 | 
			
		||||
  padding-right: 32px;
 | 
			
		||||
  padding-top: 24px;
 | 
			
		||||
  padding-bottom: 24px;
 | 
			
		||||
  border-bottom: 1px solid ${props => props.theme.colors.alternate};
 | 
			
		||||
  border-bottom: 1px solid #414561;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
`;
 | 
			
		||||
@@ -203,10 +205,10 @@ export const AssignUserIcon = styled.div`
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border: 1px solid ${props => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
 | 
			
		||||
    border: 1px solid rgba(${props => props.theme.colors.text.secondary}, 0.75);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover svg {
 | 
			
		||||
    fill: ${props => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.text.secondary}, 0.75);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -221,17 +223,17 @@ export const AssignUsersButton = styled.div`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  border: 1px solid transparent;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    border: 1px solid ${props => mixin.darken(props.theme.colors.alternate, 0.15)};
 | 
			
		||||
    border: 1px solid ${mixin.darken('#414561', 0.15)};
 | 
			
		||||
  }
 | 
			
		||||
  &:hover ${AssignUserIcon} {
 | 
			
		||||
    border: 1px solid ${props => props.theme.colors.alternate};
 | 
			
		||||
    border: 1px solid #414561;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const AssignUserLabel = styled.span`
 | 
			
		||||
  flex: 1 1 auto;
 | 
			
		||||
  line-height: 15px;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.75);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ExtraActionsSection = styled.div`
 | 
			
		||||
@@ -243,7 +245,7 @@ export const ExtraActionsSection = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActionButtonsTitle = styled.h3`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  letter-spacing: 0.04em;
 | 
			
		||||
@@ -253,7 +255,7 @@ export const ActionButton = styled(Button)`
 | 
			
		||||
  margin-top: 8px;
 | 
			
		||||
  margin-left: -10px;
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.5)};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.bg.primary}, 0.5);
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  transition: transform 0.2s ease;
 | 
			
		||||
  & span {
 | 
			
		||||
@@ -262,7 +264,7 @@ export const ActionButton = styled(Button)`
 | 
			
		||||
  &:hover {
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
    transform: translateX(4px);
 | 
			
		||||
    background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.75)};
 | 
			
		||||
    background: rgba(${props => props.theme.colors.bg.primary}, 0.75);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -281,10 +283,10 @@ export const HeaderActionIcon = styled.div`
 | 
			
		||||
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  svg {
 | 
			
		||||
    fill: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.text.primary}, 0.75);
 | 
			
		||||
  }
 | 
			
		||||
  &:hover svg {
 | 
			
		||||
    fill: ${props => mixin.rgba(props.theme.colors.primary, 0.75)});
 | 
			
		||||
    fill: rgba(${props => props.theme.colors.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -341,7 +343,7 @@ export const MetaDetail = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const MetaDetailTitle = styled.h3`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  letter-spacing: 0.04em;
 | 
			
		||||
@@ -360,7 +362,7 @@ export const MetaDetailContent = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
export const TaskDetailsAddLabel = styled.div`
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
 | 
			
		||||
  background: ${mixin.darken('#262c49', 0.15)};
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
@@ -375,7 +377,7 @@ export const TaskDetailsAddLabelIcon = styled.div`
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
 | 
			
		||||
  background: ${mixin.darken('#262c49', 0.15)};
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
@@ -450,11 +452,11 @@ export const TabBarSection = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const TabBarItem = styled.div`
 | 
			
		||||
  box-shadow: inset 0 -2px ${props => props.theme.colors.primary};
 | 
			
		||||
  box-shadow: inset 0 -2px rgba(216, 93, 216);
 | 
			
		||||
  padding: 12px 7px 14px 7px;
 | 
			
		||||
  margin-bottom: -1px;
 | 
			
		||||
  margin-right: 36px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const CommentContainer = styled.div`
 | 
			
		||||
@@ -489,7 +491,7 @@ export const CommentTextArea = styled(TextareaAutosize)`
 | 
			
		||||
  line-height: 28px;
 | 
			
		||||
  padding: 4px 6px;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  background: #1f243e;
 | 
			
		||||
  border: none;
 | 
			
		||||
  transition: max-height 200ms, height 200ms, min-height 200ms;
 | 
			
		||||
@@ -537,31 +539,30 @@ export const ActivityItem = styled.div`
 | 
			
		||||
  overflow-wrap: break-word;
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
  word-break: break-word;
 | 
			
		||||
  display: flex;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActivityItemHeader = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  padding-left: 8px;
 | 
			
		||||
`;
 | 
			
		||||
export const ActivityItemHeaderUser = styled(TaskAssignee)``;
 | 
			
		||||
export const ActivityItemHeaderUser = styled(TaskAssignee)`
 | 
			
		||||
  margin-right: 4px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActivityItemHeaderTitle = styled.div`
 | 
			
		||||
  margin-left: 4px;
 | 
			
		||||
  line-height: 18px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  padding-bottom: 2px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActivityItemHeaderTitleName = styled.span`
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  padding-right: 2px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActivityItemTimestamp = styled.span<{ margin: number }>`
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.65)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.65);
 | 
			
		||||
  margin-left: ${props => props.margin}px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -574,15 +575,15 @@ export const ActivityItemComment = styled.div`
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  ${mixin.boxShadowCard}
 | 
			
		||||
  position: relative;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  padding: 8px 12px;
 | 
			
		||||
  margin: 4px 0;
 | 
			
		||||
  background-color: ${props => mixin.darken(props.theme.colors.alternate, 0.1)};
 | 
			
		||||
  background-color: ${mixin.darken('#262c49', 0.1)};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ActivityItemLog = styled.span`
 | 
			
		||||
  margin-left: 2px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ViewRawButton = styled.button`
 | 
			
		||||
@@ -593,9 +594,9 @@ export const ViewRawButton = styled.button`
 | 
			
		||||
  right: 4px;
 | 
			
		||||
  bottom: -24px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.25)};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary}, 0.25);
 | 
			
		||||
  &:hover {
 | 
			
		||||
    color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,8 @@ import styled from 'styled-components';
 | 
			
		||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
import ActivityMessage from './ActivityMessage';
 | 
			
		||||
import Task from 'shared/icons/Task';
 | 
			
		||||
import {
 | 
			
		||||
  ActivityItemHeader,
 | 
			
		||||
  ActivityItemTimestamp,
 | 
			
		||||
  ActivityItem,
 | 
			
		||||
  TaskDetailLabel,
 | 
			
		||||
  CommentContainer,
 | 
			
		||||
  MetaDetailContent,
 | 
			
		||||
@@ -71,13 +67,9 @@ import {
 | 
			
		||||
  CommentInnerWrapper,
 | 
			
		||||
  ActivitySection,
 | 
			
		||||
  TaskDetailsEditor,
 | 
			
		||||
  ActivityItemHeaderUser,
 | 
			
		||||
  ActivityItemHeaderTitle,
 | 
			
		||||
  ActivityItemHeaderTitleName,
 | 
			
		||||
} from './Styles';
 | 
			
		||||
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
 | 
			
		||||
import onDragEnd from './onDragEnd';
 | 
			
		||||
import TaskAssignee from 'shared/components/TaskAssignee';
 | 
			
		||||
 | 
			
		||||
const ChecklistContainer = styled.div``;
 | 
			
		||||
 | 
			
		||||
@@ -433,36 +425,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
 | 
			
		||||
          <TabBarSection>
 | 
			
		||||
            <TabBarItem>Activity</TabBarItem>
 | 
			
		||||
          </TabBarSection>
 | 
			
		||||
          <ActivitySection>
 | 
			
		||||
            {task.activity &&
 | 
			
		||||
              task.activity.map(activity => (
 | 
			
		||||
                <ActivityItem>
 | 
			
		||||
                  <ActivityItemHeaderUser
 | 
			
		||||
                    size={32}
 | 
			
		||||
                    member={{
 | 
			
		||||
                      id: activity.causedBy.id,
 | 
			
		||||
                      fullName: activity.causedBy.fullName,
 | 
			
		||||
                      profileIcon: activity.causedBy.profileIcon
 | 
			
		||||
                        ? activity.causedBy.profileIcon
 | 
			
		||||
                        : {
 | 
			
		||||
                            url: null,
 | 
			
		||||
                            initials: activity.causedBy.fullName.charAt(0),
 | 
			
		||||
                            bgColor: '#fff',
 | 
			
		||||
                          },
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
                  <ActivityItemHeader>
 | 
			
		||||
                    <ActivityItemHeaderTitle>
 | 
			
		||||
                      <ActivityItemHeaderTitleName>{activity.causedBy.fullName}</ActivityItemHeaderTitleName>
 | 
			
		||||
                      <ActivityMessage type={activity.type} data={activity.data} />
 | 
			
		||||
                    </ActivityItemHeaderTitle>
 | 
			
		||||
                    <ActivityItemTimestamp margin={0}>
 | 
			
		||||
                      {dayjs(activity.createdAt).format('MMM D [at] h:mm A')}
 | 
			
		||||
                    </ActivityItemTimestamp>
 | 
			
		||||
                  </ActivityItemHeader>
 | 
			
		||||
                </ActivityItem>
 | 
			
		||||
              ))}
 | 
			
		||||
          </ActivitySection>
 | 
			
		||||
          <ActivitySection />
 | 
			
		||||
        </InnerContentContainer>
 | 
			
		||||
        <CommentContainer>
 | 
			
		||||
          {me && (
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ const Textarea = styled(TextareaAutosize)`
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  padding: 3px 10px 3px 8px;
 | 
			
		||||
  &:focus {
 | 
			
		||||
    box-shadow: ${props => props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
    box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,7 @@ export const ProjectMember = styled(TaskAssignee)<{ zIndex: number }>`
 | 
			
		||||
  z-index: ${props => props.zIndex};
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  box-shadow: 0 0 0 2px ${props => props.theme.colors.bg.primary},
 | 
			
		||||
    inset 0 0 0 1px ${props => mixin.rgba(props.theme.colors.bg.primary, 0.07)};
 | 
			
		||||
  box-shadow: 0 0 0 2px rgba(16, 22, 58), inset 0 0 0 1px rgba(16, 22, 58, 0.07);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const NavbarWrapper = styled.div`
 | 
			
		||||
@@ -29,9 +28,9 @@ export const NavbarHeader = styled.header`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  background: ${props => props.theme.colors.bg.primary};
 | 
			
		||||
  background: rgb(16, 22, 58);
 | 
			
		||||
  box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.05);
 | 
			
		||||
  border-bottom: 1px solid ${props => mixin.rgba(props.theme.colors.alternate, 0.65)};
 | 
			
		||||
  border-bottom: 1px solid rgba(65, 69, 97, 0.65);
 | 
			
		||||
`;
 | 
			
		||||
export const Breadcrumbs = styled.div`
 | 
			
		||||
  color: rgb(94, 108, 132);
 | 
			
		||||
@@ -125,7 +124,7 @@ export const ProjectTabs = styled.div`
 | 
			
		||||
 | 
			
		||||
export const ProjectTab = styled(NavLink)`
 | 
			
		||||
  font-size: 80%;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  display: flex;
 | 
			
		||||
@@ -142,22 +141,22 @@ export const ProjectTab = styled(NavLink)`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    box-shadow: inset 0 -2px ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    color: ${props => props.theme.colors.text.secondary};
 | 
			
		||||
    box-shadow: inset 0 -2px rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.text.secondary});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.active {
 | 
			
		||||
    box-shadow: inset 0 -2px ${props => props.theme.colors.secondary};
 | 
			
		||||
    color: ${props => props.theme.colors.secondary};
 | 
			
		||||
    box-shadow: inset 0 -2px rgba(${props => props.theme.colors.secondary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.secondary});
 | 
			
		||||
  }
 | 
			
		||||
  &.active:hover {
 | 
			
		||||
    box-shadow: inset 0 -2px ${props => props.theme.colors.secondary};
 | 
			
		||||
    color: ${props => props.theme.colors.secondary};
 | 
			
		||||
    box-shadow: inset 0 -2px rgba(${props => props.theme.colors.secondary});
 | 
			
		||||
    color: rgba(${props => props.theme.colors.secondary});
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ProjectName = styled.h1`
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  padding: 3px 10px 3px 8px;
 | 
			
		||||
@@ -186,7 +185,7 @@ export const ProjectNameTextarea = styled(TextareaAutosize)`
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  padding: 3px 10px 3px 8px;
 | 
			
		||||
  &:focus {
 | 
			
		||||
    box-shadow: ${props => props.theme.colors.primary} 0px 0px 0px 1px;
 | 
			
		||||
    box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -204,7 +203,7 @@ export const ProjectSwitcher = styled.button`
 | 
			
		||||
  color: #c2c6dc;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -228,7 +227,7 @@ export const ProjectSettingsButton = styled.button`
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: ${props => props.theme.colors.primary};
 | 
			
		||||
    background: rgb(115, 103, 240);
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +243,7 @@ export const ProjectFinder = styled(Button)`
 | 
			
		||||
 | 
			
		||||
export const NavSeparator = styled.div`
 | 
			
		||||
  width: 1px;
 | 
			
		||||
  background: ${props => props.theme.colors.border};
 | 
			
		||||
  background: rgba(${props => props.theme.colors.border});
 | 
			
		||||
  height: 34px;
 | 
			
		||||
  margin: 0 20px;
 | 
			
		||||
`;
 | 
			
		||||
@@ -261,11 +260,11 @@ export const LogoContainer = styled(Link)`
 | 
			
		||||
 | 
			
		||||
export const TaskcafeTitle = styled.h2`
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  color: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  color: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const TaskcafeLogo = styled(Taskcafe)`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  stroke: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  stroke: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
 | 
			
		||||
import NormalizeStyles from 'App/NormalizeStyles';
 | 
			
		||||
import BaseStyles from 'App/BaseStyles';
 | 
			
		||||
import { action } from '@storybook/addon-actions';
 | 
			
		||||
import DropdownMenu from 'shared/components/DropdownMenu';
 | 
			
		||||
import TopNavbar from '.';
 | 
			
		||||
import theme from '../../../App/ThemeStyles';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  component: TopNavbar,
 | 
			
		||||
@@ -15,7 +15,7 @@ export default {
 | 
			
		||||
    backgrounds: [
 | 
			
		||||
      { name: 'white', value: '#ffffff' },
 | 
			
		||||
      { name: 'gray', value: '#f8f8f8' },
 | 
			
		||||
      { name: 'darkBlue', value: theme.colors.bg.secondary, default: true },
 | 
			
		||||
      { name: 'darkBlue', value: '#262c49', default: true },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import ProfileIcon from 'shared/components/ProfileIcon';
 | 
			
		||||
import { usePopup } from 'shared/components/PopupMenu';
 | 
			
		||||
import { RoleCode } from 'shared/generated/graphql';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import { useHistory } from 'react-router';
 | 
			
		||||
import {
 | 
			
		||||
  TaskcafeLogo,
 | 
			
		||||
  TaskcafeTitle,
 | 
			
		||||
@@ -31,6 +30,7 @@ import {
 | 
			
		||||
  ProjectMember,
 | 
			
		||||
  ProjectMembers,
 | 
			
		||||
} from './Styles';
 | 
			
		||||
import { useHistory } from 'react-router';
 | 
			
		||||
 | 
			
		||||
type IconContainerProps = {
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
@@ -309,7 +309,7 @@ const NavBar: React.FC<NavBarProps> = ({
 | 
			
		||||
          <IconContainer disabled onClick={NOOP}>
 | 
			
		||||
            <CheckCircle width={20} height={20} />
 | 
			
		||||
          </IconContainer>
 | 
			
		||||
          <IconContainer disabled onClick={NOOP}>
 | 
			
		||||
          <IconContainer onClick={() => history.push('/outline')}>
 | 
			
		||||
            <ListUnordered width={20} height={20} />
 | 
			
		||||
          </IconContainer>
 | 
			
		||||
          <IconContainer disabled onClick={onNotificationClick}>
 | 
			
		||||
 
 | 
			
		||||
@@ -168,41 +168,6 @@ export type TaskBadges = {
 | 
			
		||||
  checklist?: Maybe<ChecklistBadge>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CausedBy = {
 | 
			
		||||
   __typename?: 'CausedBy';
 | 
			
		||||
  id: Scalars['ID'];
 | 
			
		||||
  fullName: Scalars['String'];
 | 
			
		||||
  profileIcon?: Maybe<ProfileIcon>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TaskActivityData = {
 | 
			
		||||
   __typename?: 'TaskActivityData';
 | 
			
		||||
  name: Scalars['String'];
 | 
			
		||||
  value: Scalars['String'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export enum ActivityType {
 | 
			
		||||
  TaskAdded = 'TASK_ADDED',
 | 
			
		||||
  TaskMoved = 'TASK_MOVED',
 | 
			
		||||
  TaskMarkedComplete = 'TASK_MARKED_COMPLETE',
 | 
			
		||||
  TaskMarkedIncomplete = 'TASK_MARKED_INCOMPLETE',
 | 
			
		||||
  TaskDueDateChanged = 'TASK_DUE_DATE_CHANGED',
 | 
			
		||||
  TaskDueDateAdded = 'TASK_DUE_DATE_ADDED',
 | 
			
		||||
  TaskDueDateRemoved = 'TASK_DUE_DATE_REMOVED',
 | 
			
		||||
  TaskChecklistChanged = 'TASK_CHECKLIST_CHANGED',
 | 
			
		||||
  TaskChecklistAdded = 'TASK_CHECKLIST_ADDED',
 | 
			
		||||
  TaskChecklistRemoved = 'TASK_CHECKLIST_REMOVED'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type TaskActivity = {
 | 
			
		||||
   __typename?: 'TaskActivity';
 | 
			
		||||
  id: Scalars['ID'];
 | 
			
		||||
  type: ActivityType;
 | 
			
		||||
  data: Array<TaskActivityData>;
 | 
			
		||||
  causedBy: CausedBy;
 | 
			
		||||
  createdAt: Scalars['Time'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Task = {
 | 
			
		||||
   __typename?: 'Task';
 | 
			
		||||
  id: Scalars['ID'];
 | 
			
		||||
@@ -218,7 +183,6 @@ export type Task = {
 | 
			
		||||
  labels: Array<TaskLabel>;
 | 
			
		||||
  checklists: Array<TaskChecklist>;
 | 
			
		||||
  badges: TaskBadges;
 | 
			
		||||
  activity: Array<TaskActivity>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Organization = {
 | 
			
		||||
@@ -245,11 +209,6 @@ export type TaskChecklist = {
 | 
			
		||||
  items: Array<TaskChecklistItem>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export enum ShareStatus {
 | 
			
		||||
  Invited = 'INVITED',
 | 
			
		||||
  Joined = 'JOINED'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum RoleLevel {
 | 
			
		||||
  Admin = 'ADMIN',
 | 
			
		||||
  Member = 'MEMBER'
 | 
			
		||||
@@ -1091,16 +1050,17 @@ export type DeleteInvitedUserAccountPayload = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type MemberSearchFilter = {
 | 
			
		||||
  searchFilter: Scalars['String'];
 | 
			
		||||
  SearchFilter: Scalars['String'];
 | 
			
		||||
  projectID?: Maybe<Scalars['UUID']>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type MemberSearchResult = {
 | 
			
		||||
   __typename?: 'MemberSearchResult';
 | 
			
		||||
  similarity: Scalars['Int'];
 | 
			
		||||
  id: Scalars['String'];
 | 
			
		||||
  user?: Maybe<UserAccount>;
 | 
			
		||||
  status: ShareStatus;
 | 
			
		||||
  user: UserAccount;
 | 
			
		||||
  confirmed: Scalars['Boolean'];
 | 
			
		||||
  invited: Scalars['Boolean'];
 | 
			
		||||
  joined: Scalars['Boolean'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UpdateUserInfoPayload = {
 | 
			
		||||
@@ -1384,21 +1344,7 @@ export type FindTaskQuery = (
 | 
			
		||||
    & { taskGroup: (
 | 
			
		||||
      { __typename?: 'TaskGroup' }
 | 
			
		||||
      & Pick<TaskGroup, 'id' | 'name'>
 | 
			
		||||
    ), activity: Array<(
 | 
			
		||||
      { __typename?: 'TaskActivity' }
 | 
			
		||||
      & Pick<TaskActivity, 'id' | 'type' | 'createdAt'>
 | 
			
		||||
      & { causedBy: (
 | 
			
		||||
        { __typename?: 'CausedBy' }
 | 
			
		||||
        & Pick<CausedBy, 'id' | 'fullName'>
 | 
			
		||||
        & { profileIcon?: Maybe<(
 | 
			
		||||
          { __typename?: 'ProfileIcon' }
 | 
			
		||||
          & Pick<ProfileIcon, 'initials' | 'bgColor' | 'url'>
 | 
			
		||||
        )> }
 | 
			
		||||
      ), data: Array<(
 | 
			
		||||
        { __typename?: 'TaskActivityData' }
 | 
			
		||||
        & Pick<TaskActivityData, 'name' | 'value'>
 | 
			
		||||
      )> }
 | 
			
		||||
    )>, badges: (
 | 
			
		||||
    ), badges: (
 | 
			
		||||
      { __typename?: 'TaskBadges' }
 | 
			
		||||
      & { checklist?: Maybe<(
 | 
			
		||||
        { __typename?: 'ChecklistBadge' }
 | 
			
		||||
@@ -2879,24 +2825,6 @@ export const FindTaskDocument = gql`
 | 
			
		||||
      id
 | 
			
		||||
      name
 | 
			
		||||
    }
 | 
			
		||||
    activity {
 | 
			
		||||
      id
 | 
			
		||||
      type
 | 
			
		||||
      causedBy {
 | 
			
		||||
        id
 | 
			
		||||
        fullName
 | 
			
		||||
        profileIcon {
 | 
			
		||||
          initials
 | 
			
		||||
          bgColor
 | 
			
		||||
          url
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      createdAt
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    badges {
 | 
			
		||||
      checklist {
 | 
			
		||||
        total
 | 
			
		||||
 
 | 
			
		||||
@@ -10,24 +10,6 @@ query findTask($taskID: UUID!) {
 | 
			
		||||
      id
 | 
			
		||||
      name
 | 
			
		||||
    }
 | 
			
		||||
    activity {
 | 
			
		||||
      id
 | 
			
		||||
      type
 | 
			
		||||
      causedBy {
 | 
			
		||||
        id
 | 
			
		||||
        fullName
 | 
			
		||||
        profileIcon {
 | 
			
		||||
          initials
 | 
			
		||||
          bgColor
 | 
			
		||||
          url
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      createdAt
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    badges {
 | 
			
		||||
      checklist {
 | 
			
		||||
        total
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@ type Props = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Svg = styled.svg`
 | 
			
		||||
  fill: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  stroke: ${props => props.theme.colors.text.primary};
 | 
			
		||||
  fill: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
  stroke: rgba(${props => props.theme.colors.text.primary});
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Icon: React.FC<Props> = ({ width, height, viewBox, className, onClick, children }) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export function updateApolloCache<T>(
 | 
			
		||||
  update: UpdateCacheFn<T>,
 | 
			
		||||
  variables?: object,
 | 
			
		||||
) {
 | 
			
		||||
  let queryArgs: DataProxy.Query<any, any>;
 | 
			
		||||
  let queryArgs: DataProxy.Query<any>;
 | 
			
		||||
  if (variables) {
 | 
			
		||||
    queryArgs = {
 | 
			
		||||
      query: document,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								frontend/src/styled.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/src/styled.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -10,7 +10,6 @@ declare module 'styled-components' {
 | 
			
		||||
    };
 | 
			
		||||
    colors: {
 | 
			
		||||
      [key: string]: any;
 | 
			
		||||
      multiColors: string[];
 | 
			
		||||
      primary: string;
 | 
			
		||||
      secondary: string;
 | 
			
		||||
      success: string;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								frontend/src/taskcafe.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								frontend/src/taskcafe.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -61,7 +61,7 @@ type User = TaskUser & {
 | 
			
		||||
 | 
			
		||||
type RefreshTokenResponse = {
 | 
			
		||||
  accessToken: string;
 | 
			
		||||
  setup?: null | { confirmToken: string };
 | 
			
		||||
  isInstalled: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type LoginFormData = {
 | 
			
		||||
@@ -91,14 +91,7 @@ type ErrorOption =
 | 
			
		||||
      type: string;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
type SetFailedFn = () => void;
 | 
			
		||||
type ConfirmProps = {
 | 
			
		||||
  hasConfirmToken: boolean;
 | 
			
		||||
  onConfirmUser: (setFailed: SetFailedFn) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type RegisterProps = {
 | 
			
		||||
  registered?: boolean;
 | 
			
		||||
  onSubmit: (
 | 
			
		||||
    data: RegisterFormData,
 | 
			
		||||
    setComplete: (val: boolean) => void,
 | 
			
		||||
@@ -206,14 +199,10 @@ type ImpactAction = {
 | 
			
		||||
type ItemElement = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  parent: null | string;
 | 
			
		||||
  text: string;
 | 
			
		||||
  focus: null | { caret: number | null };
 | 
			
		||||
  zooming?: { x: number; y: number };
 | 
			
		||||
  position: number;
 | 
			
		||||
  collapsed: boolean;
 | 
			
		||||
  children?: Array<ItemElement>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type NodeDimensions = {
 | 
			
		||||
  entry: React.RefObject<HTMLElement>;
 | 
			
		||||
  children: React.RefObject<HTMLElement> | null;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								frontend/src/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								frontend/src/types.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,10 +1,3 @@
 | 
			
		||||
type ProjectLabel = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  createdDate: string;
 | 
			
		||||
  name?: string | null;
 | 
			
		||||
  labelColor: LabelColor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ProfileIcon = {
 | 
			
		||||
  url?: string | null;
 | 
			
		||||
  initials?: string | null;
 | 
			
		||||
@@ -63,24 +56,6 @@ type TaskBadges = {
 | 
			
		||||
  checklist?: ChecklistBadge | null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type TaskActivityData = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  value: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type CausedBy = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  fullName: string;
 | 
			
		||||
  profileIcon?: null | ProfileIcon;
 | 
			
		||||
};
 | 
			
		||||
type TaskActivity = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  type: any;
 | 
			
		||||
  data: Array<TaskActivityData>;
 | 
			
		||||
  causedBy: CausedBy;
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Task = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  taskGroup: InnerTaskGroup;
 | 
			
		||||
@@ -94,7 +69,6 @@ type Task = {
 | 
			
		||||
  description?: string | null;
 | 
			
		||||
  assigned?: Array<TaskUser>;
 | 
			
		||||
  checklists?: Array<TaskChecklist> | null;
 | 
			
		||||
  activity?: Array<TaskActivity> | null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Project = {
 | 
			
		||||
@@ -115,3 +89,10 @@ type Team = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ProjectLabel = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  createdDate: string;
 | 
			
		||||
  name?: string | null;
 | 
			
		||||
  labelColor: LabelColor;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9166
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										9166
									
								
								frontend/yarn.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -13,7 +13,6 @@ require (
 | 
			
		||||
	github.com/lib/pq v1.3.0
 | 
			
		||||
	github.com/lithammer/fuzzysearch v1.1.0
 | 
			
		||||
	github.com/magefile/mage v1.9.0
 | 
			
		||||
	github.com/matcornic/hermes/v2 v2.1.0
 | 
			
		||||
	github.com/pelletier/go-toml v1.8.0 // indirect
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0
 | 
			
		||||
@@ -23,5 +22,4 @@ require (
 | 
			
		||||
	github.com/spf13/viper v1.4.0
 | 
			
		||||
	github.com/vektah/gqlparser/v2 v2.0.1
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
 | 
			
		||||
	gopkg.in/mail.v2 v2.3.1
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								go.sum
									
									
									
									
									
								
							@@ -50,17 +50,11 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
 | 
			
		||||
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
 | 
			
		||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs=
 | 
			
		||||
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
 | 
			
		||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
 | 
			
		||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 | 
			
		||||
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
 | 
			
		||||
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 | 
			
		||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
 | 
			
		||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 | 
			
		||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 | 
			
		||||
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9Ct2d6sC7ol0/ynxc2pO1cpGUM+f4t5adg=
 | 
			
		||||
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU=
 | 
			
		||||
github.com/RichardKnop/machinery v1.9.1 h1:Q4WInk0OWGMbXDH3Q8dm8uadN5Wcyquc+7IcM4p9ECs=
 | 
			
		||||
@@ -76,10 +70,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 | 
			
		||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
			
		||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
 | 
			
		||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 | 
			
		||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
 | 
			
		||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 | 
			
		||||
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
 | 
			
		||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
 | 
			
		||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 | 
			
		||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
 | 
			
		||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
 | 
			
		||||
@@ -161,7 +151,6 @@ github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm
 | 
			
		||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
 | 
			
		||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 | 
			
		||||
@@ -269,7 +258,6 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
 | 
			
		||||
github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
			
		||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 | 
			
		||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
@@ -277,8 +265,6 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 | 
			
		||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 | 
			
		||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 | 
			
		||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 | 
			
		||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 | 
			
		||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 | 
			
		||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 | 
			
		||||
@@ -305,11 +291,7 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
 | 
			
		||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 | 
			
		||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 | 
			
		||||
@@ -336,8 +318,6 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
 | 
			
		||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 | 
			
		||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 | 
			
		||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
 | 
			
		||||
@@ -388,9 +368,6 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
 | 
			
		||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 | 
			
		||||
github.com/matcornic/hermes v1.2.0 h1:AuqZpYcTOtTB7cahdevLfnhIpfzmpqw5Czv8vpdnFDU=
 | 
			
		||||
github.com/matcornic/hermes/v2 v2.1.0 h1:9TDYFBPFv6mcXanaDmRDEp/RTWj0dTTi+LpFnnnfNWc=
 | 
			
		||||
github.com/matcornic/hermes/v2 v2.1.0/go.mod h1:2+ziJeoyRfaLiATIL8VZ7f9hpzH4oDHqTmn0bhrsgVI=
 | 
			
		||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
 | 
			
		||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 | 
			
		||||
@@ -399,8 +376,6 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 | 
			
		||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
 | 
			
		||||
@@ -419,8 +394,6 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w
 | 
			
		||||
github.com/neo4j-drivers/gobolt v1.7.4/go.mod h1:O9AUbip4Dgre+CD3p40dnMD4a4r52QBIfblg5k7CTbE=
 | 
			
		||||
github.com/neo4j/neo4j-go-driver v1.7.4/go.mod h1:aPO0vVr+WnhEJne+FgFjfsjzAnssPFLucHgGZ76Zb/U=
 | 
			
		||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 | 
			
		||||
@@ -509,8 +482,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
			
		||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
			
		||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
 | 
			
		||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 | 
			
		||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
 | 
			
		||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
 | 
			
		||||
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
 | 
			
		||||
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
@@ -534,10 +505,6 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
 | 
			
		||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 | 
			
		||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
 | 
			
		||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
 | 
			
		||||
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ=
 | 
			
		||||
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w=
 | 
			
		||||
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe h1:9YnI5plmy+ad6BM+JCLJb2ZV7/TNiE5l7SNKfumYKgc=
 | 
			
		||||
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe/go.mod h1:JTFJA/t820uFDoyPpErFQ3rb3amdZoPtxcKervG0OE4=
 | 
			
		||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
 | 
			
		||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
 | 
			
		||||
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
 | 
			
		||||
@@ -576,7 +543,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
 | 
			
		||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181029175232-7e6ffbd03851/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 | 
			
		||||
@@ -628,7 +594,6 @@ golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
 | 
			
		||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 | 
			
		||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
@@ -692,7 +657,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
 | 
			
		||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190225065934-cc5685c2db12/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
@@ -901,8 +865,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
 | 
			
		||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 | 
			
		||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
 | 
			
		||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
 | 
			
		||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
 
 | 
			
		||||
@@ -34,12 +34,11 @@ func newMigrateCmd() *cobra.Command {
 | 
			
		||||
		Short: "Run the database schema migrations",
 | 
			
		||||
		Long:  "Run the database schema migrations",
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s port=%s sslmode=disable",
 | 
			
		||||
			connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
 | 
			
		||||
				viper.GetString("database.user"),
 | 
			
		||||
				viper.GetString("database.password"),
 | 
			
		||||
				viper.GetString("database.host"),
 | 
			
		||||
				viper.GetString("database.name"),
 | 
			
		||||
				viper.GetString("database.port"),
 | 
			
		||||
			)
 | 
			
		||||
			db, err := sqlx.Connect("postgres", connection)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
@@ -104,22 +103,6 @@ type Task struct {
 | 
			
		||||
	CompletedAt sql.NullTime   `json:"completed_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivity struct {
 | 
			
		||||
	TaskActivityID uuid.UUID       `json:"task_activity_id"`
 | 
			
		||||
	Active         bool            `json:"active"`
 | 
			
		||||
	TaskID         uuid.UUID       `json:"task_id"`
 | 
			
		||||
	CreatedAt      time.Time       `json:"created_at"`
 | 
			
		||||
	CausedBy       uuid.UUID       `json:"caused_by"`
 | 
			
		||||
	ActivityTypeID int32           `json:"activity_type_id"`
 | 
			
		||||
	Data           json.RawMessage `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivityType struct {
 | 
			
		||||
	TaskActivityTypeID int32  `json:"task_activity_type_id"`
 | 
			
		||||
	Code               string `json:"code"`
 | 
			
		||||
	Template           string `json:"template"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskAssigned struct {
 | 
			
		||||
	TaskAssignedID uuid.UUID `json:"task_assigned_id"`
 | 
			
		||||
	TaskID         uuid.UUID `json:"task_id"`
 | 
			
		||||
@@ -187,12 +170,6 @@ type UserAccount struct {
 | 
			
		||||
	ProfileAvatarUrl sql.NullString `json:"profile_avatar_url"`
 | 
			
		||||
	RoleCode         string         `json:"role_code"`
 | 
			
		||||
	Bio              string         `json:"bio"`
 | 
			
		||||
	Active           bool           `json:"active"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UserAccountConfirmToken struct {
 | 
			
		||||
	ConfirmTokenID uuid.UUID `json:"confirm_token_id"`
 | 
			
		||||
	Email          string    `json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UserAccountInvited struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Querier interface {
 | 
			
		||||
	CreateConfirmToken(ctx context.Context, email string) (UserAccountConfirmToken, error)
 | 
			
		||||
	CreateInvitedProjectMember(ctx context.Context, arg CreateInvitedProjectMemberParams) (ProjectMemberInvited, error)
 | 
			
		||||
	CreateInvitedUser(ctx context.Context, email string) (UserAccountInvited, error)
 | 
			
		||||
	CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
 | 
			
		||||
@@ -23,7 +22,6 @@ type Querier interface {
 | 
			
		||||
	CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
 | 
			
		||||
	CreateSystemOption(ctx context.Context, arg CreateSystemOptionParams) (SystemOption, error)
 | 
			
		||||
	CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
 | 
			
		||||
	CreateTaskActivity(ctx context.Context, arg CreateTaskActivityParams) (TaskActivity, error)
 | 
			
		||||
	CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (Task, error)
 | 
			
		||||
	CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
 | 
			
		||||
	CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
 | 
			
		||||
@@ -34,14 +32,12 @@ type Querier interface {
 | 
			
		||||
	CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error)
 | 
			
		||||
	CreateTeamProject(ctx context.Context, arg CreateTeamProjectParams) (Project, error)
 | 
			
		||||
	CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
 | 
			
		||||
	DeleteConfirmTokenForEmail(ctx context.Context, email string) error
 | 
			
		||||
	DeleteExpiredTokens(ctx context.Context) error
 | 
			
		||||
	DeleteInvitedProjectMemberByID(ctx context.Context, projectMemberInvitedID uuid.UUID) error
 | 
			
		||||
	DeleteInvitedUserAccount(ctx context.Context, userAccountInvitedID uuid.UUID) (UserAccountInvited, error)
 | 
			
		||||
	DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
 | 
			
		||||
	DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error
 | 
			
		||||
	DeleteProjectMember(ctx context.Context, arg DeleteProjectMemberParams) error
 | 
			
		||||
	DeleteProjectMemberInvitedForEmail(ctx context.Context, email string) error
 | 
			
		||||
	DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
 | 
			
		||||
	DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
 | 
			
		||||
	DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
 | 
			
		||||
@@ -55,8 +51,6 @@ type Querier interface {
 | 
			
		||||
	DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
 | 
			
		||||
	DeleteTeamMember(ctx context.Context, arg DeleteTeamMemberParams) error
 | 
			
		||||
	DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
 | 
			
		||||
	DeleteUserAccountInvitedForEmail(ctx context.Context, email string) error
 | 
			
		||||
	GetActivityForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskActivity, error)
 | 
			
		||||
	GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error)
 | 
			
		||||
	GetAllOrganizations(ctx context.Context) ([]Organization, error)
 | 
			
		||||
	GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
 | 
			
		||||
@@ -67,8 +61,6 @@ type Querier interface {
 | 
			
		||||
	GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
 | 
			
		||||
	GetAllVisibleProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error)
 | 
			
		||||
	GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
 | 
			
		||||
	GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
 | 
			
		||||
	GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
 | 
			
		||||
	GetEntityForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetEntityForNotificationIDRow, error)
 | 
			
		||||
	GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
 | 
			
		||||
	GetInvitedMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]GetInvitedMembersForProjectIDRow, error)
 | 
			
		||||
@@ -76,7 +68,6 @@ type Querier interface {
 | 
			
		||||
	GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
 | 
			
		||||
	GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
 | 
			
		||||
	GetLabelColors(ctx context.Context) ([]LabelColor, error)
 | 
			
		||||
	GetLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) (GetLastMoveForTaskIDRow, error)
 | 
			
		||||
	GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error)
 | 
			
		||||
	GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
 | 
			
		||||
	GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
 | 
			
		||||
@@ -92,7 +83,6 @@ type Querier interface {
 | 
			
		||||
	GetProjectMemberInvitedIDByEmail(ctx context.Context, email string) (GetProjectMemberInvitedIDByEmailRow, error)
 | 
			
		||||
	GetProjectMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectMember, error)
 | 
			
		||||
	GetProjectRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetProjectRolesForUserIDRow, error)
 | 
			
		||||
	GetProjectsForInvitedMember(ctx context.Context, email string) ([]uuid.UUID, error)
 | 
			
		||||
	GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
 | 
			
		||||
	GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRoleForProjectMemberByUserIDParams) (Role, error)
 | 
			
		||||
	GetRoleForTeamMember(ctx context.Context, arg GetRoleForTeamMemberParams) (Role, error)
 | 
			
		||||
@@ -116,19 +106,12 @@ type Querier interface {
 | 
			
		||||
	GetTeamRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetTeamRolesForUserIDRow, error)
 | 
			
		||||
	GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
 | 
			
		||||
	GetTeamsForUserIDWhereAdmin(ctx context.Context, userID uuid.UUID) ([]Team, error)
 | 
			
		||||
	GetTemplateForActivityID(ctx context.Context, taskActivityTypeID int32) (string, error)
 | 
			
		||||
	GetUserAccountByEmail(ctx context.Context, email string) (UserAccount, error)
 | 
			
		||||
	GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
 | 
			
		||||
	GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
 | 
			
		||||
	GetUserRolesForProject(ctx context.Context, arg GetUserRolesForProjectParams) (GetUserRolesForProjectRow, error)
 | 
			
		||||
	HasActiveUser(ctx context.Context) (bool, error)
 | 
			
		||||
	HasAnyUser(ctx context.Context) (bool, error)
 | 
			
		||||
	SetFirstUserActive(ctx context.Context) (UserAccount, error)
 | 
			
		||||
	SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error
 | 
			
		||||
	SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
 | 
			
		||||
	SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
 | 
			
		||||
	SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
 | 
			
		||||
	SetUserActiveByEmail(ctx context.Context, email string) (UserAccount, error)
 | 
			
		||||
	SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
 | 
			
		||||
	UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
 | 
			
		||||
	UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
-- name: CreateTaskActivity :one
 | 
			
		||||
INSERT INTO task_activity (task_id, caused_by, created_at, activity_type_id, data)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5) RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: GetActivityForTaskID :many
 | 
			
		||||
SELECT * FROM task_activity WHERE task_id = $1 AND active = true;
 | 
			
		||||
 | 
			
		||||
-- name: GetTemplateForActivityID :one
 | 
			
		||||
SELECT template FROM task_activity_type WHERE task_activity_type_id = $1;
 | 
			
		||||
 | 
			
		||||
-- name: GetLastMoveForTaskID :one
 | 
			
		||||
SELECT active, created_at, data->>'CurTaskGroupID' AS cur_task_group_id, data->>'PrevTaskGroupID' AS prev_task_group_id FROM task_activity
 | 
			
		||||
  WHERE task_id = $1 AND activity_type_id = 2 AND created_at >= NOW() - INTERVAL '5 minutes'
 | 
			
		||||
ORDER BY created_at DESC LIMIT 1;
 | 
			
		||||
 | 
			
		||||
-- name: SetInactiveLastMoveForTaskID :exec
 | 
			
		||||
UPDATE task_activity SET active = false WHERE task_activity_id = (
 | 
			
		||||
  SELECT task_activity_id FROM task_activity AS ta
 | 
			
		||||
  WHERE ta.activity_type_id = 2 AND ta.task_id = $1
 | 
			
		||||
  AND ta.created_at >= NOW() - INTERVAL '5 minutes'
 | 
			
		||||
  ORDER BY created_at DESC LIMIT 1
 | 
			
		||||
);
 | 
			
		||||
@@ -7,12 +7,9 @@ SELECT * FROM user_account WHERE username != 'system';
 | 
			
		||||
-- name: GetUserAccountByUsername :one
 | 
			
		||||
SELECT * FROM user_account WHERE username = $1;
 | 
			
		||||
 | 
			
		||||
-- name: GetUserAccountByEmail :one
 | 
			
		||||
SELECT * FROM user_account WHERE email = $1;
 | 
			
		||||
 | 
			
		||||
-- name: CreateUserAccount :one
 | 
			
		||||
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code, active)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
 | 
			
		||||
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: UpdateUserAccountProfileAvatarURL :one
 | 
			
		||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
 | 
			
		||||
@@ -56,48 +53,3 @@ SELECT * FROM user_account_invited;
 | 
			
		||||
 | 
			
		||||
-- name: DeleteInvitedUserAccount :one
 | 
			
		||||
DELETE FROM user_account_invited WHERE user_account_invited_id = $1 RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: HasAnyUser :one
 | 
			
		||||
SELECT EXISTS(SELECT 1 FROM user_account WHERE username != 'system');
 | 
			
		||||
 | 
			
		||||
-- name: HasActiveUser :one
 | 
			
		||||
SELECT EXISTS(SELECT 1 FROM user_account WHERE username != 'system' AND active = true);
 | 
			
		||||
 | 
			
		||||
-- name: CreateConfirmToken :one
 | 
			
		||||
INSERT INTO user_account_confirm_token (email) VALUES ($1) RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: GetConfirmTokenByEmail :one
 | 
			
		||||
SELECT * FROM user_account_confirm_token WHERE email = $1;
 | 
			
		||||
 | 
			
		||||
-- name: GetConfirmTokenByID :one
 | 
			
		||||
SELECT * FROM user_account_confirm_token WHERE confirm_token_id = $1;
 | 
			
		||||
 | 
			
		||||
-- name: SetFirstUserActive :one
 | 
			
		||||
UPDATE user_account SET active = true WHERE user_id = (
 | 
			
		||||
  SELECT user_id from user_account WHERE active = false LIMIT 1
 | 
			
		||||
) RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: SetUserActiveByEmail :one
 | 
			
		||||
UPDATE user_account SET active = true WHERE email = $1 RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: GetProjectsForInvitedMember :many
 | 
			
		||||
SELECT project_id FROM user_account_invited AS uai
 | 
			
		||||
  INNER JOIN project_member_invited AS pmi
 | 
			
		||||
  ON pmi.user_account_invited_id = uai.user_account_invited_id
 | 
			
		||||
  WHERE uai.email = $1;
 | 
			
		||||
 | 
			
		||||
-- name: DeleteProjectMemberInvitedForEmail :exec
 | 
			
		||||
DELETE FROM project_member_invited WHERE project_member_invited_id IN (
 | 
			
		||||
  SELECT pmi.project_member_invited_id FROM user_account_invited AS uai
 | 
			
		||||
  INNER JOIN project_member_invited AS pmi
 | 
			
		||||
  ON pmi.user_account_invited_id = uai.user_account_invited_id
 | 
			
		||||
  WHERE uai.email = $1
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- name: DeleteUserAccountInvitedForEmail :exec
 | 
			
		||||
DELETE FROM user_account_invited WHERE email = $1;
 | 
			
		||||
 | 
			
		||||
-- name: DeleteConfirmTokenForEmail :exec
 | 
			
		||||
DELETE FROM user_account_confirm_token WHERE email = $1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,131 +0,0 @@
 | 
			
		||||
// Code generated by sqlc. DO NOT EDIT.
 | 
			
		||||
// source: task_activity.sql
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const createTaskActivity = `-- name: CreateTaskActivity :one
 | 
			
		||||
INSERT INTO task_activity (task_id, caused_by, created_at, activity_type_id, data)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5) RETURNING task_activity_id, active, task_id, created_at, caused_by, activity_type_id, data
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type CreateTaskActivityParams struct {
 | 
			
		||||
	TaskID         uuid.UUID       `json:"task_id"`
 | 
			
		||||
	CausedBy       uuid.UUID       `json:"caused_by"`
 | 
			
		||||
	CreatedAt      time.Time       `json:"created_at"`
 | 
			
		||||
	ActivityTypeID int32           `json:"activity_type_id"`
 | 
			
		||||
	Data           json.RawMessage `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *Queries) CreateTaskActivity(ctx context.Context, arg CreateTaskActivityParams) (TaskActivity, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, createTaskActivity,
 | 
			
		||||
		arg.TaskID,
 | 
			
		||||
		arg.CausedBy,
 | 
			
		||||
		arg.CreatedAt,
 | 
			
		||||
		arg.ActivityTypeID,
 | 
			
		||||
		arg.Data,
 | 
			
		||||
	)
 | 
			
		||||
	var i TaskActivity
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.TaskActivityID,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
		&i.TaskID,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.CausedBy,
 | 
			
		||||
		&i.ActivityTypeID,
 | 
			
		||||
		&i.Data,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getActivityForTaskID = `-- name: GetActivityForTaskID :many
 | 
			
		||||
SELECT task_activity_id, active, task_id, created_at, caused_by, activity_type_id, data FROM task_activity WHERE task_id = $1 AND active = true
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetActivityForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskActivity, error) {
 | 
			
		||||
	rows, err := q.db.QueryContext(ctx, getActivityForTaskID, taskID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
	var items []TaskActivity
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		var i TaskActivity
 | 
			
		||||
		if err := rows.Scan(
 | 
			
		||||
			&i.TaskActivityID,
 | 
			
		||||
			&i.Active,
 | 
			
		||||
			&i.TaskID,
 | 
			
		||||
			&i.CreatedAt,
 | 
			
		||||
			&i.CausedBy,
 | 
			
		||||
			&i.ActivityTypeID,
 | 
			
		||||
			&i.Data,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		items = append(items, i)
 | 
			
		||||
	}
 | 
			
		||||
	if err := rows.Close(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := rows.Err(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return items, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getLastMoveForTaskID = `-- name: GetLastMoveForTaskID :one
 | 
			
		||||
SELECT active, created_at, data->>'CurTaskGroupID' AS cur_task_group_id, data->>'PrevTaskGroupID' AS prev_task_group_id FROM task_activity
 | 
			
		||||
  WHERE task_id = $1 AND activity_type_id = 2 AND created_at >= NOW() - INTERVAL '5 minutes'
 | 
			
		||||
ORDER BY created_at DESC LIMIT 1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type GetLastMoveForTaskIDRow struct {
 | 
			
		||||
	Active          bool        `json:"active"`
 | 
			
		||||
	CreatedAt       time.Time   `json:"created_at"`
 | 
			
		||||
	CurTaskGroupID  interface{} `json:"cur_task_group_id"`
 | 
			
		||||
	PrevTaskGroupID interface{} `json:"prev_task_group_id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) (GetLastMoveForTaskIDRow, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getLastMoveForTaskID, taskID)
 | 
			
		||||
	var i GetLastMoveForTaskIDRow
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.Active,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.CurTaskGroupID,
 | 
			
		||||
		&i.PrevTaskGroupID,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getTemplateForActivityID = `-- name: GetTemplateForActivityID :one
 | 
			
		||||
SELECT template FROM task_activity_type WHERE task_activity_type_id = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetTemplateForActivityID(ctx context.Context, taskActivityTypeID int32) (string, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getTemplateForActivityID, taskActivityTypeID)
 | 
			
		||||
	var template string
 | 
			
		||||
	err := row.Scan(&template)
 | 
			
		||||
	return template, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setInactiveLastMoveForTaskID = `-- name: SetInactiveLastMoveForTaskID :exec
 | 
			
		||||
UPDATE task_activity SET active = false WHERE task_activity_id = (
 | 
			
		||||
  SELECT task_activity_id FROM task_activity AS ta
 | 
			
		||||
  WHERE ta.activity_type_id = 2 AND ta.task_id = $1
 | 
			
		||||
  AND ta.created_at >= NOW() - INTERVAL '5 minutes'
 | 
			
		||||
  ORDER BY created_at DESC LIMIT 1
 | 
			
		||||
)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error {
 | 
			
		||||
	_, err := q.db.ExecContext(ctx, setInactiveLastMoveForTaskID, taskID)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -11,17 +11,6 @@ import (
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const createConfirmToken = `-- name: CreateConfirmToken :one
 | 
			
		||||
INSERT INTO user_account_confirm_token (email) VALUES ($1) RETURNING confirm_token_id, email
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) CreateConfirmToken(ctx context.Context, email string) (UserAccountConfirmToken, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, createConfirmToken, email)
 | 
			
		||||
	var i UserAccountConfirmToken
 | 
			
		||||
	err := row.Scan(&i.ConfirmTokenID, &i.Email)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createInvitedProjectMember = `-- name: CreateInvitedProjectMember :one
 | 
			
		||||
INSERT INTO project_member_invited (project_id, user_account_invited_id) VALUES ($1, $2)
 | 
			
		||||
  RETURNING project_member_invited_id, project_id, user_account_invited_id
 | 
			
		||||
@@ -56,8 +45,8 @@ func (q *Queries) CreateInvitedUser(ctx context.Context, email string) (UserAcco
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createUserAccount = `-- name: CreateUserAccount :one
 | 
			
		||||
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code, active)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type CreateUserAccountParams struct {
 | 
			
		||||
@@ -68,7 +57,6 @@ type CreateUserAccountParams struct {
 | 
			
		||||
	CreatedAt    time.Time `json:"created_at"`
 | 
			
		||||
	PasswordHash string    `json:"password_hash"`
 | 
			
		||||
	RoleCode     string    `json:"role_code"`
 | 
			
		||||
	Active       bool      `json:"active"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error) {
 | 
			
		||||
@@ -80,7 +68,6 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
 | 
			
		||||
		arg.CreatedAt,
 | 
			
		||||
		arg.PasswordHash,
 | 
			
		||||
		arg.RoleCode,
 | 
			
		||||
		arg.Active,
 | 
			
		||||
	)
 | 
			
		||||
	var i UserAccount
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
@@ -95,20 +82,10 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteConfirmTokenForEmail = `-- name: DeleteConfirmTokenForEmail :exec
 | 
			
		||||
DELETE FROM user_account_confirm_token WHERE email = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) DeleteConfirmTokenForEmail(ctx context.Context, email string) error {
 | 
			
		||||
	_, err := q.db.ExecContext(ctx, deleteConfirmTokenForEmail, email)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteInvitedUserAccount = `-- name: DeleteInvitedUserAccount :one
 | 
			
		||||
DELETE FROM user_account_invited WHERE user_account_invited_id = $1 RETURNING user_account_invited_id, email, invited_on, has_joined
 | 
			
		||||
`
 | 
			
		||||
@@ -125,20 +102,6 @@ func (q *Queries) DeleteInvitedUserAccount(ctx context.Context, userAccountInvit
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteProjectMemberInvitedForEmail = `-- name: DeleteProjectMemberInvitedForEmail :exec
 | 
			
		||||
DELETE FROM project_member_invited WHERE project_member_invited_id IN (
 | 
			
		||||
  SELECT pmi.project_member_invited_id FROM user_account_invited AS uai
 | 
			
		||||
  INNER JOIN project_member_invited AS pmi
 | 
			
		||||
  ON pmi.user_account_invited_id = uai.user_account_invited_id
 | 
			
		||||
  WHERE uai.email = $1
 | 
			
		||||
)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) DeleteProjectMemberInvitedForEmail(ctx context.Context, email string) error {
 | 
			
		||||
	_, err := q.db.ExecContext(ctx, deleteProjectMemberInvitedForEmail, email)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteUserAccountByID = `-- name: DeleteUserAccountByID :exec
 | 
			
		||||
DELETE FROM user_account WHERE user_id = $1
 | 
			
		||||
`
 | 
			
		||||
@@ -148,17 +111,8 @@ func (q *Queries) DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) e
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const deleteUserAccountInvitedForEmail = `-- name: DeleteUserAccountInvitedForEmail :exec
 | 
			
		||||
DELETE FROM user_account_invited WHERE email = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) DeleteUserAccountInvitedForEmail(ctx context.Context, email string) error {
 | 
			
		||||
	_, err := q.db.ExecContext(ctx, deleteUserAccountInvitedForEmail, email)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getAllUserAccounts = `-- name: GetAllUserAccounts :many
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM user_account WHERE username != 'system'
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE username != 'system'
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) {
 | 
			
		||||
@@ -182,7 +136,6 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
 | 
			
		||||
			&i.ProfileAvatarUrl,
 | 
			
		||||
			&i.RoleCode,
 | 
			
		||||
			&i.Bio,
 | 
			
		||||
			&i.Active,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -197,28 +150,6 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
 | 
			
		||||
	return items, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getConfirmTokenByEmail = `-- name: GetConfirmTokenByEmail :one
 | 
			
		||||
SELECT confirm_token_id, email FROM user_account_confirm_token WHERE email = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getConfirmTokenByEmail, email)
 | 
			
		||||
	var i UserAccountConfirmToken
 | 
			
		||||
	err := row.Scan(&i.ConfirmTokenID, &i.Email)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getConfirmTokenByID = `-- name: GetConfirmTokenByID :one
 | 
			
		||||
SELECT confirm_token_id, email FROM user_account_confirm_token WHERE confirm_token_id = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getConfirmTokenByID, confirmTokenID)
 | 
			
		||||
	var i UserAccountConfirmToken
 | 
			
		||||
	err := row.Scan(&i.ConfirmTokenID, &i.Email)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getInvitedUserAccounts = `-- name: GetInvitedUserAccounts :many
 | 
			
		||||
SELECT user_account_invited_id, email, invited_on, has_joined FROM user_account_invited
 | 
			
		||||
`
 | 
			
		||||
@@ -268,7 +199,7 @@ func (q *Queries) GetInvitedUserByEmail(ctx context.Context, email string) (User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getMemberData = `-- name: GetMemberData :many
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM user_account
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account
 | 
			
		||||
  WHERE username != 'system'
 | 
			
		||||
  AND user_id NOT IN (SELECT user_id FROM project_member WHERE project_id = $1)
 | 
			
		||||
`
 | 
			
		||||
@@ -294,7 +225,6 @@ func (q *Queries) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]Use
 | 
			
		||||
			&i.ProfileAvatarUrl,
 | 
			
		||||
			&i.RoleCode,
 | 
			
		||||
			&i.Bio,
 | 
			
		||||
			&i.Active,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -309,36 +239,6 @@ func (q *Queries) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]Use
 | 
			
		||||
	return items, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getProjectsForInvitedMember = `-- name: GetProjectsForInvitedMember :many
 | 
			
		||||
SELECT project_id FROM user_account_invited AS uai
 | 
			
		||||
  INNER JOIN project_member_invited AS pmi
 | 
			
		||||
  ON pmi.user_account_invited_id = uai.user_account_invited_id
 | 
			
		||||
  WHERE uai.email = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetProjectsForInvitedMember(ctx context.Context, email string) ([]uuid.UUID, error) {
 | 
			
		||||
	rows, err := q.db.QueryContext(ctx, getProjectsForInvitedMember, email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
	var items []uuid.UUID
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		var project_id uuid.UUID
 | 
			
		||||
		if err := rows.Scan(&project_id); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		items = append(items, project_id)
 | 
			
		||||
	}
 | 
			
		||||
	if err := rows.Close(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := rows.Err(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return items, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getRoleForUserID = `-- name: GetRoleForUserID :one
 | 
			
		||||
SELECT username, role.code, role.name FROM user_account
 | 
			
		||||
  INNER JOIN role ON role.code = user_account.role_code
 | 
			
		||||
@@ -358,32 +258,8 @@ func (q *Queries) GetRoleForUserID(ctx context.Context, userID uuid.UUID) (GetRo
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserAccountByEmail = `-- name: GetUserAccountByEmail :one
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM user_account WHERE email = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserAccountByEmail(ctx context.Context, email string) (UserAccount, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, getUserAccountByEmail, email)
 | 
			
		||||
	var i UserAccount
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.UserID,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.Email,
 | 
			
		||||
		&i.Username,
 | 
			
		||||
		&i.PasswordHash,
 | 
			
		||||
		&i.ProfileBgColor,
 | 
			
		||||
		&i.FullName,
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserAccountByID = `-- name: GetUserAccountByID :one
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM user_account WHERE user_id = $1
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE user_id = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) {
 | 
			
		||||
@@ -401,13 +277,12 @@ func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (Use
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserAccountByUsername = `-- name: GetUserAccountByUsername :one
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active FROM user_account WHERE username = $1
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE username = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) {
 | 
			
		||||
@@ -425,85 +300,12 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const hasActiveUser = `-- name: HasActiveUser :one
 | 
			
		||||
SELECT EXISTS(SELECT 1 FROM user_account WHERE username != 'system' AND active = true)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) HasActiveUser(ctx context.Context) (bool, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, hasActiveUser)
 | 
			
		||||
	var exists bool
 | 
			
		||||
	err := row.Scan(&exists)
 | 
			
		||||
	return exists, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const hasAnyUser = `-- name: HasAnyUser :one
 | 
			
		||||
SELECT EXISTS(SELECT 1 FROM user_account WHERE username != 'system')
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) HasAnyUser(ctx context.Context) (bool, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, hasAnyUser)
 | 
			
		||||
	var exists bool
 | 
			
		||||
	err := row.Scan(&exists)
 | 
			
		||||
	return exists, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setFirstUserActive = `-- name: SetFirstUserActive :one
 | 
			
		||||
UPDATE user_account SET active = true WHERE user_id = (
 | 
			
		||||
  SELECT user_id from user_account WHERE active = false LIMIT 1
 | 
			
		||||
) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) SetFirstUserActive(ctx context.Context) (UserAccount, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, setFirstUserActive)
 | 
			
		||||
	var i UserAccount
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.UserID,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.Email,
 | 
			
		||||
		&i.Username,
 | 
			
		||||
		&i.PasswordHash,
 | 
			
		||||
		&i.ProfileBgColor,
 | 
			
		||||
		&i.FullName,
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setUserActiveByEmail = `-- name: SetUserActiveByEmail :one
 | 
			
		||||
UPDATE user_account SET active = true WHERE email = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) SetUserActiveByEmail(ctx context.Context, email string) (UserAccount, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, setUserActiveByEmail, email)
 | 
			
		||||
	var i UserAccount
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.UserID,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.Email,
 | 
			
		||||
		&i.Username,
 | 
			
		||||
		&i.PasswordHash,
 | 
			
		||||
		&i.ProfileBgColor,
 | 
			
		||||
		&i.FullName,
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setUserPassword = `-- name: SetUserPassword :one
 | 
			
		||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type SetUserPasswordParams struct {
 | 
			
		||||
@@ -526,14 +328,13 @@ func (q *Queries) SetUserPassword(ctx context.Context, arg SetUserPasswordParams
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserAccountInfo = `-- name: UpdateUserAccountInfo :one
 | 
			
		||||
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
 | 
			
		||||
  WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
  WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserAccountInfoParams struct {
 | 
			
		||||
@@ -565,14 +366,13 @@ func (q *Queries) UpdateUserAccountInfo(ctx context.Context, arg UpdateUserAccou
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one
 | 
			
		||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
 | 
			
		||||
  RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
  RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserAccountProfileAvatarURLParams struct {
 | 
			
		||||
@@ -595,13 +395,12 @@ func (q *Queries) UpdateUserAccountProfileAvatarURL(ctx context.Context, arg Upd
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserRole = `-- name: UpdateUserRole :one
 | 
			
		||||
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio, active
 | 
			
		||||
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserRoleParams struct {
 | 
			
		||||
@@ -624,7 +423,6 @@ func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams)
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
		&i.Active,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ type ResolverRoot interface {
 | 
			
		||||
	Query() QueryResolver
 | 
			
		||||
	RefreshToken() RefreshTokenResolver
 | 
			
		||||
	Task() TaskResolver
 | 
			
		||||
	TaskActivity() TaskActivityResolver
 | 
			
		||||
	TaskChecklist() TaskChecklistResolver
 | 
			
		||||
	TaskChecklistItem() TaskChecklistItemResolver
 | 
			
		||||
	TaskGroup() TaskGroupResolver
 | 
			
		||||
@@ -61,12 +60,6 @@ type DirectiveRoot struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComplexityRoot struct {
 | 
			
		||||
	CausedBy struct {
 | 
			
		||||
		FullName    func(childComplexity int) int
 | 
			
		||||
		ID          func(childComplexity int) int
 | 
			
		||||
		ProfileIcon func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ChecklistBadge struct {
 | 
			
		||||
		Complete func(childComplexity int) int
 | 
			
		||||
		Total    func(childComplexity int) int
 | 
			
		||||
@@ -353,7 +346,6 @@ type ComplexityRoot struct {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Task struct {
 | 
			
		||||
		Activity    func(childComplexity int) int
 | 
			
		||||
		Assigned    func(childComplexity int) int
 | 
			
		||||
		Badges      func(childComplexity int) int
 | 
			
		||||
		Checklists  func(childComplexity int) int
 | 
			
		||||
@@ -369,19 +361,6 @@ type ComplexityRoot struct {
 | 
			
		||||
		TaskGroup   func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TaskActivity struct {
 | 
			
		||||
		CausedBy  func(childComplexity int) int
 | 
			
		||||
		CreatedAt func(childComplexity int) int
 | 
			
		||||
		Data      func(childComplexity int) int
 | 
			
		||||
		ID        func(childComplexity int) int
 | 
			
		||||
		Type      func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TaskActivityData struct {
 | 
			
		||||
		Name  func(childComplexity int) int
 | 
			
		||||
		Value func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TaskBadges struct {
 | 
			
		||||
		Checklist func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
@@ -604,13 +583,6 @@ type TaskResolver interface {
 | 
			
		||||
	Labels(ctx context.Context, obj *db.Task) ([]db.TaskLabel, error)
 | 
			
		||||
	Checklists(ctx context.Context, obj *db.Task) ([]db.TaskChecklist, error)
 | 
			
		||||
	Badges(ctx context.Context, obj *db.Task) (*TaskBadges, error)
 | 
			
		||||
	Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error)
 | 
			
		||||
}
 | 
			
		||||
type TaskActivityResolver interface {
 | 
			
		||||
	ID(ctx context.Context, obj *db.TaskActivity) (uuid.UUID, error)
 | 
			
		||||
	Type(ctx context.Context, obj *db.TaskActivity) (ActivityType, error)
 | 
			
		||||
	Data(ctx context.Context, obj *db.TaskActivity) ([]TaskActivityData, error)
 | 
			
		||||
	CausedBy(ctx context.Context, obj *db.TaskActivity) (*CausedBy, error)
 | 
			
		||||
}
 | 
			
		||||
type TaskChecklistResolver interface {
 | 
			
		||||
	ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error)
 | 
			
		||||
@@ -662,27 +634,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
	_ = ec
 | 
			
		||||
	switch typeName + "." + field {
 | 
			
		||||
 | 
			
		||||
	case "CausedBy.fullName":
 | 
			
		||||
		if e.complexity.CausedBy.FullName == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.CausedBy.FullName(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "CausedBy.id":
 | 
			
		||||
		if e.complexity.CausedBy.ID == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.CausedBy.ID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "CausedBy.profileIcon":
 | 
			
		||||
		if e.complexity.CausedBy.ProfileIcon == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.CausedBy.ProfileIcon(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "ChecklistBadge.complete":
 | 
			
		||||
		if e.complexity.ChecklistBadge.Complete == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2175,13 +2126,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.SortTaskGroupPayload.Tasks(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "Task.activity":
 | 
			
		||||
		if e.complexity.Task.Activity == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Task.Activity(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "Task.assigned":
 | 
			
		||||
		if e.complexity.Task.Assigned == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2273,55 +2217,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Task.TaskGroup(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivity.causedBy":
 | 
			
		||||
		if e.complexity.TaskActivity.CausedBy == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivity.CausedBy(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivity.createdAt":
 | 
			
		||||
		if e.complexity.TaskActivity.CreatedAt == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivity.CreatedAt(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivity.data":
 | 
			
		||||
		if e.complexity.TaskActivity.Data == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivity.Data(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivity.id":
 | 
			
		||||
		if e.complexity.TaskActivity.ID == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivity.ID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivity.type":
 | 
			
		||||
		if e.complexity.TaskActivity.Type == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivity.Type(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivityData.name":
 | 
			
		||||
		if e.complexity.TaskActivityData.Name == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivityData.Name(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskActivityData.value":
 | 
			
		||||
		if e.complexity.TaskActivityData.Value == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.TaskActivityData.Value(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "TaskBadges.checklist":
 | 
			
		||||
		if e.complexity.TaskBadges.Checklist == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2901,38 +2796,6 @@ type TaskBadges {
 | 
			
		||||
  checklist: ChecklistBadge
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CausedBy {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  fullName: String!
 | 
			
		||||
  profileIcon: ProfileIcon
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivityData {
 | 
			
		||||
  name: String!
 | 
			
		||||
  value: String!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ActivityType {
 | 
			
		||||
  TASK_ADDED
 | 
			
		||||
  TASK_MOVED
 | 
			
		||||
  TASK_MARKED_COMPLETE
 | 
			
		||||
  TASK_MARKED_INCOMPLETE
 | 
			
		||||
  TASK_DUE_DATE_CHANGED
 | 
			
		||||
  TASK_DUE_DATE_ADDED
 | 
			
		||||
  TASK_DUE_DATE_REMOVED
 | 
			
		||||
  TASK_CHECKLIST_CHANGED
 | 
			
		||||
  TASK_CHECKLIST_ADDED
 | 
			
		||||
  TASK_CHECKLIST_REMOVED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivity {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  type: ActivityType!
 | 
			
		||||
  data: [TaskActivityData!]!
 | 
			
		||||
  causedBy: CausedBy!
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Task {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  taskGroup: TaskGroup!
 | 
			
		||||
@@ -2947,7 +2810,6 @@ type Task {
 | 
			
		||||
  labels: [TaskLabel!]!
 | 
			
		||||
  checklists: [TaskChecklist!]!
 | 
			
		||||
  badges: TaskBadges!
 | 
			
		||||
  activity: [TaskActivity!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Organization {
 | 
			
		||||
@@ -4570,105 +4432,6 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
 | 
			
		||||
 | 
			
		||||
// region    **************************** field.gotpl *****************************
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _CausedBy_id(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "CausedBy",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.ID, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(uuid.UUID)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _CausedBy_fullName(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "CausedBy",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.FullName, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(string)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNString2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _CausedBy_profileIcon(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "CausedBy",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.ProfileIcon, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*ProfileIcon)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalOProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _ChecklistBadge_complete(ctx context.Context, field graphql.CollectedField, obj *ChecklistBadge) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -13012,278 +12775,6 @@ func (ec *executionContext) _Task_badges(ctx context.Context, field graphql.Coll
 | 
			
		||||
	return ec.marshalNTaskBadges2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Task_activity(ctx context.Context, field graphql.CollectedField, obj *db.Task) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "Task",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return ec.resolvers.Task().Activity(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.([]db.TaskActivity)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNTaskActivity2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivityᚄ(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity_id(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivity",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return ec.resolvers.TaskActivity().ID(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(uuid.UUID)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity_type(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivity",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return ec.resolvers.TaskActivity().Type(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(ActivityType)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity_data(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivity",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return ec.resolvers.TaskActivity().Data(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.([]TaskActivityData)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNTaskActivityData2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityDataᚄ(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity_causedBy(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivity",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return ec.resolvers.TaskActivity().CausedBy(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*CausedBy)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNCausedBy2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity_createdAt(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivity",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.CreatedAt, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(time.Time)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivityData_name(ctx context.Context, field graphql.CollectedField, obj *TaskActivityData) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivityData",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.Name, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(string)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNString2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivityData_value(ctx context.Context, field graphql.CollectedField, obj *TaskActivityData) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "TaskActivityData",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.Value, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, fc) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(string)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNString2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskBadges_checklist(ctx context.Context, field graphql.CollectedField, obj *TaskBadges) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -17662,40 +17153,6 @@ func (ec *executionContext) unmarshalInputUpdateUserRole(ctx context.Context, ob
 | 
			
		||||
 | 
			
		||||
// region    **************************** object.gotpl ****************************
 | 
			
		||||
 | 
			
		||||
var causedByImplementors = []string{"CausedBy"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _CausedBy(ctx context.Context, sel ast.SelectionSet, obj *CausedBy) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, causedByImplementors)
 | 
			
		||||
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("CausedBy")
 | 
			
		||||
		case "id":
 | 
			
		||||
			out.Values[i] = ec._CausedBy_id(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "fullName":
 | 
			
		||||
			out.Values[i] = ec._CausedBy_fullName(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "profileIcon":
 | 
			
		||||
			out.Values[i] = ec._CausedBy_profileIcon(ctx, field, obj)
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.Dispatch()
 | 
			
		||||
	if invalids > 0 {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var checklistBadgeImplementors = []string{"ChecklistBadge"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.SelectionSet, obj *ChecklistBadge) graphql.Marshaler {
 | 
			
		||||
@@ -19808,135 +19265,6 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		case "activity":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._Task_activity(ctx, field, obj)
 | 
			
		||||
				if res == graphql.Null {
 | 
			
		||||
					atomic.AddUint32(&invalids, 1)
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.Dispatch()
 | 
			
		||||
	if invalids > 0 {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var taskActivityImplementors = []string{"TaskActivity"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivity(ctx context.Context, sel ast.SelectionSet, obj *db.TaskActivity) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, taskActivityImplementors)
 | 
			
		||||
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("TaskActivity")
 | 
			
		||||
		case "id":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._TaskActivity_id(ctx, field, obj)
 | 
			
		||||
				if res == graphql.Null {
 | 
			
		||||
					atomic.AddUint32(&invalids, 1)
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		case "type":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._TaskActivity_type(ctx, field, obj)
 | 
			
		||||
				if res == graphql.Null {
 | 
			
		||||
					atomic.AddUint32(&invalids, 1)
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		case "data":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._TaskActivity_data(ctx, field, obj)
 | 
			
		||||
				if res == graphql.Null {
 | 
			
		||||
					atomic.AddUint32(&invalids, 1)
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		case "causedBy":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._TaskActivity_causedBy(ctx, field, obj)
 | 
			
		||||
				if res == graphql.Null {
 | 
			
		||||
					atomic.AddUint32(&invalids, 1)
 | 
			
		||||
				}
 | 
			
		||||
				return res
 | 
			
		||||
			})
 | 
			
		||||
		case "createdAt":
 | 
			
		||||
			out.Values[i] = ec._TaskActivity_createdAt(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				atomic.AddUint32(&invalids, 1)
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.Dispatch()
 | 
			
		||||
	if invalids > 0 {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var taskActivityDataImplementors = []string{"TaskActivityData"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _TaskActivityData(ctx context.Context, sel ast.SelectionSet, obj *TaskActivityData) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, taskActivityDataImplementors)
 | 
			
		||||
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("TaskActivityData")
 | 
			
		||||
		case "name":
 | 
			
		||||
			out.Values[i] = ec._TaskActivityData_name(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "value":
 | 
			
		||||
			out.Values[i] = ec._TaskActivityData_value(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
@@ -20996,15 +20324,6 @@ func (ec *executionContext) marshalNActionType2githubᚗcomᚋjordanknottᚋtask
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx context.Context, v interface{}) (ActivityType, error) {
 | 
			
		||||
	var res ActivityType
 | 
			
		||||
	return res, res.UnmarshalGQL(v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx context.Context, sel ast.SelectionSet, v ActivityType) graphql.Marshaler {
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNActorType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActorType(ctx context.Context, v interface{}) (ActorType, error) {
 | 
			
		||||
	var res ActorType
 | 
			
		||||
	return res, res.UnmarshalGQL(v)
 | 
			
		||||
@@ -21028,20 +20347,6 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNCausedBy2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx context.Context, sel ast.SelectionSet, v CausedBy) graphql.Marshaler {
 | 
			
		||||
	return ec._CausedBy(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNCausedBy2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx context.Context, sel ast.SelectionSet, v *CausedBy) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return ec._CausedBy(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNCreateTaskChecklist2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskChecklist(ctx context.Context, v interface{}) (CreateTaskChecklist, error) {
 | 
			
		||||
	return ec.unmarshalInputCreateTaskChecklist(ctx, v)
 | 
			
		||||
}
 | 
			
		||||
@@ -22225,88 +21530,6 @@ func (ec *executionContext) marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋtaskcaf
 | 
			
		||||
	return ec._Task(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNTaskActivity2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivity(ctx context.Context, sel ast.SelectionSet, v db.TaskActivity) graphql.Marshaler {
 | 
			
		||||
	return ec._TaskActivity(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNTaskActivity2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivityᚄ(ctx context.Context, sel ast.SelectionSet, v []db.TaskActivity) graphql.Marshaler {
 | 
			
		||||
	ret := make(graphql.Array, len(v))
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	isLen1 := len(v) == 1
 | 
			
		||||
	if !isLen1 {
 | 
			
		||||
		wg.Add(len(v))
 | 
			
		||||
	}
 | 
			
		||||
	for i := range v {
 | 
			
		||||
		i := i
 | 
			
		||||
		fc := &graphql.FieldContext{
 | 
			
		||||
			Index:  &i,
 | 
			
		||||
			Result: &v[i],
 | 
			
		||||
		}
 | 
			
		||||
		ctx := graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
		f := func(i int) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if r := recover(); r != nil {
 | 
			
		||||
					ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					ret = nil
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if !isLen1 {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
			}
 | 
			
		||||
			ret[i] = ec.marshalNTaskActivity2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivity(ctx, sel, v[i])
 | 
			
		||||
		}
 | 
			
		||||
		if isLen1 {
 | 
			
		||||
			f(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			go f(i)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNTaskActivityData2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityData(ctx context.Context, sel ast.SelectionSet, v TaskActivityData) graphql.Marshaler {
 | 
			
		||||
	return ec._TaskActivityData(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNTaskActivityData2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityDataᚄ(ctx context.Context, sel ast.SelectionSet, v []TaskActivityData) graphql.Marshaler {
 | 
			
		||||
	ret := make(graphql.Array, len(v))
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	isLen1 := len(v) == 1
 | 
			
		||||
	if !isLen1 {
 | 
			
		||||
		wg.Add(len(v))
 | 
			
		||||
	}
 | 
			
		||||
	for i := range v {
 | 
			
		||||
		i := i
 | 
			
		||||
		fc := &graphql.FieldContext{
 | 
			
		||||
			Index:  &i,
 | 
			
		||||
			Result: &v[i],
 | 
			
		||||
		}
 | 
			
		||||
		ctx := graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
		f := func(i int) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if r := recover(); r != nil {
 | 
			
		||||
					ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					ret = nil
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if !isLen1 {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
			}
 | 
			
		||||
			ret[i] = ec.marshalNTaskActivityData2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityData(ctx, sel, v[i])
 | 
			
		||||
		}
 | 
			
		||||
		if isLen1 {
 | 
			
		||||
			f(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			go f(i)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNTaskBadges2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx context.Context, sel ast.SelectionSet, v TaskBadges) graphql.Marshaler {
 | 
			
		||||
	return ec._TaskBadges(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
@@ -23235,17 +22458,6 @@ func (ec *executionContext) marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknott
 | 
			
		||||
	return ec._ChecklistBadge(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalOProfileIcon2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v ProfileIcon) graphql.Marshaler {
 | 
			
		||||
	return ec._ProfileIcon(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalOProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v *ProfileIcon) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return ec._ProfileIcon(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalOProjectsFilter2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProjectsFilter(ctx context.Context, v interface{}) (ProjectsFilter, error) {
 | 
			
		||||
	return ec.unmarshalInputProjectsFilter(ctx, v)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var subjectID uuid.UUID
 | 
			
		||||
		in := graphql.GetFieldContext(ctx).Args["input"]
 | 
			
		||||
		in := graphql.GetResolverContext(ctx).Args["input"]
 | 
			
		||||
		val := reflect.ValueOf(in) // could be any underlying type
 | 
			
		||||
		if val.Kind() == reflect.Ptr {
 | 
			
		||||
			val = reflect.Indirect(val)
 | 
			
		||||
@@ -255,15 +255,3 @@ func GetActionType(actionType int32) ActionType {
 | 
			
		||||
		panic("Not a valid entity type!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MemberType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	MemberTypeInvited MemberType = "INVITED"
 | 
			
		||||
	MemberTypeJoined  MemberType = "JOINED"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MasterEntry struct {
 | 
			
		||||
	MemberType MemberType
 | 
			
		||||
	ID         uuid.UUID
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,3 @@ func GetMemberList(ctx context.Context, r db.Repository, user db.UserAccount) (*
 | 
			
		||||
 | 
			
		||||
	return &MemberList{Teams: teams, Projects: projects}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ActivityData struct {
 | 
			
		||||
	Data map[string]string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,12 +22,6 @@ type AssignTaskInput struct {
 | 
			
		||||
	UserID uuid.UUID `json:"userID"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CausedBy struct {
 | 
			
		||||
	ID          uuid.UUID    `json:"id"`
 | 
			
		||||
	FullName    string       `json:"fullName"`
 | 
			
		||||
	ProfileIcon *ProfileIcon `json:"profileIcon"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChecklistBadge struct {
 | 
			
		||||
	Complete int `json:"complete"`
 | 
			
		||||
	Total    int `json:"total"`
 | 
			
		||||
@@ -380,11 +374,6 @@ type SortTaskGroupPayload struct {
 | 
			
		||||
	Tasks       []db.Task `json:"tasks"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivityData struct {
 | 
			
		||||
	Name  string `json:"name"`
 | 
			
		||||
	Value string `json:"value"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskBadges struct {
 | 
			
		||||
	Checklist *ChecklistBadge `json:"checklist"`
 | 
			
		||||
}
 | 
			
		||||
@@ -626,63 +615,6 @@ func (e ActionType) MarshalGQL(w io.Writer) {
 | 
			
		||||
	fmt.Fprint(w, strconv.Quote(e.String()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ActivityType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ActivityTypeTaskAdded            ActivityType = "TASK_ADDED"
 | 
			
		||||
	ActivityTypeTaskMoved            ActivityType = "TASK_MOVED"
 | 
			
		||||
	ActivityTypeTaskMarkedComplete   ActivityType = "TASK_MARKED_COMPLETE"
 | 
			
		||||
	ActivityTypeTaskMarkedIncomplete ActivityType = "TASK_MARKED_INCOMPLETE"
 | 
			
		||||
	ActivityTypeTaskDueDateChanged   ActivityType = "TASK_DUE_DATE_CHANGED"
 | 
			
		||||
	ActivityTypeTaskDueDateAdded     ActivityType = "TASK_DUE_DATE_ADDED"
 | 
			
		||||
	ActivityTypeTaskDueDateRemoved   ActivityType = "TASK_DUE_DATE_REMOVED"
 | 
			
		||||
	ActivityTypeTaskChecklistChanged ActivityType = "TASK_CHECKLIST_CHANGED"
 | 
			
		||||
	ActivityTypeTaskChecklistAdded   ActivityType = "TASK_CHECKLIST_ADDED"
 | 
			
		||||
	ActivityTypeTaskChecklistRemoved ActivityType = "TASK_CHECKLIST_REMOVED"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var AllActivityType = []ActivityType{
 | 
			
		||||
	ActivityTypeTaskAdded,
 | 
			
		||||
	ActivityTypeTaskMoved,
 | 
			
		||||
	ActivityTypeTaskMarkedComplete,
 | 
			
		||||
	ActivityTypeTaskMarkedIncomplete,
 | 
			
		||||
	ActivityTypeTaskDueDateChanged,
 | 
			
		||||
	ActivityTypeTaskDueDateAdded,
 | 
			
		||||
	ActivityTypeTaskDueDateRemoved,
 | 
			
		||||
	ActivityTypeTaskChecklistChanged,
 | 
			
		||||
	ActivityTypeTaskChecklistAdded,
 | 
			
		||||
	ActivityTypeTaskChecklistRemoved,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e ActivityType) IsValid() bool {
 | 
			
		||||
	switch e {
 | 
			
		||||
	case ActivityTypeTaskAdded, ActivityTypeTaskMoved, ActivityTypeTaskMarkedComplete, ActivityTypeTaskMarkedIncomplete, ActivityTypeTaskDueDateChanged, ActivityTypeTaskDueDateAdded, ActivityTypeTaskDueDateRemoved, ActivityTypeTaskChecklistChanged, ActivityTypeTaskChecklistAdded, ActivityTypeTaskChecklistRemoved:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e ActivityType) String() string {
 | 
			
		||||
	return string(e)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ActivityType) UnmarshalGQL(v interface{}) error {
 | 
			
		||||
	str, ok := v.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("enums must be strings")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*e = ActivityType(str)
 | 
			
		||||
	if !e.IsValid() {
 | 
			
		||||
		return fmt.Errorf("%s is not a valid ActivityType", str)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e ActivityType) MarshalGQL(w io.Writer) {
 | 
			
		||||
	fmt.Fprint(w, strconv.Quote(e.String()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ActorType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
 
 | 
			
		||||
@@ -135,38 +135,6 @@ type TaskBadges {
 | 
			
		||||
  checklist: ChecklistBadge
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CausedBy {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  fullName: String!
 | 
			
		||||
  profileIcon: ProfileIcon
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivityData {
 | 
			
		||||
  name: String!
 | 
			
		||||
  value: String!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ActivityType {
 | 
			
		||||
  TASK_ADDED
 | 
			
		||||
  TASK_MOVED
 | 
			
		||||
  TASK_MARKED_COMPLETE
 | 
			
		||||
  TASK_MARKED_INCOMPLETE
 | 
			
		||||
  TASK_DUE_DATE_CHANGED
 | 
			
		||||
  TASK_DUE_DATE_ADDED
 | 
			
		||||
  TASK_DUE_DATE_REMOVED
 | 
			
		||||
  TASK_CHECKLIST_CHANGED
 | 
			
		||||
  TASK_CHECKLIST_ADDED
 | 
			
		||||
  TASK_CHECKLIST_REMOVED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TaskActivity {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  type: ActivityType!
 | 
			
		||||
  data: [TaskActivityData!]!
 | 
			
		||||
  causedBy: CausedBy!
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Task {
 | 
			
		||||
  id: ID!
 | 
			
		||||
  taskGroup: TaskGroup!
 | 
			
		||||
@@ -181,7 +149,6 @@ type Task {
 | 
			
		||||
  labels: [TaskLabel!]!
 | 
			
		||||
  checklists: [TaskChecklist!]!
 | 
			
		||||
  badges: TaskBadges!
 | 
			
		||||
  activity: [TaskActivity!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Organization {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,7 @@ package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -17,11 +15,9 @@ import (
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/db"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/logger"
 | 
			
		||||
	"github.com/lithammer/fuzzysearch/fuzzy"
 | 
			
		||||
	hermes "github.com/matcornic/hermes/v2"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/vektah/gqlparser/v2/gqlerror"
 | 
			
		||||
	"golang.org/x/crypto/bcrypt"
 | 
			
		||||
	gomail "gopkg.in/mail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.UUID, error) {
 | 
			
		||||
@@ -189,84 +185,6 @@ func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input Invit
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
					}
 | 
			
		||||
					confirmToken, err := r.Repository.CreateConfirmToken(ctx, *invitedMember.Email)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
					}
 | 
			
		||||
					// send out invitation
 | 
			
		||||
					// add project invite entry
 | 
			
		||||
					// send out notification?
 | 
			
		||||
					h := hermes.Hermes{
 | 
			
		||||
						// Optional Theme
 | 
			
		||||
						Product: hermes.Product{
 | 
			
		||||
							// Appears in header & footer of e-mails
 | 
			
		||||
							Name: "Taskscafe",
 | 
			
		||||
							Link: "http://localhost:3333/",
 | 
			
		||||
							// Optional product logo
 | 
			
		||||
							Logo: "https://github.com/JordanKnott/taskcafe/raw/master/.github/taskcafe-full.png",
 | 
			
		||||
						},
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					email := hermes.Email{
 | 
			
		||||
						Body: hermes.Body{
 | 
			
		||||
							Name: "Jordan Knott",
 | 
			
		||||
							Intros: []string{
 | 
			
		||||
								"You have been invited to join Taskcafe",
 | 
			
		||||
							},
 | 
			
		||||
							Actions: []hermes.Action{
 | 
			
		||||
								{
 | 
			
		||||
									Instructions: "To get started with Taskcafe, please click here:",
 | 
			
		||||
									Button: hermes.Button{
 | 
			
		||||
										Color:     "#7367F0", // Optional action button color
 | 
			
		||||
										TextColor: "#FFFFFF",
 | 
			
		||||
										Text:      "Register your account",
 | 
			
		||||
										Link:      "http://localhost:3000/register?confirmToken=" + confirmToken.ConfirmTokenID.String(),
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
							Outros: []string{
 | 
			
		||||
								"Need help, or have questions? Just reply to this email, we'd love to help.",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// Generate an HTML email with the provided contents (for modern clients)
 | 
			
		||||
					emailBody, err := h.GenerateHTML(email)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						panic(err) // Tip: Handle error with something else than a panic ;)
 | 
			
		||||
					}
 | 
			
		||||
					emailBodyPlain, err := h.GeneratePlainText(email)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						panic(err) // Tip: Handle error with something else than a panic ;)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					m := gomail.NewMessage()
 | 
			
		||||
 | 
			
		||||
					// Set E-Mail sender
 | 
			
		||||
					m.SetHeader("From", "no-reply@taskcafe.com")
 | 
			
		||||
 | 
			
		||||
					// Set E-Mail receivers
 | 
			
		||||
					m.SetHeader("To", invitedUser.Email)
 | 
			
		||||
 | 
			
		||||
					// Set E-Mail subject
 | 
			
		||||
					m.SetHeader("Subject", "You have been invited to Taskcafe")
 | 
			
		||||
 | 
			
		||||
					// Set E-Mail body. You can set plain text or html with text/html
 | 
			
		||||
					m.SetBody("text/html", emailBody)
 | 
			
		||||
					m.AddAlternative("text/plain", emailBodyPlain)
 | 
			
		||||
 | 
			
		||||
					// Settings for SMTP server
 | 
			
		||||
					d := gomail.NewDialer("127.0.0.1", 11500, "no-reply@taskcafe.com", "")
 | 
			
		||||
 | 
			
		||||
					// This is only needed when SSL/TLS certificate is not valid on server.
 | 
			
		||||
					// In production this should be set to false.
 | 
			
		||||
					d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
 | 
			
		||||
 | 
			
		||||
					// Now send E-Mail
 | 
			
		||||
					if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
						fmt.Println(err)
 | 
			
		||||
						panic(err)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
				}
 | 
			
		||||
@@ -281,6 +199,9 @@ func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input Invit
 | 
			
		||||
			}
 | 
			
		||||
			logger.New(ctx).Info("adding invited member")
 | 
			
		||||
			invitedMembers = append(invitedMembers, InvitedMember{Email: *invitedMember.Email, InvitedOn: now})
 | 
			
		||||
			// send out invitation
 | 
			
		||||
			// add project invite entry
 | 
			
		||||
			// send out notification?
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -362,26 +283,6 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.T
 | 
			
		||||
	createdAt := time.Now().UTC()
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task")
 | 
			
		||||
	task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("issue while creating task")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
	}
 | 
			
		||||
	taskGroup, err := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("issue while creating task")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
	}
 | 
			
		||||
	data := map[string]string{
 | 
			
		||||
		"TaskGroup": taskGroup.Name,
 | 
			
		||||
	}
 | 
			
		||||
	d, err := json.Marshal(data)
 | 
			
		||||
	_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
 | 
			
		||||
		TaskID:         task.TaskID,
 | 
			
		||||
		Data:           d,
 | 
			
		||||
		CreatedAt:      createdAt,
 | 
			
		||||
		ActivityTypeID: 1,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("issue while creating task")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
@@ -406,44 +307,12 @@ func (r *mutationResolver) UpdateTaskDescription(ctx context.Context, input Upda
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error) {
 | 
			
		||||
	userID, _ := GetUserID(ctx)
 | 
			
		||||
	previousTask, err := r.Repository.GetTaskByID(ctx, input.TaskID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &UpdateTaskLocationPayload{}, err
 | 
			
		||||
	}
 | 
			
		||||
	task, _ := r.Repository.UpdateTaskLocation(ctx, db.UpdateTaskLocationParams{TaskID: input.TaskID, TaskGroupID: input.TaskGroupID, Position: input.Position})
 | 
			
		||||
	if previousTask.TaskGroupID != input.TaskGroupID {
 | 
			
		||||
		skipAndDelete := false
 | 
			
		||||
		lastMove, err := r.Repository.GetLastMoveForTaskID(ctx, input.TaskID)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			if lastMove.Active && lastMove.PrevTaskGroupID == input.TaskGroupID.String() {
 | 
			
		||||
				skipAndDelete = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if skipAndDelete {
 | 
			
		||||
			_ = r.Repository.SetInactiveLastMoveForTaskID(ctx, input.TaskID)
 | 
			
		||||
		} else {
 | 
			
		||||
			prevTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, previousTask.TaskGroupID)
 | 
			
		||||
			curTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID)
 | 
			
		||||
	task, err := r.Repository.UpdateTaskLocation(ctx, db.UpdateTaskLocationParams{input.TaskID, input.TaskGroupID, input.Position})
 | 
			
		||||
 | 
			
		||||
			data := map[string]string{
 | 
			
		||||
				"PrevTaskGroup":   prevTaskGroup.Name,
 | 
			
		||||
				"PrevTaskGroupID": prevTaskGroup.TaskGroupID.String(),
 | 
			
		||||
				"CurTaskGroup":    curTaskGroup.Name,
 | 
			
		||||
				"CurTaskGroupID":  curTaskGroup.TaskGroupID.String(),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			createdAt := time.Now().UTC()
 | 
			
		||||
			d, _ := json.Marshal(data)
 | 
			
		||||
			_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
 | 
			
		||||
				TaskID:         task.TaskID,
 | 
			
		||||
				Data:           d,
 | 
			
		||||
				CausedBy:       userID,
 | 
			
		||||
				CreatedAt:      createdAt,
 | 
			
		||||
				ActivityTypeID: 2,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &UpdateTaskLocationPayload{Task: &task, PreviousTaskGroupID: previousTask.TaskGroupID}, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -991,11 +860,6 @@ func (r *mutationResolver) DeleteInvitedUserAccount(ctx context.Context, input D
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &DeleteInvitedUserAccountPayload{}, err
 | 
			
		||||
	}
 | 
			
		||||
	err = r.Repository.DeleteConfirmTokenForEmail(ctx, user.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("issue deleting confirm token")
 | 
			
		||||
		return &DeleteInvitedUserAccountPayload{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return &DeleteInvitedUserAccountPayload{
 | 
			
		||||
		InvitedUser: &InvitedUserAccount{
 | 
			
		||||
			Email:     user.Email,
 | 
			
		||||
@@ -1490,7 +1354,6 @@ func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFil
 | 
			
		||||
			} else {
 | 
			
		||||
				logger.New(ctx).WithField("id", rank.Target).Info("adding target")
 | 
			
		||||
				results = append(results, MemberSearchResult{ID: rank.Target, Status: ShareStatusInvited, Similarity: rank.Distance})
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			memberList[entry.ID] = true
 | 
			
		||||
		}
 | 
			
		||||
@@ -1609,72 +1472,6 @@ func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, e
 | 
			
		||||
	return &TaskBadges{Checklist: &ChecklistBadge{Total: total, Complete: complete}}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskResolver) Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error) {
 | 
			
		||||
	activity, err := r.Repository.GetActivityForTaskID(ctx, obj.TaskID)
 | 
			
		||||
	if err == sql.ErrNoRows {
 | 
			
		||||
		return []db.TaskActivity{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	return activity, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskActivityResolver) ID(ctx context.Context, obj *db.TaskActivity) (uuid.UUID, error) {
 | 
			
		||||
	return obj.TaskActivityID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskActivityResolver) Type(ctx context.Context, obj *db.TaskActivity) (ActivityType, error) {
 | 
			
		||||
	switch obj.ActivityTypeID {
 | 
			
		||||
	case 1:
 | 
			
		||||
		return ActivityTypeTaskAdded, nil
 | 
			
		||||
	case 2:
 | 
			
		||||
		return ActivityTypeTaskMoved, nil
 | 
			
		||||
	case 3:
 | 
			
		||||
		return ActivityTypeTaskMarkedComplete, nil
 | 
			
		||||
	case 4:
 | 
			
		||||
		return ActivityTypeTaskMarkedIncomplete, nil
 | 
			
		||||
	case 5:
 | 
			
		||||
		return ActivityTypeTaskDueDateChanged, nil
 | 
			
		||||
	case 6:
 | 
			
		||||
		return ActivityTypeTaskDueDateAdded, nil
 | 
			
		||||
	case 7:
 | 
			
		||||
		return ActivityTypeTaskDueDateRemoved, nil
 | 
			
		||||
	case 8:
 | 
			
		||||
		return ActivityTypeTaskChecklistChanged, nil
 | 
			
		||||
	case 9:
 | 
			
		||||
		return ActivityTypeTaskChecklistAdded, nil
 | 
			
		||||
	case 10:
 | 
			
		||||
		return ActivityTypeTaskChecklistRemoved, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return ActivityTypeTaskAdded, errors.New("unknown type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskActivityResolver) Data(ctx context.Context, obj *db.TaskActivity) ([]TaskActivityData, error) {
 | 
			
		||||
	var data map[string]string
 | 
			
		||||
	_ = json.Unmarshal(obj.Data, &data)
 | 
			
		||||
	activity := []TaskActivityData{}
 | 
			
		||||
	for name, value := range data {
 | 
			
		||||
		activity = append(activity, TaskActivityData{
 | 
			
		||||
			Name:  name,
 | 
			
		||||
			Value: value,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return activity, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskActivityResolver) CausedBy(ctx context.Context, obj *db.TaskActivity) (*CausedBy, error) {
 | 
			
		||||
	user, err := r.Repository.GetUserAccountByID(ctx, obj.CausedBy)
 | 
			
		||||
	var url *string
 | 
			
		||||
	if user.ProfileAvatarUrl.Valid {
 | 
			
		||||
		url = &user.ProfileAvatarUrl.String
 | 
			
		||||
	}
 | 
			
		||||
	profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
	return &CausedBy{
 | 
			
		||||
		ID:          obj.CausedBy,
 | 
			
		||||
		FullName:    user.FullName,
 | 
			
		||||
		ProfileIcon: profileIcon,
 | 
			
		||||
	}, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskChecklistResolver) ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error) {
 | 
			
		||||
	return obj.TaskChecklistID, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1736,7 +1533,6 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
 | 
			
		||||
		if user.ProfileAvatarUrl.Valid {
 | 
			
		||||
			url = &user.ProfileAvatarUrl.String
 | 
			
		||||
		}
 | 
			
		||||
		profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
		role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: obj.TeamID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
 | 
			
		||||
@@ -1752,6 +1548,7 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
 | 
			
		||||
			return members, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
		members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
 | 
			
		||||
			Username: user.Username, Owned: ownedList, Member: memberList, Role: &db.Role{Code: role.Code, Name: role.Name},
 | 
			
		||||
		})
 | 
			
		||||
@@ -1841,16 +1638,11 @@ func (r *Resolver) RefreshToken() RefreshTokenResolver { return &refreshTokenRes
 | 
			
		||||
// Task returns TaskResolver implementation.
 | 
			
		||||
func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
 | 
			
		||||
 | 
			
		||||
// TaskActivity returns TaskActivityResolver implementation.
 | 
			
		||||
func (r *Resolver) TaskActivity() TaskActivityResolver { return &taskActivityResolver{r} }
 | 
			
		||||
 | 
			
		||||
// TaskChecklist returns TaskChecklistResolver implementation.
 | 
			
		||||
func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} }
 | 
			
		||||
 | 
			
		||||
// TaskChecklistItem returns TaskChecklistItemResolver implementation.
 | 
			
		||||
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver {
 | 
			
		||||
	return &taskChecklistItemResolver{r}
 | 
			
		||||
}
 | 
			
		||||
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver { return &taskChecklistItemResolver{r} }
 | 
			
		||||
 | 
			
		||||
// TaskGroup returns TaskGroupResolver implementation.
 | 
			
		||||
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }
 | 
			
		||||
@@ -1873,10 +1665,27 @@ type projectLabelResolver struct{ *Resolver }
 | 
			
		||||
type queryResolver struct{ *Resolver }
 | 
			
		||||
type refreshTokenResolver struct{ *Resolver }
 | 
			
		||||
type taskResolver struct{ *Resolver }
 | 
			
		||||
type taskActivityResolver struct{ *Resolver }
 | 
			
		||||
type taskChecklistResolver struct{ *Resolver }
 | 
			
		||||
type taskChecklistItemResolver struct{ *Resolver }
 | 
			
		||||
type taskGroupResolver struct{ *Resolver }
 | 
			
		||||
type taskLabelResolver struct{ *Resolver }
 | 
			
		||||
type teamResolver struct{ *Resolver }
 | 
			
		||||
type userAccountResolver struct{ *Resolver }
 | 
			
		||||
 | 
			
		||||
// !!! WARNING !!!
 | 
			
		||||
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
 | 
			
		||||
// one last chance to move it out of harms way if you want. There are two reasons this happens:
 | 
			
		||||
//  - When renaming or deleting a resolver the old code will be put in here. You can safely delete
 | 
			
		||||
//    it when you're done.
 | 
			
		||||
//  - You have helper methods in this file. Move them out to keep these resolver files clean.
 | 
			
		||||
type MemberType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	MemberTypeInvited MemberType = "INVITED"
 | 
			
		||||
	MemberTypeJoined  MemberType = "JOINED"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MasterEntry struct {
 | 
			
		||||
	MemberType MemberType
 | 
			
		||||
	ID         uuid.UUID
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user