Compare commits
1 Commits
refactor/c
...
feat/updat
Author | SHA1 | Date | |
---|---|---|---|
4a8d4a6ec3 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -18,8 +18,6 @@ If applicable, add screenshots to help explain your problem.
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
Please send the Taskcafe web service logs if applicable.
|
||||
|
||||
<!--
|
||||
|
||||
Please read the contributing guide before working on any new pull requests!
|
||||
|
@ -32,10 +32,6 @@ The `description` is a decriptive summary of the change the PR will make.
|
||||
- One PR per fix or feature
|
||||
- Setup & install [pre-commit hooks](https://pre-commit.com/#install) then install the hooks `pre-commit install && pre-commit install --hook-type commit-msg`
|
||||
|
||||
### Unwanted PRs
|
||||
|
||||
- Please do not submit pull requests containing only typo fixes, fixed spelling mistakes, or minor wording changes.
|
||||
|
||||
### Git Commit Message Style
|
||||
|
||||
This project uses the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) format.
|
||||
|
@ -9,14 +9,11 @@
|
||||
<img alt="Releases" src="https://img.shields.io/github/v/release/JordanKnott/taskcafe" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/taskcafe/taskcafe">
|
||||
<img alt="Dockerhub" src="https://img.shields.io/docker/v/taskcafe/taskcafe?label=docker&sort=semver" />
|
||||
<img alt="Dockerhub" src="https://img.shields.io/docker/v/taskcafe/taskcafe?label=docker" />
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/JordanKnott/taskcafe">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/JordanKnott/taskcafe" />
|
||||
</a>
|
||||
<a href="">
|
||||
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/taskcafe/taskcafe" />
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
Was this project useful? Please consider <a href="https://www.buymeacoffee.com/jordanknott">donating</a> to help me improve it!
|
||||
|
@ -1,47 +0,0 @@
|
||||
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format
|
||||
|
||||
# Working directory
|
||||
# . or absolute path, please note that the directories following must be under root.
|
||||
root = "."
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
# Just plain old shell command. You could use `make` as well.
|
||||
cmd = "go build -o ./dist/taskcafe cmd/taskcafe/main.go"
|
||||
# Binary file yields from `cmd`.
|
||||
bin = "dist/taskcafe"
|
||||
# Customize binary.
|
||||
full_bin = "./dist/taskcafe web"
|
||||
# Watch these filename extensions.
|
||||
include_ext = ["go"]
|
||||
# Ignore these filename extensions or directories.
|
||||
exclude_dir = ["dist", "frontend"]
|
||||
# Watch these directories if you specified.
|
||||
include_dir = []
|
||||
# Exclude files.
|
||||
exclude_file = []
|
||||
# This log file places in your tmp_dir.
|
||||
log = "air.log"
|
||||
# It's not necessary to trigger build each time file changes if it's too frequent.
|
||||
delay = 1000 # ms
|
||||
# Stop running old binary when build errors occur.
|
||||
stop_on_error = true
|
||||
# Send Interrupt signal before killing process (windows does not support this feature)
|
||||
send_interrupt = false
|
||||
# Delay after sending Interrupt signal
|
||||
kill_delay = 500 # ms
|
||||
|
||||
[log]
|
||||
# Show log time
|
||||
time = false
|
||||
|
||||
[color]
|
||||
# Customize each part's color. If no color found, use the raw app log.
|
||||
main = "magenta"
|
||||
watcher = "cyan"
|
||||
build = "yellow"
|
||||
runner = "green"
|
||||
|
||||
[misc]
|
||||
# Delete tmp directory on exit
|
||||
clean_on_exit = true
|
@ -1,5 +1,5 @@
|
||||
[server]
|
||||
hostname = '0.0.0.0:3333'
|
||||
[general]
|
||||
host = '0.0.0.0:3333'
|
||||
|
||||
[email_notifications]
|
||||
enabled = true
|
||||
|
@ -12,7 +12,7 @@ services:
|
||||
volumes:
|
||||
- taskcafe-postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 8855:5432
|
||||
- 5432:5432
|
||||
mailhog:
|
||||
image: mailhog/mailhog:latest
|
||||
restart: always
|
||||
|
@ -31,9 +31,7 @@
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
|
||||
"no-case-declarations": "off",
|
||||
"no-plusplus": "off",
|
||||
"react/prop-types": 0,
|
||||
"no-continue": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"no-param-reassign": "off",
|
||||
"import/extensions": [
|
||||
|
@ -6,6 +6,14 @@
|
||||
"@apollo/client": "^3.0.0-rc.8",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.27",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.12.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.12.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.12.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.8",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/date-fns": "^2.6.0",
|
||||
@ -13,7 +21,6 @@
|
||||
"@types/jwt-decode": "^2.2.1",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/query-string": "^6.3.0",
|
||||
"@types/react": "^16.9.21",
|
||||
"@types/react-beautiful-dnd": "^12.1.1",
|
||||
"@types/react-datepicker": "^2.11.0",
|
||||
@ -34,15 +41,14 @@
|
||||
"axios-auth-refresh": "^2.2.7",
|
||||
"color": "^3.1.2",
|
||||
"date-fns": "^2.14.0",
|
||||
"dayjs": "^1.9.1",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"history": "^4.10.1",
|
||||
"immer": "^6.0.3",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.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",
|
||||
@ -54,9 +60,9 @@
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.0",
|
||||
"react-select": "^3.1.0",
|
||||
"rich-markdown-editor": "^10.6.5",
|
||||
"react-timeago": "^4.4.0",
|
||||
"react-toastify": "^6.0.8",
|
||||
"rich-markdown-editor": "^10.6.5",
|
||||
"styled-components": "^5.0.1",
|
||||
"typescript": "~3.7.2"
|
||||
},
|
||||
|
@ -5,7 +5,6 @@ import GlobalTopNavbar from 'App/TopNavbar';
|
||||
import {
|
||||
useUsersQuery,
|
||||
useDeleteUserAccountMutation,
|
||||
useDeleteInvitedUserAccountMutation,
|
||||
useCreateUserAccountMutation,
|
||||
UsersDocument,
|
||||
UsersQuery,
|
||||
@ -82,7 +81,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;
|
||||
`;
|
||||
|
||||
@ -177,17 +176,6 @@ const AdminRoute = () => {
|
||||
const { loading, data } = useUsersQuery();
|
||||
const { showPopup, hidePopup } = usePopup();
|
||||
const { user } = useCurrentUser();
|
||||
const [deleteInvitedUser] = useDeleteInvitedUserAccountMutation({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
||||
produce(cache, draftCache => {
|
||||
draftCache.invitedUsers = cache.invitedUsers.filter(
|
||||
u => u.id !== response.data.deleteInvitedUserAccount.invitedUser.id,
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
const [deleteUser] = useDeleteUserAccountMutation({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
|
||||
@ -227,16 +215,11 @@ const AdminRoute = () => {
|
||||
<Admin
|
||||
initialTab={0}
|
||||
users={data.users}
|
||||
invitedUsers={data.invitedUsers}
|
||||
canInviteUser={user.roles.org === 'admin'}
|
||||
onInviteUser={NOOP}
|
||||
onUpdateUserPassword={() => {
|
||||
hidePopup();
|
||||
}}
|
||||
onDeleteInvitedUser={invitedUserID => {
|
||||
deleteInvitedUser({ variables: { invitedUserID } });
|
||||
hidePopup();
|
||||
}}
|
||||
onDeleteUser={(userID, newOwnerID) => {
|
||||
deleteUser({ variables: { userID, newOwnerID } });
|
||||
hidePopup();
|
||||
|
@ -1,20 +1,16 @@
|
||||
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 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';
|
||||
|
||||
const MainContent = styled.div`
|
||||
padding: 0 0 0 0;
|
||||
@ -25,50 +21,6 @@ const MainContent = styled.div`
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const AuthorizedRoutes = () => {
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setUser } = useCurrentUser();
|
||||
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, 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 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="/profile" component={Profile} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
</MainContent>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
type RoutesProps = {
|
||||
history: H.History;
|
||||
};
|
||||
@ -76,9 +28,15 @@ 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} />
|
||||
</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();
|
||||
@ -138,7 +137,7 @@ const ProjectFinder = () => {
|
||||
return {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
projects: projects.filter(project => project.team && project.team.id === team.id),
|
||||
projects: projects.filter(project => project.team.id === team.id),
|
||||
};
|
||||
});
|
||||
return (
|
||||
@ -231,12 +230,10 @@ type GlobalTopNavbarProps = {
|
||||
menuType?: Array<MenuItem>;
|
||||
onChangeRole?: (userID: string, roleCode: RoleCode) => void;
|
||||
projectMembers?: null | Array<TaskUser>;
|
||||
projectInvitedMembers?: null | Array<InvitedUser>;
|
||||
onSaveProjectName?: (projectName: string) => void;
|
||||
onInviteUser?: ($target: React.RefObject<HTMLElement>) => void;
|
||||
onSetTab?: (tab: number) => void;
|
||||
onRemoveFromBoard?: (userID: string) => void;
|
||||
onRemoveInvitedFromBoard?: (email: string) => void;
|
||||
};
|
||||
|
||||
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
@ -249,10 +246,8 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
name,
|
||||
popupContent,
|
||||
projectMembers,
|
||||
projectInvitedMembers,
|
||||
onInviteUser,
|
||||
onSaveProjectName,
|
||||
onRemoveInvitedFromBoard,
|
||||
onRemoveFromBoard,
|
||||
}) => {
|
||||
const { user, setUserRoles, setUser } = useCurrentUser();
|
||||
@ -329,7 +324,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
/>
|
||||
))}
|
||||
</NotificationPopup>,
|
||||
{ width: 415, borders: false, diamondColor: theme.colors.primary },
|
||||
{ width: 415, borders: false, diamondColor: '#7367f0' },
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -338,34 +333,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
return null;
|
||||
}
|
||||
const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
|
||||
const onInvitedMemberProfile = ($targetRef: React.RefObject<HTMLElement>, email: string) => {
|
||||
const member = projectInvitedMembers ? projectInvitedMembers.find(u => u.email === email) : null;
|
||||
if (member) {
|
||||
showPopup(
|
||||
$targetRef,
|
||||
<MiniProfile
|
||||
onRemoveFromBoard={() => {
|
||||
if (onRemoveInvitedFromBoard) {
|
||||
onRemoveInvitedFromBoard(member.email);
|
||||
}
|
||||
}}
|
||||
invited
|
||||
user={{
|
||||
id: member.email,
|
||||
fullName: member.email,
|
||||
bio: 'Invited',
|
||||
profileIcon: {
|
||||
bgColor: '#000',
|
||||
url: null,
|
||||
initials: member.email.charAt(0),
|
||||
},
|
||||
}}
|
||||
bio=""
|
||||
/>,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onMemberProfile = ($targetRef: React.RefObject<HTMLElement>, memberID: string) => {
|
||||
const member = projectMembers ? projectMembers.find(u => u.id === memberID) : null;
|
||||
const warning =
|
||||
@ -415,7 +382,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
canEditProjectName={userIsTeamOrProjectAdmin}
|
||||
canInviteUser={userIsTeamOrProjectAdmin}
|
||||
onMemberProfile={onMemberProfile}
|
||||
onInvitedMemberProfile={onInvitedMemberProfile}
|
||||
onInviteUser={onInviteUser}
|
||||
onChangeRole={onChangeRole}
|
||||
onChangeProjectOwner={onChangeProjectOwner}
|
||||
@ -426,7 +392,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
history.push('/');
|
||||
}}
|
||||
projectMembers={projectMembers}
|
||||
projectInvitedMembers={projectInvitedMembers}
|
||||
onProfileClick={onProfileClick}
|
||||
onSaveName={onSaveProjectName}
|
||||
onOpenSettings={onOpenSettings}
|
||||
|
@ -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 {
|
||||
}
|
||||
@ -52,6 +52,7 @@ type RefreshTokenResponse = {
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [user, setUser] = useState<CurrentUserRaw | null>(null);
|
||||
const setUserRoles = (roles: CurrentUserRoles) => {
|
||||
if (user) {
|
||||
@ -62,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 }}>
|
||||
@ -70,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(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');
|
||||
}
|
||||
}
|
||||
history.push('/projects');
|
||||
});
|
||||
}
|
||||
setComplete(true);
|
||||
}}
|
||||
/>
|
||||
</LoginWrapper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Install;
|
@ -5,6 +5,7 @@ import { TaskMetaFilters, TaskMeta, TaskMetaMatch, DueDateFilterType } from 'sha
|
||||
import Input from 'shared/components/ControlledInput';
|
||||
import { Popup, usePopup } from 'shared/components/PopupMenu';
|
||||
import produce from 'immer';
|
||||
import moment from 'moment';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import Member from 'shared/components/Member';
|
||||
|
||||
@ -12,7 +13,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 +72,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 +81,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;
|
||||
@ -74,11 +73,6 @@ const SortPopup: React.FC<SortPopupProps> = ({ sorting, onChangeTaskSorting }) =
|
||||
>
|
||||
<ActionTitle>Task title</ActionTitle>
|
||||
</ActionItem>
|
||||
<ActionItem
|
||||
onClick={() => handleSetSorting({ type: TaskSortingType.COMPLETE, direction: TaskSortingDirection.ASC })}
|
||||
>
|
||||
<ActionTitle>Complete</ActionTitle>
|
||||
</ActionItem>
|
||||
</ActionsList>
|
||||
);
|
||||
};
|
||||
|
@ -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 &&
|
||||
@ -323,6 +323,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
||||
const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({});
|
||||
const { loading, data } = useFindProjectQuery({
|
||||
variables: { projectID },
|
||||
pollInterval: 5000,
|
||||
});
|
||||
const [deleteTaskGroupTasks] = useDeleteTaskGroupTasksMutation({
|
||||
update: (client, resp) =>
|
||||
|
@ -3,7 +3,7 @@ import Modal from 'shared/components/Modal';
|
||||
import TaskDetails from 'shared/components/TaskDetails';
|
||||
import { Popup, usePopup } from 'shared/components/PopupMenu';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import { useRouteMatch, useHistory } from 'react-router';
|
||||
import { useRouteMatch, useHistory, Redirect } from 'react-router';
|
||||
import {
|
||||
useDeleteTaskChecklistMutation,
|
||||
useUpdateTaskChecklistNameMutation,
|
||||
@ -32,6 +32,7 @@ import Input from 'shared/components/Input';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import updateApolloCache from 'shared/utils/cache';
|
||||
import NOOP from 'shared/utils/noop';
|
||||
import hasNotFoundError from 'shared/utils/error';
|
||||
|
||||
const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => {
|
||||
const total = checklists.reduce((prev: any, next: any) => {
|
||||
@ -269,8 +270,8 @@ const Details: React.FC<DetailsProps> = ({
|
||||
);
|
||||
},
|
||||
});
|
||||
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
|
||||
const [setTaskComplete] = useSetTaskCompleteMutation();
|
||||
const { loading, data, refetch, error } = useFindTaskQuery({ variables: { taskID }, pollInterval: 5000 });
|
||||
const [setTaskComplete, { error: setTaskCompleteError }] = useSetTaskCompleteMutation();
|
||||
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
|
||||
onCompleted: () => {
|
||||
refetch();
|
||||
@ -289,9 +290,13 @@ const Details: React.FC<DetailsProps> = ({
|
||||
refreshCache();
|
||||
},
|
||||
});
|
||||
if (loading) {
|
||||
return null;
|
||||
if (hasNotFoundError(error, setTaskCompleteError)) {
|
||||
return <Redirect to={projectURL} />;
|
||||
}
|
||||
if (setTaskCompleteError && setTaskCompleteError)
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
@ -309,7 +314,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
task={data.findTask}
|
||||
onChecklistDrop={checklist => {
|
||||
updateTaskChecklistLocation({
|
||||
variables: { taskChecklistID: checklist.id, position: checklist.position },
|
||||
variables: { checklistID: checklist.id, position: checklist.position },
|
||||
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
@ -324,24 +329,20 @@ const Details: React.FC<DetailsProps> = ({
|
||||
},
|
||||
});
|
||||
}}
|
||||
onChecklistItemDrop={(prevChecklistID, taskChecklistID, checklistItem) => {
|
||||
onChecklistItemDrop={(prevChecklistID, checklistID, checklistItem) => {
|
||||
updateTaskChecklistItemLocation({
|
||||
variables: {
|
||||
taskChecklistID,
|
||||
taskChecklistItemID: checklistItem.id,
|
||||
position: checklistItem.position,
|
||||
},
|
||||
variables: { checklistID, checklistItemID: checklistItem.id, position: checklistItem.position },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updateTaskChecklistItemLocation: {
|
||||
__typename: 'UpdateTaskChecklistItemLocationPayload',
|
||||
prevChecklistID,
|
||||
taskChecklistID,
|
||||
checklistID,
|
||||
checklistItem: {
|
||||
__typename: 'TaskChecklistItem',
|
||||
position: checklistItem.position,
|
||||
id: checklistItem.id,
|
||||
taskChecklistID,
|
||||
taskChecklistID: checklistID,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -350,7 +351,11 @@ const Details: React.FC<DetailsProps> = ({
|
||||
onTaskNameChange={onTaskNameChange}
|
||||
onTaskDescriptionChange={onTaskDescriptionChange}
|
||||
onToggleTaskComplete={task => {
|
||||
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
|
||||
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } }).catch(r => {
|
||||
if (hasNotFoundError(r)) {
|
||||
history.push(projectURL);
|
||||
}
|
||||
});
|
||||
}}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onChangeItemName={(itemID, itemName) => {
|
||||
|
@ -3,11 +3,32 @@ import updateApolloCache from 'shared/utils/cache';
|
||||
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||
import produce from 'immer';
|
||||
import {
|
||||
useUpdateProjectMemberRoleMutation,
|
||||
useCreateProjectMemberMutation,
|
||||
useDeleteProjectMemberMutation,
|
||||
useSetTaskCompleteMutation,
|
||||
useToggleTaskLabelMutation,
|
||||
useUpdateProjectNameMutation,
|
||||
useFindProjectQuery,
|
||||
useUpdateTaskGroupNameMutation,
|
||||
useUpdateTaskNameMutation,
|
||||
useUpdateProjectLabelMutation,
|
||||
useCreateTaskMutation,
|
||||
useDeleteProjectLabelMutation,
|
||||
useDeleteTaskMutation,
|
||||
useUpdateTaskLocationMutation,
|
||||
useUpdateTaskGroupLocationMutation,
|
||||
useCreateTaskGroupMutation,
|
||||
useDeleteTaskGroupMutation,
|
||||
useUpdateTaskDescriptionMutation,
|
||||
useAssignTaskMutation,
|
||||
DeleteTaskDocument,
|
||||
FindProjectDocument,
|
||||
useCreateProjectLabelMutation,
|
||||
useUnassignTaskMutation,
|
||||
useUpdateTaskDueDateMutation,
|
||||
FindProjectQuery,
|
||||
useUsersQuery,
|
||||
} from 'shared/generated/graphql';
|
||||
import LabelManager from 'shared/components/PopupMenu/LabelManager';
|
||||
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
|
||||
|
@ -3,7 +3,6 @@ import React, { useState, useRef, useEffect, useContext } from 'react';
|
||||
import updateApolloCache from 'shared/utils/cache';
|
||||
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
|
||||
import styled from 'styled-components/macro';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||
import {
|
||||
useParams,
|
||||
@ -16,12 +15,11 @@ import {
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
useUpdateProjectMemberRoleMutation,
|
||||
useInviteProjectMembersMutation,
|
||||
useCreateProjectMemberMutation,
|
||||
useDeleteProjectMemberMutation,
|
||||
useToggleTaskLabelMutation,
|
||||
useUpdateProjectNameMutation,
|
||||
useFindProjectQuery,
|
||||
useDeleteInvitedProjectMemberMutation,
|
||||
useUpdateTaskNameMutation,
|
||||
useCreateTaskMutation,
|
||||
useDeleteTaskMutation,
|
||||
@ -39,21 +37,12 @@ import Input from 'shared/components/Input';
|
||||
import Member from 'shared/components/Member';
|
||||
import EmptyBoard from 'shared/components/EmptyBoard';
|
||||
import NOOP from 'shared/utils/noop';
|
||||
import { Lock, Cross } from 'shared/icons';
|
||||
import Button from 'shared/components/Button';
|
||||
import { useApolloClient } from '@apollo/react-hooks';
|
||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||
import gql from 'graphql-tag';
|
||||
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';
|
||||
|
||||
const RFC2822_EMAIL = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
|
||||
|
||||
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
|
||||
const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
|
||||
|
||||
@ -72,7 +61,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;
|
||||
`;
|
||||
@ -81,299 +70,29 @@ const MemberList = styled.div`
|
||||
margin: 8px 0;
|
||||
`;
|
||||
|
||||
type InviteUserData = {
|
||||
email?: string;
|
||||
suerID?: string;
|
||||
};
|
||||
type UserManagementPopupProps = {
|
||||
projectID: string;
|
||||
users: Array<User>;
|
||||
projectMembers: Array<TaskUser>;
|
||||
onInviteProjectMembers: (data: Array<InviteUserData>) => void;
|
||||
onAddProjectMember: (userID: string) => void;
|
||||
};
|
||||
|
||||
const VisibiltyPrivateIcon = styled(Lock)`
|
||||
padding-right: 4px;
|
||||
`;
|
||||
|
||||
const VisibiltyButtonText = styled.span`
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
|
||||
const ShareActions = styled.div`
|
||||
border-top: 1px solid #414561;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const VisibiltyButton = styled.button`
|
||||
cursor: pointer;
|
||||
margin: 2px 4px;
|
||||
padding: 2px 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
&:hover ${VisibiltyButtonText} {
|
||||
color: rgba(${props => props.theme.colors.text.secondary});
|
||||
}
|
||||
&:hover ${VisibiltyPrivateIcon} {
|
||||
fill: rgba(${props => props.theme.colors.text.secondary});
|
||||
stroke: rgba(${props => props.theme.colors.text.secondary});
|
||||
}
|
||||
&:hover {
|
||||
border-bottom: 1px solid rgba(${props => props.theme.colors.primary});
|
||||
}
|
||||
`;
|
||||
|
||||
type MemberFilterOptions = {
|
||||
projectID?: null | string;
|
||||
teamID?: null | string;
|
||||
organization?: boolean;
|
||||
};
|
||||
|
||||
const fetchMembers = async (client: any, projectID: string, options: MemberFilterOptions, input: string, cb: any) => {
|
||||
console.log(input.trim().length < 3);
|
||||
if (input && input.trim().length < 3) {
|
||||
return [];
|
||||
}
|
||||
const res = await client.query({
|
||||
query: gql`
|
||||
query {
|
||||
searchMembers(input: {searchFilter:"${input}", projectID:"${projectID}"}) {
|
||||
id
|
||||
similarity
|
||||
status
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
email
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
let results: any = [];
|
||||
const emails: Array<string> = [];
|
||||
console.log(res.data && res.data.searchMembers);
|
||||
if (res.data && res.data.searchMembers) {
|
||||
results = [
|
||||
...res.data.searchMembers.map((m: any) => {
|
||||
if (m.status === 'INVITED') {
|
||||
console.log(`${m.id} is added`);
|
||||
return {
|
||||
label: m.id,
|
||||
value: {
|
||||
id: m.id,
|
||||
type: 2,
|
||||
profileIcon: {
|
||||
bgColor: '#ccc',
|
||||
initials: m.id.charAt(0),
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
console.log(`${m.user.email} is added`);
|
||||
emails.push(m.user.email);
|
||||
return {
|
||||
label: m.user.fullName,
|
||||
value: { id: m.user.id, type: 0, profileIcon: m.user.profileIcon },
|
||||
};
|
||||
}
|
||||
}),
|
||||
];
|
||||
console.log(results);
|
||||
}
|
||||
|
||||
if (RFC2822_EMAIL.test(input) && !emails.find(e => e === input)) {
|
||||
results = [
|
||||
...results,
|
||||
{
|
||||
label: input,
|
||||
value: {
|
||||
id: input,
|
||||
type: 1,
|
||||
profileIcon: {
|
||||
bgColor: '#ccc',
|
||||
initials: input.charAt(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
type UserOptionProps = {
|
||||
innerProps: any;
|
||||
isDisabled: boolean;
|
||||
isFocused: boolean;
|
||||
label: string;
|
||||
data: any;
|
||||
getValue: any;
|
||||
};
|
||||
|
||||
const OptionWrapper = styled.div<{ isFocused: boolean }>`
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
${props => props.isFocused && `background: rgba(${props.theme.colors.primary});`}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
const OptionContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 12px;
|
||||
`;
|
||||
|
||||
const OptionLabel = styled.span<{ fontSize: number; quiet: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: ${p => p.fontSize}px;
|
||||
color: rgba(${p => (p.quiet ? p.theme.colors.text.primary : p.theme.colors.text.primary)});
|
||||
`;
|
||||
|
||||
const UserOption: React.FC<UserOptionProps> = ({ isDisabled, isFocused, innerProps, label, data }) => {
|
||||
console.log(data);
|
||||
return !isDisabled ? (
|
||||
<OptionWrapper {...innerProps} isFocused={isFocused}>
|
||||
<TaskAssignee
|
||||
size={32}
|
||||
member={{
|
||||
id: '',
|
||||
fullName: data.value.label,
|
||||
profileIcon: data.value.profileIcon,
|
||||
}}
|
||||
/>
|
||||
<OptionContent>
|
||||
<OptionLabel fontSize={16} quiet={false}>
|
||||
{label}
|
||||
</OptionLabel>
|
||||
{data.value.type === 2 && (
|
||||
<OptionLabel fontSize={14} quiet>
|
||||
Joined
|
||||
</OptionLabel>
|
||||
)}
|
||||
</OptionContent>
|
||||
</OptionWrapper>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const OptionValueWrapper = styled.div`
|
||||
background: rgba(${props => props.theme.colors.bg.primary});
|
||||
border-radius: 4px;
|
||||
margin: 2px;
|
||||
padding: 3px 6px 3px 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const OptionValueLabel = styled.span`
|
||||
font-size: 12px;
|
||||
color: rgba(${props => props.theme.colors.text.secondary});
|
||||
`;
|
||||
|
||||
const OptionValueRemove = styled.button`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
const OptionValue = ({ data, removeProps }: any) => {
|
||||
return (
|
||||
<OptionValueWrapper>
|
||||
<OptionValueLabel>{data.label}</OptionValueLabel>
|
||||
<OptionValueRemove {...removeProps}>
|
||||
<Cross width={14} height={14} />
|
||||
</OptionValueRemove>
|
||||
</OptionValueWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const InviteButton = styled(Button)`
|
||||
margin-top: 12px;
|
||||
height: 32px;
|
||||
padding: 4px 12px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const InviteContainer = styled.div`
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({
|
||||
projectID,
|
||||
users,
|
||||
projectMembers,
|
||||
onInviteProjectMembers,
|
||||
}) => {
|
||||
const client = useApolloClient();
|
||||
const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null);
|
||||
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
|
||||
return (
|
||||
<Popup tab={0} title="Invite a user">
|
||||
<InviteContainer>
|
||||
<AsyncSelect
|
||||
getOptionValue={option => option.value.id}
|
||||
placeholder="Email address or username"
|
||||
noOptionsMessage={() => null}
|
||||
onChange={(e: any) => {
|
||||
setInvitedUsers(e);
|
||||
}}
|
||||
isMulti
|
||||
autoFocus
|
||||
cacheOptions
|
||||
styles={colourStyles}
|
||||
defaultOption
|
||||
components={{
|
||||
MultiValue: OptionValue,
|
||||
Option: UserOption,
|
||||
IndicatorSeparator: null,
|
||||
DropdownIndicator: null,
|
||||
}}
|
||||
loadOptions={(i, cb) => fetchMembers(client, projectID, {}, i, cb)}
|
||||
/>
|
||||
</InviteContainer>
|
||||
<InviteButton
|
||||
onClick={() => {
|
||||
if (invitedUsers) {
|
||||
onInviteProjectMembers(
|
||||
invitedUsers.map(user => {
|
||||
if (user.value.type === 0) {
|
||||
return {
|
||||
userID: user.value.id,
|
||||
};
|
||||
}
|
||||
return {
|
||||
email: user.value.id,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
disabled={invitedUsers === null}
|
||||
hoverVariant="none"
|
||||
fontSize="16px"
|
||||
>
|
||||
Send Invite
|
||||
</InviteButton>
|
||||
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
|
||||
<MemberList>
|
||||
{users
|
||||
.filter(u => u.id !== projectMembers.find(p => p.id === u.id)?.id)
|
||||
.map(user => (
|
||||
<UserMember
|
||||
key={user.id}
|
||||
onCardMemberClick={() => onAddProjectMember(user.id)}
|
||||
showName
|
||||
member={user}
|
||||
taskID=""
|
||||
/>
|
||||
))}
|
||||
</MemberList>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
@ -416,30 +135,11 @@ const Project = () => {
|
||||
const [value, setValue] = useStateWithLocalStorage(CARD_LABEL_VARIANT_STORAGE_KEY);
|
||||
const [updateProjectMemberRole] = useUpdateProjectMemberRoleMutation();
|
||||
|
||||
const [deleteTask] = useDeleteTaskMutation({
|
||||
update: (client, resp) =>
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
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);
|
||||
}
|
||||
}),
|
||||
{ projectID },
|
||||
),
|
||||
});
|
||||
const [deleteTask] = useDeleteTaskMutation();
|
||||
|
||||
const [updateTaskName] = useUpdateTaskNameMutation();
|
||||
|
||||
const { loading, data, error } = useFindProjectQuery({
|
||||
const { loading, data } = useFindProjectQuery({
|
||||
variables: { projectID },
|
||||
});
|
||||
|
||||
@ -457,36 +157,14 @@ const Project = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const [inviteProjectMembers] = useInviteProjectMembersMutation({
|
||||
const [createProjectMember] = useCreateProjectMemberMutation({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
draftCache.findProject.members = [
|
||||
...cache.findProject.members,
|
||||
...response.data.inviteProjectMembers.members,
|
||||
];
|
||||
draftCache.findProject.invitedMembers = [
|
||||
...cache.findProject.invitedMembers,
|
||||
...response.data.inviteProjectMembers.invitedMembers,
|
||||
];
|
||||
}),
|
||||
{ projectID },
|
||||
);
|
||||
},
|
||||
});
|
||||
const [deleteInvitedProjectMember] = useDeleteInvitedProjectMemberMutation({
|
||||
update: (client, response) => {
|
||||
updateApolloCache<FindProjectQuery>(
|
||||
client,
|
||||
FindProjectDocument,
|
||||
cache =>
|
||||
produce(cache, draftCache => {
|
||||
draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter(
|
||||
m => m.email !== response.data.deleteInvitedProjectMember.invitedMember.email,
|
||||
);
|
||||
draftCache.findProject.members.push({ ...response.data.createProjectMember.member });
|
||||
}),
|
||||
{ projectID },
|
||||
);
|
||||
@ -527,9 +205,6 @@ const Project = () => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
history.push('/projects');
|
||||
}
|
||||
if (data) {
|
||||
labelsRef.current = data.findProject.labels;
|
||||
|
||||
@ -546,10 +221,6 @@ const Project = () => {
|
||||
deleteProjectMember({ variables: { userID, projectID } });
|
||||
hidePopup();
|
||||
}}
|
||||
onRemoveInvitedFromBoard={email => {
|
||||
deleteInvitedProjectMember({ variables: { projectID, email } });
|
||||
hidePopup();
|
||||
}}
|
||||
onSaveProjectName={projectName => {
|
||||
updateProjectName({ variables: { projectID, name: projectName } });
|
||||
}}
|
||||
@ -557,10 +228,8 @@ const Project = () => {
|
||||
showPopup(
|
||||
$target,
|
||||
<UserManagementPopup
|
||||
projectID={projectID}
|
||||
onInviteProjectMembers={members => {
|
||||
inviteProjectMembers({ variables: { projectID, members } });
|
||||
hidePopup();
|
||||
onAddProjectMember={userID => {
|
||||
createProjectMember({ variables: { userID, projectID } });
|
||||
}}
|
||||
users={data.users}
|
||||
projectMembers={data.findProject.members}
|
||||
@ -571,9 +240,8 @@ const Project = () => {
|
||||
menuType={[{ name: 'Board', link: location.pathname }]}
|
||||
currentTab={0}
|
||||
projectMembers={data.findProject.members}
|
||||
projectInvitedMembers={data.findProject.invitedMembers}
|
||||
projectID={projectID}
|
||||
teamID={data.findProject.team ? data.findProject.team.id : null}
|
||||
teamID={data.findProject.team.id}
|
||||
name={data.findProject.name}
|
||||
/>
|
||||
<Route path={`${match.path}`} exact render={() => <Redirect to={`${match.url}/board`} />} />
|
||||
@ -616,7 +284,6 @@ const Project = () => {
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
deleteTask({ variables: { taskID: deletedTask.id } });
|
||||
history.push(`${match.url}/board`);
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, $targetRef) => {
|
||||
taskLabelsRef.current = task.labels;
|
||||
|
@ -20,8 +20,33 @@ 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';
|
||||
|
||||
const EmptyStateContent = styled.div`
|
||||
display: flex;
|
||||
justy-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const EmptyStateTitle = styled.h3`
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
const EmptyStatePrompt = styled.span`
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
font-size: 16px;
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const EmptyState = styled(Empty)`
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
`;
|
||||
|
||||
const CreateTeamButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type CreateTeamData = { teamName: string };
|
||||
|
||||
@ -31,10 +56,6 @@ type CreateTeamFormProps = {
|
||||
|
||||
const CreateTeamFormContainer = styled.form``;
|
||||
|
||||
const CreateTeamButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const CreateTeamForm: React.FC<CreateTeamFormProps> = ({ onCreateTeam }) => {
|
||||
const { register, handleSubmit } = useForm<CreateTeamData>();
|
||||
const createTeam = (data: CreateTeamData) => {
|
||||
@ -56,7 +77,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 +199,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`
|
||||
@ -188,6 +209,13 @@ const ProjectsContainer = styled.div`
|
||||
min-width: 288px;
|
||||
`;
|
||||
|
||||
const ProjectGrid = styled.div`
|
||||
max-width: 780px;
|
||||
display: grid;
|
||||
grid-template-columns: 240px 240px 240px;
|
||||
gap: 20px 10px;
|
||||
`;
|
||||
|
||||
const AddTeamButton = styled(Button)`
|
||||
padding: 6px 12px;
|
||||
position: absolute;
|
||||
@ -195,6 +223,10 @@ const AddTeamButton = styled(Button)`
|
||||
right: 12px;
|
||||
`;
|
||||
|
||||
const CreateFirstTeam = styled(Button)`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
type ShowNewProject = {
|
||||
open: boolean;
|
||||
initialTeamID: null | string;
|
||||
@ -202,7 +234,7 @@ type ShowNewProject = {
|
||||
|
||||
const Projects = () => {
|
||||
const { showPopup, hidePopup } = usePopup();
|
||||
const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only' });
|
||||
const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only', pollInterval: 5000 });
|
||||
useEffect(() => {
|
||||
document.title = 'Taskcafé';
|
||||
}, []);
|
||||
@ -231,17 +263,10 @@ 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;
|
||||
const personalProjects = projects
|
||||
.filter(p => p.team === null)
|
||||
.sort((a, b) => {
|
||||
const textA = a.name.toUpperCase();
|
||||
const textB = b.name.toUpperCase();
|
||||
return textA < textB ? -1 : textA > textB ? 1 : 0; // eslint-disable-line no-nested-ternary
|
||||
});
|
||||
const projectTeams = teams
|
||||
.sort((a, b) => {
|
||||
const textA = a.name.toUpperCase();
|
||||
@ -253,7 +278,7 @@ const Projects = () => {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
projects: projects
|
||||
.filter(project => project.team && project.team.id === team.id)
|
||||
.filter(project => project.team.id === team.id)
|
||||
.sort((a, b) => {
|
||||
const textA = a.name.toUpperCase();
|
||||
const textB = b.name.toUpperCase();
|
||||
@ -294,35 +319,39 @@ const Projects = () => {
|
||||
Add Team
|
||||
</AddTeamButton>
|
||||
)}
|
||||
<div>
|
||||
<ProjectSectionTitleWrapper>
|
||||
<ProjectSectionTitle>Personal Projects</ProjectSectionTitle>
|
||||
</ProjectSectionTitleWrapper>
|
||||
<ProjectList>
|
||||
{personalProjects.map((project, idx) => (
|
||||
<ProjectListItem key={project.id}>
|
||||
<ProjectTile color={colors[idx % 5]} to={`/projects/${project.id}`}>
|
||||
<ProjectTileFade />
|
||||
<ProjectTileDetails>
|
||||
<ProjectTileName>{project.name}</ProjectTileName>
|
||||
</ProjectTileDetails>
|
||||
</ProjectTile>
|
||||
</ProjectListItem>
|
||||
))}
|
||||
<ProjectListItem>
|
||||
<ProjectAddTile
|
||||
onClick={() => {
|
||||
setShowNewProject({ open: true, initialTeamID: 'no-team' });
|
||||
}}
|
||||
>
|
||||
<ProjectTileFade />
|
||||
<ProjectAddTileDetails>
|
||||
<ProjectTileName centered>Create new project</ProjectTileName>
|
||||
</ProjectAddTileDetails>
|
||||
</ProjectAddTile>
|
||||
</ProjectListItem>
|
||||
</ProjectList>
|
||||
</div>
|
||||
{projectTeams.length === 0 && (
|
||||
<EmptyStateContent>
|
||||
<EmptyState width={425} height={425} />
|
||||
<EmptyStateTitle>No teams exist</EmptyStateTitle>
|
||||
<EmptyStatePrompt>Create a new team to get started</EmptyStatePrompt>
|
||||
<CreateFirstTeam
|
||||
variant="outline"
|
||||
onClick={$target => {
|
||||
showPopup(
|
||||
$target,
|
||||
<Popup
|
||||
title="Create team"
|
||||
tab={0}
|
||||
onClose={() => {
|
||||
hidePopup();
|
||||
}}
|
||||
>
|
||||
<CreateTeamForm
|
||||
onCreateTeam={teamName => {
|
||||
if (organizationID) {
|
||||
createTeam({ variables: { name: teamName, organizationID } });
|
||||
hidePopup();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Popup>,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Create new team
|
||||
</CreateFirstTeam>
|
||||
</EmptyStateContent>
|
||||
)}
|
||||
{projectTeams.map(team => {
|
||||
return (
|
||||
<div key={team.id}>
|
||||
@ -376,7 +405,7 @@ const Projects = () => {
|
||||
initialTeamID={showNewProject.initialTeamID}
|
||||
onCreateProject={(name, teamID) => {
|
||||
if (user) {
|
||||
createProject({ variables: { teamID, name } });
|
||||
createProject({ variables: { teamID, name, userID: user.id } });
|
||||
setShowNewProject({ open: false, initialTeamID: 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});
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -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,14 +147,14 @@ const ProjectListWrapper = styled.div`
|
||||
flex: 1 1;
|
||||
`;
|
||||
|
||||
const colors = theme.colors.multiColors;
|
||||
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
|
||||
|
||||
type TeamProjectsProps = {
|
||||
teamID: string;
|
||||
};
|
||||
|
||||
const TeamProjects: React.FC<TeamProjectsProps> = ({ teamID }) => {
|
||||
const { loading, data } = useGetTeamQuery({ variables: { teamID } });
|
||||
const { loading, data } = useGetTeamQuery({ variables: { teamID }, pollInterval: 5000 });
|
||||
if (loading) {
|
||||
return <span>loading</span>;
|
||||
}
|
||||
|
@ -8,28 +8,15 @@ import { HttpLink } from 'apollo-link-http';
|
||||
import { onError } from 'apollo-link-error';
|
||||
import { enableMapSet } from 'immer';
|
||||
import { ApolloLink, Observable, fromPromise } from 'apollo-link';
|
||||
import dayjs from 'dayjs';
|
||||
import updateLocale from 'dayjs/plugin/updateLocale';
|
||||
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import isBetween from 'dayjs/plugin/isBetween';
|
||||
import weekday from 'dayjs/plugin/weekday';
|
||||
import moment from 'moment';
|
||||
import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken';
|
||||
import cache from './App/cache';
|
||||
import App from './App';
|
||||
|
||||
// https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8
|
||||
|
||||
dayjs.extend(isSameOrAfter);
|
||||
|
||||
dayjs.extend(weekday);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(customParseFormat);
|
||||
enableMapSet();
|
||||
|
||||
dayjs.extend(updateLocale);
|
||||
|
||||
dayjs.updateLocale('en', {
|
||||
moment.updateLocale('en', {
|
||||
week: {
|
||||
dow: 1, // First day of week is Monday
|
||||
doy: 7, // First week of year must contain 1 January (7 + 1 - 1)
|
||||
|
0
frontend/src/outline.d.ts
vendored
0
frontend/src/outline.d.ts
vendored
@ -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)};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -51,9 +51,7 @@ export const Default = () => {
|
||||
},
|
||||
},
|
||||
]}
|
||||
invitedUsers={[]}
|
||||
onAddUser={action('add user')}
|
||||
onDeleteInvitedUser={action('delete invited user')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
|
@ -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)`
|
||||
@ -105,8 +104,8 @@ type TeamRoleManagerPopupProps = {
|
||||
user: User;
|
||||
users: Array<User>;
|
||||
warning?: string | null;
|
||||
canChangeRole?: boolean;
|
||||
onChangeRole?: (roleCode: RoleCode) => void;
|
||||
canChangeRole: boolean;
|
||||
onChangeRole: (roleCode: RoleCode) => void;
|
||||
updateUserPassword?: (user: TaskUser, password: string) => void;
|
||||
onDeleteUser?: (userID: string, newOwnerID: string | null) => void;
|
||||
};
|
||||
@ -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;
|
||||
@ -531,10 +530,8 @@ type AdminProps = {
|
||||
onDeleteUser: (userID: string, newOwnerID: string | null) => void;
|
||||
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
|
||||
users: Array<User>;
|
||||
invitedUsers: Array<InvitedUserAccount>;
|
||||
canInviteUser: boolean;
|
||||
onUpdateUserPassword: (user: TaskUser, password: string) => void;
|
||||
onDeleteInvitedUser: (invitedUserID: string) => void;
|
||||
};
|
||||
|
||||
const Admin: React.FC<AdminProps> = ({
|
||||
@ -543,9 +540,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
onUpdateUserPassword,
|
||||
canInviteUser,
|
||||
onDeleteUser,
|
||||
onDeleteInvitedUser,
|
||||
onInviteUser,
|
||||
invitedUsers,
|
||||
users,
|
||||
}) => {
|
||||
const warning =
|
||||
@ -582,7 +577,7 @@ const Admin: React.FC<AdminProps> = ({
|
||||
<TabContent>
|
||||
<MemberListWrapper>
|
||||
<MemberListHeader>
|
||||
<ListTitle>{`Members (${users.length + invitedUsers.length})`}</ListTitle>
|
||||
<ListTitle>{`Members (${users.length})`}</ListTitle>
|
||||
<ListDesc>
|
||||
Organization admins can create / manage / delete all projects & teams. Members only have access to teams
|
||||
or projects they have been added to.
|
||||
@ -640,65 +635,6 @@ const Admin: React.FC<AdminProps> = ({
|
||||
</MemberListItem>
|
||||
);
|
||||
})}
|
||||
{invitedUsers.map(member => {
|
||||
return (
|
||||
<MemberListItem>
|
||||
<MemberProfile
|
||||
showRoleIcons
|
||||
size={32}
|
||||
onMemberProfile={NOOP}
|
||||
member={{
|
||||
id: member.id,
|
||||
fullName: member.email,
|
||||
profileIcon: {
|
||||
bgColor: '#fff',
|
||||
url: null,
|
||||
initials: member.email.charAt(0),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<MemberListItemDetails>
|
||||
<MemberItemName>{member.email}</MemberItemName>
|
||||
<MemberItemUsername>Invited</MemberItemUsername>
|
||||
</MemberListItemDetails>
|
||||
<MemberItemOptions>
|
||||
<MemberItemOption
|
||||
variant="outline"
|
||||
onClick={$target => {
|
||||
showPopup(
|
||||
$target,
|
||||
<TeamRoleManagerPopup
|
||||
user={{
|
||||
id: member.id,
|
||||
fullName: member.email,
|
||||
profileIcon: {
|
||||
bgColor: '#fff',
|
||||
url: null,
|
||||
initials: member.email.charAt(0),
|
||||
},
|
||||
member: {
|
||||
teams: [],
|
||||
projects: [],
|
||||
},
|
||||
owned: {
|
||||
teams: [],
|
||||
projects: [],
|
||||
},
|
||||
}}
|
||||
users={users}
|
||||
onDeleteUser={() => {
|
||||
onDeleteInvitedUser(member.id);
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Manage
|
||||
</MemberItemOption>
|
||||
</MemberItemOptions>
|
||||
</MemberListItem>
|
||||
);
|
||||
})}
|
||||
</MemberList>
|
||||
</MemberListWrapper>
|
||||
</TabContent>
|
||||
|
@ -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`
|
||||
@ -36,37 +35,32 @@ const Base = styled.button<{ color: string; disabled: boolean }>`
|
||||
`}
|
||||
`;
|
||||
|
||||
const Filled = styled(Base)<{ hoverVariant: HoverVariant }>`
|
||||
background: ${props => props.theme.colors[props.color]};
|
||||
${props =>
|
||||
props.hoverVariant === 'boxShadow' &&
|
||||
css`
|
||||
&:hover {
|
||||
box-shadow: 0 8px 25px -8px ${props.theme.colors[props.color]};
|
||||
}
|
||||
`}
|
||||
const Filled = styled(Base)`
|
||||
background: rgba(${props => props.theme.colors[props.color]});
|
||||
&:hover {
|
||||
box-shadow: 0 8px 25px -8px rgba(${props => 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 +68,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 +81,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 +90,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 +103,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 +113,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);
|
||||
|
||||
@ -129,11 +123,9 @@ const Relief = styled(Base)`
|
||||
}
|
||||
`;
|
||||
|
||||
type HoverVariant = 'boxShadow' | 'none';
|
||||
type ButtonProps = {
|
||||
fontSize?: string;
|
||||
variant?: 'filled' | 'outline' | 'flat' | 'lineDown' | 'gradient' | 'relief';
|
||||
hoverVariant?: HoverVariant;
|
||||
color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
|
||||
disabled?: boolean;
|
||||
type?: 'button' | 'submit';
|
||||
@ -150,7 +142,6 @@ const Button: React.FC<ButtonProps> = ({
|
||||
invert = false,
|
||||
color = 'primary',
|
||||
variant = 'filled',
|
||||
hoverVariant = 'boxShadow',
|
||||
type = 'button',
|
||||
justifyTextContent = 'center',
|
||||
icon,
|
||||
@ -167,15 +158,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
switch (variant) {
|
||||
case 'filled':
|
||||
return (
|
||||
<Filled
|
||||
ref={$button}
|
||||
hoverVariant={hoverVariant}
|
||||
type={type}
|
||||
onClick={handleClick}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
color={color}
|
||||
>
|
||||
<Filled ref={$button} type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
|
||||
{icon && icon}
|
||||
<Text hasIcon={typeof icon !== 'undefined'} justifyTextContent={justifyTextContent} fontSize={fontSize}>
|
||||
{children}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import styled, { css, keyframes } from 'styled-components';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import TextareaAutosize from 'react-autosize-textarea';
|
||||
import { CheckCircle, CheckSquareOutline, Clock } from 'shared/icons';
|
||||
import { CheckCircle, CheckSquareOutline } from 'shared/icons';
|
||||
import { RefObject } from 'react';
|
||||
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,13 +16,11 @@ 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 }>`
|
||||
fill: ${props => props.color};
|
||||
`;
|
||||
export const ClockIcon = styled(FontAwesomeIcon)``;
|
||||
|
||||
export const EditorTextarea = styled(TextareaAutosize)`
|
||||
overflow: hidden;
|
||||
@ -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;
|
||||
`;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Pencil, Eye, List } from 'shared/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faClock, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
EditorTextarea,
|
||||
CardMember,
|
||||
@ -153,7 +155,7 @@ const Card = React.forwardRef(
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Pencil width={8} height={8} />
|
||||
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
|
||||
</ListCardOperation>
|
||||
)}
|
||||
<ListCardDetails complete={complete ?? false}>
|
||||
@ -216,18 +218,18 @@ const Card = React.forwardRef(
|
||||
<ListCardBadges>
|
||||
{watched && (
|
||||
<ListCardBadge>
|
||||
<Eye width={8} height={8} />
|
||||
<FontAwesomeIcon color="#6b778c" icon={faEye} size="xs" />
|
||||
</ListCardBadge>
|
||||
)}
|
||||
{dueDate && (
|
||||
<DueDateCardBadge isPastDue={dueDate.isPastDue}>
|
||||
<ClockIcon color={dueDate.isPastDue ? '#fff' : '#6b778c'} width={8} height={8} />
|
||||
<ClockIcon color={dueDate.isPastDue ? '#fff' : '#6b778c'} icon={faClock} size="xs" />
|
||||
<ListCardBadgeText>{dueDate.formattedDate}</ListCardBadgeText>
|
||||
</DueDateCardBadge>
|
||||
)}
|
||||
{description && (
|
||||
<DescriptionBadge>
|
||||
<List width={8} height={8} />
|
||||
<FontAwesomeIcon color="#6b778c" icon={faList} size="xs" />
|
||||
</DescriptionBadge>
|
||||
)}
|
||||
{checklists && (
|
||||
|
@ -1,15 +1,15 @@
|
||||
import styled from 'styled-components';
|
||||
import Button from 'shared/components/Button';
|
||||
import TextareaAutosize from 'react-autosize-textarea';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
|
||||
export const CancelIconWrapper = styled.div`
|
||||
export const CancelIcon = styled(FontAwesomeIcon)`
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
font-size: 1.25em;
|
||||
padding-left: 5px;
|
||||
`;
|
||||
|
||||
export const CardComposerWrapper = styled.div<{ isOpen: boolean }>`
|
||||
padding-bottom: 8px;
|
||||
display: ${props => (props.isOpen ? 'flex' : 'none')};
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useOnEscapeKeyDown from 'shared/hooks/onEscapeKeyDown';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||
import { Cross } from 'shared/icons';
|
||||
|
||||
import {
|
||||
CardComposerWrapper,
|
||||
CancelIconWrapper,
|
||||
CancelIcon,
|
||||
AddCardButton,
|
||||
ComposerControls,
|
||||
ComposerControlsSaveSection,
|
||||
@ -26,9 +26,10 @@ const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
|
||||
useOnOutsideClick($cardRef, true, onClose, null);
|
||||
useOnEscapeKeyDown(isOpen, onClose);
|
||||
return (
|
||||
<CardComposerWrapper isOpen={isOpen} ref={$cardRef}>
|
||||
<CardComposerWrapper isOpen={isOpen}>
|
||||
<Card
|
||||
title={cardName}
|
||||
ref={$cardRef}
|
||||
taskID=""
|
||||
taskGroupID=""
|
||||
editable
|
||||
@ -51,9 +52,7 @@ const CardComposer = ({ isOpen, onCreateCard, onClose }: Props) => {
|
||||
>
|
||||
Add Card
|
||||
</AddCardButton>
|
||||
<CancelIconWrapper onClick={() => onClose()}>
|
||||
<Cross width={12} height={12} />
|
||||
</CancelIconWrapper>
|
||||
<CancelIcon onClick={onClose} icon={faTimes} color="#42526e" />
|
||||
</ComposerControlsSaveSection>
|
||||
<ComposerControlsActionsSection />
|
||||
</ComposerControls>
|
||||
|
@ -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});
|
||||
}
|
||||
|
||||
`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, forwardRef } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import _ from 'lodash';
|
||||
@ -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;
|
||||
}
|
||||
`;
|
||||
@ -110,12 +110,12 @@ const HeaderActions = styled.div`
|
||||
`;
|
||||
|
||||
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
||||
const now = dayjs();
|
||||
const now = moment();
|
||||
const { register, handleSubmit, errors, setValue, setError, formState, control } = useForm<DueDateFormData>();
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const newDate = dayjs(startDate).format('YYYY-MM-DD');
|
||||
const newDate = moment(startDate).format('YYYY-MM-DD');
|
||||
setValue('endDate', newDate);
|
||||
}, [startDate]);
|
||||
|
||||
@ -135,7 +135,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
'December',
|
||||
];
|
||||
const saveDueDate = (data: any) => {
|
||||
const newDate = dayjs(`${data.endDate} ${dayjs(data.endTime).format('h:mm A')}`, 'YYYY-MM-DD h:mm A');
|
||||
const newDate = moment(`${data.endDate} ${moment(data.endTime).format('h:mm A')}`, 'YYYY-MM-DD h:mm A');
|
||||
if (newDate.isValid()) {
|
||||
onDueDateChange(task, newDate.toDate());
|
||||
}
|
||||
|
@ -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`
|
||||
@ -72,6 +73,7 @@ export const HeaderName = styled(TextareaAutosize)`
|
||||
box-shadow: none;
|
||||
font-weight: 600;
|
||||
margin: -4px 0;
|
||||
padding: 4px 8px;
|
||||
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
@ -95,7 +97,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' },
|
||||
],
|
||||
},
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
getNewDraggablePosition,
|
||||
getAfterDropDraggableList,
|
||||
} from 'shared/utils/draggables';
|
||||
import dayjs from 'dayjs';
|
||||
import moment from 'moment';
|
||||
import { TaskSorting, TaskSortingType, TaskSortingDirection, sortTasks } from 'shared/utils/sorting';
|
||||
|
||||
import { Container, BoardContainer, BoardWrapper } from './Styles';
|
||||
@ -51,7 +51,7 @@ export type TaskStatusFilter = {
|
||||
|
||||
export interface TaskMetaFilterName {
|
||||
meta: TaskMeta;
|
||||
value?: string | dayjs.Dayjs | null;
|
||||
value?: string | moment.Moment | null;
|
||||
id?: string | null;
|
||||
}
|
||||
|
||||
@ -104,30 +104,30 @@ function shouldStatusFilter(task: Task, filter: TaskStatusFilter) {
|
||||
return true;
|
||||
}
|
||||
if (filter.status === TaskStatus.COMPLETE && task.completedAt && task.complete === true) {
|
||||
const completedAt = dayjs(task.completedAt);
|
||||
const REFERENCE = dayjs();
|
||||
const completedAt = moment(task.completedAt);
|
||||
const REFERENCE = moment(); // fixed just for testing, use moment();
|
||||
switch (filter.since) {
|
||||
case TaskSince.TODAY:
|
||||
const TODAY = REFERENCE.clone().startOf('day');
|
||||
return completedAt.isSame(TODAY, 'd');
|
||||
case TaskSince.YESTERDAY:
|
||||
const YESTERDAY = REFERENCE.clone()
|
||||
.subtract(1, 'day')
|
||||
.subtract(1, 'days')
|
||||
.startOf('day');
|
||||
return completedAt.isSameOrAfter(YESTERDAY, 'd');
|
||||
case TaskSince.ONE_WEEK:
|
||||
const ONE_WEEK = REFERENCE.clone()
|
||||
.subtract(7, 'day')
|
||||
.subtract(7, 'days')
|
||||
.startOf('day');
|
||||
return completedAt.isSameOrAfter(ONE_WEEK, 'd');
|
||||
case TaskSince.TWO_WEEKS:
|
||||
const TWO_WEEKS = REFERENCE.clone()
|
||||
.subtract(14, 'day')
|
||||
.subtract(14, 'days')
|
||||
.startOf('day');
|
||||
return completedAt.isSameOrAfter(TWO_WEEKS, 'd');
|
||||
case TaskSince.THREE_WEEKS:
|
||||
const THREE_WEEKS = REFERENCE.clone()
|
||||
.subtract(21, 'day')
|
||||
.subtract(21, 'days')
|
||||
.startOf('day');
|
||||
return completedAt.isSameOrAfter(THREE_WEEKS, 'd');
|
||||
default:
|
||||
@ -353,7 +353,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
||||
task.dueDate
|
||||
? {
|
||||
isPastDue: false,
|
||||
formattedDate: dayjs(task.dueDate).format('MMM D, YYYY'),
|
||||
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TaskMetaFilters, DueDateFilterType } from 'shared/components/Lists';
|
||||
import dayjs from 'dayjs';
|
||||
import moment from 'moment';
|
||||
|
||||
enum ShouldFilter {
|
||||
NO_FILTER,
|
||||
@ -24,8 +24,8 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
isFiltered = shouldFilter(!(task.dueDate && task.dueDate !== null));
|
||||
}
|
||||
if (task.dueDate) {
|
||||
const taskDueDate = dayjs(task.dueDate);
|
||||
const today = dayjs();
|
||||
const taskDueDate = moment(task.dueDate);
|
||||
const today = moment();
|
||||
let start;
|
||||
let end;
|
||||
switch (filters.dueDate.type) {
|
||||
@ -40,7 +40,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
taskDueDate.isBefore(
|
||||
today
|
||||
.clone()
|
||||
.add(1, 'day')
|
||||
.add(1, 'days')
|
||||
.endOf('day'),
|
||||
),
|
||||
);
|
||||
@ -60,12 +60,12 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
start = today
|
||||
.clone()
|
||||
.weekday(0)
|
||||
.add(7, 'day')
|
||||
.add(7, 'days')
|
||||
.startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.weekday(6)
|
||||
.add(7, 'day')
|
||||
.add(7, 'days')
|
||||
.endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
@ -73,7 +73,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(7, 'day')
|
||||
.add(7, 'days')
|
||||
.endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
@ -81,7 +81,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(14, 'day')
|
||||
.add(14, 'days')
|
||||
.endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
@ -89,7 +89,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(21, 'day')
|
||||
.add(21, 'days')
|
||||
.endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
|
@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
import BaseStyles from 'App/BaseStyles';
|
||||
import LoadingSpinner from '.';
|
||||
|
||||
export default {
|
||||
component: LoadingSpinner,
|
||||
title: 'Login',
|
||||
parameters: {
|
||||
backgrounds: [
|
||||
{ name: 'white', value: '#ffffff' },
|
||||
{ name: 'gray', value: '#cdd3e1', default: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
export const Default = () => {
|
||||
return (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<LoadingSpinner />
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
const LoadingSpinnerKeyframes = keyframes`
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
|
||||
export const LoadingSpinnerWrapper = styled.div<{ color: string; size: string; borderSize: string; thickness: string }>`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: ${props => props.borderSize};
|
||||
height: ${props => props.borderSize};
|
||||
|
||||
& > div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: ${props => props.size};
|
||||
height: ${props => props.size};
|
||||
margin: ${props => props.thickness};
|
||||
border: ${props => props.thickness} solid ${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;
|
||||
}
|
||||
|
||||
& > div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
& > div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
& > div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
`;
|
||||
|
||||
export default LoadingSpinnerWrapper;
|
@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { LoadingSpinnerWrapper } from './Styles';
|
||||
|
||||
type LoadingSpinnerProps = {
|
||||
color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
|
||||
size?: string;
|
||||
borderSize?: string;
|
||||
thickness?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The default parameters may not be applicable to every scenario
|
||||
*
|
||||
* While borderSize and size should be a single prop,
|
||||
* it is currently not as such because it would require math to be done to strings
|
||||
* e.g "80px - 16"
|
||||
*
|
||||
*
|
||||
* @param color
|
||||
* @param size The size of the spinner. It is recommended to be at least 16 px less than the borderSize
|
||||
* @param thickness
|
||||
* @param borderSize Border size affects the size of the border which if is too small may break the spinner.
|
||||
* @constructor
|
||||
*/
|
||||
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
|
||||
color = 'primary',
|
||||
size = '64px',
|
||||
thickness = '8px',
|
||||
borderSize = '80px',
|
||||
}) => {
|
||||
return (
|
||||
<LoadingSpinnerWrapper color={color} size={size} thickness={thickness} borderSize={borderSize}>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingSpinnerWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingSpinner;
|
@ -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);
|
||||
`;
|
||||
|
@ -3,7 +3,6 @@ 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,
|
||||
@ -74,7 +73,6 @@ const Login = ({ onSubmit }: LoginProps) => {
|
||||
|
||||
<ActionButtons>
|
||||
<RegisterButton variant="outline">Register</RegisterButton>
|
||||
{!isComplete && <LoadingSpinner size="32px" thickness="2px" borderSize="48px" />}
|
||||
<LoginButton type="submit" disabled={!isComplete}>
|
||||
Login
|
||||
</LoginButton>
|
||||
|
@ -20,14 +20,14 @@ export const MemberManagerSearch = styled(TextareaAutosize)`
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
||||
background: ${props => props.theme.colors.bgColor.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.bgColor.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)`
|
||||
|
@ -47,7 +47,6 @@ const permissions = [
|
||||
type MiniProfileProps = {
|
||||
bio: string;
|
||||
user: TaskUser;
|
||||
invited?: boolean;
|
||||
onRemoveFromTask?: () => void;
|
||||
onChangeRole?: (roleCode: RoleCode) => void;
|
||||
onRemoveFromBoard?: () => void;
|
||||
@ -57,7 +56,6 @@ type MiniProfileProps = {
|
||||
const MiniProfile: React.FC<MiniProfileProps> = ({
|
||||
user,
|
||||
bio,
|
||||
invited,
|
||||
canChangeRole,
|
||||
onRemoveFromTask,
|
||||
onChangeRole,
|
||||
@ -76,7 +74,7 @@ const MiniProfile: React.FC<MiniProfileProps> = ({
|
||||
)}
|
||||
<ProfileInfo>
|
||||
<InfoTitle>{user.fullName}</InfoTitle>
|
||||
{invited ? <InfoUsername>Invited</InfoUsername> : <InfoUsername>{`@${user.username}`}</InfoUsername>}
|
||||
<InfoUsername>{`@${user.username}`}</InfoUsername>
|
||||
<InfoBio>{bio}</InfoBio>
|
||||
</ProfileInfo>
|
||||
</Profile>
|
||||
|
@ -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,21 +209,21 @@ 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 = {
|
||||
initialTeamID: string | null;
|
||||
teams: Array<Team>;
|
||||
onClose: () => void;
|
||||
onCreateProject: (projectName: string, teamID: string | null) => void;
|
||||
onCreateProject: (projectName: string, teamID: string) => void;
|
||||
};
|
||||
|
||||
const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose, onCreateProject }) => {
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const [team, setTeam] = useState<null | string>(initialTeamID);
|
||||
const options = [{ label: 'No team', value: 'no-team' }, ...teams.map(t => ({ label: t.name, value: t.id }))];
|
||||
const options = teams.map(t => ({ label: t.name, value: t.id }));
|
||||
return (
|
||||
<Overlay>
|
||||
<Content>
|
||||
@ -272,8 +271,8 @@ const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose,
|
||||
</ProjectInfo>
|
||||
<CreateButton
|
||||
onClick={() => {
|
||||
if (projectName !== '') {
|
||||
onCreateProject(projectName, team === 'no-team' ? null : team);
|
||||
if (team && projectName !== '') {
|
||||
onCreateProject(projectName, team);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -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,54 +2,53 @@ 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;
|
||||
}
|
||||
|
||||
export const colourStyles = {
|
||||
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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useRef } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import styled from 'styled-components';
|
||||
import { DoubleChevronUp, Crown } from 'shared/icons';
|
||||
|
||||
export const AdminIcon = styled(DoubleChevronUp)`
|
||||
@ -24,78 +24,46 @@ const TaskDetailAssignee = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const Wrapper = styled.div<{
|
||||
size: number | string;
|
||||
bgColor: string | null;
|
||||
backgroundURL: string | null;
|
||||
hasClick: boolean;
|
||||
}>`
|
||||
export const Wrapper = styled.div<{ size: number | string; bgColor: string | null; backgroundURL: string | null }>`
|
||||
width: ${props => props.size}px;
|
||||
height: ${props => props.size}px;
|
||||
border-radius: 9999px;
|
||||
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;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
${props =>
|
||||
props.hasClick &&
|
||||
css`
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`}
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
type TaskAssigneeProps = {
|
||||
size: number | string;
|
||||
showRoleIcons?: boolean;
|
||||
member: TaskUser;
|
||||
invited?: boolean;
|
||||
onMemberProfile?: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
|
||||
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const TaskAssignee: React.FC<TaskAssigneeProps> = ({
|
||||
showRoleIcons,
|
||||
member,
|
||||
invited,
|
||||
onMemberProfile,
|
||||
size,
|
||||
className,
|
||||
}) => {
|
||||
const TaskAssignee: React.FC<TaskAssigneeProps> = ({ showRoleIcons, member, onMemberProfile, size, className }) => {
|
||||
const $memberRef = useRef<HTMLDivElement>(null);
|
||||
let profileIcon: ProfileIcon = {
|
||||
url: null,
|
||||
bgColor: null,
|
||||
initials: null,
|
||||
};
|
||||
if (member.profileIcon) {
|
||||
profileIcon = member.profileIcon;
|
||||
}
|
||||
return (
|
||||
<TaskDetailAssignee
|
||||
className={className}
|
||||
ref={$memberRef}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (onMemberProfile) {
|
||||
onMemberProfile($memberRef, member.id);
|
||||
}
|
||||
onMemberProfile($memberRef, member.id);
|
||||
}}
|
||||
key={member.id}
|
||||
>
|
||||
<Wrapper
|
||||
hasClick={typeof onMemberProfile !== undefined}
|
||||
backgroundURL={profileIcon.url ?? null}
|
||||
bgColor={profileIcon.bgColor ?? null}
|
||||
size={size}
|
||||
>
|
||||
{(!profileIcon.url && profileIcon.initials) ?? ''}
|
||||
<Wrapper backgroundURL={member.profileIcon.url ?? null} bgColor={member.profileIcon.bgColor ?? null} size={size}>
|
||||
{(!member.profileIcon.url && member.profileIcon.initials) ?? ''}
|
||||
</Wrapper>
|
||||
{showRoleIcons && member.role && member.role.code === 'admin' && <AdminIcon width={10} height={10} />}
|
||||
{showRoleIcons && member.role && member.role.code === 'owner' && <OwnerIcon width={10} height={10} />}
|
||||
|
@ -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;
|
||||
@ -554,13 +556,13 @@ export const ActivityItemHeaderTitle = styled.div`
|
||||
`;
|
||||
|
||||
export const ActivityItemHeaderTitleName = styled.span`
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
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;
|
||||
`;
|
||||
|
||||
@ -573,40 +575,13 @@ 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};
|
||||
`;
|
||||
|
||||
export const ViewRawButton = styled.button`
|
||||
border-radius: 3px;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: -24px;
|
||||
cursor: pointer;
|
||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.25)};
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsEditor = styled(TextareaAutosize)`
|
||||
min-height: 108px;
|
||||
color: #c2c6dc;
|
||||
background: #262c49;
|
||||
border-radius: 3px;
|
||||
line-height: 20px;
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
padding: 9px 8px 7px 8px;
|
||||
outline: none;
|
||||
border: none;
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
`;
|
||||
|
@ -17,7 +17,7 @@ import dark from 'shared/utils/editorTheme';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import dayjs from 'dayjs';
|
||||
import moment from 'moment';
|
||||
|
||||
import Task from 'shared/icons/Task';
|
||||
import {
|
||||
@ -30,7 +30,6 @@ import {
|
||||
AssignUserLabel,
|
||||
AssignUsersButton,
|
||||
AssignedUsersSection,
|
||||
ViewRawButton,
|
||||
DueDateTitle,
|
||||
Container,
|
||||
LeftSidebar,
|
||||
@ -66,7 +65,6 @@ import {
|
||||
CommentProfile,
|
||||
CommentInnerWrapper,
|
||||
ActivitySection,
|
||||
TaskDetailsEditor,
|
||||
} from './Styles';
|
||||
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
|
||||
import onDragEnd from './onDragEnd';
|
||||
@ -155,7 +153,6 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
return true;
|
||||
});
|
||||
const [saveTimeout, setSaveTimeout] = useState<any>(null);
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const [showCommentActions, setShowCommentActions] = useState(false);
|
||||
const taskDescriptionRef = useRef(task.description ?? '');
|
||||
const $noMemberBtn = useRef<HTMLDivElement>(null);
|
||||
@ -182,7 +179,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
}}
|
||||
>
|
||||
{task.dueDate ? (
|
||||
<SidebarButtonText>{dayjs(task.dueDate).format('MMM D [at] h:mm A')}</SidebarButtonText>
|
||||
<SidebarButtonText>{moment(task.dueDate).format('MMM D [at] h:mm A')}</SidebarButtonText>
|
||||
) : (
|
||||
<SidebarButtonText>No due date</SidebarButtonText>
|
||||
)}
|
||||
@ -312,34 +309,28 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
</HeaderContainer>
|
||||
<InnerContentContainer>
|
||||
<DescriptionContainer>
|
||||
{showRaw ? (
|
||||
<TaskDetailsEditor value={taskDescriptionRef.current} />
|
||||
) : (
|
||||
<EditorContainer
|
||||
onClick={e => {
|
||||
if (!editTaskDescription) {
|
||||
setEditTaskDescription(true);
|
||||
}
|
||||
<EditorContainer
|
||||
onClick={e => {
|
||||
if (!editTaskDescription) {
|
||||
setEditTaskDescription(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
defaultValue={task.description ?? ''}
|
||||
theme={dark}
|
||||
readOnly={!editTaskDescription}
|
||||
autoFocus
|
||||
onChange={value => {
|
||||
setSaveTimeout(() => {
|
||||
clearTimeout(saveTimeout);
|
||||
return setTimeout(saveDescription, 2000);
|
||||
});
|
||||
const text = value();
|
||||
taskDescriptionRef.current = text;
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
defaultValue={task.description ?? ''}
|
||||
theme={dark}
|
||||
readOnly={!editTaskDescription}
|
||||
autoFocus
|
||||
onChange={value => {
|
||||
setSaveTimeout(() => {
|
||||
clearTimeout(saveTimeout);
|
||||
return setTimeout(saveDescription, 2000);
|
||||
});
|
||||
const text = value();
|
||||
taskDescriptionRef.current = text;
|
||||
}}
|
||||
/>
|
||||
</EditorContainer>
|
||||
)}
|
||||
|
||||
<ViewRawButton onClick={() => setShowRaw(!showRaw)}>{showRaw ? 'Show editor' : 'Show raw'}</ViewRawButton>
|
||||
/>
|
||||
</EditorContainer>
|
||||
</DescriptionContainer>
|
||||
<ChecklistSection>
|
||||
<DragDropContext onDragEnd={result => onDragEnd(result, task, onChecklistDrop, onChecklistItemDrop)}>
|
||||
|
@ -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 },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -1,11 +1,10 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle, ListUnordered } from 'shared/icons';
|
||||
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle } from 'shared/icons';
|
||||
import styled from 'styled-components';
|
||||
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,
|
||||
@ -174,11 +173,8 @@ type NavBarProps = {
|
||||
user: TaskUser | null;
|
||||
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||
projectMembers?: Array<TaskUser> | null;
|
||||
projectInvitedMembers?: Array<InvitedUser> | null;
|
||||
|
||||
onRemoveFromBoard?: (userID: string) => void;
|
||||
onMemberProfile?: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
|
||||
onInvitedMemberProfile?: ($targetRef: React.RefObject<HTMLElement>, email: string) => void;
|
||||
};
|
||||
|
||||
const NavBar: React.FC<NavBarProps> = ({
|
||||
@ -188,12 +184,10 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
onChangeProjectOwner,
|
||||
currentTab,
|
||||
onMemberProfile,
|
||||
onInvitedMemberProfile,
|
||||
canEditProjectName = false,
|
||||
onOpenProjectFinder,
|
||||
onFavorite,
|
||||
onSetTab,
|
||||
projectInvitedMembers,
|
||||
onChangeRole,
|
||||
name,
|
||||
onRemoveFromBoard,
|
||||
@ -210,7 +204,6 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
onProfileClick($target);
|
||||
}
|
||||
};
|
||||
const history = useHistory();
|
||||
const { showPopup } = usePopup();
|
||||
return (
|
||||
<NavbarWrapper>
|
||||
@ -252,38 +245,19 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
<TaskcafeTitle>Taskcafé</TaskcafeTitle>
|
||||
</LogoContainer>
|
||||
<GlobalActions>
|
||||
{projectMembers && projectInvitedMembers && onMemberProfile && onInvitedMemberProfile && (
|
||||
{projectMembers && onMemberProfile && (
|
||||
<>
|
||||
<ProjectMembers>
|
||||
{projectMembers.map((member, idx) => (
|
||||
<ProjectMember
|
||||
showRoleIcons
|
||||
zIndex={projectMembers.length - idx + projectInvitedMembers.length}
|
||||
zIndex={projectMembers.length - idx}
|
||||
key={member.id}
|
||||
size={28}
|
||||
member={member}
|
||||
onMemberProfile={onMemberProfile}
|
||||
/>
|
||||
))}
|
||||
{projectInvitedMembers.map((member, idx) => (
|
||||
<ProjectMember
|
||||
showRoleIcons
|
||||
zIndex={projectInvitedMembers.length - idx}
|
||||
key={member.email}
|
||||
size={28}
|
||||
invited
|
||||
member={{
|
||||
id: member.email,
|
||||
fullName: member.email,
|
||||
profileIcon: {
|
||||
url: null,
|
||||
initials: member.email.charAt(0),
|
||||
bgColor: '#fff',
|
||||
},
|
||||
}}
|
||||
onMemberProfile={onInvitedMemberProfile}
|
||||
/>
|
||||
))}
|
||||
{canInviteUser && (
|
||||
<InviteButton
|
||||
onClick={$target => {
|
||||
@ -309,9 +283,6 @@ const NavBar: React.FC<NavBarProps> = ({
|
||||
<IconContainer disabled onClick={NOOP}>
|
||||
<CheckCircle width={20} height={20} />
|
||||
</IconContainer>
|
||||
<IconContainer disabled onClick={NOOP}>
|
||||
<ListUnordered width={20} height={20} />
|
||||
</IconContainer>
|
||||
<IconContainer disabled onClick={onNotificationClick}>
|
||||
<Bell color="#c2c6dc" size={20} />
|
||||
</IconContainer>
|
||||
|
@ -113,14 +113,6 @@ export type UserAccount = {
|
||||
member: MemberList;
|
||||
};
|
||||
|
||||
export type InvitedUserAccount = {
|
||||
__typename?: 'InvitedUserAccount';
|
||||
id: Scalars['ID'];
|
||||
email: Scalars['String'];
|
||||
invitedOn: Scalars['Time'];
|
||||
member: MemberList;
|
||||
};
|
||||
|
||||
export type Team = {
|
||||
__typename?: 'Team';
|
||||
id: Scalars['ID'];
|
||||
@ -129,21 +121,14 @@ export type Team = {
|
||||
members: Array<Member>;
|
||||
};
|
||||
|
||||
export type InvitedMember = {
|
||||
__typename?: 'InvitedMember';
|
||||
email: Scalars['String'];
|
||||
invitedOn: Scalars['Time'];
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
__typename?: 'Project';
|
||||
id: Scalars['ID'];
|
||||
createdAt: Scalars['Time'];
|
||||
name: Scalars['String'];
|
||||
team?: Maybe<Team>;
|
||||
team: Team;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
members: Array<Member>;
|
||||
invitedMembers: Array<InvitedMember>;
|
||||
labels: Array<ProjectLabel>;
|
||||
};
|
||||
|
||||
@ -225,9 +210,7 @@ export enum ObjectType {
|
||||
Team = 'TEAM',
|
||||
Project = 'PROJECT',
|
||||
Task = 'TASK',
|
||||
TaskGroup = 'TASK_GROUP',
|
||||
TaskChecklist = 'TASK_CHECKLIST',
|
||||
TaskChecklistItem = 'TASK_CHECKLIST_ITEM'
|
||||
TaskGroup = 'TASK_GROUP'
|
||||
}
|
||||
|
||||
export type Query = {
|
||||
@ -236,13 +219,11 @@ export type Query = {
|
||||
findTask: Task;
|
||||
findTeam: Team;
|
||||
findUser: UserAccount;
|
||||
invitedUsers: Array<InvitedUserAccount>;
|
||||
labelColors: Array<LabelColor>;
|
||||
me: MePayload;
|
||||
notifications: Array<Notification>;
|
||||
organizations: Array<Organization>;
|
||||
projects: Array<Project>;
|
||||
searchMembers: Array<MemberSearchResult>;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
teams: Array<Team>;
|
||||
users: Array<UserAccount>;
|
||||
@ -273,11 +254,6 @@ export type QueryProjectsArgs = {
|
||||
input?: Maybe<ProjectsFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type QuerySearchMembersArgs = {
|
||||
input: MemberSearchFilter;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
addTaskLabel: Task;
|
||||
@ -285,6 +261,7 @@ export type Mutation = {
|
||||
clearProfileAvatar: UserAccount;
|
||||
createProject: Project;
|
||||
createProjectLabel: ProjectLabel;
|
||||
createProjectMember: CreateProjectMemberPayload;
|
||||
createRefreshToken: RefreshToken;
|
||||
createTask: Task;
|
||||
createTaskChecklist: TaskChecklist;
|
||||
@ -293,8 +270,6 @@ export type Mutation = {
|
||||
createTeam: Team;
|
||||
createTeamMember: CreateTeamMemberPayload;
|
||||
createUserAccount: UserAccount;
|
||||
deleteInvitedProjectMember: DeleteInvitedProjectMemberPayload;
|
||||
deleteInvitedUserAccount: DeleteInvitedUserAccountPayload;
|
||||
deleteProject: DeleteProjectPayload;
|
||||
deleteProjectLabel: ProjectLabel;
|
||||
deleteProjectMember: DeleteProjectMemberPayload;
|
||||
@ -307,7 +282,6 @@ export type Mutation = {
|
||||
deleteTeamMember: DeleteTeamMemberPayload;
|
||||
deleteUserAccount: DeleteUserAccountPayload;
|
||||
duplicateTaskGroup: DuplicateTaskGroupPayload;
|
||||
inviteProjectMembers: InviteProjectMembersPayload;
|
||||
logoutUser: Scalars['Boolean'];
|
||||
removeTaskLabel: Task;
|
||||
setTaskChecklistItemComplete: TaskChecklistItem;
|
||||
@ -357,6 +331,11 @@ export type MutationCreateProjectLabelArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateProjectMemberArgs = {
|
||||
input: CreateProjectMember;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateRefreshTokenArgs = {
|
||||
input: NewRefreshToken;
|
||||
};
|
||||
@ -397,16 +376,6 @@ export type MutationCreateUserAccountArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteInvitedProjectMemberArgs = {
|
||||
input: DeleteInvitedProjectMember;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteInvitedUserAccountArgs = {
|
||||
input: DeleteInvitedUserAccount;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteProjectArgs = {
|
||||
input: DeleteProject;
|
||||
};
|
||||
@ -467,11 +436,6 @@ export type MutationDuplicateTaskGroupArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationInviteProjectMembersArgs = {
|
||||
input: InviteProjectMembers;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLogoutUserArgs = {
|
||||
input: LogoutUser;
|
||||
};
|
||||
@ -625,7 +589,7 @@ export type ProjectsFilter = {
|
||||
};
|
||||
|
||||
export type FindUser = {
|
||||
userID: Scalars['UUID'];
|
||||
userId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type FindProject = {
|
||||
@ -677,7 +641,8 @@ export type Notification = {
|
||||
};
|
||||
|
||||
export type NewProject = {
|
||||
teamID?: Maybe<Scalars['UUID']>;
|
||||
userID: Scalars['UUID'];
|
||||
teamID: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -722,32 +687,15 @@ export type UpdateProjectLabelColor = {
|
||||
labelColorID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteInvitedProjectMember = {
|
||||
export type CreateProjectMember = {
|
||||
projectID: Scalars['UUID'];
|
||||
email: Scalars['String'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteInvitedProjectMemberPayload = {
|
||||
__typename?: 'DeleteInvitedProjectMemberPayload';
|
||||
invitedMember: InvitedMember;
|
||||
};
|
||||
|
||||
export type MemberInvite = {
|
||||
userID?: Maybe<Scalars['UUID']>;
|
||||
email?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InviteProjectMembers = {
|
||||
projectID: Scalars['UUID'];
|
||||
members: Array<MemberInvite>;
|
||||
};
|
||||
|
||||
export type InviteProjectMembersPayload = {
|
||||
__typename?: 'InviteProjectMembersPayload';
|
||||
export type CreateProjectMemberPayload = {
|
||||
__typename?: 'CreateProjectMemberPayload';
|
||||
ok: Scalars['Boolean'];
|
||||
projectID: Scalars['UUID'];
|
||||
members: Array<Member>;
|
||||
invitedMembers: Array<InvitedMember>;
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export type DeleteProjectMember = {
|
||||
@ -818,34 +766,34 @@ export type NewTaskLocation = {
|
||||
};
|
||||
|
||||
export type DeleteTaskInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskID: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteTaskPayload = {
|
||||
__typename?: 'DeleteTaskPayload';
|
||||
taskID: Scalars['UUID'];
|
||||
taskID: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateTaskName = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateTaskChecklistItemLocation = {
|
||||
taskChecklistID: Scalars['UUID'];
|
||||
taskChecklistItemID: Scalars['UUID'];
|
||||
checklistID: Scalars['UUID'];
|
||||
checklistItemID: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type UpdateTaskChecklistItemLocationPayload = {
|
||||
__typename?: 'UpdateTaskChecklistItemLocationPayload';
|
||||
taskChecklistID: Scalars['UUID'];
|
||||
checklistID: Scalars['UUID'];
|
||||
prevChecklistID: Scalars['UUID'];
|
||||
checklistItem: TaskChecklistItem;
|
||||
};
|
||||
|
||||
export type UpdateTaskChecklistLocation = {
|
||||
taskChecklistID: Scalars['UUID'];
|
||||
checklistID: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
|
||||
@ -961,7 +909,7 @@ export type DeleteTaskGroupPayload = {
|
||||
};
|
||||
|
||||
export type NewTaskGroup = {
|
||||
projectID: Scalars['UUID'];
|
||||
projectID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
@ -972,7 +920,6 @@ export type AddTaskLabelInput = {
|
||||
};
|
||||
|
||||
export type RemoveTaskLabelInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskLabelID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
@ -1040,29 +987,6 @@ export type UpdateTeamMemberRolePayload = {
|
||||
member: Member;
|
||||
};
|
||||
|
||||
export type DeleteInvitedUserAccount = {
|
||||
invitedUserID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteInvitedUserAccountPayload = {
|
||||
__typename?: 'DeleteInvitedUserAccountPayload';
|
||||
invitedUser: InvitedUserAccount;
|
||||
};
|
||||
|
||||
export type MemberSearchFilter = {
|
||||
SearchFilter: Scalars['String'];
|
||||
projectID?: Maybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type MemberSearchResult = {
|
||||
__typename?: 'MemberSearchResult';
|
||||
similarity: Scalars['Int'];
|
||||
user: UserAccount;
|
||||
confirmed: Scalars['Boolean'];
|
||||
invited: Scalars['Boolean'];
|
||||
joined: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type UpdateUserInfoPayload = {
|
||||
__typename?: 'UpdateUserInfoPayload';
|
||||
user: UserAccount;
|
||||
@ -1097,7 +1021,7 @@ export type UpdateUserRolePayload = {
|
||||
};
|
||||
|
||||
export type NewRefreshToken = {
|
||||
userID: Scalars['UUID'];
|
||||
userId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type NewUserAccount = {
|
||||
@ -1110,7 +1034,7 @@ export type NewUserAccount = {
|
||||
};
|
||||
|
||||
export type LogoutUser = {
|
||||
userID: Scalars['UUID'];
|
||||
userID: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteUserAccount = {
|
||||
@ -1158,7 +1082,8 @@ export type ClearProfileAvatarMutation = (
|
||||
);
|
||||
|
||||
export type CreateProjectMutationVariables = {
|
||||
teamID?: Maybe<Scalars['UUID']>;
|
||||
teamID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -1168,10 +1093,10 @@ export type CreateProjectMutation = (
|
||||
& { createProject: (
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'id' | 'name'>
|
||||
& { team?: Maybe<(
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id' | 'name'>
|
||||
)> }
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
@ -1195,7 +1120,7 @@ export type CreateProjectLabelMutation = (
|
||||
);
|
||||
|
||||
export type CreateTaskGroupMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
projectID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
@ -1223,7 +1148,7 @@ export type DeleteProjectLabelMutation = (
|
||||
);
|
||||
|
||||
export type DeleteTaskMutationVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskID: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
@ -1266,10 +1191,10 @@ export type FindProjectQuery = (
|
||||
& { findProject: (
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'name'>
|
||||
& { team?: Maybe<(
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id'>
|
||||
)>, members: Array<(
|
||||
), members: Array<(
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id' | 'fullName' | 'username'>
|
||||
& { role: (
|
||||
@ -1279,9 +1204,6 @@ export type FindProjectQuery = (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
|
||||
) }
|
||||
)>, invitedMembers: Array<(
|
||||
{ __typename?: 'InvitedMember' }
|
||||
& Pick<InvitedMember, 'email' | 'invitedOn'>
|
||||
)>, labels: Array<(
|
||||
{ __typename?: 'ProjectLabel' }
|
||||
& Pick<ProjectLabel, 'id' | 'createdDate' | 'name'>
|
||||
@ -1436,10 +1358,10 @@ export type GetProjectsQuery = (
|
||||
)>, projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'id' | 'name'>
|
||||
& { team?: Maybe<(
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id' | 'name'>
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
@ -1467,6 +1389,31 @@ export type MeQuery = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateProjectMemberMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type CreateProjectMemberMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { createProjectMember: (
|
||||
{ __typename?: 'CreateProjectMemberPayload' }
|
||||
& Pick<CreateProjectMemberPayload, 'ok'>
|
||||
& { member: (
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id' | 'fullName' | 'username'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
|
||||
), role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
) }
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteProjectMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
};
|
||||
@ -1484,23 +1431,6 @@ export type DeleteProjectMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteInvitedProjectMemberMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
email: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type DeleteInvitedProjectMemberMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { deleteInvitedProjectMember: (
|
||||
{ __typename?: 'DeleteInvitedProjectMemberPayload' }
|
||||
& { invitedMember: (
|
||||
{ __typename?: 'InvitedMember' }
|
||||
& Pick<InvitedMember, 'email'>
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteProjectMemberMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
@ -1519,34 +1449,6 @@ export type DeleteProjectMemberMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type InviteProjectMembersMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
members: Array<MemberInvite>;
|
||||
};
|
||||
|
||||
|
||||
export type InviteProjectMembersMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { inviteProjectMembers: (
|
||||
{ __typename?: 'InviteProjectMembersPayload' }
|
||||
& Pick<InviteProjectMembersPayload, 'ok'>
|
||||
& { invitedMembers: Array<(
|
||||
{ __typename?: 'InvitedMember' }
|
||||
& Pick<InvitedMember, 'email' | 'invitedOn'>
|
||||
)>, members: Array<(
|
||||
{ __typename?: 'Member' }
|
||||
& Pick<Member, 'id' | 'fullName' | 'username'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
|
||||
), role: (
|
||||
{ __typename?: 'Role' }
|
||||
& Pick<Role, 'code' | 'name'>
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateProjectMemberRoleMutationVariables = {
|
||||
projectID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
@ -1682,8 +1584,8 @@ export type SetTaskCompleteMutation = (
|
||||
);
|
||||
|
||||
export type UpdateTaskChecklistItemLocationMutationVariables = {
|
||||
taskChecklistID: Scalars['UUID'];
|
||||
taskChecklistItemID: Scalars['UUID'];
|
||||
checklistID: Scalars['UUID'];
|
||||
checklistItemID: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
|
||||
@ -1692,7 +1594,7 @@ export type UpdateTaskChecklistItemLocationMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { updateTaskChecklistItemLocation: (
|
||||
{ __typename?: 'UpdateTaskChecklistItemLocationPayload' }
|
||||
& Pick<UpdateTaskChecklistItemLocationPayload, 'taskChecklistID' | 'prevChecklistID'>
|
||||
& Pick<UpdateTaskChecklistItemLocationPayload, 'checklistID' | 'prevChecklistID'>
|
||||
& { checklistItem: (
|
||||
{ __typename?: 'TaskChecklistItem' }
|
||||
& Pick<TaskChecklistItem, 'id' | 'taskChecklistID' | 'position'>
|
||||
@ -1715,7 +1617,7 @@ export type UpdateTaskChecklistItemNameMutation = (
|
||||
);
|
||||
|
||||
export type UpdateTaskChecklistLocationMutationVariables = {
|
||||
taskChecklistID: Scalars['UUID'];
|
||||
checklistID: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
};
|
||||
|
||||
@ -1932,10 +1834,10 @@ export type GetTeamQuery = (
|
||||
), projects: Array<(
|
||||
{ __typename?: 'Project' }
|
||||
& Pick<Project, 'id' | 'name'>
|
||||
& { team?: Maybe<(
|
||||
& { team: (
|
||||
{ __typename?: 'Team' }
|
||||
& Pick<Team, 'id' | 'name'>
|
||||
)> }
|
||||
) }
|
||||
)>, users: Array<(
|
||||
{ __typename?: 'UserAccount' }
|
||||
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'username'>
|
||||
@ -2171,7 +2073,7 @@ export type UpdateTaskLocationMutation = (
|
||||
);
|
||||
|
||||
export type UpdateTaskNameMutationVariables = {
|
||||
taskID: Scalars['UUID'];
|
||||
taskID: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -2227,22 +2129,6 @@ export type CreateUserAccountMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteInvitedUserAccountMutationVariables = {
|
||||
invitedUserID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
|
||||
export type DeleteInvitedUserAccountMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { deleteInvitedUserAccount: (
|
||||
{ __typename?: 'DeleteInvitedUserAccountPayload' }
|
||||
& { invitedUser: (
|
||||
{ __typename?: 'InvitedUserAccount' }
|
||||
& Pick<InvitedUserAccount, 'id'>
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteUserAccountMutationVariables = {
|
||||
userID: Scalars['UUID'];
|
||||
newOwnerID?: Maybe<Scalars['UUID']>;
|
||||
@ -2324,10 +2210,7 @@ export type UsersQueryVariables = {};
|
||||
|
||||
export type UsersQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { invitedUsers: Array<(
|
||||
{ __typename?: 'InvitedUserAccount' }
|
||||
& Pick<InvitedUserAccount, 'id' | 'email' | 'invitedOn'>
|
||||
)>, users: Array<(
|
||||
& { users: Array<(
|
||||
{ __typename?: 'UserAccount' }
|
||||
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'username'>
|
||||
& { role: (
|
||||
@ -2479,8 +2362,8 @@ export type ClearProfileAvatarMutationHookResult = ReturnType<typeof useClearPro
|
||||
export type ClearProfileAvatarMutationResult = ApolloReactCommon.MutationResult<ClearProfileAvatarMutation>;
|
||||
export type ClearProfileAvatarMutationOptions = ApolloReactCommon.BaseMutationOptions<ClearProfileAvatarMutation, ClearProfileAvatarMutationVariables>;
|
||||
export const CreateProjectDocument = gql`
|
||||
mutation createProject($teamID: UUID, $name: String!) {
|
||||
createProject(input: {teamID: $teamID, name: $name}) {
|
||||
mutation createProject($teamID: UUID!, $userID: UUID!, $name: String!) {
|
||||
createProject(input: {teamID: $teamID, userID: $userID, name: $name}) {
|
||||
id
|
||||
name
|
||||
team {
|
||||
@ -2506,6 +2389,7 @@ export type CreateProjectMutationFn = ApolloReactCommon.MutationFunction<CreateP
|
||||
* const [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({
|
||||
* variables: {
|
||||
* teamID: // value for 'teamID'
|
||||
* userID: // value for 'userID'
|
||||
* name: // value for 'name'
|
||||
* },
|
||||
* });
|
||||
@ -2559,7 +2443,7 @@ export type CreateProjectLabelMutationHookResult = ReturnType<typeof useCreatePr
|
||||
export type CreateProjectLabelMutationResult = ApolloReactCommon.MutationResult<CreateProjectLabelMutation>;
|
||||
export type CreateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectLabelMutation, CreateProjectLabelMutationVariables>;
|
||||
export const CreateTaskGroupDocument = gql`
|
||||
mutation createTaskGroup($projectID: UUID!, $name: String!, $position: Float!) {
|
||||
mutation createTaskGroup($projectID: String!, $name: String!, $position: Float!) {
|
||||
createTaskGroup(input: {projectID: $projectID, name: $name, position: $position}) {
|
||||
id
|
||||
name
|
||||
@ -2627,7 +2511,7 @@ export type DeleteProjectLabelMutationHookResult = ReturnType<typeof useDeletePr
|
||||
export type DeleteProjectLabelMutationResult = ApolloReactCommon.MutationResult<DeleteProjectLabelMutation>;
|
||||
export type DeleteProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectLabelMutation, DeleteProjectLabelMutationVariables>;
|
||||
export const DeleteTaskDocument = gql`
|
||||
mutation deleteTask($taskID: UUID!) {
|
||||
mutation deleteTask($taskID: String!) {
|
||||
deleteTask(input: {taskID: $taskID}) {
|
||||
taskID
|
||||
}
|
||||
@ -2719,10 +2603,6 @@ export const FindProjectDocument = gql`
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
invitedMembers {
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
labels {
|
||||
id
|
||||
createdDate
|
||||
@ -3004,6 +2884,53 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
|
||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
|
||||
export const CreateProjectMemberDocument = gql`
|
||||
mutation createProjectMember($projectID: UUID!, $userID: UUID!) {
|
||||
createProjectMember(input: {projectID: $projectID, userID: $userID}) {
|
||||
ok
|
||||
member {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
username
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateProjectMemberMutationFn = ApolloReactCommon.MutationFunction<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateProjectMemberMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateProjectMemberMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateProjectMemberMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [createProjectMemberMutation, { data, loading, error }] = useCreateProjectMemberMutation({
|
||||
* variables: {
|
||||
* projectID: // value for 'projectID'
|
||||
* userID: // value for 'userID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>(CreateProjectMemberDocument, baseOptions);
|
||||
}
|
||||
export type CreateProjectMemberMutationHookResult = ReturnType<typeof useCreateProjectMemberMutation>;
|
||||
export type CreateProjectMemberMutationResult = ApolloReactCommon.MutationResult<CreateProjectMemberMutation>;
|
||||
export type CreateProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>;
|
||||
export const DeleteProjectDocument = gql`
|
||||
mutation deleteProject($projectID: UUID!) {
|
||||
deleteProject(input: {projectID: $projectID}) {
|
||||
@ -3039,41 +2966,6 @@ export function useDeleteProjectMutation(baseOptions?: ApolloReactHooks.Mutation
|
||||
export type DeleteProjectMutationHookResult = ReturnType<typeof useDeleteProjectMutation>;
|
||||
export type DeleteProjectMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMutation>;
|
||||
export type DeleteProjectMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMutation, DeleteProjectMutationVariables>;
|
||||
export const DeleteInvitedProjectMemberDocument = gql`
|
||||
mutation deleteInvitedProjectMember($projectID: UUID!, $email: String!) {
|
||||
deleteInvitedProjectMember(input: {projectID: $projectID, email: $email}) {
|
||||
invitedMember {
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type DeleteInvitedProjectMemberMutationFn = ApolloReactCommon.MutationFunction<DeleteInvitedProjectMemberMutation, DeleteInvitedProjectMemberMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteInvitedProjectMemberMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteInvitedProjectMemberMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteInvitedProjectMemberMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [deleteInvitedProjectMemberMutation, { data, loading, error }] = useDeleteInvitedProjectMemberMutation({
|
||||
* variables: {
|
||||
* projectID: // value for 'projectID'
|
||||
* email: // value for 'email'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteInvitedProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteInvitedProjectMemberMutation, DeleteInvitedProjectMemberMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<DeleteInvitedProjectMemberMutation, DeleteInvitedProjectMemberMutationVariables>(DeleteInvitedProjectMemberDocument, baseOptions);
|
||||
}
|
||||
export type DeleteInvitedProjectMemberMutationHookResult = ReturnType<typeof useDeleteInvitedProjectMemberMutation>;
|
||||
export type DeleteInvitedProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteInvitedProjectMemberMutation>;
|
||||
export type DeleteInvitedProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteInvitedProjectMemberMutation, DeleteInvitedProjectMemberMutationVariables>;
|
||||
export const DeleteProjectMemberDocument = gql`
|
||||
mutation deleteProjectMember($projectID: UUID!, $userID: UUID!) {
|
||||
deleteProjectMember(input: {projectID: $projectID, userID: $userID}) {
|
||||
@ -3111,57 +3003,6 @@ export function useDeleteProjectMemberMutation(baseOptions?: ApolloReactHooks.Mu
|
||||
export type DeleteProjectMemberMutationHookResult = ReturnType<typeof useDeleteProjectMemberMutation>;
|
||||
export type DeleteProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMemberMutation>;
|
||||
export type DeleteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMemberMutation, DeleteProjectMemberMutationVariables>;
|
||||
export const InviteProjectMembersDocument = gql`
|
||||
mutation inviteProjectMembers($projectID: UUID!, $members: [MemberInvite!]!) {
|
||||
inviteProjectMembers(input: {projectID: $projectID, members: $members}) {
|
||||
ok
|
||||
invitedMembers {
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
members {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
username
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type InviteProjectMembersMutationFn = ApolloReactCommon.MutationFunction<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useInviteProjectMembersMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useInviteProjectMembersMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useInviteProjectMembersMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [inviteProjectMembersMutation, { data, loading, error }] = useInviteProjectMembersMutation({
|
||||
* variables: {
|
||||
* projectID: // value for 'projectID'
|
||||
* members: // value for 'members'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useInviteProjectMembersMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>(InviteProjectMembersDocument, baseOptions);
|
||||
}
|
||||
export type InviteProjectMembersMutationHookResult = ReturnType<typeof useInviteProjectMembersMutation>;
|
||||
export type InviteProjectMembersMutationResult = ApolloReactCommon.MutationResult<InviteProjectMembersMutation>;
|
||||
export type InviteProjectMembersMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>;
|
||||
export const UpdateProjectMemberRoleDocument = gql`
|
||||
mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
|
||||
updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) {
|
||||
@ -3457,9 +3298,9 @@ export type SetTaskCompleteMutationHookResult = ReturnType<typeof useSetTaskComp
|
||||
export type SetTaskCompleteMutationResult = ApolloReactCommon.MutationResult<SetTaskCompleteMutation>;
|
||||
export type SetTaskCompleteMutationOptions = ApolloReactCommon.BaseMutationOptions<SetTaskCompleteMutation, SetTaskCompleteMutationVariables>;
|
||||
export const UpdateTaskChecklistItemLocationDocument = gql`
|
||||
mutation updateTaskChecklistItemLocation($taskChecklistID: UUID!, $taskChecklistItemID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistItemLocation(input: {taskChecklistID: $taskChecklistID, taskChecklistItemID: $taskChecklistItemID, position: $position}) {
|
||||
taskChecklistID
|
||||
mutation updateTaskChecklistItemLocation($checklistID: UUID!, $checklistItemID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistItemLocation(input: {checklistID: $checklistID, checklistItemID: $checklistItemID, position: $position}) {
|
||||
checklistID
|
||||
prevChecklistID
|
||||
checklistItem {
|
||||
id
|
||||
@ -3484,8 +3325,8 @@ export type UpdateTaskChecklistItemLocationMutationFn = ApolloReactCommon.Mutati
|
||||
* @example
|
||||
* const [updateTaskChecklistItemLocationMutation, { data, loading, error }] = useUpdateTaskChecklistItemLocationMutation({
|
||||
* variables: {
|
||||
* taskChecklistID: // value for 'taskChecklistID'
|
||||
* taskChecklistItemID: // value for 'taskChecklistItemID'
|
||||
* checklistID: // value for 'checklistID'
|
||||
* checklistItemID: // value for 'checklistItemID'
|
||||
* position: // value for 'position'
|
||||
* },
|
||||
* });
|
||||
@ -3531,8 +3372,8 @@ export type UpdateTaskChecklistItemNameMutationHookResult = ReturnType<typeof us
|
||||
export type UpdateTaskChecklistItemNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistItemNameMutation>;
|
||||
export type UpdateTaskChecklistItemNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>;
|
||||
export const UpdateTaskChecklistLocationDocument = gql`
|
||||
mutation updateTaskChecklistLocation($taskChecklistID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistLocation(input: {taskChecklistID: $taskChecklistID, position: $position}) {
|
||||
mutation updateTaskChecklistLocation($checklistID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistLocation(input: {checklistID: $checklistID, position: $position}) {
|
||||
checklist {
|
||||
id
|
||||
position
|
||||
@ -3555,7 +3396,7 @@ export type UpdateTaskChecklistLocationMutationFn = ApolloReactCommon.MutationFu
|
||||
* @example
|
||||
* const [updateTaskChecklistLocationMutation, { data, loading, error }] = useUpdateTaskChecklistLocationMutation({
|
||||
* variables: {
|
||||
* taskChecklistID: // value for 'taskChecklistID'
|
||||
* checklistID: // value for 'checklistID'
|
||||
* position: // value for 'position'
|
||||
* },
|
||||
* });
|
||||
@ -4435,7 +4276,7 @@ export type UpdateTaskLocationMutationHookResult = ReturnType<typeof useUpdateTa
|
||||
export type UpdateTaskLocationMutationResult = ApolloReactCommon.MutationResult<UpdateTaskLocationMutation>;
|
||||
export type UpdateTaskLocationMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskLocationMutation, UpdateTaskLocationMutationVariables>;
|
||||
export const UpdateTaskNameDocument = gql`
|
||||
mutation updateTaskName($taskID: UUID!, $name: String!) {
|
||||
mutation updateTaskName($taskID: String!, $name: String!) {
|
||||
updateTaskName(input: {taskID: $taskID, name: $name}) {
|
||||
id
|
||||
name
|
||||
@ -4540,40 +4381,6 @@ export function useCreateUserAccountMutation(baseOptions?: ApolloReactHooks.Muta
|
||||
export type CreateUserAccountMutationHookResult = ReturnType<typeof useCreateUserAccountMutation>;
|
||||
export type CreateUserAccountMutationResult = ApolloReactCommon.MutationResult<CreateUserAccountMutation>;
|
||||
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
|
||||
export const DeleteInvitedUserAccountDocument = gql`
|
||||
mutation deleteInvitedUserAccount($invitedUserID: UUID!) {
|
||||
deleteInvitedUserAccount(input: {invitedUserID: $invitedUserID}) {
|
||||
invitedUser {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type DeleteInvitedUserAccountMutationFn = ApolloReactCommon.MutationFunction<DeleteInvitedUserAccountMutation, DeleteInvitedUserAccountMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteInvitedUserAccountMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteInvitedUserAccountMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteInvitedUserAccountMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [deleteInvitedUserAccountMutation, { data, loading, error }] = useDeleteInvitedUserAccountMutation({
|
||||
* variables: {
|
||||
* invitedUserID: // value for 'invitedUserID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteInvitedUserAccountMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteInvitedUserAccountMutation, DeleteInvitedUserAccountMutationVariables>) {
|
||||
return ApolloReactHooks.useMutation<DeleteInvitedUserAccountMutation, DeleteInvitedUserAccountMutationVariables>(DeleteInvitedUserAccountDocument, baseOptions);
|
||||
}
|
||||
export type DeleteInvitedUserAccountMutationHookResult = ReturnType<typeof useDeleteInvitedUserAccountMutation>;
|
||||
export type DeleteInvitedUserAccountMutationResult = ApolloReactCommon.MutationResult<DeleteInvitedUserAccountMutation>;
|
||||
export type DeleteInvitedUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteInvitedUserAccountMutation, DeleteInvitedUserAccountMutationVariables>;
|
||||
export const DeleteUserAccountDocument = gql`
|
||||
mutation deleteUserAccount($userID: UUID!, $newOwnerID: UUID) {
|
||||
deleteUserAccount(input: {userID: $userID, newOwnerID: $newOwnerID}) {
|
||||
@ -4727,11 +4534,6 @@ export type UpdateUserRoleMutationResult = ApolloReactCommon.MutationResult<Upda
|
||||
export type UpdateUserRoleMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateUserRoleMutation, UpdateUserRoleMutationVariables>;
|
||||
export const UsersDocument = gql`
|
||||
query users {
|
||||
invitedUsers {
|
||||
id
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
users {
|
||||
id
|
||||
email
|
||||
|
@ -1,5 +1,5 @@
|
||||
mutation createProject($teamID: UUID, $name: String!) {
|
||||
createProject(input: {teamID: $teamID, name: $name}) {
|
||||
mutation createProject($teamID: UUID!, $userID: UUID!, $name: String!) {
|
||||
createProject(input: {teamID: $teamID, userID: $userID, name: $name}) {
|
||||
id
|
||||
name
|
||||
team {
|
||||
|
@ -1,4 +1,4 @@
|
||||
mutation createTaskGroup( $projectID: UUID!, $name: String!, $position: Float! ) {
|
||||
mutation createTaskGroup( $projectID: String!, $name: String!, $position: Float! ) {
|
||||
createTaskGroup(
|
||||
input: { projectID: $projectID, name: $name, position: $position }
|
||||
) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
mutation deleteTask($taskID: UUID!) {
|
||||
mutation deleteTask($taskID: String!) {
|
||||
deleteTask(input: { taskID: $taskID }) {
|
||||
taskID
|
||||
}
|
||||
|
@ -22,10 +22,6 @@ query findProject($projectID: UUID!) {
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
invitedMembers {
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
labels {
|
||||
id
|
||||
createdDate
|
||||
|
25
frontend/src/shared/graphql/project/createProjectMember.ts
Normal file
25
frontend/src/shared/graphql/project/createProjectMember.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const CREATE_PROJECT_MEMBER_MUTATION = gql`
|
||||
mutation createProjectMember($projectID: UUID!, $userID: UUID!) {
|
||||
createProjectMember(input: { projectID: $projectID, userID: $userID }) {
|
||||
ok
|
||||
member {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
username
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default CREATE_PROJECT_MEMBER_MUTATION;
|
@ -1,13 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const DELETE_PROJECT_INVITED_MEMBER_MUTATION = gql`
|
||||
mutation deleteInvitedProjectMember($projectID: UUID!, $email: String!) {
|
||||
deleteInvitedProjectMember(input: { projectID: $projectID, email: $email }) {
|
||||
invitedMember {
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default DELETE_PROJECT_INVITED_MEMBER_MUTATION;
|
@ -1,29 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const INVITE_PROJECT_MEMBERS_MUTATION = gql`
|
||||
mutation inviteProjectMembers($projectID: UUID!, $members: [MemberInvite!]!) {
|
||||
inviteProjectMembers(input: { projectID: $projectID, members: $members }) {
|
||||
ok
|
||||
invitedMembers {
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
members {
|
||||
id
|
||||
fullName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
username
|
||||
role {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default INVITE_PROJECT_MEMBERS_MUTATION;
|
@ -1,11 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
const UPDATE_TASK_CHECKLIST_ITEM_LOCATION_MUTATION = gql`
|
||||
mutation updateTaskChecklistItemLocation($taskChecklistID: UUID!, $taskChecklistItemID: UUID!, $position: Float!) {
|
||||
mutation updateTaskChecklistItemLocation($checklistID: UUID!, $checklistItemID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistItemLocation(
|
||||
input: { taskChecklistID: $taskChecklistID, taskChecklistItemID: $taskChecklistItemID, position: $position }
|
||||
input: { checklistID: $checklistID, checklistItemID: $checklistItemID, position: $position }
|
||||
) {
|
||||
taskChecklistID
|
||||
checklistID
|
||||
prevChecklistID
|
||||
checklistItem {
|
||||
id
|
||||
|
@ -1,8 +1,8 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
const UPDATE_TASK_CHECKLIST_LOCATION_MUTATION = gql`
|
||||
mutation updateTaskChecklistLocation($taskChecklistID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistLocation(input: { taskChecklistID: $taskChecklistID, position: $position }) {
|
||||
mutation updateTaskChecklistLocation($checklistID: UUID!, $position: Float!) {
|
||||
updateTaskChecklistLocation(input: { checklistID: $checklistID, position: $position }) {
|
||||
checklist {
|
||||
id
|
||||
position
|
||||
|
@ -1,4 +1,4 @@
|
||||
mutation updateTaskName($taskID: UUID!, $name: String!) {
|
||||
mutation updateTaskName($taskID: String!, $name: String!) {
|
||||
updateTaskName(input: { taskID: $taskID, name: $name }) {
|
||||
id
|
||||
name
|
||||
|
@ -1,13 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const DELETE_INVITED_USER_MUTATION = gql`
|
||||
mutation deleteInvitedUserAccount($invitedUserID: UUID!) {
|
||||
deleteInvitedUserAccount(input: { invitedUserID: $invitedUserID }) {
|
||||
invitedUser {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default DELETE_INVITED_USER_MUTATION;
|
@ -1,9 +1,4 @@
|
||||
query users {
|
||||
invitedUsers {
|
||||
id
|
||||
email
|
||||
invitedOn
|
||||
}
|
||||
users {
|
||||
id
|
||||
email
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user