feature: remove sidebar & redesign top navbar

This commit is contained in:
Jordan Knott
2020-06-23 15:20:53 -05:00
parent fd7c006b73
commit 57382de9d0
78 changed files with 4124 additions and 465 deletions

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@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",

View File

@ -0,0 +1,471 @@
{
"header": {
"reportVersion": 1,
"event": "Allocation failed - JavaScript heap out of memory",
"trigger": "FatalError",
"filename": "report.20200621.183857.68808.0.001.json",
"dumpEventTime": "2020-06-21T18:38:57Z",
"dumpEventTimeStamp": "1592782737343",
"processId": 68808,
"cwd": "/home/jordan/Projects/project-citadel/web",
"commandLine": [
"/usr/bin/node",
"/home/jordan/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart",
"/home/jordan/Projects/project-citadel/web/node_modules/typescript/lib/tsserver.js",
"--allowLocalPluginLoads",
"--useInferredProjectPerProjectRoot",
"--cancellationPipeName",
"/tmp/coc-nvim-tscancellation-49e6b7aafa04f6c486f4.sock*",
"--npmLocation",
"\"/usr/bin/npm\"",
"--noGetErrOnBackgroundUpdate",
"--validateDefaultNpmLocation"
],
"nodejsVersion": "v13.8.0",
"glibcVersionRuntime": "2.30",
"glibcVersionCompiler": "2.30",
"wordSize": 64,
"arch": "x64",
"platform": "linux",
"componentVersions": {
"node": "13.8.0",
"v8": "7.9.317.25-node.28",
"uv": "1.34.1",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "79",
"nghttp2": "1.39.2",
"napi": "5",
"llhttp": "2.0.4",
"openssl": "1.1.1d",
"cldr": "36.0",
"icu": "65.1",
"tz": "2019c",
"unicode": "12.1"
},
"release": {
"name": "node",
"headersUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0-headers.tar.gz",
"sourceUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0.tar.gz"
},
"osName": "Linux",
"osRelease": "4.19.101-1-lts",
"osVersion": "#1 SMP Sat, 01 Feb 2020 16:35:36 +0000",
"osMachine": "x86_64",
"cpus": [
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2646,
"user": 48577700,
"nice": 14200,
"sys": 8014100,
"idle": 166454900,
"irq": 735500
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2646,
"user": 48745100,
"nice": 19200,
"sys": 7840700,
"idle": 42737200,
"irq": 528800
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2647,
"user": 48543900,
"nice": 15800,
"sys": 7804900,
"idle": 42914200,
"irq": 530200
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2726,
"user": 48996200,
"nice": 19900,
"sys": 7808300,
"idle": 42957300,
"irq": 390800
}
],
"networkInterfaces": [
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"family": "IPv4"
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "192.168.43.5",
"netmask": "255.255.255.0",
"family": "IPv4"
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:13:a9:89:9d",
"address": "172.17.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:48:f4:23:30",
"address": "172.19.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "::1",
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "2600:100b:b000:9bf1:7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "fe80::7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 3
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:13:a9:89:9d",
"address": "fe80::42:13ff:fea9:899d",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 5
},
{
"name": "veth79cfd83",
"internal": false,
"mac": "a2:e9:86:a8:58:bb",
"address": "fe80::a0e9:86ff:fea8:58bb",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 7
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:48:f4:23:30",
"address": "fe80::42:48ff:fef4:2330",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 8
},
{
"name": "vethe27cc3e",
"internal": false,
"mac": "c6:bd:d7:3b:6c:96",
"address": "fe80::c4bd:d7ff:fe3b:6c96",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 10
}
],
"host": "archlinux"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
{
"pc": "0x0000557aee1dae8a",
"symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, v8::Local<v8::String>) [/usr/bin/node]"
},
{
"pc": "0x0000557aee09bd48",
"symbol": "node::OnFatalError(char const*, char const*) [/usr/bin/node]"
},
{
"pc": "0x0000557aee20f382",
"symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x0000557aee20f5e8",
"symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x0000557aee399906",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x0000557aee399a49",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x0000557aee3ac22d",
"symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3acf58",
"symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3af48c",
"symbol": "v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x0000557aee3af4f4",
"symbol": "v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x0000557aee37499b",
"symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/bin/node]"
},
{
"pc": "0x0000557aee6a5540",
"symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]"
},
{
"pc": "0x0000557aee9febd9",
"symbol": " [/usr/bin/node]"
}
],
"javascriptHeap": {
"totalMemory": 2152407040,
"totalCommittedMemory": 2150546232,
"usedMemory": 2137632424,
"availableMemory": 47662336,
"memoryLimit": 2197815296,
"heapSpaces": {
"read_only_space": {
"memorySize": 262144,
"committedMemory": 33328,
"capacity": 33040,
"used": 33040,
"available": 0
},
"new_space": {
"memorySize": 2097152,
"committedMemory": 1029144,
"capacity": 1047424,
"used": 21136,
"available": 1026288
},
"old_space": {
"memorySize": 2058231808,
"committedMemory": 2057888048,
"capacity": 2048399928,
"used": 2048219560,
"available": 180368
},
"code_space": {
"memorySize": 9080832,
"committedMemory": 8864320,
"capacity": 7755264,
"used": 7755264,
"available": 0
},
"map_space": {
"memorySize": 1052672,
"committedMemory": 1048960,
"capacity": 740080,
"used": 740080,
"available": 0
},
"large_object_space": {
"memorySize": 81305600,
"committedMemory": 81305600,
"capacity": 80548528,
"used": 80548528,
"available": 0
},
"code_large_object_space": {
"memorySize": 376832,
"committedMemory": 376832,
"capacity": 314816,
"used": 314816,
"available": 0
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 1047424,
"used": 0,
"available": 1047424
}
}
},
"resourceUsage": {
"userCpuSeconds": 3484.11,
"kernelCpuSeconds": 64.7409,
"cpuConsumptionPercent": 35.606,
"maxRss": 2330890240,
"pageFaults": {
"IORequired": 31,
"IONotRequired": 9729381
},
"fsActivity": {
"reads": 53144,
"writes": 16
}
},
"uvthreadResourceUsage": {
"userCpuSeconds": 2308.89,
"kernelCpuSeconds": 31.2857,
"cpuConsumptionPercent": 23.4792,
"fsActivity": {
"reads": 53248,
"writes": 16
}
},
"libuv": [
],
"environmentVariables": {
"COLORTERM": "truecolor",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
"DESKTOP_SESSION": "awesome",
"DISPLAY": ":0.0",
"EDITOR": "nano",
"FZF_DEFAULT_COMMAND": "ag --hidden --ignore .git -g \"\"",
"GDMSESSION": "awesome",
"GO111MODULE": "on",
"GOBIN": "/home/jordan/go/bin",
"GREP_COLOR": "37;45",
"GREP_COLORS": "mt=37;45",
"GTK_MODULES": "canberra-gtk-module",
"HOME": "/home/jordan",
"LANG": "C",
"LESS": "-F -g -i -M -R -S -w -X -z-4",
"LESS_TERMCAP_mb": "\u001b[01;31m",
"LESS_TERMCAP_md": "\u001b[01;31m",
"LESS_TERMCAP_me": "\u001b[0m",
"LESS_TERMCAP_se": "\u001b[0m",
"LESS_TERMCAP_so": "\u001b[00;47;30m",
"LESS_TERMCAP_ue": "\u001b[0m",
"LESS_TERMCAP_us": "\u001b[01;32m",
"LOGNAME": "jordan",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:",
"MAIL": "/var/spool/mail/jordan",
"OLDPWD": "/home/jordan/Projects/project-citadel/web",
"PAGER": "less",
"PATH": "/home/jordan/.local/bin:/usr/local/bin:/usr/local/sbin:/home/nightwolf/Programs/cmake/bin:/home/nightwolf/Programs/idea-IU-163.13906.18/bin:/home/nightwolf/Programs/wpcli:/home/nightwolf/neovim/bin:/home/nightwolf/Programs/Postman:/home/nightwolf/Programs/Android_SDK/tools/bin:/home/nightwolf/Development/PhantomJS/bin:/home/nightwolf/Programs/node/bin:/home/nightwolf/pyenv/bin:/home/nightwolf/Programs/vv:/usr/bin:/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/local/go/bin:/home/jordan/go/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.fzf/bin:/home/jordan/.gem/ruby/2.6.0/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.gem/ruby/2.6.0/bin",
"PWD": "/home/jordan/Projects/project-citadel/web",
"SHELL": "/usr/bin/zsh",
"SHLVL": "2",
"SSH_AGENT_PID": "773",
"SSH_AUTH_SOCK": "/tmp/ssh-agent.sock.1000",
"TERM": "xterm-256color",
"TMUX": "/tmp/tmux-1000/default,13203,1",
"TMUX_PANE": "%2",
"USER": "jordan",
"VIRTUALENVWRAPPER_PYTHON": "/usr/bin/python3",
"VIRTUAL_ENV_DISABLE_PROMPT": "12",
"VISUAL": "nano",
"VTE_VERSION": "5602",
"WINDOWID": "6291459",
"WORKON_HOME": "/home/jordan/.virtualenvs",
"XAUTHORITY": "/home/jordan/.Xauthority",
"XDG_GREETER_DATA_DIR": "/var/lib/lightdm-data/jordan",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_SEAT": "seat0",
"XDG_SEAT_PATH": "/org/freedesktop/DisplayManager/Seat0",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_DESKTOP": "awesome",
"XDG_SESSION_ID": "1",
"XDG_SESSION_PATH": "/org/freedesktop/DisplayManager/Session0",
"XDG_SESSION_TYPE": "x11",
"XDG_VTNR": "7",
"_": "/usr/bin/nvim",
"is_vim": "ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'",
"tmux_version": "$(tmux -V | sed -En \"s/^tmux ([0-9]+(.[0-9]+)?).*/\\1/p\")",
"LC_MESSAGES": "",
"VIMRUNTIME": "/usr/share/nvim/runtime",
"NVIM_LISTEN_ADDRESS": "/tmp/nvimaHxiZq/0",
"MYVIMRC": "/home/jordan/.config/nvim/init.vim",
"COC_VIMCONFIG": "/home/jordan/.config/nvim",
"COC_DATA_HOME": "/home/jordan/.config/coc",
"TSS_LOG": "-level verbose -file /tmp/coc-nvim-tsc.log",
"NODE_PATH": "/home/jordan/Projects/project-citadel/web/node_modules"
},
"userLimits": {
"core_file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": 65536,
"hard": 65536
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 524288,
"hard": 524288
},
"stack_size_bytes": {
"soft": 8388608,
"hard": "unlimited"
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 31138,
"hard": 31138
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"linux-vdso.so.1",
"/usr/lib/libz.so.1",
"/usr/lib/libcares.so.2",
"/usr/lib/libnghttp2.so.14",
"/usr/lib/libcrypto.so.1.1",
"/usr/lib/libssl.so.1.1",
"/usr/lib/libicui18n.so.65",
"/usr/lib/libicuuc.so.65",
"/usr/lib/libdl.so.2",
"/usr/lib/libstdc++.so.6",
"/usr/lib/libm.so.6",
"/usr/lib/libgcc_s.so.1",
"/usr/lib/libpthread.so.0",
"/usr/lib/libc.so.6",
"/usr/lib/libicudata.so.65",
"/lib64/ld-linux-x86-64.so.2"
]
}

158
web/src/Admin/index.tsx Normal file
View File

