feat: projects can be set to public
This commit is contained in:
@ -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);
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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={() => {
|
||||
|
@ -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}`);
|
||||
}}
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -69,7 +69,7 @@ const CommentCreator: React.FC<CommentCreatorProps> = ({
|
||||
)}
|
||||
<CommentEditorContainer>
|
||||
<CommentTextArea
|
||||
showCommentActions={showCommentActions}
|
||||
$showCommentActions={showCommentActions}
|
||||
placeholder="Write a comment..."
|
||||
ref={$comment}
|
||||
disabled={disabled}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
63
frontend/src/shared/components/TopNavbar/LoggedOut.tsx
Normal file
63
frontend/src/shared/components/TopNavbar/LoggedOut.tsx
Normal 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;
|
@ -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
@ -5,6 +5,7 @@ const FIND_PROJECT_QUERY = gql`
|
||||
query findProject($projectID: UUID!) {
|
||||
findProject(input: { projectID: $projectID }) {
|
||||
name
|
||||
publicOn
|
||||
team {
|
||||
id
|
||||
}
|
||||
|
14
frontend/src/shared/graphql/toggleProjectVisibility.ts
Normal file
14
frontend/src/shared/graphql/toggleProjectVisibility.ts
Normal 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;
|
Reference in New Issue
Block a user