fix: issue relation mutation and draft issue (#2340)

* fix: issue relation mutation and draft issue

* fix: 'New Issue' in gantt view

fix: emoji select going under

* fix: profile page typo
This commit is contained in:
Dakshesh Jain 2023-10-04 14:55:41 +05:30 committed by GitHub
parent d9bd07886f
commit cecdf890de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 268 additions and 184 deletions

View File

@ -6,6 +6,7 @@ import { useRouter } from "next/router";
import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
// components // components
import { CreateUpdateDraftIssueModal } from "components/issues";
import { BoardHeader, SingleBoardIssue, BoardInlineCreateIssueForm } from "components/core"; import { BoardHeader, SingleBoardIssue, BoardInlineCreateIssueForm } from "components/core";
// ui // ui
import { CustomMenu } from "components/ui"; import { CustomMenu } from "components/ui";
@ -57,6 +58,7 @@ export const SingleBoard: React.FC<Props> = (props) => {
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false);
const [isCreateDraftIssueModalOpen, setIsCreateDraftIssueModalOpen] = useState(false);
const { displayFilters, groupedIssues } = viewProps; const { displayFilters, groupedIssues } = viewProps;
@ -96,10 +98,27 @@ export const SingleBoard: React.FC<Props> = (props) => {
scrollToBottom(); scrollToBottom();
}; };
const handleAddIssueToGroup = () => {
if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else onCreateClick();
};
return ( return (
<div className={`flex-shrink-0 ${!isCollapsed ? "" : "flex h-full flex-col w-96"}`}> <div className={`flex-shrink-0 ${!isCollapsed ? "" : "flex h-full flex-col w-96"}`}>
<CreateUpdateDraftIssueModal
isOpen={isCreateDraftIssueModalOpen}
handleClose={() => setIsCreateDraftIssueModalOpen(false)}
prePopulateData={{
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
[displayFilters?.group_by! === "labels" ? "labels_list" : displayFilters?.group_by!]:
displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle,
}}
/>
<BoardHeader <BoardHeader
addIssueToGroup={addIssueToGroup} addIssueToGroup={handleAddIssueToGroup}
currentState={currentState} currentState={currentState}
groupTitle={groupTitle} groupTitle={groupTitle}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
@ -218,21 +237,22 @@ export const SingleBoard: React.FC<Props> = (props) => {
{displayFilters?.group_by !== "created_by" && ( {displayFilters?.group_by !== "created_by" && (
<div> <div>
{type === "issue" {type === "issue"
? !disableAddIssueOption && ( ? !disableAddIssueOption &&
!isDraftIssuesPage && (
<button <button
type="button" type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1" className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
onClick={() => { onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) { if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
addIssueToGroup(); else onCreateClick();
} else onCreateClick();
}} }}
> >
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
Add Issue Add Issue
</button> </button>
) )
: !disableUserActions && ( : !disableUserActions &&
!isDraftIssuesPage && (
<CustomMenu <CustomMenu
customButton={ customButton={
<button <button
@ -246,7 +266,13 @@ export const SingleBoard: React.FC<Props> = (props) => {
position="left" position="left"
noBorder noBorder
> >
<CustomMenu.MenuItem onClick={() => onCreateClick()}> <CustomMenu.MenuItem
onClick={() => {
if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else onCreateClick();
}}
>
Create new Create new
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
{openIssuesListModal && ( {openIssuesListModal && (

View File

@ -13,6 +13,7 @@ import projectService from "services/project.service";
// hooks // hooks
import useProjects from "hooks/use-projects"; import useProjects from "hooks/use-projects";
// components // components
import { CreateUpdateDraftIssueModal } from "components/issues";
import { SingleListIssue, ListInlineCreateIssueForm } from "components/core"; import { SingleListIssue, ListInlineCreateIssueForm } from "components/core";
// ui // ui
import { Avatar, CustomMenu } from "components/ui"; import { Avatar, CustomMenu } from "components/ui";
@ -75,6 +76,7 @@ export const SingleList: React.FC<Props> = (props) => {
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false); const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false);
const [isDraftIssuesModalOpen, setIsDraftIssuesModalOpen] = useState(false);
const isMyIssuesPage = router.pathname.split("/")[3] === "my-issues"; const isMyIssuesPage = router.pathname.split("/")[3] === "my-issues";
const isProfileIssuesPage = router.pathname.split("/")[2] === "profile"; const isProfileIssuesPage = router.pathname.split("/")[2] === "profile";
@ -208,6 +210,18 @@ export const SingleList: React.FC<Props> = (props) => {
if (!groupedIssues) return null; if (!groupedIssues) return null;
return ( return (
<>
<CreateUpdateDraftIssueModal
isOpen={isDraftIssuesModalOpen}
handleClose={() => setIsDraftIssuesModalOpen(false)}
prePopulateData={{
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
[displayFilters?.group_by! === "labels" ? "labels_list" : displayFilters?.group_by!]:
displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle,
}}
/>
<Disclosure as="div" defaultOpen> <Disclosure as="div" defaultOpen>
{({ open }) => ( {({ open }) => (
<div> <div>
@ -241,9 +255,9 @@ export const SingleList: React.FC<Props> = (props) => {
type="button" type="button"
className="p-1 text-custom-text-200 hover:bg-custom-background-80" className="p-1 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => { onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) { if (isDraftIssuesPage) setIsDraftIssuesModalOpen(true);
addIssueToGroup(); else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
} else setIsCreateIssueFormOpen(true); else setIsCreateIssueFormOpen(true);
}} }}
> >
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
@ -331,19 +345,21 @@ export const SingleList: React.FC<Props> = (props) => {
prePopulatedData={{ prePopulatedData={{
...(cycleId && { cycle: cycleId.toString() }), ...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }), ...(moduleId && { module: moduleId.toString() }),
[displayFilters?.group_by!]: groupTitle, [displayFilters?.group_by! === "labels"
? "labels_list"
: displayFilters?.group_by!]:
displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle,
}} }}
/> />
{!disableAddIssueOption && !isCreateIssueFormOpen && ( {!disableAddIssueOption && !isCreateIssueFormOpen && !isDraftIssuesPage && (
// TODO: add border here
<div className="w-full bg-custom-background-100 px-6 py-3 border-b border-custom-border-100"> <div className="w-full bg-custom-background-100 px-6 py-3 border-b border-custom-border-100">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) { if (isDraftIssuesPage) setIsDraftIssuesModalOpen(true);
addIssueToGroup(); else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
} else setIsCreateIssueFormOpen(true); else setIsCreateIssueFormOpen(true);
}} }}
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md" className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md"
> >
@ -357,5 +373,6 @@ export const SingleList: React.FC<Props> = (props) => {
</div> </div>
)} )}
</Disclosure> </Disclosure>
</>
); );
}; };

View File

@ -4,6 +4,7 @@ import { Tab, Transition, Popover } from "@headlessui/react";
// react colors // react colors
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
// hooks // hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// types // types
import { Props } from "./types"; import { Props } from "./types";
@ -38,6 +39,7 @@ const EmojiIconPicker: React.FC<Props> = ({
const [recentEmojis, setRecentEmojis] = useState<string[]>([]); const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
const buttonRef = useRef<HTMLButtonElement>(null);
const emojiPickerRef = useRef<HTMLDivElement>(null); const emojiPickerRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
@ -49,10 +51,12 @@ const EmojiIconPicker: React.FC<Props> = ({
}, [value, onChange]); }, [value, onChange]);
useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false)); useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false));
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), buttonRef, emojiPickerRef);
return ( return (
<Popover className="relative z-[1]"> <Popover className="relative z-[1]">
<Popover.Button <Popover.Button
ref={buttonRef}
onClick={() => setIsOpen((prev) => !prev)} onClick={() => setIsOpen((prev) => !prev)}
className="outline-none" className="outline-none"
disabled={disabled} disabled={disabled}
@ -61,6 +65,8 @@ const EmojiIconPicker: React.FC<Props> = ({
</Popover.Button> </Popover.Button>
<Transition <Transition
show={isOpen} show={isOpen}
static
as={React.Fragment}
enter="transition ease-out duration-100" enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95" enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100" enterTo="transform opacity-100 scale-100"
@ -68,11 +74,11 @@ const EmojiIconPicker: React.FC<Props> = ({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Popover.Panel className="absolute z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg"> <Popover.Panel
<div
ref={emojiPickerRef} ref={emojiPickerRef}
className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl" className="fixed z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg"
> >
<div className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl">
<Tab.Group as="div" className="flex h-full w-full flex-col"> <Tab.Group as="div" className="flex h-full w-full flex-col">
<Tab.List className="flex-0 -mx-2 flex justify-around gap-1 p-1"> <Tab.List className="flex-0 -mx-2 flex justify-around gap-1 p-1">
{tabOptions.map((tab) => ( {tabOptions.map((tab) => (

View File

@ -80,6 +80,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
const router = useRouter(); const router = useRouter();
const { cycleId, moduleId } = router.query; const { cycleId, moduleId } = router.query;
const isCyclePage = router.pathname.split("/")[4] === "cycles" && !cycleId;
const isModulePage = router.pathname.split("/")[4] === "modules" && !moduleId;
const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) => const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) =>
blocks && blocks.length > 0 blocks && blocks.length > 0
? blocks.map((block: any) => ({ ? blocks.map((block: any) => ({
@ -317,7 +320,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
SidebarBlockRender={SidebarBlockRender} SidebarBlockRender={SidebarBlockRender}
enableReorder={enableReorder} enableReorder={enableReorder}
/> />
{chartBlocks && ( {chartBlocks && !(isCyclePage || isModulePage) && (
<div className="pl-2.5 py-3"> <div className="pl-2.5 py-3">
<GanttInlineCreateIssueForm <GanttInlineCreateIssueForm
isOpen={isCreateIssueFormOpen} isOpen={isCreateIssueFormOpen}

View File

@ -8,6 +8,7 @@ import { Controller, useForm } from "react-hook-form";
import aiService from "services/ai.service"; import aiService from "services/ai.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components // components
import { GptAssistantModal } from "components/core"; import { GptAssistantModal } from "components/core";
import { ParentIssuesListModal } from "components/issues"; import { ParentIssuesListModal } from "components/issues";
@ -60,6 +61,7 @@ interface IssueFormProps {
action?: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue" action?: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue"
) => Promise<void>; ) => Promise<void>;
data?: Partial<IIssue> | null; data?: Partial<IIssue> | null;
isOpen: boolean;
prePopulatedData?: Partial<IIssue> | null; prePopulatedData?: Partial<IIssue> | null;
projectId: string; projectId: string;
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>; setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
@ -89,6 +91,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const { const {
handleFormSubmit, handleFormSubmit,
data, data,
isOpen,
prePopulatedData, prePopulatedData,
projectId, projectId,
setActiveProject, setActiveProject,
@ -109,6 +112,8 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const [gptAssistantModal, setGptAssistantModal] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false);
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {});
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const router = useRouter(); const router = useRouter();
@ -133,6 +138,33 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const issueName = watch("name"); const issueName = watch("name");
const payload: Partial<IIssue> = {
name: watch("name"),
description: watch("description"),
description_html: watch("description_html"),
state: watch("state"),
priority: watch("priority"),
assignees: watch("assignees"),
labels: watch("labels"),
start_date: watch("start_date"),
target_date: watch("target_date"),
project: watch("project"),
parent: watch("parent"),
cycle: watch("cycle"),
module: watch("module"),
};
useEffect(() => {
if (!isOpen || data) return;
setLocalStorageValue(
JSON.stringify({
...payload,
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(payload), isOpen, data]);
const onClose = () => { const onClose = () => {
handleClose(); handleClose();
}; };
@ -273,7 +305,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
)} )}
<form <form
onSubmit={handleSubmit((formData) => onSubmit={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, "convertToNewIssue") handleCreateUpdateIssue(formData, data ? "convertToNewIssue" : "createDraft")
)} )}
> >
<div className="space-y-5"> <div className="space-y-5">

View File

@ -385,6 +385,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
> >
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl"> <Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<DraftIssueForm <DraftIssueForm
isOpen={isOpen}
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
prePopulatedData={prePopulateData} prePopulatedData={prePopulateData}
data={data} data={data}

View File

@ -129,18 +129,19 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
const issueName = watch("name"); const issueName = watch("name");
const payload: Partial<IIssue> = { const payload: Partial<IIssue> = {
name: getValues("name"), name: watch("name"),
description: getValues("description"), description: watch("description"),
state: getValues("state"), description_html: watch("description_html"),
priority: getValues("priority"), state: watch("state"),
assignees: getValues("assignees"), priority: watch("priority"),
labels: getValues("labels"), assignees: watch("assignees"),
start_date: getValues("start_date"), labels: watch("labels"),
target_date: getValues("target_date"), start_date: watch("start_date"),
project: getValues("project"), target_date: watch("target_date"),
parent: getValues("parent"), project: watch("project"),
cycle: getValues("cycle"), parent: watch("parent"),
module: getValues("module"), cycle: watch("cycle"),
module: watch("module"),
}; };
useEffect(() => { useEffect(() => {

View File

@ -14,7 +14,7 @@ import { ExistingIssuesListModal } from "components/core";
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import { BlockedIcon } from "components/icons"; import { BlockedIcon } from "components/icons";
// types // types
import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
type Props = { type Props = {
issueId?: string; issueId?: string;
@ -41,6 +41,9 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
setIsBlockedModalOpen(false); setIsBlockedModalOpen(false);
}; };
const blockedByIssue =
watch("related_issues")?.filter((i) => i.relation_type === "blocked_by") || [];
const onSubmit = async (data: ISearchIssueResponse[]) => { const onSubmit = async (data: ISearchIssueResponse[]) => {
if (data.length === 0) { if (data.length === 0) {
setToastAlert({ setToastAlert({
@ -80,18 +83,13 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
}) })
.then((response) => { .then((response) => {
submitChanges({ submitChanges({
related_issues: [ related_issues: [...watch("related_issues"), ...response],
...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"),
...response,
],
}); });
}); });
handleClose(); handleClose();
}; };
const blockedByIssue = watch("related_issues")?.filter((i) => i.relation_type === "blocked_by");
return ( return (
<> <>
<ExistingIssuesListModal <ExistingIssuesListModal

View File

@ -15,7 +15,7 @@ import issuesService from "services/issues.service";
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import { BlockerIcon } from "components/icons"; import { BlockerIcon } from "components/icons";
// types // types
import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
type Props = { type Props = {
issueId?: string; issueId?: string;

View File

@ -75,10 +75,8 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
})), })),
], ],
}) })
.then((response) => { .then(() => {
submitChanges({ submitChanges();
related_issues: [...watch("related_issues"), ...(response ?? [])],
});
}); });
handleClose(); handleClose();

View File

@ -75,10 +75,8 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
})), })),
], ],
}) })
.then((response) => { .then(() => {
submitChanges({ submitChanges();
related_issues: [...watch("related_issues"), ...(response ?? [])],
});
}); });
handleClose(); handleClose();

View File

@ -53,7 +53,7 @@ import { copyTextToClipboard } from "helpers/string.helper";
// types // types
import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types"; import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types";
// fetch-keys // fetch-keys
import { ISSUE_DETAILS } from "constants/fetch-keys"; import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
import { ContrastIcon } from "components/icons"; import { ContrastIcon } from "components/icons";
type Props = { type Props = {
@ -480,6 +480,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
}, },
false false
); );
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
@ -500,6 +501,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
}, },
false false
); );
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
@ -517,6 +519,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
...data, ...data,
}; };
}); });
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
@ -534,6 +537,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
...data, ...data,
}; };
}); });
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}

View File

@ -375,7 +375,7 @@ const Profile: NextPage = () => {
<div className="flex items-center justify-between py-2"> <div className="flex items-center justify-between py-2">
<PrimaryButton type="submit" loading={isSubmitting}> <PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Updating Project..." : "Update Project"} {isSubmitting ? "Updating Profile..." : "Update Profile"}
</PrimaryButton> </PrimaryButton>
</div> </div>
</div> </div>