Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions apps/app/components/core/modals/link-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";

// react-hook-form
import { useForm } from "react-hook-form";
Expand All @@ -7,20 +7,30 @@ import { Dialog, Transition } from "@headlessui/react";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// types
import type { IIssueLink, ModuleLink } from "types";
import type { IIssueLink, linkDetails, ModuleLink } from "types";

type Props = {
isOpen: boolean;
handleClose: () => void;
onFormSubmit: (formData: IIssueLink | ModuleLink) => Promise<void>;
data?: linkDetails | null;
status: boolean;
createIssueLink: (formData: IIssueLink | ModuleLink) => Promise<void>;
updateIssueLink: (formData: IIssueLink | ModuleLink, linkId: string) => Promise<void>;
};

const defaultValues: IIssueLink | ModuleLink = {
title: "",
url: "",
};

export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }) => {
export const LinkModal: React.FC<Props> = ({
isOpen,
handleClose,
createIssueLink,
updateIssueLink,
status,
data,
}) => {
const {
register,
formState: { errors, isSubmitting },
Expand All @@ -30,11 +40,6 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
defaultValues,
});

const onSubmit = async (formData: IIssueLink | ModuleLink) => {
await onFormSubmit({ title: formData.title, url: formData.url });
onClose();
};

const onClose = () => {
handleClose();
const timeout = setTimeout(() => {
Expand All @@ -43,6 +48,27 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
}, 500);
};

const handleFormSubmit = async (formData: IIssueLink | ModuleLink) => {
if (!data) await createIssueLink({ title: formData.title, url: formData.url });
else await updateIssueLink({ title: formData.title, url: formData.url }, data.id);
onClose();
};

const handleCreateUpdatePage = async (formData: IIssueLink | ModuleLink) => {
await handleFormSubmit(formData);

reset({
...defaultValues,
});
};

useEffect(() => {
reset({
...defaultValues,
...data,
});
}, [data, reset]);

return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={onClose}>
Expand Down Expand Up @@ -70,14 +96,14 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-200 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(handleCreateUpdatePage)}>
<div>
<div className="space-y-5">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
Add Link
{status ? "Update Link" : "Add Link"}
</Dialog.Title>
<div className="mt-2 space-y-3">
<div>
Expand Down Expand Up @@ -113,7 +139,13 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Adding Link..." : "Add Link"}
{status
? isSubmitting
? "Updating Link..."
: "Update Link"
: isSubmitting
? "Adding Link..."
: "Add Link"}
</PrimaryButton>
</div>
</form>
Expand Down
28 changes: 17 additions & 11 deletions apps/app/components/core/sidebar/links-list.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
// icons
import { ArrowTopRightOnSquareIcon, LinkIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Icon } from "components/ui";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
import { IUserLite, UserAuth } from "types";
import { linkDetails, UserAuth } from "types";

type Props = {
links: {
id: string;
created_at: Date;
created_by: string;
created_by_detail: IUserLite;
metadata: any;
title: string;
url: string;
}[];
links: linkDetails[];
handleDeleteLink: (linkId: string) => void;
handleEditLink: (link: linkDetails) => void;
userAuth: UserAuth;
};

export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }) => {
export const LinksList: React.FC<Props> = ({
links,
handleDeleteLink,
handleEditLink,
userAuth,
}) => {
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;

return (
Expand All @@ -28,6 +27,13 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
<div key={link.id} className="relative">
{!isNotAllowed && (
<div className="absolute top-1.5 right-1.5 z-[1] flex items-center gap-1">
<button
type="button"
className="grid h-7 w-7 place-items-center rounded bg-custom-background-90 p-1 outline-none hover:bg-custom-background-80"
onClick={() => handleEditLink(link)}
>
<Icon iconName="edit" className="text-custom-text-200" />
</button>
<a
href={link.url}
target="_blank"
Expand Down
56 changes: 53 additions & 3 deletions apps/app/components/issues/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { LinkIcon, CalendarDaysIcon, TrashIcon, PlusIcon } from "@heroicons/reac
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import type { ICycle, IIssue, IIssueLink, IModule } from "types";
import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types";
// fetch-keys
import { ISSUE_DETAILS } from "constants/fetch-keys";

Expand Down Expand Up @@ -77,6 +77,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
}) => {
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);

const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
Expand Down Expand Up @@ -156,6 +157,43 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
});
};

const handleUpdateLink = async (formData: IIssueLink, linkId: string) => {
if (!workspaceSlug || !projectId || !issueDetail) return;

const payload = { metadata: {}, ...formData };

const updatedLinks = issueDetail.issue_link.map((l) =>
l.id === linkId
? {
...l,
title: formData.title,
url: formData.url,
}
: l
);

mutate<IIssue>(
ISSUE_DETAILS(issueDetail.id),
(prevData) => ({ ...(prevData as IIssue), issue_link: updatedLinks }),
false
);

await issuesService
.updateIssueLink(
workspaceSlug as string,
projectId as string,
issueDetail.id,
linkId,
payload
)
.then((res) => {
mutate(ISSUE_DETAILS(issueDetail.id));
})
.catch((err) => {
console.log(err);
});
};