@ -0,0 +1,158 @@
import React, { useEffect } from 'react';
import Admin from 'shared/components/Admin';
import GlobalTopNavbar from 'App/TopNavbar';
import { useUsersQuery, useCreateUserAccountMutation, UsersDocument } from 'shared/generated/graphql';
import Input from 'shared/components/Input';
import styled from 'styled-components';
import Button from 'shared/components/Button';
import { useForm } from 'react-hook-form';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
type CreateUserData = {
email: string;
username: string;
fullName: string;
initials: string;
password: string;
};
const CreateUserForm = styled.form`
display: flex;
flex-direction: column;
`;
const CreateUserButton = styled(Button)`
margin-top: 8px;
padding: 6px 12px;
width: 100%;
`;
const AddUserInput = styled(Input)`
margin-bottom: 8px;
`;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type AddUserPopupProps = {
onAddUser: (user: CreateUserData) => void;
};
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors } = useForm<CreateUserData>();
const createUser = (data: CreateUserData) => {
onAddUser(data);
};
return (
<CreateUserForm onSubmit={handleSubmit(createUser)}>
<AddUserInput
floatingLabel
width="100%"
label="Full Name"
id="fullName"
name="fullName"
variant="alternate"
ref={register({ required: 'Full name is required' })}
/>
{errors.fullName && <InputError>{errors.fullName.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Email"
id="email"
name="email"
variant="alternate"
ref={register({ required: 'Email is required' })}
/>
{errors.email && <InputError>{errors.email.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Username"
id="username"
name="username"
variant="alternate"
ref={register({ required: 'Username is required' })}
/>
{errors.username && <InputError>{errors.username.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Initials"
id="initials"
name="initials"
variant="alternate"
ref={register({ required: 'Initials is required' })}
/>
{errors.initials && <InputError>{errors.initials.message}</InputError>}
<AddUserInput
floatingLabel
width="100%"
label="Password"
id="password"
name="password"
variant="alternate"
ref={register({ required: 'Password is required' })}
/>
{errors.password && <InputError>{errors.password.message}</InputError>}
<CreateUserButton type="submit">Create</CreateUserButton>
</CreateUserForm>
);
};
const AdminRoute = () => {
useEffect(() => {
document.title = 'Citadel | Admin';
}, []);
const { loading, data } = useUsersQuery();
const { showPopup, hidePopup } = usePopup();
const [createUser] = useCreateUserAccountMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: UsersDocument,
});
console.log(cacheData);
console.log(createData);
const newData = produce(cacheData, (draftState: any) => {
draftState.users = [...draftState.users, { ...createData.data.createUserAccount }];
});
client.writeQuery({
query: UsersDocument,
data: {
...newData,
},
});
},
});
if (loading) {
return <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />;
}
if (data) {
return (
<>
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
<Admin
initialTab={1}
users={data.users.map((user: any) => ({ ...user, role: 'TBD' }))}
onInviteUser={() => {}}
onAddUser={$target => {
showPopup(
$target,
<Popup tab={0} title="Add member" onClose={() => hidePopup()}>
<AddUserPopup
onAddUser={user => {
createUser({ variables: { ...user } });
hidePopup();
}}
/>
</Popup>,
);
}}
/>
</>
);
}
return <span>error</span>;
};
export default AdminRoute;

View File

