fix: minor bug fixes and quality of life improvements (#3444)

* add concurrency to dev command to avaoid erroring out

* add context to issue activity

* minor quality of life improvement for exporter modal

* show the option to save draft issue only when there is content in name and description

* maintain commonality while referencing the user in activity

* fix minor changes in draft save issue modal logical condition

* minor change is state component for filter selection

* change logic for create issue activity

* change use last draft issue button to state control over previous on hover as that was inconsistent
This commit is contained in:
rahulramesha 2024-01-23 20:45:44 +05:30 committed by GitHub
parent f27efb80e1
commit 47681fe9f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 88 additions and 51 deletions

View File

@ -15,7 +15,7 @@
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"dev": "turbo run dev --concurrency=13",
"start": "turbo run start",
"lint": "turbo run lint",
"clean": "turbo run clean",
@ -34,4 +34,4 @@
"@types/react": "18.2.42"
},
"packageManager": "yarn@1.22.19"
}
}

View File

@ -27,6 +27,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
onChange,
options,
onOpen,
onClose,
optionsClassName = "",
value,
tabIndex,
@ -58,7 +59,10 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
setIsOpen(true);
if (referenceElement) referenceElement.focus();
};
const closeDropdown = () => setIsOpen(false);
const closeDropdown = () => {
setIsOpen(false);
onClose && onClose();
};
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
useOutsideClickDetector(dropdownRef, closeDropdown);

View File