const handleDeleteLink = async (linkId: string) => {
if (!workspaceSlug || !projectId || !issueDetail) return;

Expand Down Expand Up @@ -220,14 +258,25 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
const maxDate = targetDate ? new Date(targetDate) : null;
maxDate?.setDate(maxDate.getDate());

const handleEditLink = (link: linkDetails) => {
setSelectedLinkToUpdate(link);
setLinkModal(true);
};

const isNotAllowed = memberRole.isGuest || memberRole.isViewer;

return (
<>
<LinkModal
isOpen={linkModal}
handleClose={() => setLinkModal(false)}
onFormSubmit={handleCreateLink}
handleClose={() => {
setLinkModal(false);
setSelectedLinkToUpdate(null);
}}
data={selectedLinkToUpdate}
status={selectedLinkToUpdate ? true : false}
createIssueLink={handleCreateLink}
updateIssueLink={handleUpdateLink}
/>
<DeleteIssueModal
handleClose={() => setDeleteIssueModal(false)}
Expand Down Expand Up @@ -488,6 +537,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<LinksList
links={issueDetail.issue_link}
handleDeleteLink={handleDeleteLink}
handleEditLink={handleEditLink}
userAuth={memberRole}
/>
) : null}
Expand Down
52 changes: 48 additions & 4 deletions apps/app/components/modules/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { LinkIcon } from "@heroicons/react/20/solid";
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
// types
import { ICurrentUserResponse, IIssue, IModule, ModuleLink } from "types";
import { ICurrentUserResponse, IIssue, linkDetails, IModule, ModuleLink } from "types";
// fetch-keys
import { MODULE_DETAILS } from "constants/fetch-keys";
// constant
Expand All @@ -61,6 +61,7 @@ type Props = {
export const ModuleDetailsSidebar: React.FC<Props> = ({ module, isOpen, moduleIssues, user }) => {
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
const [moduleLinkModal, setModuleLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);

const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
Expand Down Expand Up @@ -115,6 +116,37 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({ module, isOpen, moduleIs
});
};

const handleUpdateLink = async (formData: ModuleLink, linkId: string) => {
if (!workspaceSlug || !projectId || !module) return;

const payload = { metadata: {}, ...formData };

const updatedLinks = module.link_module.map((l) =>
l.id === linkId
? {
...l,
title: formData.title,
url: formData.url,
}
: l
);

mutate<IModule>(
MODULE_DETAILS(module.id),
(prevData) => ({ ...(prevData as IModule), link_module: updatedLinks }),
false
);

await modulesService
.updateModuleLink(workspaceSlug as string, projectId as string, module.id, linkId, payload)
.then((res) => {
mutate(MODULE_DETAILS(module.id));
})
.catch((err) => {
console.log(err);
});
};

const handleDeleteLink = async (linkId: string) => {
if (!workspaceSlug || !projectId || !module) return;

Expand Down Expand Up @@ -170,12 +202,23 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({ module, isOpen, moduleIs
? Math.round((module.completed_issues / module.total_issues) * 100)
: null;

const handleEditLink = (link: linkDetails) => {
setSelectedLinkToUpdate(link);
setModuleLinkModal(true);
};

return (
<>
<LinkModal
isOpen={moduleLinkModal}
handleClose={() => setModuleLinkModal(false)}
onFormSubmit={handleCreateLink}
handleClose={() => {
setModuleLinkModal(false);
setSelectedLinkToUpdate(null);
}}
data={selectedLinkToUpdate}
status={selectedLinkToUpdate ? true : false}
createIssueLink={handleCreateLink}
updateIssueLink={handleUpdateLink}
/>
<DeleteModuleModal
isOpen={moduleDeleteModal}
Expand Down Expand Up @@ -544,7 +587,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({ module, isOpen, moduleIs
</Disclosure>
</div>

<div className="flex w-full flex-col border-t border-custom-border-200 px-6 py-6 text-xs">
<div className="flex w-full flex-col border-t border-custom-border-200 px-6 pt-6 pb-10 text-xs">
<div className="flex w-full items-center justify-between">
<h4 className="text-sm font-medium text-custom-text-200">Links</h4>
<button
Expand All @@ -558,6 +601,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({ module, isOpen, moduleIs
{memberRole && module.link_module && module.link_module.length > 0 ? (
<LinksList
links={module.link_module}
handleEditLink={handleEditLink}
handleDeleteLink={handleDeleteLink}
userAuth={memberRole}
/>
Expand Down
23 changes: 23 additions & 0 deletions apps/app/services/issues.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,29 @@ class ProjectIssuesServices extends APIService {
});
}

async updateIssueLink(
workspaceSlug: string,
projectId: string,
issueId: string,
linkId: string,
data: {
metadata: any;
title: string;
url: string;
},

): Promise<any> {
return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-links/${linkId}/`,
data
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response;
});
}


async deleteIssueLink(
workspaceSlug: string,
projectId: string,
Expand Down
Loading