@ -15,7 +15,7 @@ const GlobalNavbar = () => {
<ButtonContainer>
<Link to="/">
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
</Link>
<Link to="/projects">

View File

@ -3,14 +3,16 @@ import { Router, Switch, Route } from 'react-router-dom';
import * as H from 'history';
import Dashboard from 'Dashboard';
import Admin from 'Admin';
import Projects from 'Projects';
import Project from 'Projects/Project';
import Teams from 'Teams';
import Login from 'Auth';
import Profile from 'Profile';
import styled from 'styled-components';
const MainContent = styled.div`
padding: 0 0 0 80px;
padding: 0 0 0 0;
background: #262c49;
height: 100%;
display: flex;
@ -28,7 +30,9 @@ const Routes = ({ history }: RoutesProps) => (
<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>
);

View File

@ -1,24 +1,172 @@
import React, { useState, useContext } from 'react';
import React, { useState, useContext, useEffect } from 'react';
import TopNavbar from 'shared/components/TopNavbar';
import styled from 'styled-components/macro';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu';
import ProjectSettings, { DeleteProject } from 'shared/components/ProjectSettings';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router';
import UserIDContext from 'App/context';
import { useMeQuery, useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
import {
useMeQuery,
useDeleteProjectMutation,
useGetProjectsQuery,
GetProjectsDocument,
} from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { History } from 'history';
import produce from 'immer';
import { Link } from 'react-router-dom';
type GlobalTopNavbarProps = {
projectID: string | null;
name: string | null;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
const TeamContainer = styled.div`
display: flex;
flex-direction: column;
`;
const TeamTitle = styled.h3`
font-size: 14px;
font-weight: 700;
`;
const TeamProjects = styled.div`
display: flex;
flex-direction: column;
margin-top: 8px;
margin-bottom: 4px;
`;
const TeamProjectLink = styled(Link)`
display: flex;
font-weight: 700;
height: 36px;
overflow: hidden;
padding: 0;
position: relative;
text-decoration: none;
user-select: none;
`;
const TeamProjectBackground = styled.div<{ color: string }>`
background-image: url(null);
background-color: ${props => props.color};
background-size: cover;
background-position: 50%;
position: absolute;
width: 100%;
height: 36px;
opacity: 1;
border-radius: 3px;
&:before {
background: rgba(${props => props.theme.colors.bg.secondary});
bottom: 0;
content: '';
left: 0;
opacity: 0.88;
position: absolute;
right: 0;
top: 0;
}
`;
const TeamProjectAvatar = styled.div<{ color: string }>`
background-image: url(null);
background-color: ${props => props.color};
display: inline-block;
flex: 0 0 auto;
background-size: cover;
border-radius: 3px 0 0 3px;
height: 36px;
width: 36px;
position: relative;
opacity: 0.7;
`;
const TeamProjectContent = styled.div`
display: flex;
position: relative;
flex: 1;
width: 100%;
padding: 9px 0 9px 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const TeamProjectTitle = styled.div`
font-weight: 700;
display: block;
padding-right: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const TeamProjectContainer = styled.div`
box-sizing: border-box;
border-radius: 3px;
position: relative;
margin: 0 4px 4px 0;
min-width: 0;
&:hover ${TeamProjectTitle} {
color: rgba(${props => props.theme.colors.text.secondary});
}
&:hover ${TeamProjectAvatar} {
opacity: 1;
}
&:hover ${TeamProjectBackground}:before {
opacity: 0.78;
}
`;
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
const ProjectFinder = () => {
const { loading, data } = useGetProjectsQuery();
if (loading) {
return <span>loading</span>;
}
if (data) {
const { projects, teams, organizations } = data;
const projectTeams = teams.map(team => {
return {
id: team.id,
name: team.name,
projects: projects.filter(project => project.team.id === team.id),
};
});
return (
<>
{projectTeams.map(team => (
<TeamContainer>
<TeamTitle>{team.name}</TeamTitle>
<TeamProjects>
{team.projects.map((project, idx) => (
<TeamProjectContainer>
<TeamProjectLink to={`/projects/${project.id}`}>
<TeamProjectBackground color={colors[idx % 5]} />
<TeamProjectAvatar color={colors[idx % 5]} />
<TeamProjectContent>
<TeamProjectTitle>{project.name}</TeamProjectTitle>
</TeamProjectContent>
</TeamProjectLink>
</TeamProjectContainer>
))}
</TeamProjects>
</TeamContainer>
))}
</>
);
}
return <span>error</span>;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, projectMembers, onSaveProjectName }) => {
const { loading, data } = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext);
type ProjectPopupProps = {
history: History<History.PoorMansUnknown>;
name: string;
projectID: string;
};
export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID }) => {
const { hidePopup, setTab } = usePopup();
const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -42,6 +190,62 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
});
},
});
return (
<>
<Popup title={null} tab={0}>
<ProjectSettings
onDeleteProject={() => {
setTab(1);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteConfirm
description={DELETE_INFO.DELETE_PROJECTS.description}
deletedItems={DELETE_INFO.DELETE_PROJECTS.deletedItems}
onConfirmDelete={() => {
if (projectID) {
deleteProject({ variables: { projectID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>
);
};
type GlobalTopNavbarProps = {
nameOnly?: boolean;
projectID: string | null;
name: string | null;
initialTab?: number;
popupContent?: JSX.Element;
menuType?: Array<string>;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
initialTab,
menuType,
projectID,
name,
popupContent,
projectMembers,
onSaveProjectName,
nameOnly,
}) => {
console.log(popupContent);
const { loading, data } = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory();
const [currentTab, setCurrentTab] = useState(initialTab);
useEffect(() => {
setCurrentTab(initialTab);
}, [initialTab]);
const { userID, setUserID } = useContext(UserIDContext);
const onLogout = () => {
fetch('http://localhost:3333/auth/logout', {
method: 'POST',
@ -61,42 +265,26 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
<Popup title={null} tab={0}>
<ProfileMenu
onLogout={onLogout}
onAdminConsole={() => {
history.push('/admin');
hidePopup();
}}
onProfile={() => {
history.push('/profile');
hidePopup();
}}
/>
</Popup>,
185,
195,
);
};
const onOpenSettings = ($target: React.RefObject<HTMLElement>) => {
showPopup(
$target,
<>
<Popup title={null} tab={0}>
<ProjectSettings
onDeleteProject={() => {
setTab(1, 325);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteProject
name={name ?? ''}
onDeleteProject={() => {
if (projectID) {
deleteProject({ variables: { projectID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>,
185,
);
console.log('maybe firing popup');
if (popupContent) {
console.log('showing popup');
showPopup($target, popupContent, 185);
}
};
if (!userID) {
@ -105,12 +293,25 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ projectID, name, proj
return (
<>
<TopNavbar
projectName={name}
name={name}
menuType={menuType}
onOpenProjectFinder={$target => {
showPopup(
$target,
<Popup tab={0} title={null}>
<ProjectFinder />
</Popup>,
);
}}
currentTab={currentTab}
user={data ? data.me : null}
onNotificationClick={() => {}}
onDashboardClick={() => {
history.push('/');
}}
projectMembers={projectMembers}
onProfileClick={onProfileClick}
onSaveProjectName={onSaveProjectName}
onSaveName={onSaveProjectName}
onOpenSettings={onOpenSettings}
/>
</>

View File

@ -41,20 +41,19 @@ const App = () => {
<>
<UserIDContext.Provider value={{ userID, setUserID }}>
<ThemeProvider theme={theme}>
<PopupProvider>
<NormalizeStyles />
<BaseStyles />
<Router history={history}>
<NormalizeStyles />
<BaseStyles />
<Router history={history}>
<PopupProvider>
{loading ? (
<div>loading</div>
) : (
<>
<Navbar />
<Routes history={history} />
</>
)}
</Router>
</PopupProvider>
</PopupProvider>
</Router>
</ThemeProvider>
</UserIDContext.Provider>
</>

View File

@ -5,6 +5,9 @@ import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router';
import {
useDeleteTaskChecklistMutation,
useUpdateTaskChecklistNameMutation,
useCreateTaskChecklistMutation,
useFindTaskQuery,
useUpdateTaskDueDateMutation,
useSetTaskCompleteMutation,
@ -20,6 +23,82 @@ import UserIDContext from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager';
import produce from 'immer';
import styled from 'styled-components';
import Button from 'shared/components/Button';
import Input from 'shared/components/Input';
import { useForm } from 'react-hook-form';
const calculateChecklistBadge = (draftState: any) => {
const total = draftState.checklists.reduce((prev: any, next: any) => {
return (
prev +
next.items.reduce((innerPrev: any, _item: any) => {
return innerPrev + 1;
}, 0)
);
}, 0);
const complete = draftState.checklists.reduce(
(prev: any, next: any) =>
prev +
next.items.reduce((innerPrev: any, item: any) => {
return innerPrev + (item.complete ? 1 : 0);
}, 0),
0,
);
return { total, complete };
};
const DeleteChecklistButton = styled(Button)`
width: 100%;
padding: 6px 12px;
margin-top: 8px;
`;
type CreateChecklistData = {
name: string;
};
const CreateChecklistForm = styled.form`
display: flex;
flex-direction: column;
`;
const CreateChecklistButton = styled(Button)`
margin-top: 8px;
padding: 6px 12px;
width: 100%;
`;
const CreateChecklistInput = styled(Input)`
margin-bottom: 8px;
`;
const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type CreateChecklistPopupProps = {
onCreateChecklist: (data: CreateChecklistData) => void;
};
const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChecklist }) => {
const { register, handleSubmit, errors } = useForm<CreateChecklistData>();
const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data);
};
console.log(errors);
return (
<CreateChecklistForm onSubmit={handleSubmit(createUser)}>
<CreateChecklistInput
floatingLabel
width="100%"
label="Name"
id="name"
name="name"
variant="alternate"
ref={register({ required: 'Checklist name is required' })}
/>
<CreateChecklistButton type="submit">Create</CreateChecklistButton>
</CreateChecklistForm>
);
};
type DetailsProps = {
taskID: string;
@ -50,8 +129,93 @@ const Details: React.FC<DetailsProps> = ({
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation();
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation({
update: client => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
const newData = produce(cacheData.findTask, (draftState: any) => {
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const [deleteTaskChecklist] = useDeleteTaskChecklistMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(deleteData);
const newData = produce(cacheData.findTask, (draftState: any) => {
draftState.checklists = cacheData.findTask.checklists.filter(
(checklist: any) => checklist.id !== deleteData.data.deleteTaskChecklist.taskChecklist.id,
);
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
if (complete === 0 && total === 0) {
draftState.badges.checklist = null;
}
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const [updateTaskChecklistItemName] = useUpdateTaskChecklistItemNameMutation();
const [createTaskChecklist] = useCreateTaskChecklistMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(createData);
const newData = {
findTask: {
...cacheData.findTask,
checklists: [...cacheData.findTask.checklists, { ...createData.data.createTaskChecklist }],
},
};
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: newData,
});
},
});
const [updateTaskChecklistName] = useUpdateTaskChecklistNameMutation();
const [deleteTaskChecklistItem] = useDeleteTaskChecklistItemMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -69,6 +233,17 @@ const Details: React.FC<DetailsProps> = ({
draftState.checklists[idx].items = cacheData.findTask.checklists[idx].items.filter(
(item: any) => item.id !== deleteData.data.deleteTaskChecklistItem.taskChecklistItem.id,
);
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}
});
client.writeQuery({
@ -97,8 +272,23 @@ const Details: React.FC<DetailsProps> = ({
...cacheData.findTask.checklists[idx].items,
{ ...newTaskItem.data.createTaskChecklistItem },
];
console.log(draftState.checklists.map((item: any) => item.items));
const { complete, total } = calculateChecklistBadge(draftState);
if (!draftState.badges) {
draftState.badges = {
checklist: {},
};
}
draftState.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
console.log(draftState.badges.checklist);
}
});
console.log(newData);
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
@ -156,11 +346,24 @@ const Details: React.FC<DetailsProps> = ({
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
}}
onCloseModal={() => history.push(projectURL)}
onChangeChecklistName={(checklistID, newName) => {
updateTaskChecklistName({ variables: { taskChecklistID: checklistID, name: newName } });
}}
onDeleteItem={itemID => {
deleteTaskChecklistItem({ variables: { taskChecklistItemID: itemID } });
}}
onToggleChecklistItem={(itemID, complete) => {
setTaskChecklistItemComplete({ variables: { taskChecklistItemID: itemID, complete } });
setTaskChecklistItemComplete({
variables: { taskChecklistItemID: itemID, complete },
optimisticResponse: {
__typename: 'Mutation',
setTaskChecklistItemComplete: {
__typename: 'TaskChecklistItem',
id: itemID,
complete,
},
},
});
}}
onAddItem={(taskChecklistID, name, position) => {
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
@ -202,6 +405,57 @@ const Details: React.FC<DetailsProps> = ({
);
}}
onOpenAddLabelPopup={onOpenAddLabelPopup}
onOpenAddChecklistPopup={(_task, $target) => {
showPopup(
$target,
<Popup
title={'Add checklist'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateChecklistPopup
onCreateChecklist={checklistData => {
let position = 65535;
console.log(data.findTask.checklists);
if (data.findTask.checklists) {
const [lastChecklist] = data.findTask.checklists.slice(-1);
console.log(`lastCheclist ${lastChecklist}`);
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
}
}
createTaskChecklist({
variables: {
taskID: data.findTask.id,
name: checklistData.name,
position,
},
});
hidePopup();
}}
/>
</Popup>,
);
}}
onDeleteChecklist={($target, checklistID) => {
showPopup(
$target,
<Popup tab={0} title="Delete checklist?" onClose={() => hidePopup()}>
<p>Deleting a checklist is permanent and there is no way to get it back.</p>
<DeleteChecklistButton
color="danger"
onClick={() => {
deleteTaskChecklist({ variables: { taskChecklistID: checklistID } });
hidePopup();
}}
>
Delete Checklist
</DeleteChecklistButton>
</Popup>,
);
}}
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,

View File

@ -1,7 +1,9 @@
import React, { useState, useRef, useContext, useEffect } from 'react';
import GlobalTopNavbar from 'App/TopNavbar';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled from 'styled-components/macro';
import { Bolt, ToggleOn, Tags } from 'shared/icons';
import { DataProxy } from '@apollo/client';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import {
@ -26,6 +28,7 @@ import {
useCreateProjectLabelMutation,
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
FindProjectQuery,
} from 'shared/generated/graphql';
import TaskAssignee from 'shared/components/TaskAssignee';
@ -42,11 +45,35 @@ import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile';
import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
import { DocumentNode } from 'graphql';
import UserIDContext from 'App/context';
import DueDateManager from 'shared/components/DueDateManager';
type UpdateCacheFn<T> = (cache: T) => T;
function updateApolloCache<T>(client: DataProxy, document: DocumentNode, update: UpdateCacheFn<T>, variables?: object) {
let queryArgs: DataProxy.Query<any>;
if (variables) {
queryArgs = {
query: document,
variables,
};
} else {
queryArgs = {
query: document,
};
}
const cache: T | null = client.readQuery(queryArgs);
if (cache) {
const newCache = update(cache);
client.writeQuery({
...queryArgs,
data: newCache,
});
}
}
const getCacheData = (client: any, projectID: string) => {
const cacheData: any = client.readQuery({
const cacheData: FindProjectQuery = client.readQuery({
query: FindProjectDocument,
variables: {
projectId: projectID,
@ -220,7 +247,7 @@ const initialQuickCardEditorState: QuickCardEditorState = {
const ProjectBar = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
justify-content: space-between;
height: 40px;
padding: 0 12px;
`;
@ -302,13 +329,17 @@ const Project = () => {
const [createTaskGroup] = useCreateTaskGroupMutation({
onCompleted: newTaskGroupData => {},
update: (client, newTaskGroupData) => {
const cacheData = getCacheData(client, projectID);
const newData = {
...cacheData.findProject,
taskGroups: [...cacheData.findProject.taskGroups, { ...newTaskGroupData.data.createTaskGroup, tasks: [] }],
};
writeCacheData(client, projectID, cacheData, newData);
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache => {
console.log(cache);
return produce(cache, draftCache => {
draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
});
},
{ projectId: projectID },
);
},
});
@ -316,7 +347,7 @@ const Project = () => {
onCompleted: newTaskData => {},
update: (client, newTaskData) => {
const cacheData = getCacheData(client, projectID);
const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => {
const newTaskGroups = produce(cacheData.findProject.taskGroups, draftState => {
const targetIndex = draftState.findIndex(
(taskGroup: any) => taskGroup.id === newTaskData.data.createTask.taskGroup.id,
);
@ -388,14 +419,18 @@ const Project = () => {
createTask: {
__typename: 'Task',
id: '' + Math.round(Math.random() * -1000000),
name: name,
name,
complete: false,
taskGroup: {
__typename: 'TaskGroup',
id: taskGroup.id,
name: taskGroup.name,
position: taskGroup.position,
},
position: position,
badges: {
checklist: null,
},
position,
dueDate: null,
description: null,
labels: [],
@ -509,11 +544,28 @@ const Project = () => {
onSaveProjectName={projectName => {
updateProjectName({ variables: { projectID, name: projectName } });
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
menuType={MENU_TYPES.PROJECT_MENU}
initialTab={0}
projectMembers={data.findProject.members}
projectID={projectID}
name={data.findProject.name}
/>
<ProjectBar>
<ProjectActions>
<ProjectAction>
<CheckCircle width={13} height={13} />
<ProjectActionText>All Tasks</ProjectActionText>
</ProjectAction>
<ProjectAction>
<Filter width={13} height={13} />
<ProjectActionText>Filter</ProjectActionText>
</ProjectAction>
<ProjectAction>
<Sort width={13} height={13} />
<ProjectActionText>Sort</ProjectActionText>
</ProjectAction>
</ProjectActions>
<ProjectActions>
<ProjectAction
ref={$labelsRef}

View File

@ -137,6 +137,7 @@ const ProjectTileName = styled.div<{ centered?: boolean }>`
`;
const Wrapper = styled.div`
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
@ -146,10 +147,24 @@ const Wrapper = styled.div`
const ProjectSectionTitleWrapper = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
height: 32px;
margin-bottom: 24px;
padding: 8px 0;
position: relative;
margin-top: 16px;
`;
const SectionActions = styled.div`
display: flex;
align-items: center;
`;
const SectionAction = styled(Button)`
padding: 6px 12px;
`;
const SectionActionLink = styled(Link)`
margin-right: 8px;
`;
const ProjectSectionTitle = styled.h3`
@ -171,13 +186,19 @@ const ProjectGrid = styled.div`
`;
const AddTeamButton = styled(Button)`
padding: 6px 12px;
float: right;
position: absolute;
top: 6px;
right: 12px;
`;
type ShowNewProject = {
open: boolean;
initialTeamID: null | string;
};
const ProjectLink = styled(Link)``;
const Projects = () => {
const { showPopup } = usePopup();
const { showPopup, hidePopup } = usePopup();
const { loading, data } = useGetProjectsQuery();
useEffect(() => {
document.title = 'Citadel';
@ -202,9 +223,24 @@ const Projects = () => {
});
},
});
const [showNewProject, setShowNewProject] = useState(false);
const [showNewProject, setShowNewProject] = useState<ShowNewProject>({ open: false, initialTeamID: null });
const { userID, setUserID } = useContext(UserIDContext);
const [createTeam] = useCreateTeamMutation();
const [createTeam] = useCreateTeamMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
query: GetProjectsDocument,
});
const newData = {
...cacheData,
teams: [...cacheData.teams, { ...createData.data.createTeam }],
};
client.writeQuery({
query: GetProjectsDocument,
data: newData,
});
},
});
if (loading) {
return (
<>
@ -234,11 +270,18 @@ const Projects = () => {
onClick={$target => {
showPopup(
$target,
<Popup title="Create team" tab={0}>
<Popup
title="Create team"
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateTeamForm
onCreateTeam={teamName => {
if (organizationID) {
createTeam({ variables: { name: teamName, organizationID } });
hidePopup();
}
}}
/>
@ -253,6 +296,17 @@ const Projects = () => {
<div key={team.id}>
<ProjectSectionTitleWrapper>
<ProjectSectionTitle>{team.name}</ProjectSectionTitle>
<SectionActions>
<SectionActionLink to={`/teams/${team.id}`}>
<SectionAction variant="outline">Projects</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionAction variant="outline">Members</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionAction variant="outline">Settings</SectionAction>
</SectionActionLink>
</SectionActions>
</ProjectSectionTitleWrapper>
<ProjectList>
{team.projects.map((project, idx) => (
@ -268,7 +322,7 @@ const Projects = () => {
<ProjectListItem>
<ProjectAddTile
onClick={() => {
setShowNewProject(true);
setShowNewProject({ open: true, initialTeamID: team.id });
}}
>
<ProjectTileFade />
@ -281,16 +335,17 @@ const Projects = () => {
</div>
);
})}
{showNewProject && (
{showNewProject.open && (
<NewProject
initialTeamID={showNewProject.initialTeamID}
onCreateProject={(name, teamID) => {
if (userID) {
createProject({ variables: { teamID, name, userID } });
setShowNewProject(false);
setShowNewProject({ open: false, initialTeamID: null });
}
}}
onClose={() => {
setShowNewProject(false);
setShowNewProject({ open: false, initialTeamID: null });
}}
teams={teams}
/>

227
web/src/Teams/index.tsx Normal file
View File

@ -0,0 +1,227 @@
import React, { useState, useContext, useEffect } from 'react';
import styled from 'styled-components/macro';
import { MENU_TYPES } from 'shared/components/TopNavbar';
import GlobalTopNavbar from 'App/TopNavbar';
import { useGetTeamQuery, useDeleteTeamMutation, GetProjectsDocument } from 'shared/generated/graphql';
import { useParams, useHistory } from 'react-router';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { History } from 'history';
import produce from 'immer';
import { TeamSettings, DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { Link } from 'react-router-dom';
const ProjectAddTile = styled.div`
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`;
const ProjectTile = styled(Link)<{ color: string }>`
background-color: ${props => props.color};
background-size: cover;
background-position: 50%;
color: #fff;
line-height: 20px;
padding: 8px;
position: relative;
text-decoration: none;
border-radius: 3px;
display: block;
`;
const ProjectTileFade = styled.div`
background-color: rgba(0, 0, 0, 0.15);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
`;
const ProjectListItem = styled.li`
width: 23.5%;
padding: 0;
margin: 0 2% 2% 0;
box-sizing: border-box;
position: relative;
cursor: pointer;
&:hover ${ProjectTileFade} {
background-color: rgba(0, 0, 0, 0.25);
}
`;
const ProjectList = styled.ul`
display: flex;
flex-wrap: wrap;
& ${ProjectListItem}:nth-of-type(4n) {
margin-right: 0;
}
`;
const ProjectTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
justify-content: space-between;
`;
const ProjectAddTileDetails = styled.div`
display: flex;
height: 80px;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
`;
const ProjectTileName = styled.div<{ centered?: boolean }>`
flex: 0 0 auto;
font-size: 16px;
font-weight: 700;
display: inline-block;
overflow: hidden;
max-height: 40px;
width: 100%;
word-wrap: break-word;
${props => props.centered && 'text-align: center;'}
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
`;
type TeamPopupProps = {
history: History<History.PoorMansUnknown>;
name: string;
teamID: string;
};
export const TeamPopup: React.FC<TeamPopupProps> = ({ history, name, teamID }) => {
const { hidePopup, setTab } = usePopup();
const [deleteTeam] = useDeleteTeamMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: GetProjectsDocument,
});
console.log(cacheData);
console.log(deleteData);
const newData = produce(cacheData, (draftState: any) => {
draftState.teams = draftState.teams.filter((team: any) => team.id !== deleteData.data.deleteTeam.team.id);
draftState.projects = draftState.projects.filter(
(project: any) => project.team.id !== deleteData.data.deleteTeam.team.id,
);
});
client.writeQuery({
query: GetProjectsDocument,
data: {
...newData,
},
});
},
});
return (
<>
<Popup title={null} tab={0}>
<TeamSettings
onDeleteTeam={() => {
setTab(1, 340);
}}
/>
</Popup>
<Popup title={`Delete the "${name}" team?`} tab={1} onClose={() => hidePopup()}>
<DeleteConfirm
description={DELETE_INFO.DELETE_TEAMS.description}
deletedItems={DELETE_INFO.DELETE_TEAMS.deletedItems}
onConfirmDelete={() => {
if (teamID) {
deleteTeam({ variables: { teamID } });
hidePopup();
history.push('/projects');
}
}}
/>
</Popup>
</>
);
};
const ProjectsContainer = styled.div`
margin: 40px 16px 0;
width: 100%;
max-width: 825px;
min-width: 288px;
`;
type TeamsRouteProps = {
teamID: string;
};
const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
const Projects = () => {
const { teamID } = useParams<TeamsRouteProps>();
const history = useHistory();
const { loading, data } = useGetTeamQuery({ variables: { teamID } });
useEffect(() => {
document.title = 'Citadel | Teams';
}, []);
if (loading) {
return (
<>
<span>loading</span>
</>
);
}
if (data) {
return (
<>
<GlobalTopNavbar
menuType={MENU_TYPES.TEAM_MENU}
initialTab={0}
popupContent={<TeamPopup history={history} name={data.findTeam.name} teamID={teamID} />}
onSaveProjectName={() => {}}
projectID={null}
name={data.findTeam.name}
/>
<Wrapper>
<ProjectsContainer>
<ProjectList>
{data.projects.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>
))}
</ProjectList>
</ProjectsContainer>
</Wrapper>
</>
);
}
return <div>Error!</div>;
};
export default Projects;

View File

@ -17,6 +17,15 @@ type ContextMenuEvent = {
taskGroupID: string;
};
type User = {
id: string;
fullName: string;
username: string;
email: string;
role: string;
profileIcon: ProfileIcon;
};
type TaskUser = {
id: string;
fullName: string;

10
web/src/projects.d.ts vendored
View File

@ -47,10 +47,20 @@ type TaskChecklistItem = {
dueDate?: null | string;
};
type ChecklistBadge = {
complete: number;
total: number;
};
type TaskBadges = {
checklist?: ChecklistBadge | null;
};
type Task = {
id: string;
taskGroup: InnerTaskGroup;
name: string;
badges?: TaskBadges;
position: number;
dueDate?: string;
complete?: boolean;

View File

@ -1,7 +1,10 @@
import React, { useRef } from 'react';
import Admin from '.';
import { theme } from 'App/ThemeStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles';
import { ThemeProvider } from 'styled-components';
import { action } from '@storybook/addon-actions';
export default {
component: Admin,
@ -19,7 +22,27 @@ export const Default = () => {
<>
<NormalizeStyles />
<BaseStyles />
<Admin />
<ThemeProvider theme={theme}>
<Admin
onInviteUser={action('invite user')}
initialTab={1}
users={[
{
id: '1',
username: 'jordanthedev',
email: 'jordan@jordanthedev.com',
role: 'Admin',
fullName: 'Jordan Knott',
profileIcon: {
bgColor: '#fff',
initials: 'JK',
url: null,
},
},
]}
onAddUser={action('add user')}
/>
</ThemeProvider>
</>
);
};

View File

@ -1,34 +1,28 @@
import React, { useState, useRef } from 'react';
import styled from 'styled-components';
import { User, Plus } from 'shared/icons';
import { User, Plus, Lock, Pencil, Trash } from 'shared/icons';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-material.css';
import Button from 'shared/components/Button';
const NewUserButton = styled.button`
outline: none;
border: none;
cursor: pointer;
line-height: 20px;
padding: 0.75rem;
background-color: transparent;
display: flex;
align-items: center;
justify-content: center;
color: rgba(115, 103, 240);
font-size: 14px;
border-radius: 0.5rem;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image: initial;
border-color: rgba(115, 103, 240);
span {
padding-left: 0.5rem;
}
const NewUserButton = styled(Button)`
padding: 6px 12px;
margin-right: 12px;
`;
const InviteUserButton = styled(Button)`
padding: 6px 12px;
margin-right: 8px;
`;
const MemberActions = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
`;
const GridTable = styled.div`
height: 620px;
`;
@ -93,8 +87,29 @@ const Header = styled.div`
min-height: 112px;
`;
const ActionButtonsContainer = styled.div`
display: flex;
align-items: center;
`;
const EditUserIcon = styled(Pencil)`
margin-right: 8px;
`;
const LockUserIcon = styled(Lock)`
margin-right: 8px;
`;
const DeleteUserIcon = styled(Trash)``;
const ActionButtons = () => {
return <span>Hello!</span>;
return (
<>
<EditUserIcon width={16} height={16} />
<LockUserIcon width={16} height={16} />
<DeleteUserIcon width={16} height={16} />
</>
);
};
const data = {
defaultColDef: {
@ -103,16 +118,14 @@ const data = {
},
columnDefs: [
{
minWidth: 125,
width: 125,
minWidth: 55,
width: 55,
headerCheckboxSelection: true,
checkboxSelection: true,
headerName: 'ID',
field: 'id',
},
{ minWidth: 210, headerName: 'Username', editable: true, field: 'username' },
{ minWidth: 225, headerName: 'Email', field: 'email' },
{ minWidth: 200, headerName: 'Name', editable: true, field: 'full_name' },
{ minWidth: 200, headerName: 'Name', editable: true, field: 'fullName' },
{ minWidth: 200, headerName: 'Role', editable: true, field: 'role' },
{
minWidth: 200,
@ -123,14 +136,13 @@ const data = {
frameworkComponents: {
actionButtons: ActionButtons,
},
rowData: [
{ id: '1', full_name: 'Jordan Knott', username: 'jordan', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '2', full_name: 'Jordan Test', username: 'jordantest', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '3', full_name: 'Jordan Other', username: 'alphatest1050', email: 'jordan@jordanthedev.com', role: 'Admin' },
{ id: '5', full_name: 'Jordan French', username: 'other', email: 'jordan@jordanthedev.com', role: 'Admin' },
],
};
const ListTable = () => {
type ListTableProps = {
users: Array<User>;
};
const ListTable: React.FC<ListTableProps> = ({ users }) => {
return (
<Root>
<div className="ag-theme-material" style={{ height: '296px', width: '100%' }}>
@ -138,7 +150,7 @@ const ListTable = () => {
rowSelection="multiple"
defaultColDef={data.defaultColDef}
columnDefs={data.columnDefs}
rowData={data.rowData}
rowData={users}
frameworkComponents={data.frameworkComponents}
onFirstDataRendered={params => {
params.api.sizeColumnsToFit();
@ -146,7 +158,7 @@ const ListTable = () => {
onGridSizeChanged={params => {
params.api.sizeColumnsToFit();
}}
></AgGridReact>
/>
</div>
</Root>
);
@ -184,6 +196,7 @@ const TabNavContent = styled.ul`
const TabNavItem = styled.li`
padding: 0.35rem 0.3rem;
height: 48px;
display: block;
position: relative;
`;
@ -282,9 +295,16 @@ const NavItem: React.FC<NavItemProps> = ({ active, name, tab, onClick }) => {
);
};
const Admin = () => {
const [currentTop, setTop] = useState(0);
const [currentTab, setTab] = useState(0);
type AdminProps = {
initialTab: number;
onAddUser: ($target: React.RefObject<HTMLElement>) => void;
onInviteUser: ($target: React.RefObject<HTMLElement>) => void;
users: Array<User>;
};
const Admin: React.FC<AdminProps> = ({ initialTab, onAddUser, onInviteUser, users }) => {
const [currentTop, setTop] = useState(initialTab * 48);
const [currentTab, setTab] = useState(initialTab);
const $tabNav = useRef<HTMLDivElement>(null);
return (
<Container>
@ -309,11 +329,17 @@ const Admin = () => {
</TabNav>
<TabContentWrapper>
<TabContent>
<NewUserButton>
<Plus color="rgba(115, 103, 240)" size={10} />
<span>Add New</span>
</NewUserButton>
<ListTable />
<MemberActions>
<NewUserButton variant="outline" onClick={onAddUser}>
<Plus color="rgba(115, 103, 240)" size={10} />
<span style={{ paddingLeft: '5px' }}>Create member</span>
</NewUserButton>
<InviteUserButton variant="outline" onClick={onInviteUser}>
<Plus color="rgba(115, 103, 240)" size={10} />
<span style={{ paddingLeft: '5px' }}>Invite member</span>
</InviteUserButton>
</MemberActions>
<ListTable users={users} />
</TabContent>
</TabContentWrapper>
</Container>

View File

@ -42,7 +42,7 @@ type Props = {
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
description?: null | string;
dueDate?: DueDate;
checklists?: Checklist;
checklists?: Checklist | null;
labels?: Array<ProjectLabel>;
watched?: boolean;
wrapperProps?: any;

View File

@ -501,7 +501,7 @@ const ChecklistTitleEditor = React.forwardRef(
);
type ChecklistProps = {
checklistID: string;
onDeleteChecklist: (checklistID: string) => void;
onDeleteChecklist: ($target: React.RefObject<HTMLElement>, checklistID: string) => void;
name: string;
onChangeName: (item: string) => void;
onToggleItem: (taskID: string, complete: boolean) => void;
@ -554,8 +554,8 @@ const Checklist: React.FC<ChecklistProps> = ({
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
<WindowOptions>
<DeleteButton
onClick={() => {
onDeleteChecklist(checklistID);
onClick={$target => {
onDeleteChecklist($target, checklistID);
}}
color="danger"
variant="outline"

View File

@ -51,6 +51,7 @@ export const Default = () => {
</Container>
{menu.isOpen && (
<DropdownMenu
onAdminConsole={action('admin')}
onCloseDropdown={() => {
setMenu({ top: 0, left: 0, isOpen: false });
}}

View File

@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { Exit, User } from 'shared/icons';
import { Exit, User, Cog } from 'shared/icons';
import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles';
type DropdownMenuProps = {
@ -8,15 +8,16 @@ type DropdownMenuProps = {
top: number;
onLogout: () => void;
onCloseDropdown: () => void;
onAdminConsole: () => void;
};
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown }) => {
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown, onAdminConsole }) => {
const $containerRef = useRef<HTMLDivElement>(null);
useOnOutsideClick($containerRef, true, onCloseDropdown, null);
return (
<Container ref={$containerRef} left={left} top={top}>
<Wrapper>
<ActionItem>
<ActionItem onClick={onAdminConsole}>
<User size={16} color="#c2c6dc" />
<ActionTitle>Profile</ActionTitle>
</ActionItem>
@ -36,16 +37,21 @@ const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onClos
type ProfileMenuProps = {
onProfile: () => void;
onLogout: () => void;
onAdminConsole: () => void;
};
const ProfileMenu: React.FC<ProfileMenuProps> = ({ onProfile, onLogout }) => {
const ProfileMenu: React.FC<ProfileMenuProps> = ({ onAdminConsole, onProfile, onLogout }) => {
return (
<>
<ActionItem onClick={onAdminConsole}>
<Cog size={16} color="#c2c6dc" />
<ActionTitle>Admin Console</ActionTitle>
</ActionItem>
<Separator />
<ActionItem onClick={onProfile}>
<User size={16} color="#c2c6dc" />
<ActionTitle>Profile</ActionTitle>
</ActionItem>
<Separator />
<ActionsList>
<ActionItem onClick={onLogout}>
<Exit size={16} color="#c2c6dc" />

View File

@ -80,8 +80,10 @@ type InputProps = {
variant?: 'normal' | 'alternate';
label?: string;
width?: string;
floatingLabel?: boolean;
placeholder?: string;
icon?: JSX.Element;
autocomplete?: boolean;
id?: string;
name?: string;
className?: string;
@ -95,6 +97,7 @@ const Input = React.forwardRef(
{
width = 'auto',
variant = 'normal',
autocomplete,
label,
placeholder,
icon,
@ -102,6 +105,7 @@ const Input = React.forwardRef(
onChange,
className,
onClick,
floatingLabel,
value: initialValue,
id,
}: InputProps,
@ -124,12 +128,13 @@ const Input = React.forwardRef(
return (
<InputWrapper className={className} width={width}>
<InputInput
hasValue={value !== ''}
hasValue={floatingLabel || value !== ''}
ref={$ref}
id={id}
name={name}
onClick={onClick}
onChange={handleChange}
autoComplete={autocomplete ? 'on' : 'off'}
value={value}
hasIcon={typeof icon !== 'undefined'}
width={width}

View File

@ -1,6 +1,7 @@
import styled from 'styled-components';
export const Container = styled.div`
flex: 1;
user-select: none;
white-space: nowrap;
margin-bottom: 8px;

View File

@ -181,6 +181,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onClick={() => {
onTaskClick(task);
}}
checklists={task.badges && task.badges.checklist}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>

View File

@ -38,7 +38,7 @@ const Login = ({ onSubmit }: LoginProps) => {
<LoginFormWrapper>
<LoginFormContainer>
<LogoWrapper>
<Citadel size={42} />
<Citadel width={42} height={42} />
<LogoTitle>Citadel</LogoTitle>
</LogoWrapper>
<Title>Login</Title>
@ -66,7 +66,7 @@ const Login = ({ onSubmit }: LoginProps) => {
ref={register({ required: 'Password is required' })}
/>
<FormIcon>
<Lock color="#c2c6dc" size={20} />
<Lock width={20} height={20} />
</FormIcon>
</FormLabel>
{errors.password && <FormError>{errors.password.message}</FormError>}

View File

@ -28,10 +28,10 @@ export const Default = () => {
<PrimaryLogo />
<ButtonContainer>
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
<ActionButton name="Home">
<Home size={28} color="#c2c6dc" />
<Home width={28} height={28} />
</ActionButton>
</ButtonContainer>
</Navbar>

View File

@ -35,7 +35,7 @@ export const ButtonContainer: React.FC = ({ children }) => (
export const PrimaryLogo = () => {
return (
<LogoWrapper>
<Citadel size={42} />
<Citadel width={42} height={42} />
<LogoTitle>Citadel</LogoTitle>
</LogoWrapper>
);

View File

@ -23,6 +23,7 @@ export const Default = () => {
<NormalizeStyles />
<BaseStyles />
<NewProject
initialTeamID={null}
onCreateProject={action('create project')}
teams={[{ name: 'General', id: 'general', createdAt: '' }]}
onClose={() => {}}

View File

@ -207,14 +207,15 @@ const CreateButton = styled.button`
}
`;
type NewProjectProps = {
initialTeamID: string | null;
teams: Array<Team>;
onClose: () => void;
onCreateProject: (projectName: string, teamID: string) => void;
};
const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject }) => {
const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose, onCreateProject }) => {
const [projectName, setProjectName] = useState('');
const [team, setTeam] = useState<null | string>(null);
const [team, setTeam] = useState<null | string>(initialTeamID);
const options = teams.map(t => ({ label: t.name, value: t.id }));
return (
<Overlay>

View File

@ -58,7 +58,7 @@ const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLa
onLabelEdit(label.id);
}}
>
<Pencil color="#c2c6dc" />
<Pencil width={16} height={16} />
</LabelIcon>
<CardLabel
key={label.id}

View File

@ -42,7 +42,6 @@ export const HeaderTitle = styled.span`
box-sizing: border-box;
color: #c2c6dc;
display: block;
line-height: 40px;
border-bottom: 1px solid #414561;
margin: 0 12px;
overflow: hidden;
@ -51,6 +50,13 @@ export const HeaderTitle = styled.span`
text-overflow: ellipsis;
white-space: nowrap;
z-index: 1;
height: 40px;
line-height: 18px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
`;
export const Content = styled.div`
@ -138,7 +144,7 @@ export const CardLabel = styled.span<{ active: boolean; color: string }>`
`;
export const CloseButton = styled.div`
padding: 10px 12px 10px 8px;
padding: 18px 18px 14px 12px;
position: absolute;
top: 0;
right: 0;
@ -312,7 +318,7 @@ export const CreateLabelButton = styled.button`
`;
export const PreviousButton = styled.div`
padding: 10px 12px 10px 8px;
padding: 18px 18px 14px 12px;
position: absolute;
top: 0;
left: 0;

View File

@ -2,7 +2,6 @@ import React, { useRef } from 'react';
import styled from 'styled-components';
export const Container = styled.div<{ size: number | string; bgColor: string | null; backgroundURL: string | null }>`
margin-left: 10px;
width: ${props => props.size}px;
height: ${props => props.size}px;
border-radius: 9999px;

View File

@ -52,6 +52,21 @@ const ProjectSettings: React.FC<Props> = ({ onDeleteProject }) => {
);
};
type TeamSettingsProps = {
onDeleteTeam: () => void;
};
export const TeamSettings: React.FC<TeamSettingsProps> = ({ onDeleteTeam }) => {
return (
<>
<ListActionsWrapper>
<ListActionItemWrapper onClick={() => onDeleteTeam()}>
<ListActionItem>Delete Team</ListActionItem>
</ListActionItemWrapper>
</ListActionsWrapper>
</>
);
};
const ConfirmWrapper = styled.div``;
const ConfirmSubTitle = styled.h3`
@ -76,25 +91,40 @@ const ConfirmDeleteButton = styled(Button)`
padding: 6px 12px;
`;
type DeleteProjectProps = {
name: string;
onDeleteProject: () => void;
type DeleteConfirmProps = {
description: string;
deletedItems: Array<string>;
onConfirmDelete: () => void;
};
const DeleteProject: React.FC<DeleteProjectProps> = ({ name, onDeleteProject }) => {
export const DELETE_INFO = {
DELETE_PROJECTS: {
description: 'Deleting the project will also delete the following:',
deletedItems: ['Task groups and tasks'],
},
DELETE_TEAMS: {
description: 'Deleting the team will also delete the following:',
deletedItems: ['Projects under the team', 'All task groups & tasks associated with the team projects'],
},
};
const DeleteConfirm: React.FC<DeleteConfirmProps> = ({ description, deletedItems, onConfirmDelete }) => {
return (
<ConfirmWrapper>
<ConfirmDescription>
Deleting the project will also delete the following:
{description}
<DeleteList>
<DeleteListItem>Task groups and tasks</DeleteListItem>
{deletedItems.map(item => (
<DeleteListItem>{item}</DeleteListItem>
))}
</DeleteList>
</ConfirmDescription>
<ConfirmDeleteButton onClick={() => onDeleteProject()} color="danger">
<ConfirmDeleteButton onClick={() => onConfirmDelete()} color="danger">
Delete
</ConfirmDeleteButton>
</ConfirmWrapper>
);
};
export { DeleteProject };
export { DeleteConfirm };
export default ProjectSettings;

View File

@ -71,6 +71,9 @@ export const Default = () => {
onToggleTaskComplete={action('toggle task complete')}
onToggleChecklistItem={action('toggle checklist item')}
onOpenAddLabelPopup={action('open add label popup')}
onChangeChecklistName={action('change checklist name')}
onDeleteChecklist={action('delete checklist')}
onOpenAddChecklistPopup={action(' open checklist')}
onOpenDueDatePopop={action('open due date popup')}
/>
);

View File

@ -140,13 +140,19 @@ type TaskDetailsProps = {
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenDueDatePopop: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddChecklistPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
onChangeChecklistName: (checklistID: string, name: string) => void;
onDeleteChecklist: ($target: React.RefObject<HTMLElement>, checklistID: string) => void;
onCloseModal: () => void;
};
const TaskDetails: React.FC<TaskDetailsProps> = ({
task,
onDeleteChecklist,
onTaskNameChange,
onOpenAddChecklistPopup,
onChangeChecklistName,
onToggleTaskComplete,
onTaskDescriptionChange,
onChangeItemName,
@ -183,6 +189,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const onAddMember = ($target: React.RefObject<HTMLElement>) => {
onOpenAddMemberPopup(task, $target);
};
const onAddChecklist = ($target: React.RefObject<HTMLElement>) => {
onOpenAddChecklistPopup(task, $target)
}
const $dueDateLabel = useRef<HTMLDivElement>(null);
const $addLabelRef = useRef<HTMLDivElement>(null);
@ -290,16 +299,16 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
name={checklist.name}
checklistID={checklist.id}
items={checklist.items}
onDeleteChecklist={() => {}}
onChangeName={() => {}}
onDeleteChecklist={onDeleteChecklist}
onChangeName={newName => onChangeChecklistName(checklist.id, newName)}
onToggleItem={onToggleChecklistItem}
onDeleteItem={onDeleteItem}
onAddItem={n => {
if (task.checklists) {
let position = 1;
const lastChecklist = task.checklists.sort((a, b) => a.position - b.position)[-1];
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
let position = 65535;
const [lastItem] = checklist.items.sort((a, b) => a.position - b.position).slice(-1);
if (lastItem) {
position = lastItem.position * 2 + 1;
}
onAddItem(checklist.id, n, position);
}
@ -317,7 +326,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</ActionButton>
<ActionButton onClick={$target => onAddMember($target)}>Members</ActionButton>
<ActionButton onClick={$target => onAddLabel($target)}>Labels</ActionButton>
<ActionButton>Checklist</ActionButton>
<ActionButton onClick={$target => onAddChecklist($target)}>Checklist</ActionButton>
<ActionButton onClick={$target => onOpenDueDatePopop(task, $target)}>Due Date</ActionButton>
<ActionButton>Attachment</ActionButton>
<ActionButton>Cover</ActionButton>

View File

@ -2,6 +2,8 @@ import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
import { Citadel } from 'shared/icons';
import { Link } from 'react-router-dom';
export const NavbarWrapper = styled.div`
width: 100%;
@ -9,7 +11,6 @@ export const NavbarWrapper = styled.div`
export const ProjectMembers = styled.div`
display: flex;
padding-right: 18px;
align-items: center;
`;
export const NavbarHeader = styled.header`
@ -34,9 +35,9 @@ export const BreadcrumpSeparator = styled.span`
`;
export const ProjectActions = styled.div`
flex: 1;
align-items: flex-start;
display: flex;
flex: 1;
flex-direction: column;
min-width: 1px;
`;
@ -56,10 +57,11 @@ export const ProfileNameWrapper = styled.div`
line-height: 1.25;
`;
export const NotificationContainer = styled.div`
export const IconContainer = styled.div`
margin-right: 20px;
cursor: pointer;
`;
export const ProfileNamePrimary = styled.div`
color: #c2c6dc;
font-weight: 600;
@ -70,7 +72,6 @@ export const ProfileNameSecondary = styled.small`
`;
export const ProfileIcon = styled.div<{ bgColor: string | null; backgroundURL: string | null }>`
margin-left: 10px;
width: 40px;
height: 40px;
border-radius: 9999px;
@ -84,9 +85,9 @@ export const ProfileIcon = styled.div<{ bgColor: string | null; backgroundURL: s
background-size: contain;
`;
export const ProjectMeta = styled.div`
export const ProjectMeta = styled.div<{ nameOnly?: boolean }>`
display: flex;
padding-top: 9px;
${props => !props.nameOnly && 'padding-top: 9px;'}
margin-left: -14px;
align-items: center;
max-width: 100%;
@ -188,7 +189,7 @@ export const ProjectSwitcher = styled.button`
export const Separator = styled.div`
color: #c2c6dc;
font-size: 16px;
font-size: 20px;
padding-left: 4px;
padding-right: 4px;
`;
@ -214,3 +215,36 @@ export const InviteButton = styled(Button)`
margin: 0 0 0 8px;
padding: 6px 12px;
`;
export const ProjectFinder = styled(Button)`
margin-right: 20px;
padding: 6px 12px;
`;
export const NavSeparator = styled.div`
width: 1px;
background: rgba(${props => props.theme.colors.border});
height: 34px;
margin: 0 20px;
`;
export const LogoContainer = styled(Link)`
display: block;
left: 50%;
position: absolute;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
`;
export const CitadelTitle = styled.h2`
margin-left: 5px;
color: rgba(${props => props.theme.colors.text.primary});
font-size: 20px;
`;
export const CitadelLogo = styled(Citadel)`
fill: rgba(${props => props.theme.colors.text.primary});
stroke: rgba(${props => props.theme.colors.text.primary});
`;

View File

@ -26,7 +26,8 @@ export const Default = () => {
<NormalizeStyles />
<BaseStyles />
<TopNavbar
projectName="Projects"
onOpenProjectFinder={action('finder')}
name="Projects"
user={{
id: '1',
fullName: 'Jordan Knott',
@ -38,6 +39,7 @@ export const Default = () => {
}}
onNotificationClick={action('notifications click')}
onOpenSettings={action('open settings')}
onDashboardClick={action('open dashboard')}
onProfileClick={action('profile click')}
/>
</>

View File

@ -1,22 +1,27 @@
import React, { useRef, useState, useEffect } from 'react';
import { Star, Ellipsis, Bell, Cog, AngleDown } 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 TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile';
import {
NotificationContainer,
CitadelLogo,
CitadelTitle,
ProjectFinder,
LogoContainer,
NavSeparator,
IconContainer,
ProjectNameTextarea,
InviteButton,
GlobalActions,
ProjectActions,
ProjectSwitcher,
Separator,
ProjectMeta,
ProjectName,
ProjectTabs,
ProjectTab,
NavbarWrapper,
NavbarHeader,
Breadcrumbs,
BreadcrumpSeparator,
ProjectSettingsButton,
ProfileContainer,
ProfileNameWrapper,
@ -24,18 +29,20 @@ import {
ProfileNameSecondary,
ProjectMembers,
} from './Styles';
import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile';
import { Link } from 'react-router-dom';
const HomeDashboard = styled(Home)``;
type ProjectHeadingProps = {
projectName: string;
onFavorite?: () => void;
name: string;
onSaveProjectName?: (projectName: string) => void;
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
};
const ProjectHeading: React.FC<ProjectHeadingProps> = ({
projectName: initialProjectName,
onFavorite,
name: initialProjectName,
onSaveProjectName,
onOpenSettings,
}) => {
@ -73,7 +80,6 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
const $settings = useRef<HTMLButtonElement>(null);
return (
<>
<Separator>»</Separator>
{isEditProjectName ? (
<ProjectNameTextarea
ref={$projectName}
@ -100,28 +106,54 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({
>
<AngleDown color="#c2c6dc" />
</ProjectSettingsButton>
<ProjectSettingsButton>
<Star width={16} height={16} color="#c2c6dc" />
</ProjectSettingsButton>
{onFavorite && (
<ProjectSettingsButton onClick={() => onFavorite()}>
<Star width={16} height={16} color="#c2c6dc" />
</ProjectSettingsButton>
)}
</>
);
};
type MenuType = {
[key: number]: string;
};
type MenuTypes = {
[key: string]: Array<string>;
};
export const MENU_TYPES: MenuTypes = {
PROJECT_MENU: ['Board', 'Timeline', 'Calender'],
TEAM_MENU: ['Projects', 'Members', 'Settings'],
};
type NavBarProps = {
projectName: string | null;
menuType?: Array<string> | null;
name: string | null;
currentTab?: number;
onOpenProjectFinder: ($target: React.RefObject<HTMLElement>) => void;
onFavorite?: () => void;
onProfileClick: ($target: React.RefObject<HTMLElement>) => void;
onSaveProjectName?: (projectName: string) => void;
onTabClick?: (tab: number) => void;
onSaveName?: (name: string) => void;
onNotificationClick: () => void;
onDashboardClick: () => void;
user: TaskUser | null;
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
projectMembers?: Array<TaskUser> | null;
};
const NavBar: React.FC<NavBarProps> = ({
projectName,
onSaveProjectName,
menuType,
currentTab,
onOpenProjectFinder,
onFavorite,
onTabClick,
name,
onSaveName,
onProfileClick,
onNotificationClick,
onDashboardClick,
user,
projectMembers,
onOpenSettings,
@ -152,45 +184,61 @@ const NavBar: React.FC<NavBarProps> = ({
<NavbarHeader>
<ProjectActions>
<ProjectMeta>
<ProjectSwitcher>Projects</ProjectSwitcher>
{projectName && (
{name && (
<ProjectHeading
onFavorite={onFavorite}
onOpenSettings={onOpenSettings}
projectName={projectName}
onSaveProjectName={onSaveProjectName}
name={name}
onSaveProjectName={onSaveName}
/>
)}
</ProjectMeta>
{projectName && (
{name && (
<ProjectTabs>
<ProjectTab active>Board</ProjectTab>
<ProjectTab>Calender</ProjectTab>
<ProjectTab>Timeline</ProjectTab>
<ProjectTab>Wiki</ProjectTab>
{menuType &&
menuType.map((name, idx) => {
console.log(`${name} : ${idx} === ${currentTab}`);
return <ProjectTab active={currentTab === idx}>{name}</ProjectTab>;
})}
</ProjectTabs>
)}
</ProjectActions>
<LogoContainer to="/">
<CitadelLogo width={24} height={24} />
<CitadelTitle>Citadel</CitadelTitle>
</LogoContainer>
<GlobalActions>
{projectMembers && (
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton variant="outline">Invite</InviteButton>
</ProjectMembers>
<>
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton variant="outline">Invite</InviteButton>
</ProjectMembers>
<NavSeparator />
</>
)}
<NotificationContainer onClick={onNotificationClick}>
<ProjectFinder onClick={onOpenProjectFinder} variant="gradient">
Projects
</ProjectFinder>
<IconContainer onClick={onDashboardClick}>
<HomeDashboard width={20} height={20} />
</IconContainer>
<IconContainer>
<CheckCircle width={20} height={20} />
</IconContainer>
<IconContainer onClick={onNotificationClick}>
<Bell color="#c2c6dc" size={20} />
</NotificationContainer>
</IconContainer>
<IconContainer>
<BarChart width={20} height={20} />
</IconContainer>
{user && (
<ProfileContainer>
<ProfileNameWrapper>
<ProfileNamePrimary>{user.fullName}</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper>
<ProfileIcon user={user} size={40} onProfileClick={handleProfileClick} />}
</ProfileContainer>
<IconContainer>
<ProfileIcon user={user} size={30} onProfileClick={handleProfileClick} />
</IconContainer>
)}
</GlobalActions>
</NavbarHeader>

View File

@ -131,7 +131,7 @@ export type Task = {
};
export type ProjectsFilter = {
teamID?: Maybe<Scalars['String']>;
teamID?: Maybe<Scalars['UUID']>;
};
export type FindUser = {
@ -152,6 +152,10 @@ export type Organization = {
name: Scalars['String'];
};
export type FindTeam = {
teamID: Scalars['UUID'];
};
export type Query = {
__typename?: 'Query';
organizations: Array<Organization>;
@ -160,6 +164,7 @@ export type Query = {
findProject: Project;
findTask: Task;
projects: Array<Project>;
findTeam: Team;
teams: Array<Team>;
labelColors: Array<LabelColor>;
taskGroups: Array<TaskGroup>;
@ -186,6 +191,11 @@ export type QueryProjectsArgs = {
input?: Maybe<ProjectsFilter>;
};
export type QueryFindTeamArgs = {
input: FindTeam;
};
export type NewRefreshToken = {
userId: Scalars['String'];
};
@ -419,10 +429,48 @@ export type DeleteProjectPayload = {
project: Project;
};
export type DeleteTeam = {
teamID: Scalars['UUID'];
};
export type DeleteTeamPayload = {
__typename?: 'DeleteTeamPayload';
ok: Scalars['Boolean'];
team: Team;
projects: Array<Project>;
};
export type DeleteUserAccount = {
userID: Scalars['UUID'];
};
export type DeleteUserAccountPayload = {
__typename?: 'DeleteUserAccountPayload';
ok: Scalars['Boolean'];
userAccount: UserAccount;
};
export type UpdateTaskChecklistName = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
};
export type DeleteTaskChecklist = {
taskChecklistID: Scalars['UUID'];
};
export type DeleteTaskChecklistPayload = {
__typename?: 'DeleteTaskChecklistPayload';
ok: Scalars['Boolean'];
taskChecklist: TaskChecklist;
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
createUserAccount: UserAccount;
deleteUserAccount: DeleteUserAccountPayload;
deleteTeam: DeleteTeamPayload;
createTeam: Team;
clearProfileAvatar: UserAccount;
createTeamMember: CreateTeamMemberPayload;
@ -442,6 +490,8 @@ export type Mutation = {
removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTaskChecklist: TaskChecklist;
deleteTaskChecklist: DeleteTaskChecklistPayload;
updateTaskChecklistName: TaskChecklist;
createTaskChecklistItem: TaskChecklistItem;
updateTaskChecklistItemName: TaskChecklistItem;
setTaskChecklistItemComplete: TaskChecklistItem;
@ -469,6 +519,16 @@ export type MutationCreateUserAccountArgs = {
};
export type MutationDeleteUserAccountArgs = {
input: DeleteUserAccount;
};
export type MutationDeleteTeamArgs = {
input: DeleteTeam;
};
export type MutationCreateTeamArgs = {
input: NewTeam;
};
@ -559,6 +619,16 @@ export type MutationCreateTaskChecklistArgs = {
};
export type MutationDeleteTaskChecklistArgs = {
input: DeleteTaskChecklist;
};
export type MutationUpdateTaskChecklistNameArgs = {
input: UpdateTaskChecklistName;
};
export type MutationCreateTaskChecklistItemArgs = {
input: CreateTaskChecklistItem;
};
@ -699,43 +769,6 @@ export type CreateProjectLabelMutation = (
) }
);
export type CreateTaskMutationVariables = {
taskGroupID: Scalars['String'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'fullName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
) }
);
export type CreateTaskGroupMutationVariables = {
projectID: Scalars['String'];
name: Scalars['String'];
@ -849,6 +882,12 @@ export type FindTaskQuery = (
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), badges: (
{ __typename?: 'TaskBadges' }
& { checklist?: Maybe<(
{ __typename?: 'ChecklistBadge' }
& Pick<ChecklistBadge, 'total' | 'complete'>
)> }
), checklists: Array<(
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
@ -881,9 +920,15 @@ export type FindTaskQuery = (
export type TaskFieldsFragment = (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'complete' | 'position'>
& { taskGroup: (
& { badges: (
{ __typename?: 'TaskBadges' }
& { checklist?: Maybe<(
{ __typename?: 'ChecklistBadge' }
& Pick<ChecklistBadge, 'complete' | 'total'>
)> }
), taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
@ -958,6 +1003,40 @@ export type DeleteProjectMutation = (
) }
);
export type CreateTaskMutationVariables = {
taskGroupID: Scalars['String'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& TaskFieldsFragment
) }
);
export type CreateTaskChecklistMutationVariables = {
taskID: Scalars['UUID'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskChecklistMutation = (
{ __typename?: 'Mutation' }
& { createTaskChecklist: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
& { items: Array<(
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
)> }
) }
);
export type CreateTaskChecklistItemMutationVariables = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
@ -973,6 +1052,23 @@ export type CreateTaskChecklistItemMutation = (
) }
);
export type DeleteTaskChecklistMutationVariables = {
taskChecklistID: Scalars['UUID'];
};
export type DeleteTaskChecklistMutation = (
{ __typename?: 'Mutation' }
& { deleteTaskChecklist: (
{ __typename?: 'DeleteTaskChecklistPayload' }
& Pick<DeleteTaskChecklistPayload, 'ok'>
& { taskChecklist: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id'>
) }
) }
);
export type DeleteTaskChecklistItemMutationVariables = {
taskChecklistItemID: Scalars['UUID'];
};
@ -1000,7 +1096,7 @@ export type SetTaskChecklistItemCompleteMutation = (
{ __typename?: 'Mutation' }
& { setTaskChecklistItemComplete: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
& Pick<TaskChecklistItem, 'id' | 'complete'>
) }
);
@ -1032,6 +1128,24 @@ export type UpdateTaskChecklistItemNameMutation = (
) }
);
export type UpdateTaskChecklistNameMutationVariables = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateTaskChecklistNameMutation = (
{ __typename?: 'Mutation' }
& { updateTaskChecklistName: (
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
& { items: Array<(
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
)> }
) }
);
export type UpdateTaskGroupNameMutationVariables = {
taskGroupID: Scalars['UUID'];
name: Scalars['String'];
@ -1060,6 +1174,43 @@ export type CreateTeamMutation = (
) }
);
export type DeleteTeamMutationVariables = {
teamID: Scalars['UUID'];
};
export type DeleteTeamMutation = (
{ __typename?: 'Mutation' }
& { deleteTeam: (
{ __typename?: 'DeleteTeamPayload' }
& Pick<DeleteTeamPayload, 'ok'>
& { team: (
{ __typename?: 'Team' }
& Pick<Team, 'id'>
) }
) }
);
export type GetTeamQueryVariables = {
teamID: Scalars['UUID'];
};
export type GetTeamQuery = (
{ __typename?: 'Query' }
& { findTeam: (
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'createdAt' | 'name'>
), projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
& { team: (
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
) }
)> }
);
export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
@ -1220,6 +1371,42 @@ export type UpdateTaskNameMutation = (
) }
);
export type CreateUserAccountMutationVariables = {
username: Scalars['String'];
email: Scalars['String'];
fullName: Scalars['String'];
initials: Scalars['String'];
password: Scalars['String'];
};
export type CreateUserAccountMutation = (
{ __typename?: 'Mutation' }
& { createUserAccount: (
{ __typename?: 'UserAccount' }
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'initials' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
) }
);
export type UsersQueryVariables = {};
export type UsersQuery = (
{ __typename?: 'Query' }
& { users: Array<(
{ __typename?: 'UserAccount' }
& Pick<UserAccount, 'id' | 'email' | 'fullName' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
);
export const TaskFieldsFragmentDoc = gql`
fragment TaskFields on Task {
id
@ -1228,8 +1415,16 @@ export const TaskFieldsFragmentDoc = gql`
dueDate
complete
position
badges {
checklist {
complete
total
}
}
taskGroup {
id
name
position
}
labels {
id
@ -1412,73 +1607,6 @@ export function useCreateProjectLabelMutation(baseOptions?: ApolloReactHooks.Mut
export type CreateProjectLabelMutationHookResult = ReturnType<typeof useCreateProjectLabelMutation>;
export type CreateProjectLabelMutationResult = ApolloReactCommon.MutationResult<CreateProjectLabelMutation>;
export type CreateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectLabelMutation, CreateProjectLabelMutationVariables>;
export const CreateTaskDocument = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
}
`;
export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>;
/**
* __useCreateTaskMutation__
*
* To run a mutation, you first call `useCreateTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskMutation` 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 [createTaskMutation, { data, loading, error }] = useCreateTaskMutation({
* variables: {
* taskGroupID: // value for 'taskGroupID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
}
export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>;
export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>;
export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskMutation, CreateTaskMutationVariables>;
export const CreateTaskGroupDocument = gql`
mutation createTaskGroup($projectID: String!, $name: String!, $position: Float!) {
createTaskGroup(input: {projectID: $projectID, name: $name, position: $position}) {
@ -1698,6 +1826,12 @@ export const FindTaskDocument = gql`
taskGroup {
id
}
badges {
checklist {
total
complete
}
}
checklists {
id
name
@ -1882,6 +2016,83 @@ 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 CreateTaskDocument = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
...TaskFields
}
}
${TaskFieldsFragmentDoc}`;
export type CreateTaskMutationFn = ApolloReactCommon.MutationFunction<CreateTaskMutation, CreateTaskMutationVariables>;
/**
* __useCreateTaskMutation__
*
* To run a mutation, you first call `useCreateTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskMutation` 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 [createTaskMutation, { data, loading, error }] = useCreateTaskMutation({
* variables: {
* taskGroupID: // value for 'taskGroupID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskMutation, CreateTaskMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskMutation, CreateTaskMutationVariables>(CreateTaskDocument, baseOptions);
}
export type CreateTaskMutationHookResult = ReturnType<typeof useCreateTaskMutation>;
export type CreateTaskMutationResult = ApolloReactCommon.MutationResult<CreateTaskMutation>;
export type CreateTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskMutation, CreateTaskMutationVariables>;
export const CreateTaskChecklistDocument = gql`
mutation createTaskChecklist($taskID: UUID!, $name: String!, $position: Float!) {
createTaskChecklist(input: {taskID: $taskID, name: $name, position: $position}) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export type CreateTaskChecklistMutationFn = ApolloReactCommon.MutationFunction<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>;
/**
* __useCreateTaskChecklistMutation__
*
* To run a mutation, you first call `useCreateTaskChecklistMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskChecklistMutation` 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 [createTaskChecklistMutation, { data, loading, error }] = useCreateTaskChecklistMutation({
* variables: {
* taskID: // value for 'taskID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskChecklistMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>(CreateTaskChecklistDocument, baseOptions);
}
export type CreateTaskChecklistMutationHookResult = ReturnType<typeof useCreateTaskChecklistMutation>;
export type CreateTaskChecklistMutationResult = ApolloReactCommon.MutationResult<CreateTaskChecklistMutation>;
export type CreateTaskChecklistMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskChecklistMutation, CreateTaskChecklistMutationVariables>;
export const CreateTaskChecklistItemDocument = gql`
mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) {
createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) {
@ -1920,6 +2131,41 @@ export function useCreateTaskChecklistItemMutation(baseOptions?: ApolloReactHook
export type CreateTaskChecklistItemMutationHookResult = ReturnType<typeof useCreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationResult = ApolloReactCommon.MutationResult<CreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>;
export const DeleteTaskChecklistDocument = gql`
mutation deleteTaskChecklist($taskChecklistID: UUID!) {
deleteTaskChecklist(input: {taskChecklistID: $taskChecklistID}) {
ok
taskChecklist {
id
}
}
}
`;
export type DeleteTaskChecklistMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>;
/**
* __useDeleteTaskChecklistMutation__
*
* To run a mutation, you first call `useDeleteTaskChecklistMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTaskChecklistMutation` 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 [deleteTaskChecklistMutation, { data, loading, error }] = useDeleteTaskChecklistMutation({
* variables: {
* taskChecklistID: // value for 'taskChecklistID'
* },
* });
*/
export function useDeleteTaskChecklistMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>(DeleteTaskChecklistDocument, baseOptions);
}
export type DeleteTaskChecklistMutationHookResult = ReturnType<typeof useDeleteTaskChecklistMutation>;
export type DeleteTaskChecklistMutationResult = ApolloReactCommon.MutationResult<DeleteTaskChecklistMutation>;
export type DeleteTaskChecklistMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskChecklistMutation, DeleteTaskChecklistMutationVariables>;
export const DeleteTaskChecklistItemDocument = gql`
mutation deleteTaskChecklistItem($taskChecklistItemID: UUID!) {
deleteTaskChecklistItem(input: {taskChecklistItemID: $taskChecklistItemID}) {
@ -1960,10 +2206,7 @@ export const SetTaskChecklistItemCompleteDocument = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: {taskChecklistItemID: $taskChecklistItemID, complete: $complete}) {
id
name
taskChecklistID
complete
position
}
}
`;
@ -2060,6 +2303,48 @@ export function useUpdateTaskChecklistItemNameMutation(baseOptions?: ApolloReact
export type UpdateTaskChecklistItemNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>;
export const UpdateTaskChecklistNameDocument = gql`
mutation updateTaskChecklistName($taskChecklistID: UUID!, $name: String!) {
updateTaskChecklistName(input: {taskChecklistID: $taskChecklistID, name: $name}) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export type UpdateTaskChecklistNameMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
/**
* __useUpdateTaskChecklistNameMutation__
*
* To run a mutation, you first call `useUpdateTaskChecklistNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateTaskChecklistNameMutation` 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 [updateTaskChecklistNameMutation, { data, loading, error }] = useUpdateTaskChecklistNameMutation({
* variables: {
* taskChecklistID: // value for 'taskChecklistID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateTaskChecklistNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>(UpdateTaskChecklistNameDocument, baseOptions);
}
export type UpdateTaskChecklistNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistNameMutation>;
export type UpdateTaskChecklistNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistNameMutation>;
export type UpdateTaskChecklistNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistNameMutation, UpdateTaskChecklistNameMutationVariables>;
export const UpdateTaskGroupNameDocument = gql`
mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
updateTaskGroupName(input: {taskGroupID: $taskGroupID, name: $name}) {
@ -2129,6 +2414,84 @@ export function useCreateTeamMutation(baseOptions?: ApolloReactHooks.MutationHoo
export type CreateTeamMutationHookResult = ReturnType<typeof useCreateTeamMutation>;
export type CreateTeamMutationResult = ApolloReactCommon.MutationResult<CreateTeamMutation>;
export type CreateTeamMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTeamMutation, CreateTeamMutationVariables>;
export const DeleteTeamDocument = gql`
mutation deleteTeam($teamID: UUID!) {
deleteTeam(input: {teamID: $teamID}) {
ok
team {
id
}
}
}
`;
export type DeleteTeamMutationFn = ApolloReactCommon.MutationFunction<DeleteTeamMutation, DeleteTeamMutationVariables>;
/**
* __useDeleteTeamMutation__
*
* To run a mutation, you first call `useDeleteTeamMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTeamMutation` 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 [deleteTeamMutation, { data, loading, error }] = useDeleteTeamMutation({
* variables: {
* teamID: // value for 'teamID'
* },
* });
*/
export function useDeleteTeamMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTeamMutation, DeleteTeamMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteTeamMutation, DeleteTeamMutationVariables>(DeleteTeamDocument, baseOptions);
}
export type DeleteTeamMutationHookResult = ReturnType<typeof useDeleteTeamMutation>;
export type DeleteTeamMutationResult = ApolloReactCommon.MutationResult<DeleteTeamMutation>;
export type DeleteTeamMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTeamMutation, DeleteTeamMutationVariables>;
export const GetTeamDocument = gql`
query getTeam($teamID: UUID!) {
findTeam(input: {teamID: $teamID}) {
id
createdAt
name
}
projects(input: {teamID: $teamID}) {
id
name
team {
id
name
}
}
}
`;
/**
* __useGetTeamQuery__
*
* To run a query within a React component, call `useGetTeamQuery` and pass it any options that fit your needs.
* When your component renders, `useGetTeamQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetTeamQuery({
* variables: {
* teamID: // value for 'teamID'
* },
* });
*/
export function useGetTeamQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetTeamQuery, GetTeamQueryVariables>) {
return ApolloReactHooks.useQuery<GetTeamQuery, GetTeamQueryVariables>(GetTeamDocument, baseOptions);
}
export function useGetTeamLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetTeamQuery, GetTeamQueryVariables>) {
return ApolloReactHooks.useLazyQuery<GetTeamQuery, GetTeamQueryVariables>(GetTeamDocument, baseOptions);
}
export type GetTeamQueryHookResult = ReturnType<typeof useGetTeamQuery>;
export type GetTeamLazyQueryHookResult = ReturnType<typeof useGetTeamLazyQuery>;
export type GetTeamQueryResult = ApolloReactCommon.QueryResult<GetTeamQuery, GetTeamQueryVariables>;
export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
@ -2472,4 +2835,89 @@ export function useUpdateTaskNameMutation(baseOptions?: ApolloReactHooks.Mutatio
}
export type UpdateTaskNameMutationHookResult = ReturnType<typeof useUpdateTaskNameMutation>;
export type UpdateTaskNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskNameMutation>;
export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
export type UpdateTaskNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskNameMutation, UpdateTaskNameMutationVariables>;
export const CreateUserAccountDocument = gql`
mutation createUserAccount($username: String!, $email: String!, $fullName: String!, $initials: String!, $password: String!) {
createUserAccount(input: {username: $username, email: $email, fullName: $fullName, initials: $initials, password: $password}) {
id
email
fullName
initials
username
profileIcon {
url
initials
bgColor
}
}
}
`;
export type CreateUserAccountMutationFn = ApolloReactCommon.MutationFunction<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
/**
* __useCreateUserAccountMutation__
*
* To run a mutation, you first call `useCreateUserAccountMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateUserAccountMutation` 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 [createUserAccountMutation, { data, loading, error }] = useCreateUserAccountMutation({
* variables: {
* username: // value for 'username'
* email: // value for 'email'
* fullName: // value for 'fullName'
* initials: // value for 'initials'
* password: // value for 'password'
* },
* });
*/
export function useCreateUserAccountMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>) {
return ApolloReactHooks.useMutation<CreateUserAccountMutation, CreateUserAccountMutationVariables>(CreateUserAccountDocument, baseOptions);
}
export type CreateUserAccountMutationHookResult = ReturnType<typeof useCreateUserAccountMutation>;
export type CreateUserAccountMutationResult = ApolloReactCommon.MutationResult<CreateUserAccountMutation>;
export type CreateUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateUserAccountMutation, CreateUserAccountMutationVariables>;
export const UsersDocument = gql`
query users {
users {
id
email
fullName
username
profileIcon {
url
initials
bgColor
}
}
}
`;
/**
* __useUsersQuery__
*
* To run a query within a React component, call `useUsersQuery` and pass it any options that fit your needs.
* When your component renders, `useUsersQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useUsersQuery({
* variables: {
* },
* });
*/
export function useUsersQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<UsersQuery, UsersQueryVariables>) {
return ApolloReactHooks.useQuery<UsersQuery, UsersQueryVariables>(UsersDocument, baseOptions);
}
export function useUsersLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<UsersQuery, UsersQueryVariables>) {
return ApolloReactHooks.useLazyQuery<UsersQuery, UsersQueryVariables>(UsersDocument, baseOptions);
}
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
export type UsersQueryResult = ApolloReactCommon.QueryResult<UsersQuery, UsersQueryVariables>;

View File

@ -1,38 +0,0 @@
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
}

View File

@ -9,6 +9,12 @@ query findTask($taskID: UUID!) {
taskGroup {
id
}
badges {
checklist {
total
complete
}
}
checklists {
id
name

View File

@ -8,8 +8,16 @@ const TASK_FRAGMENT = gql`
dueDate
complete
position
badges {
checklist {
complete
total
}
}
taskGroup {
id
name
position
}
labels {
id

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
import TASK_FRAGMENT from '../fragments/task';
const CREATE_TASK_MUTATION = gql`
mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
...TaskFields
}
}
${TASK_FRAGMENT}
`;
export default CREATE_TASK_MUTATION;

View File

@ -0,0 +1,20 @@
import gql from 'graphql-tag';
const CREATE_TASK_CHECKLIST_MUTATION = gql`
mutation createTaskChecklist($taskID: UUID!, $name: String!, $position: Float!) {
createTaskChecklist(input: { taskID: $taskID, name: $name, position: $position }) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export default CREATE_TASK_CHECKLIST_MUTATION;

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
const DELETE_TASK_CHECKLIST_MUTATION = gql`
mutation deleteTaskChecklist($taskChecklistID: UUID!) {
deleteTaskChecklist(input: { taskChecklistID: $taskChecklistID }) {
ok
taskChecklist {
id
}
}
}
`;
export default DELETE_TASK_CHECKLIST_MUTATION;

View File

@ -4,10 +4,7 @@ const SET_TASK_CHECKLIST_ITEM_COMPLETE = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: { taskChecklistItemID: $taskChecklistItemID, complete: $complete }) {
id
name
taskChecklistID
complete
position
}
}
`;

View File

@ -0,0 +1,19 @@
import gql from 'graphql-tag';
const UPDATE_TASK_CHECKLIST_NAME_MUTATION = gql`
mutation updateTaskChecklistName($taskChecklistID: UUID!, $name: String!) {
updateTaskChecklistName(input: { taskChecklistID: $taskChecklistID, name: $name }) {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
}
`;
export default UPDATE_TASK_CHECKLIST_NAME_MUTATION;

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
export const DELETE_TEAM_MUTATION = gql`
mutation deleteTeam($teamID: UUID!) {
deleteTeam(input: { teamID: $teamID }) {
ok
team {
id
}
}
}
`;
export default DELETE_TEAM_MUTATION;

View File

@ -0,0 +1,21 @@
import gql from 'graphql-tag';
export const GET_TEAM_QUERY = gql`
query getTeam($teamID: UUID!) {
findTeam(input: { teamID: $teamID }) {
id
createdAt
name
}
projects(input: { teamID: $teamID }) {
id
name
team {
id
name
}
}
}
`;
export default GET_TEAM_QUERY;

View File

@ -0,0 +1,28 @@
import gql from 'graphql-tag';
export const CREATE_USER_MUTATION = gql`
mutation createUserAccount(
$username: String!
$email: String!
$fullName: String!
$initials: String!
$password: String!
) {
createUserAccount(
input: { username: $username, email: $email, fullName: $fullName, initials: $initials, password: $password }
) {
id
email
fullName
initials
username
profileIcon {
url
initials
bgColor
}
}
}
`;
export default CREATE_USER_MUTATION;

View File

@ -0,0 +1,14 @@
query users {
users {
id
email
fullName
username
profileIcon {
url
initials
bgColor
}
}
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const BarChart: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z" />
</Icon>
);
};
export default BarChart;

View File

@ -1,29 +1,15 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Citadel = ({ size, color }: Props) => {
const Citadel: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 12.7 12.7">
<Icon width={width} height={height} className={className} viewBox="0 0 12.7 12.7">
<g transform="translate(-.26 -24.137) scale(.1249)">
<path
d="M50.886 286.515l-40.4-44.46 44.459-40.401 40.401 44.46z"
fill="none"
stroke={color}
strokeWidth="11.90597031"
/>
<circle cx="52.917" cy="244.083" r="11.025" fill={color} />
<path d="M50.886 286.515l-40.4-44.46 44.459-40.401 40.401 44.46z" fill="none" strokeWidth="11.90597031" />
<circle cx="52.917" cy="244.083" r="11.025" />
</g>
</svg>
</Icon>
);
};
Citadel.defaultProps = {
size: 16,
color: '#7367f0',
};
export default Citadel;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Filter: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M487.976 0H24.028C2.71 0-8.047 25.866 7.058 40.971L192 225.941V432c0 7.831 3.821 15.17 10.237 19.662l80 55.98C298.02 518.69 320 507.493 320 487.98V225.941l184.947-184.97C520.021 25.896 509.338 0 487.976 0z" />
</Icon>
);
};
export default Filter;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Home = ({ size, color }: Props) => {
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<path d="M16 9.226l-8-6.21-8 6.21v-2.532l8-6.21 8 6.21zM14 9v6h-4v-4h-4v4h-4v-6l6-4.5z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 576 512">
<path d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z" />
</Icon>
);
};
Home.defaultProps = {
size: 16,
color: '#000',
};
export default Home;
export default Checkmark;

View File

@ -18,6 +18,7 @@ type Props = {
const Svg = styled.svg`
fill: rgba(${props => props.theme.colors.text.primary});
stroke: rgba(${props => props.theme.colors.text.primary});
`;
const Icon: React.FC<Props> = ({ width, height, viewBox, className, onClick, children }) => {

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Lock = ({ size, color }: Props) => {
const Lock: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<path d="M9.25 7h-0.25v-3c0-1.654-1.346-3-3-3h-2c-1.654 0-3 1.346-3 3v3h-0.25c-0.412 0-0.75 0.338-0.75 0.75v7.5c0 0.412 0.338 0.75 0.75 0.75h8.5c0.412 0 0.75-0.338 0.75-0.75v-7.5c0-0.412-0.338-0.75-0.75-0.75zM3 4c0-0.551 0.449-1 1-1h2c0.551 0 1 0.449 1 1v3h-4v-3z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 448 512">
<path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z" />
</Icon>
);
};
Lock.defaultProps = {
size: 16,
color: '#000',
};
export default Lock;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Pencil = ({ size, color }: Props) => {
const Sort: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill={color} width={size} height={size} viewBox="0 0 16 16">
<path d="M13.5 0c1.381 0 2.5 1.119 2.5 2.5 0 0.563-0.186 1.082-0.5 1.5l-1 1-3.5-3.5 1-1c0.418-0.314 0.937-0.5 1.5-0.5zM1 11.5l-1 4.5 4.5-1 9.25-9.25-3.5-3.5-9.25 9.25zM11.181 5.681l-7 7-0.862-0.862 7-7 0.862 0.862z" />
</svg>
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z" />
</Icon>
);
};
Pencil.defaultProps = {
size: 16,
color: '#000',
};
export default Pencil;
export default Sort;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Sort: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 320 512">
<path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z" />
</Icon>
);
};
export default Sort;

View File

@ -1,5 +1,8 @@
import Cross from './Cross';
import Cog from './Cog';
import Sort from './Sort';
import Filter from './Filter';
import BarChart from './BarChart';
import Trash from './Trash';
import CheckCircle from './CheckCircle';
import Clock from './Clock';
@ -58,6 +61,9 @@ export {
Clock,
CheckSquareOutline,
CheckSquare,
BarChart,
Square,
Filter,
Sort,
ToggleOn,
};

View File

@ -2,6 +2,21 @@
# yarn lockfile v1
"@apollo/client@^3.0.0-rc.8":
version "3.0.0-rc.8"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.0.0-rc.8.tgz#70643547ed8bb10d1d96f64320c3510a57f0cf6a"
integrity sha512-8Sw4Htao2mZb4vERbvMj9GOf4x/+CLYNuF5gULY7TtMWG4ZANLxbe1DeFCicxVo2xRVQ960nf5vVJhxKcjDOQA==
dependencies:
"@types/zen-observable" "^0.8.0"
"@wry/equality" "^0.1.9"
fast-json-stable-stringify "^2.0.0"
graphql-tag "^2.10.2"
optimism "^0.12.1"
symbol-observable "^1.2.0"
ts-invariant "^0.4.4"
tslib "^1.10.0"
zen-observable "^0.8.14"
"@apollo/federation@0.13.2":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@apollo/federation/-/federation-0.13.2.tgz#a9f842abd1619fe5cd732c56cfbc45dab0ae784a"
@ -3637,6 +3652,13 @@
"@types/node" ">=6"
tslib "^1.9.3"
"@wry/context@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.5.2.tgz#f2a5d5ab9227343aa74c81e06533c1ef84598ec7"
integrity sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.1.2", "@wry/equality@^0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909"
@ -8349,7 +8371,7 @@ graphql-request@^1.5.0:
dependencies:
cross-fetch "2.2.2"
graphql-tag@2.10.3, graphql-tag@^2.10.1, graphql-tag@^2.10.3:
graphql-tag@2.10.3, graphql-tag@^2.10.1, graphql-tag@^2.10.2, graphql-tag@^2.10.3:
version "2.10.3"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
@ -11668,6 +11690,13 @@ optimism@^0.10.0:
dependencies:
"@wry/context" "^0.4.0"
optimism@^0.12.1:
version "0.12.1"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.12.1.tgz#933f9467b9aef0e601655adb9638f893e486ad02"
integrity sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==
dependencies:
"@wry/context" "^0.5.2"
optimize-css-assets-webpack-plugin@5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572"
@ -17236,7 +17265,7 @@ zen-observable-ts@^0.8.21:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable@^0.8.0:
zen-observable@^0.8.0, zen-observable@^0.8.14:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==