feat: projects can be set to public

This commit is contained in:
Jordan Knott
2021-04-30 22:55:37 -05:00
parent 3e72271d9b
commit 04c12e4da9
38 changed files with 1849 additions and 1186 deletions

View File

@ -41,9 +41,7 @@ const AuthorizedRoutes = () => {
const { status } = x;
const response: ValidateTokenResponse = await x.json();
const { valid, userID } = response;
if (!valid) {
history.replace(`/login`);
} else {
if (valid) {
setUser(userID);
}
setLoading(false);

View File

@ -1,17 +1,28 @@
import React from 'react';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
import React, { useState } from 'react';
import ProjectSettings, { DeleteConfirm, DELETE_INFO, PublicConfirm } from 'shared/components/ProjectSettings';
import {
useDeleteProjectMutation,
GetProjectsDocument,
useToggleProjectVisibilityMutation,
} from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
type ProjectPopupProps = {
history: any;
name: string;
publicOn: string | null;
projectID: string;
};
const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID }) => {
const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID, publicOn: initialPublicOn }) => {
const { hidePopup, setTab } = usePopup();
const [publicOn, setPublicOn] = useState(initialPublicOn);
const [toggleProjectVisibility] = useToggleProjectVisibilityMutation({
onCompleted: data => {
setPublicOn(data.toggleProjectVisibility.project.publicOn);
},
});
const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -36,11 +47,28 @@ const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID })
<>
<Popup title={null} tab={0}>
<ProjectSettings
publicOn={publicOn}
onToggleProjectVisible={visible => {
if (visible) {
setTab(2, { width: 300 });
} else {
toggleProjectVisibility({ variables: { projectID, isPublic: false } });
}
}}
onDeleteProject={() => {
setTab(1, { width: 300 });
}}
/>
</Popup>
<Popup title="Change to public?" tab={1}>
<PublicConfirm
onConfirm={() => {
if (projectID) {
toggleProjectVisibility({ variables: { projectID, isPublic: true } });
}
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteConfirm
description={DELETE_INFO.DELETE_PROJECTS.description}

View File

@ -1,5 +1,6 @@
import React from 'react';
import TopNavbar, { MenuItem } from 'shared/components/TopNavbar';
import LoggedOutNavbar from 'shared/components/TopNavbar/LoggedOut';
import { ProfileMenu } from 'shared/components/DropdownMenu';
import { useHistory } from 'react-router';
import { useCurrentUser } from 'App/context';
@ -11,6 +12,8 @@ import NotificationPopup, { NotificationItem } from 'shared/components/Notifcati
import theme from 'App/ThemeStyles';
import ProjectFinder from './ProjectFinder';
// TODO: Move to context based navbar?
type GlobalTopNavbarProps = {
nameOnly?: boolean;
projectID: string | null;
@ -30,7 +33,7 @@ type GlobalTopNavbarProps = {
onRemoveInvitedFromBoard?: (email: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab,
onSetTab,
menuType,
@ -46,9 +49,9 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onRemoveInvitedFromBoard,
onRemoveFromBoard,
}) => {
const { user, setUser } = useCurrentUser();
const { data } = useTopNavbarQuery();
const { showPopup, hidePopup } = usePopup();
const { setUser } = useCurrentUser();
const history = useHistory();
const onLogout = () => {
fetch('/auth/logout', {
@ -109,9 +112,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
}
};
if (!user) {
return null;
}
// TODO: readd permision check
// const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
const userIsTeamOrProjectAdmin = true;
@ -174,6 +174,8 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
}
};
const user = data ? data.me?.user : null;
return (
<>
<TopNavbar
@ -188,7 +190,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
);
}}
currentTab={currentTab}
user={data ? data.me.user : null}
user={user ?? null}
canEditProjectName={userIsTeamOrProjectAdmin}
canInviteUser={userIsTeamOrProjectAdmin}
onMemberProfile={onMemberProfile}
@ -215,4 +217,45 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
);
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab,
onSetTab,
menuType,
teamID,
onChangeProjectOwner,
onChangeRole,
name,
popupContent,
projectMembers,
projectInvitedMembers,
onInviteUser,
onSaveProjectName,
onRemoveInvitedFromBoard,
onRemoveFromBoard,
}) => {
const { user } = useCurrentUser();
if (user) {
return (
<LoggedInNavbar
currentTab={currentTab}
projectID={null}
onSetTab={onSetTab}
menuType={menuType}
teamID={teamID}
onChangeRole={onChangeRole}
onChangeProjectOwner={onChangeProjectOwner}
name={name}
popupContent={popupContent}
projectMembers={projectMembers}
projectInvitedMembers={projectInvitedMembers}
onInviteUser={onInviteUser}
onSaveProjectName={onSaveProjectName}
onRemoveInvitedFromBoard={onRemoveInvitedFromBoard}
onRemoveFromBoard={onRemoveFromBoard}
/>
);
}
return <LoggedOutNavbar name={name} menuType={menuType} />;
};
export default GlobalTopNavbar;

