forked from github/plane
[WEB-1134] fix: module link create and delete mutation (#4373)
* fix: module link create and delete mutation * chore: module link mutation store updates * chore: code refactor --------- Co-authored-by: gurusainath <gurusainath007@gmail.com>
This commit is contained in:
parent
06a664f6b9
commit
5ef51edad7
@ -7,107 +7,111 @@ import { Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember } from "@/hooks/store";
|
import { useMember, useModule } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// types
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
links: ILinkDetails[];
|
moduleId: string;
|
||||||
|
|
||||||
handleDeleteLink: (linkId: string) => void;
|
handleDeleteLink: (linkId: string) => void;
|
||||||
handleEditLink: (link: ILinkDetails) => void;
|
handleEditLink: (link: ILinkDetails) => void;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LinksList: React.FC<Props> = observer(
|
export const LinksList: React.FC<Props> = observer((props) => {
|
||||||
({ links, handleDeleteLink, handleEditLink, userAuth, disabled }) => {
|
const { moduleId, handleDeleteLink, handleEditLink, userAuth, disabled } = props;
|
||||||
const { getUserDetails } = useMember();
|
// hooks
|
||||||
const { isMobile } = usePlatformOS();
|
const { getUserDetails } = useMember();
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
const { isMobile } = usePlatformOS();
|
||||||
|
const { getModuleById } = useModule();
|
||||||
|
// derived values
|
||||||
|
const currentModule = getModuleById(moduleId);
|
||||||
|
const moduleLinks = currentModule?.link_module || undefined;
|
||||||
|
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disabled;
|
||||||
|
|
||||||
const copyToClipboard = (text: string) => {
|
const copyToClipboard = (text: string) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Copied to clipboard",
|
title: "Copied to clipboard",
|
||||||
message: "The URL has been successfully copied to your clipboard",
|
message: "The URL has been successfully copied to your clipboard",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
if (!moduleLinks) return <></>;
|
||||||
<>
|
return (
|
||||||
{links.map((link) => {
|
<>
|
||||||
const createdByDetails = getUserDetails(link.created_by);
|
{moduleLinks.map((link) => {
|
||||||
return (
|
const createdByDetails = getUserDetails(link.created_by);
|
||||||
<div key={link.id} className="relative flex flex-col rounded-md bg-custom-background-90 p-2.5">
|
return (
|
||||||
<div className="flex w-full items-start justify-between gap-2">
|
<div key={link.id} className="relative flex flex-col rounded-md bg-custom-background-90 p-2.5">
|
||||||
<div className="flex items-start gap-2 truncate">
|
<div className="flex w-full items-start justify-between gap-2">
|
||||||
<span className="py-1">
|
<div className="flex items-start gap-2 truncate">
|
||||||
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
<span className="py-1">
|
||||||
|
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
||||||
|
</span>
|
||||||
|
<Tooltip tooltipContent={link.title && link.title !== "" ? link.title : link.url} isMobile={isMobile}>
|
||||||
|
<span
|
||||||
|
className="cursor-pointer truncate text-xs"
|
||||||
|
onClick={() => copyToClipboard(link.title && link.title !== "" ? link.title : link.url)}
|
||||||
|
>
|
||||||
|
{link.title && link.title !== "" ? link.title : link.url}
|
||||||
</span>
|
</span>
|
||||||
<Tooltip tooltipContent={link.title && link.title !== "" ? link.title : link.url} isMobile={isMobile}>
|
</Tooltip>
|
||||||
<span
|
</div>
|
||||||
className="cursor-pointer truncate text-xs"
|
|
||||||
onClick={() => copyToClipboard(link.title && link.title !== "" ? link.title : link.url)}
|
|
||||||
>
|
|
||||||
{link.title && link.title !== "" ? link.title : link.url}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isNotAllowed && (
|
{!isNotAllowed && (
|
||||||
<div className="z-[1] flex flex-shrink-0 items-center gap-2">
|
<div className="z-[1] flex flex-shrink-0 items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleEditLink(link);
|
handleEditLink(link);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
<Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
<ExternalLink className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDeleteLink(link.id);
|
handleDeleteLink(link.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className="px-5">
|
|
||||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
|
||||||
Added {calculateTimeAgo(link.created_at)}
|
|
||||||
<br />
|
|
||||||
{createdByDetails && (
|
|
||||||
<>
|
|
||||||
by{" "}
|
|
||||||
{createdByDetails?.is_bot
|
|
||||||
? createdByDetails?.first_name + " Bot"
|
|
||||||
: createdByDetails?.display_name}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="px-5">
|
||||||
})}
|
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||||
</>
|
Added {calculateTimeAgo(link.created_at)}
|
||||||
);
|
<br />
|
||||||
}
|
{createdByDetails && (
|
||||||
);
|
<>
|
||||||
|
by{" "}
|
||||||
|
{createdByDetails?.is_bot ? createdByDetails?.first_name + " Bot" : createdByDetails?.display_name}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -673,18 +673,20 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LinksList
|
{moduleId && (
|
||||||
links={moduleDetails.link_module}
|
<LinksList
|
||||||
handleEditLink={handleEditLink}
|
moduleId={moduleId}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleEditLink={handleEditLink}
|
||||||
userAuth={{
|
handleDeleteLink={handleDeleteLink}
|
||||||
isGuest: currentProjectRole === EUserProjectRoles.GUEST,
|
userAuth={{
|
||||||
isViewer: currentProjectRole === EUserProjectRoles.VIEWER,
|
isGuest: currentProjectRole === EUserProjectRoles.GUEST,
|
||||||
isMember: currentProjectRole === EUserProjectRoles.MEMBER,
|
isViewer: currentProjectRole === EUserProjectRoles.VIEWER,
|
||||||
isOwner: currentProjectRole === EUserProjectRoles.ADMIN,
|
isMember: currentProjectRole === EUserProjectRoles.MEMBER,
|
||||||
}}
|
isOwner: currentProjectRole === EUserProjectRoles.ADMIN,
|
||||||
disabled={isArchived}
|
}}
|
||||||
/>
|
disabled={isArchived}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import concat from "lodash/concat";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
import sortBy from "lodash/sortBy";
|
import sortBy from "lodash/sortBy";
|
||||||
import update from "lodash/update";
|
import update from "lodash/update";
|
||||||
@ -372,13 +373,22 @@ export class ModulesStore implements IModuleStore {
|
|||||||
* @param data
|
* @param data
|
||||||
* @returns ILinkDetails
|
* @returns ILinkDetails
|
||||||
*/
|
*/
|
||||||
createModuleLink = async (workspaceSlug: string, projectId: string, moduleId: string, data: Partial<ILinkDetails>) =>
|
createModuleLink = async (
|
||||||
await this.moduleService.createModuleLink(workspaceSlug, projectId, moduleId, data).then((response) => {
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
data: Partial<ILinkDetails>
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const moduleLink = await this.moduleService.createModuleLink(workspaceSlug, projectId, moduleId, data);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.moduleMap, [moduleId, "link_module"], [response]);
|
update(this.moduleMap, [moduleId, "link_module"], (moduleLinks = []) => concat(moduleLinks, moduleLink));
|
||||||
});
|
});
|
||||||
return response;
|
return moduleLink;
|
||||||
});
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description updates module link details
|
* @description updates module link details
|
||||||
@ -422,14 +432,19 @@ export class ModulesStore implements IModuleStore {
|
|||||||
* @param moduleId
|
* @param moduleId
|
||||||
* @param linkId
|
* @param linkId
|
||||||
*/
|
*/
|
||||||
deleteModuleLink = async (workspaceSlug: string, projectId: string, moduleId: string, linkId: string) =>
|
deleteModuleLink = async (workspaceSlug: string, projectId: string, moduleId: string, linkId: string) => {
|
||||||
await this.moduleService.deleteModuleLink(workspaceSlug, projectId, moduleId, linkId).then(() => {
|
try {
|
||||||
const moduleDetails = this.getModuleById(moduleId);
|
const moduleLink = await this.moduleService.deleteModuleLink(workspaceSlug, projectId, moduleId, linkId);
|
||||||
const linkModules = moduleDetails?.link_module?.filter((link) => link.id !== linkId);
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.moduleMap, [moduleId, "link_module"], linkModules);
|
update(this.moduleMap, [moduleId, "link_module"], (moduleLinks = []) =>
|
||||||
|
moduleLinks.filter((link: ILinkDetails) => link.id !== linkId)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
return moduleLink;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description adds a module to favorites
|
* @description adds a module to favorites
|
||||||
|
Loading…
Reference in New Issue
Block a user