@ -36,6 +36,7 @@ export interface ICustomSelectProps extends IDropdownProps {
interface CustomSearchSelectProps {
footerOption?: JSX.Element;
onChange: any;
onClose?: () => void;
options:
| {
value: any;

View File

@ -26,7 +26,7 @@ import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { IIssueActivity } from "@plane/types";
const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
@ -341,7 +341,9 @@ const activityDetails: {
if (activity.verb === "created")
return (
<>
<span className="flex-shrink-0">added this issue to the cycle </span>
<span className="flex-shrink-0">
added {showIssue ? <IssueLink activity={activity} /> : "this issue"} to the cycle{" "}
</span>
<a
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
target="_blank"
@ -388,7 +390,7 @@ const activityDetails: {
if (activity.verb === "created")
return (
<>
added this issue to the module{" "}
added {showIssue ? <IssueLink activity={activity} /> : "this issue"} to the module{" "}
<a
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`}
target="_blank"
@ -491,11 +493,11 @@ const activityDetails: {
icon: <SignalMediumIcon size={12} color="#6b7280" aria-hidden="true" />,
},
relates_to: {
message: (activity) => {
message: (activity, showIssue) => {
if (activity.old_value === "")
return (
<>
marked that this issue relates to{" "}
marked that {showIssue ? <IssueLink activity={activity} /> : "this issue"} relates to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
@ -509,11 +511,11 @@ const activityDetails: {
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
},
blocking: {
message: (activity) => {
message: (activity, showIssue) => {
if (activity.old_value === "")
return (
<>
marked this issue is blocking issue{" "}
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} is blocking issue{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
@ -527,18 +529,18 @@ const activityDetails: {
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
},
blocked_by: {
message: (activity) => {
message: (activity, showIssue) => {
if (activity.old_value === "")
return (
<>
marked this issue is being blocked by{" "}
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} is being blocked by{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
else
return (
<>
removed this issue being blocked by issue{" "}
removed {showIssue ? <IssueLink activity={activity} /> : "this issue"} being blocked by issue{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);
@ -546,18 +548,18 @@ const activityDetails: {
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
},
duplicate: {
message: (activity) => {
message: (activity, showIssue) => {
if (activity.old_value === "")
return (
<>
marked this issue as duplicate of{" "}
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} as duplicate of{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
else
return (
<>
removed this issue as a duplicate of{" "}
removed {showIssue ? <IssueLink activity={activity} /> : "this issue"} as a duplicate of{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);

View File

@ -5,7 +5,7 @@ import { History } from "lucide-react";
// hooks
import { useDashboard, useUser } from "hooks/store";
// components
import { ActivityIcon, ActivityMessage } from "components/core";
import { ActivityIcon, ActivityMessage, IssueLink } from "components/core";
import { RecentActivityEmptyState, WidgetLoader, WidgetProps } from "components/dashboard/widgets";
// ui
import { Avatar } from "@plane/ui";
@ -75,15 +75,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
<ActivityMessage activity={activity} showIssue />
) : (
<span>
created this{" "}
<a
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline"
>
Issue.
</a>
created <IssueLink activity={activity} />
</span>
)}
</p>

View File

@ -28,6 +28,7 @@ export const Exporter: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, user, provider, mutateServices } = props;
// states
const [exportLoading, setExportLoading] = useState(false);
const [isSelectOpen, setIsSelectOpen] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
@ -91,7 +92,13 @@ export const Exporter: React.FC<Props> = observer((props) => {
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Dialog
as="div"
className="relative z-20"
onClose={() => {
if (!isSelectOpen) handleClose();
}}
>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
@ -142,6 +149,8 @@ export const Exporter: React.FC<Props> = observer((props) => {
.join(", ")
: "All projects"
}
onOpen={() => setIsSelectOpen(true)}
onClose={() => setIsSelectOpen(false)}
optionsClassName="min-w-full"
multiple
/>

View File

@ -1,4 +1,5 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
@ -13,7 +14,7 @@ type Props = {
states: IState[] | undefined;
};
export const FilterState: React.FC<Props> = (props) => {
export const FilterState: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, searchQuery, states } = props;
const [itemsToRender, setItemsToRender] = useState(5);
@ -75,4 +76,4 @@ export const FilterState: React.FC<Props> = (props) => {
)}
</>
);
};
});

View File

@ -194,7 +194,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const handleFormChange = () => {
if (!onChange) return;
if (isDirty) onChange(watch());
if (isDirty && (watch("name") || watch("description_html"))) onChange(watch());
else onChange(null);
};

View File

@ -1,9 +1,12 @@
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react";
//hooks
import { useUser } from "hooks/store";
// services
import { UserService } from "services/user.service";
// components
import { ActivityMessage } from "components/core";
import { ActivityMessage, IssueLink } from "components/core";
// ui
import { ProfileEmptyState } from "components/ui";
import { Loader } from "@plane/ui";
@ -17,9 +20,11 @@ import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
// services
const userService = new UserService();
export const ProfileActivity = () => {
export const ProfileActivity = observer(() => {
const router = useRouter();
const { workspaceSlug, userId } = router.query;
// store hooks
const { currentUser } = useUser();
const { data: userProfileActivity } = useSWR(
workspaceSlug && userId ? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString()) : null,
@ -54,20 +59,14 @@ export const ProfileActivity = () => {
</div>
<div className="-mt-1 w-4/5 break-words">
<p className="text-sm text-custom-text-200">
<span className="font-medium text-custom-text-100">{activity.actor_detail.display_name} </span>
<span className="font-medium text-custom-text-100">
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "}
</span>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />
) : (
<span>
created this{" "}
<a
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline"
>
Issue.
</a>
created <IssueLink activity={activity} />
</span>
)}
</p>
@ -95,4 +94,4 @@ export const ProfileActivity = () => {
</div>
</div>
);
};
});

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { ChevronUp, PenSquare, Search } from "lucide-react";
// hooks
@ -25,10 +25,26 @@ export const WorkspaceSidebarQuickAction = observer(() => {
const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", JSON.stringify({}));
//useState control for displaying draft issue button instead of group hover
const [isDraftButtonOpen, setIsDraftButtonOpen] = useState(false);
const timeoutRef = useRef<any>();
const isSidebarCollapsed = themeStore.sidebarCollapsed;
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const onMouseEnter = () => {
//if renet before timout clear the timeout
timeoutRef?.current && clearTimeout(timeoutRef.current);
setIsDraftButtonOpen(true);
};
const onMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setIsDraftButtonOpen(false);
}, 300);
};
return (
<>
<CreateUpdateDraftIssueModal
@ -45,10 +61,12 @@ export const WorkspaceSidebarQuickAction = observer(() => {
className={`mt-4 flex w-full cursor-pointer items-center justify-between px-4 ${
isSidebarCollapsed ? "flex-col gap-1" : "gap-2"
}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{isAuthorizedUser && (
<div
className={`group relative flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2 ${
className={`relative flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2 ${
isSidebarCollapsed
? "px-2 hover:bg-custom-sidebar-background-80"
: "border-[0.5px] border-custom-border-200 px-3 shadow-custom-sidebar-shadow-2xs"
@ -80,12 +98,16 @@ export const WorkspaceSidebarQuickAction = observer(() => {
isSidebarCollapsed ? "hidden" : "block"
}`}
>
<ChevronUp className="h-4 w-4 rotate-180 transform !text-custom-sidebar-text-300 transition-transform duration-300 group-hover:rotate-0" />
<ChevronUp
className={`h-4 w-4 rotate-180 transform !text-custom-sidebar-text-300 transition-transform duration-300 ${
isDraftButtonOpen ? "rotate-0" : ""
}`}
/>
</button>
<div
className={`pointer-events-none fixed left-4 mt-0 h-10 w-[203px] pt-2 opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 ${
isSidebarCollapsed ? "top-[5.5rem]" : "top-24"
className={`fixed left-4 mt-0 h-10 w-[203px] pt-2 ${isSidebarCollapsed ? "top-[5.5rem]" : "top-24"} ${
isDraftButtonOpen ? "block" : "hidden"
}`}
>
<div className="h-full w-full">

View File

@ -1,6 +1,9 @@
import { ReactElement } from "react";
import useSWR from "swr";
import Link from "next/link";
import { observer } from "mobx-react";
//hooks
import { useUser } from "hooks/store";
// services
import { UserService } from "services/user.service";
// layouts
@ -21,8 +24,10 @@ import { NextPageWithLayout } from "lib/types";
const userService = new UserService();
const ProfileActivityPage: NextPageWithLayout = () => {
const ProfileActivityPage: NextPageWithLayout = observer(() => {
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
// store hooks
const { currentUser } = useUser();
return (
<section className="mx-auto mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
@ -158,7 +163,9 @@ const ProfileActivityPage: NextPageWithLayout = () => {
href={`/${activityItem.workspace_detail.slug}/profile/${activityItem.actor_detail.id}`}
>
<span className="text-gray font-medium">
{activityItem.actor_detail.display_name}
{currentUser?.id === activityItem.actor_detail.id
? "You"
: activityItem.actor_detail.display_name}
</span>
</Link>
)}{" "}
@ -189,7 +196,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
)}
</section>
);
};
});
ProfileActivityPage.getLayout = function getLayout(page: ReactElement) {
return <ProfileSettingsLayout>{page}</ProfileSettingsLayout>;