View File

@ -62,7 +62,7 @@ const Projects = () => {
}}
/>
<GlobalTopNavbar projectID={null} onSaveProjectName={NOOP} name={null} />
{!loading && data && (
{!loading && data && data.me && (
<Settings
profile={data.me.user}
onProfileAvatarChange={() => {

View File

@ -198,6 +198,7 @@ type ProjectBoardProps = {
};
export const BoardLoading = () => {
const { user } = useCurrentUser();
return (
<>
<ProjectBar>
@ -215,20 +216,22 @@ export const BoardLoading = () => {
<ProjectActionText>Filter</ProjectActionText>
</ProjectAction>
</ProjectActions>
<ProjectActions>
<ProjectAction>
<Tags width={13} height={13} />
<ProjectActionText>Labels</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<ToggleOn width={13} height={13} />
<ProjectActionText>Fields</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<Bolt width={13} height={13} />
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
{user && (
<ProjectActions>
<ProjectAction>
<Tags width={13} height={13} />
<ProjectActionText>Labels</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<ToggleOn width={13} height={13} />
<ProjectActionText>Fields</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<Bolt width={13} height={13} />
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
)}
</ProjectBar>
<EmptyBoard />
</>
@ -469,7 +472,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
}
return 'All Tasks';
};
if (data && user) {
if (data) {
labelsRef.current = data.findProject.labels;
membersRef.current = data.findProject.members;
const onQuickEditorOpen = ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => {
@ -570,34 +573,37 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
);
})}
</ProjectActions>
<ProjectActions>
<ProjectAction
onClick={$labelsRef => {
showPopup(
$labelsRef,
<LabelManagerEditor
taskLabels={null}
labelColors={data.labelColors}
labels={labelsRef}
projectID={projectID ?? ''}
/>,
);
}}
>
<Tags width={13} height={13} />
<ProjectActionText>Labels</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<ToggleOn width={13} height={13} />
<ProjectActionText>Fields</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<Bolt width={13} height={13} />
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
{user && (
<ProjectActions>
<ProjectAction
onClick={$labelsRef => {
showPopup(
$labelsRef,
<LabelManagerEditor
taskLabels={null}
labelColors={data.labelColors}
labels={labelsRef}
projectID={projectID ?? ''}
/>,
);
}}
>
<Tags width={13} height={13} />
<ProjectActionText>Labels</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<ToggleOn width={13} height={13} />
<ProjectActionText>Fields</ProjectActionText>
</ProjectAction>
<ProjectAction disabled>
<Bolt width={13} height={13} />
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
)}
</ProjectBar>
<SimpleLists
isPublic={user === null}
onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`);
}}

View File

@ -424,7 +424,7 @@ const Details: React.FC<DetailsProps> = ({
updateTaskComment({ variables: { commentID, message } });
}}
editableComment={editableComment}
me={data.me.user}
me={data.me ? data.me.user : null}
onCommentShowActions={(commentID, $targetRef) => {
showPopup(
$targetRef,

View File

@ -163,10 +163,6 @@ const Project = () => {
}
}, [data]);
if (error) {
history.push('/projects');
}
if (data) {
labelsRef.current = data.findProject.labels;
@ -204,7 +200,14 @@ const Project = () => {
/>,
);
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
popupContent={
<ProjectPopup // eslint-disable-line
history={history}
publicOn={data.findProject.publicOn}
name={data.findProject.name}
projectID={projectID}
/>
}
menuType={[{ name: 'Board', link: location.pathname }]}
currentTab={0}
projectMembers={data.findProject.members}

View File

@ -58,12 +58,14 @@ type Props = {
onCardTitleChange?: (name: string) => void;
labelVariant?: CardLabelVariant;
toggleLabels?: boolean;
isPublic?: boolean;
toggleDirection?: 'shrink' | 'expand';
};
const Card = React.forwardRef(
(
{
isPublic = false,
wrapperProps,
onContextMenu,
taskID,
@ -120,9 +122,11 @@ const Card = React.forwardRef(
}
};
const onTaskContext = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onOpenComposer();
if (!isPublic) {
e.preventDefault();
e.stopPropagation();
onOpenComposer();
}
};
const onOperationClick = (e: React.MouseEvent<HTMLOrSVGElement>) => {
e.preventDefault();
@ -145,7 +149,7 @@ const Card = React.forwardRef(
{...wrapperProps}
>
<ListCardInnerContainer ref={$innerCardRef}>
{isActive && !editable && (
{!isPublic && isActive && !editable && (
<ListCardOperation
onClick={e => {
e.stopPropagation();

View File

@ -72,6 +72,9 @@ export const HeaderName = styled(TextareaAutosize)`
box-shadow: none;
font-weight: 600;
margin: -4px 0;
&:disabled {
opacity: 1;
}
letter-spacing: normal;
word-spacing: normal;

View File

@ -24,6 +24,7 @@ type Props = {
onOpenComposer: (id: string) => void;
wrapperProps?: any;
headerProps?: any;
isPublic: boolean;
index?: number;
onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void;
};
@ -37,6 +38,7 @@ const List = React.forwardRef(
isComposerOpen,
onOpenComposer,
children,
isPublic,
wrapperProps,
headerProps,
onExtraMenuOpen,
@ -86,39 +88,37 @@ const List = React.forwardRef(
<Container ref={$wrapperRef} {...wrapperProps}>
<Wrapper>
<Header {...headerProps} isEditing={isEditingTitle}>
<HeaderEditTarget onClick={onClick} isHidden={isEditingTitle} />
{!isPublic && <HeaderEditTarget onClick={onClick} isHidden={isEditingTitle} />}
<HeaderName
ref={$listNameRef}
disabled={isPublic}
onBlur={onBlur}
onChange={onChange}
onKeyDown={onKeyDown}
spellCheck={false}
value={listName}
/>
<ListExtraMenuButtonWrapper ref={$extraActionsRef} onClick={handleExtraMenuOpen}>
<Ellipsis size={16} color="#c2c6dc" />
</ListExtraMenuButtonWrapper>
{!isPublic && (
<ListExtraMenuButtonWrapper ref={$extraActionsRef} onClick={handleExtraMenuOpen}>
<Ellipsis size={16} color="#c2c6dc" />
</ListExtraMenuButtonWrapper>
)}
</Header>
{children && children}
<AddCardContainer hidden={isComposerOpen}>
<AddCardButton onClick={() => onOpenComposer(id)}>
<Plus width={12} height={12} />
<AddCardButtonText>Add another card</AddCardButtonText>
</AddCardButton>
</AddCardContainer>
{!isPublic && (
<AddCardContainer hidden={isComposerOpen}>
<AddCardButton onClick={() => onOpenComposer(id)}>
<Plus width={12} height={12} />
<AddCardButtonText>Add another card</AddCardButtonText>
</AddCardButton>
</AddCardContainer>
)}
</Wrapper>
</Container>
);
},
);
List.defaultProps = {
children: null,
isComposerOpen: false,
wrapperProps: {},
headerProps: {},
};
List.displayName = 'List';
export default List;

View File

@ -151,6 +151,7 @@ interface SimpleProps {
onCardMemberClick: OnCardMemberClick;
onCardLabelClick: () => void;
cardLabelVariant: CardLabelVariant;
isPublic?: boolean;
taskStatusFilter?: TaskStatusFilter;
taskMetaFilters?: TaskMetaFilters;
taskSorting?: TaskSorting;
@ -188,6 +189,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onExtraMenuOpen,
onCardMemberClick,
taskStatusFilter = initTaskStatusFilter,
isPublic = false,
taskMetaFilters = initTaskMetaFilters,
taskSorting = initTaskSorting,
}) => {
@ -300,6 +302,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
isPublic={isPublic}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
@ -328,6 +331,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
<Card
toggleDirection={toggleDirection}
toggleLabels={toggleLabels}
isPublic={isPublic}
labelVariant={cardLabelVariant}
wrapperProps={{
...taskProvided.draggableProps,
@ -396,11 +400,13 @@ const SimpleLists: React.FC<SimpleProps> = ({
)}
</Droppable>
</DragDropContext>
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
{!isPublic && (
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
)}
</BoardWrapper>
</BoardContainer>
);

View File

@ -38,12 +38,17 @@ export const ListSeparator = styled.hr`
`;
type Props = {
publicOn: null | string;
onDeleteProject: () => void;
onToggleProjectVisible: (visible: boolean) => void;
};
const ProjectSettings: React.FC<Props> = ({ onDeleteProject }) => {
const ProjectSettings: React.FC<Props> = ({ publicOn, onDeleteProject, onToggleProjectVisible }) => {
return (
<>
<ListActionsWrapper>
<ListActionItemWrapper onClick={() => onToggleProjectVisible(publicOn === null)}>
<ListActionItem>{`Make ${publicOn === null ? 'public' : 'private'}`}</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper onClick={() => onDeleteProject()}>
<ListActionItem>Delete Project</ListActionItem>
</ListActionItemWrapper>
@ -127,5 +132,18 @@ const DeleteConfirm: React.FC<DeleteConfirmProps> = ({ description, deletedItems
);
};
export { DeleteConfirm };
type PublicConfirmProps = {
onConfirm: () => void;
};
const PublicConfirm: React.FC<PublicConfirmProps> = ({ onConfirm }) => {
return (
<ConfirmWrapper>
<ConfirmDescription>Public projects can be accessed by anyone with a link to the project.</ConfirmDescription>
<ConfirmDeleteButton onClick={() => onConfirm()}>Make public</ConfirmDeleteButton>
</ConfirmWrapper>
);
};
export { DeleteConfirm, PublicConfirm };
export default ProjectSettings;

View File

@ -69,7 +69,7 @@ const CommentCreator: React.FC<CommentCreatorProps> = ({
)}
<CommentEditorContainer>
<CommentTextArea
showCommentActions={showCommentActions}
$showCommentActions={showCommentActions}
placeholder="Write a comment..."
ref={$comment}
disabled={disabled}

View File

@ -80,40 +80,44 @@ import {
ActivityItemHeaderTitleName,
ActivityItemComment,
} from './Styles';
import { useCurrentUser } from 'App/context';
type TaskDetailsProps = {};
const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
const { user } = useCurrentUser();
return (
<Container>
<LeftSidebar>
<LeftSidebarContent>
<LeftSidebarSection>
<SidebarTitle>TASK GROUP</SidebarTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
<DueDateTitle>DUE DATE</DueDateTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
</LeftSidebarSection>
<AssignedUsersSection>
<DueDateTitle>MEMBERS</DueDateTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
</AssignedUsersSection>
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton disabled icon={<Tags width={12} height={12} />}>
Labels
</ActionButton>
<ActionButton disabled icon={<CheckSquareOutline width={12} height={12} />}>
Checklist
</ActionButton>
<ActionButton disabled>Cover</ActionButton>
</ExtraActionsSection>
{user && (
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton disabled icon={<Tags width={12} height={12} />}>
Labels
</ActionButton>
<ActionButton disabled icon={<CheckSquareOutline width={12} height={12} />}>
Checklist
</ActionButton>
<ActionButton disabled>Cover</ActionButton>
</ExtraActionsSection>
)}
</LeftSidebarContent>
</LeftSidebar>
<ContentContainer>
@ -125,23 +129,25 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
<span>Mark complete</span>
</MarkCompleteButton>
</HeaderLeft>
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Clone width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Share width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
{user && (
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Clone width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Share width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
)}
</HeaderInnerContainer>
<TaskDetailsTitleWrapper loading>
<TaskDetailsTitle value="" disabled loading />
<TaskDetailsTitleWrapper $loading>
<TaskDetailsTitle value="" disabled $loading />
</TaskDetailsTitleWrapper>
</HeaderContainer>
<InnerContentContainer>
@ -151,9 +157,11 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
</TabBarSection>
<ActivitySection />
</InnerContentContainer>
<CommentContainer>
<CommentCreator disabled onCreateComment={() => null} onMemberProfile={() => null} />
</CommentContainer>
{user && (
<CommentContainer>
<CommentCreator disabled onCreateComment={() => null} onMemberProfile={() => null} />
</CommentContainer>
)}
</ContentContainer>
</Container>
);

View File

@ -108,7 +108,7 @@ export const skeletonKeyframes = keyframes`
}
`;
export const SidebarButton = styled.div<{ loading?: boolean }>`
export const SidebarButton = styled.div<{ $loading?: boolean }>`
font-size: 14px;
color: ${props => props.theme.colors.text.primary};
min-height: 32px;
@ -116,7 +116,7 @@ export const SidebarButton = styled.div<{ loading?: boolean }>`
border-radius: 6px;
${props =>
props.loading
props.$loading
? css`
background: ${props.theme.colors.bg.primary};
`
@ -178,15 +178,15 @@ export const HeaderLeft = styled.div`
justify-content: flex-start;
`;
export const TaskDetailsTitleWrapper = styled.div<{ loading?: boolean }>`
export const TaskDetailsTitleWrapper = styled.div<{ $loading?: boolean }>`
width: 100%;
margin: 8px 0 4px 0;
display: flex;
border-radius: 6px;
${props => props.loading && `background: ${props.theme.colors.bg.primary};`}
${props => props.$loading && `background: ${props.theme.colors.bg.primary};`}
`;
export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
export const TaskDetailsTitle = styled(TextareaAutosize)<{ $loading?: boolean }>`
padding: 9px 8px 7px 8px;
border-color: transparent;
border-radius: 6px;
@ -198,8 +198,11 @@ export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
font-weight: 700;
background: none;
&:disabled {
opacity: 1;
}
${props =>
props.loading
props.$loading
? css`
background-image: linear-gradient(90deg, ${defaultBaseColor}, ${defaultHighlightColor}, ${defaultBaseColor});
background-size: 200px 100%;
@ -207,7 +210,7 @@ export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
animation: ${skeletonKeyframes} 1.2s ease-in-out infinite;
`
: css`
&:hover {
&:not(:disabled):hover {
border-color: #414561;
border-width: 1px;
border-style: solid;
@ -534,7 +537,7 @@ export const CommentProfile = styled(TaskAssignee)`
align-items: normal;
`;
export const CommentTextArea = styled(TextareaAutosize)<{ showCommentActions: boolean }>`
export const CommentTextArea = styled(TextareaAutosize)<{ $showCommentActions: boolean }>`
width: 100%;
line-height: 28px;
padding: 4px 6px;
@ -546,7 +549,7 @@ export const CommentTextArea = styled(TextareaAutosize)<{ showCommentActions: bo
min-height: 36px;
max-height: 36px;
${props =>
props.showCommentActions
props.$showCommentActions
? css`
min-height: 80px;
max-height: none;

View File

@ -83,6 +83,7 @@ import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
import onDragEnd from './onDragEnd';
import { plugin as em } from './remark';
import ActivityMessage from './ActivityMessage';
import { useCurrentUser } from 'App/context';
const parseEmojis = (value: string) => {
const emojisArray = toArray(value);
@ -277,6 +278,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onToggleChecklistItem,
onMemberProfile,
}) => {
const { user } = useCurrentUser();
const [taskName, setTaskName] = useState(task.name);
const [editTaskDescription, setEditTaskDescription] = useState(() => {
if (task.description) {
@ -338,7 +340,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<SidebarButton
ref={$dueDateBtn}
onClick={() => {
onOpenDueDatePopop(task, $dueDateBtn);
if (user) {
onOpenDueDatePopop(task, $dueDateBtn);
}
}}
>
{task.dueDate ? (
@ -360,14 +364,18 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
member={m}
size={32}
onMemberProfile={$target => {
onMemberProfile($target, m.id);
if (user) {
onMemberProfile($target, m.id);
}
}}
/>
))}
<AssignUserIcon
ref={$addMemberBtn}
onClick={() => {
onOpenAddMemberPopup(task, $addMemberBtn);
if (user) {
onOpenAddMemberPopup(task, $addMemberBtn);
}
}}
>
<Plus width={16} height={16} />
@ -377,7 +385,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<AssignUsersButton
ref={$noMemberBtn}
onClick={() => {
onOpenAddMemberPopup(task, $noMemberBtn);
if (user) {
onOpenAddMemberPopup(task, $noMemberBtn);
}
}}
>
<AssignUserIcon>
@ -387,26 +397,28 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</AssignUsersButton>
)}
</AssignedUsersSection>
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton
onClick={$target => {
onOpenAddLabelPopup(task, $target);
}}
icon={<Tags width={12} height={12} />}
>
Labels
</ActionButton>
<ActionButton
onClick={$target => {
onOpenAddChecklistPopup(task, $target);
}}
icon={<CheckSquareOutline width={12} height={12} />}
>
Checklist
</ActionButton>
<ActionButton>Cover</ActionButton>
</ExtraActionsSection>
{user && (
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton
onClick={$target => {
onOpenAddLabelPopup(task, $target);
}}
icon={<Tags width={12} height={12} />}
>
Labels
</ActionButton>
<ActionButton
onClick={$target => {
onOpenAddChecklistPopup(task, $target);
}}
icon={<CheckSquareOutline width={12} height={12} />}
>
Checklist
</ActionButton>
<ActionButton>Cover</ActionButton>
</ExtraActionsSection>
)}
</LeftSidebarContent>
</LeftSidebar>
<ContentContainer>
@ -414,34 +426,40 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<HeaderInnerContainer>
<HeaderLeft>
<MarkCompleteButton
disabled={user === null}
invert={task.complete ?? false}
onClick={() => {
onToggleTaskComplete(task);
if (user) {
onToggleTaskComplete(task);
}
}}
>
<Checkmark width={8} height={8} />
<span>{task.complete ? 'Completed' : 'Mark complete'}</span>
</MarkCompleteButton>
</HeaderLeft>
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Clone width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Share width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon onClick={() => onDeleteTask(task)}>
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
{user && (
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Clone width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon>
<Share width={16} height={16} />
</HeaderActionIcon>
<HeaderActionIcon onClick={() => onDeleteTask(task)}>
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
)}
</HeaderInnerContainer>
<TaskDetailsTitleWrapper>
<TaskDetailsTitle
value={taskName}
ref={$detailsTitle}
disabled={user === null}
onKeyDown={e => {
if (e.keyCode === 13) {
e.preventDefault();
@ -496,7 +514,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<Editor
defaultValue={task.description ?? ''}
theme={dark}
readOnly={!editTaskDescription}
readOnly={user === null || !editTaskDescription}
autoFocus
onChange={value => {
setSaveTimeout(() => {
@ -612,15 +630,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
)}
</ActivitySection>
</InnerContentContainer>
<CommentContainer>
{me && (
{me && (
<CommentContainer>
<CommentCreator
me={me}
onCreateComment={message => onCreateComment(task, message)}
onMemberProfile={onMemberProfile}
/>
)}
</CommentContainer>
</CommentContainer>
)}
</ContentContainer>
</Container>
);

View File

@ -0,0 +1,63 @@
import React, { useRef, useState, useEffect } from 'react';
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle, ListUnordered } from 'shared/icons';
import { RoleCode } from 'shared/generated/graphql';
import * as S from './Styles';
import { Link } from 'react-router-dom';
export type MenuItem = {
name: string;
link: string;
};
type NavBarProps = {
menuType?: Array<MenuItem> | null;
name: string | null;
};
const NavBar: React.FC<NavBarProps> = ({ menuType, name }) => {
return (
<S.NavbarWrapper>
<S.NavbarHeader>
<S.ProjectActions>
<S.ProjectSwitch>
<S.ProjectSwitchInner>
<S.TaskcafeLogo innerColor="#9f46e4" outerColor="#000" width={32} height={32} />
</S.ProjectSwitchInner>
</S.ProjectSwitch>
<S.ProjectInfo>
<S.ProjectMeta>{name && <S.ProjectName>{name}</S.ProjectName>}</S.ProjectMeta>
{name && (
<S.ProjectTabs>
{menuType &&
menuType.map((menu, idx) => {
return (
<S.ProjectTab
key={menu.name}
to={menu.link}
exact
onClick={() => {
// TODO
}}
>
{menu.name}
</S.ProjectTab>
);
})}
</S.ProjectTabs>
)}
</S.ProjectInfo>
</S.ProjectActions>
<S.LogoContainer to="/">
<S.TaskcafeTitle>Taskcafé</S.TaskcafeTitle>
</S.LogoContainer>
<S.GlobalActions>
<Link to="/login">
<S.SignIn>Sign In</S.SignIn>
</Link>
</S.GlobalActions>
</S.NavbarHeader>
</S.NavbarWrapper>
);
};
export default NavBar;

View File

@ -297,6 +297,16 @@ export const ProjectFinder = styled(Button)`
padding: 6px 12px;
`;
export const SignUp = styled(Button)`
margin-right: 8px;
padding: 6px 12px;
`;
export const SignIn = styled(Button)`
margin-right: 20px;
padding: 6px 12px;
`;
export const NavSeparator = styled.div`
width: 1px;
background: ${props => props.theme.colors.border};

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ const FIND_PROJECT_QUERY = gql`
query findProject($projectID: UUID!) {
findProject(input: { projectID: $projectID }) {
name
publicOn
team {
id
}

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
export const DELETE_PROJECT_MUTATION = gql`
mutation toggleProjectVisibility($projectID: UUID!, $isPublic: Boolean!) {
toggleProjectVisibility(input: { projectID: $projectID, isPublic: $isPublic }) {
project {
id
publicOn
}
}
}
`;
export default DELETE_PROJECT_MUTATION;