forked from github/plane
fix: project description (#3678)
* fix: project description reset fix * fix: project description reset fix * chore: project settings layout loader added * chore: loader improvement * fix: project description reset fix
This commit is contained in:
parent
e1bf318317
commit
07a4cb1f7d
@ -1,7 +1,7 @@
|
|||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject, useWorkspace } from "hooks/store";
|
import { useEventTracker, useProject } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
import EmojiIconPicker from "components/emoji-icon-picker";
|
||||||
@ -19,22 +19,19 @@ import { NETWORK_CHOICES } from "constants/project";
|
|||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
import { PROJECT_UPDATED } from "constants/event-tracker";
|
import { PROJECT_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IProjectDetailsForm {
|
export interface IProjectDetailsForm {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectService = new ProjectService();
|
const projectService = new ProjectService();
|
||||||
|
|
||||||
export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||||
const { project, workspaceSlug, isAdmin } = props;
|
const { project, workspaceSlug, projectId, isAdmin } = props;
|
||||||
// states
|
// states
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureProjectEvent } = useEventTracker();
|
const { captureProjectEvent } = useEventTracker();
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
const { updateProject } = useProject();
|
const { updateProject } = useProject();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -47,6 +44,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
setError,
|
setError,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, dirtyFields },
|
formState: { errors, dirtyFields },
|
||||||
|
getValues,
|
||||||
} = useForm<IProject>({
|
} = useForm<IProject>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...project,
|
...project,
|
||||||
@ -56,26 +54,23 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!project) return;
|
if (project && projectId !== getValues("id")) {
|
||||||
reset({
|
reset({
|
||||||
...project,
|
...project,
|
||||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
emoji_and_icon: project.emoji ?? project.icon_prop,
|
||||||
workspace: (project.workspace as IWorkspace).id,
|
workspace: (project.workspace as IWorkspace).id,
|
||||||
});
|
});
|
||||||
}, [project, reset]);
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [project, projectId]);
|
||||||
const handleIdentifierChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleIdentifierChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
|
|
||||||
const alphanumericValue = value.replace(/[^a-zA-Z0-9]/g, "");
|
const alphanumericValue = value.replace(/[^a-zA-Z0-9]/g, "");
|
||||||
const formattedValue = alphanumericValue.toUpperCase();
|
const formattedValue = alphanumericValue.toUpperCase();
|
||||||
|
|
||||||
setValue("identifier", formattedValue);
|
setValue("identifier", formattedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateChange = async (payload: Partial<IProject>) => {
|
const handleUpdateChange = async (payload: Partial<IProject>) => {
|
||||||
if (!workspaceSlug || !project) return;
|
if (!workspaceSlug || !project) return;
|
||||||
|
|
||||||
return updateProject(workspaceSlug.toString(), project.id, payload)
|
return updateProject(workspaceSlug.toString(), project.id, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const changed_properties = Object.keys(dirtyFields);
|
const changed_properties = Object.keys(dirtyFields);
|
||||||
@ -107,11 +102,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (formData: IProject) => {
|
const onSubmit = async (formData: IProject) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const payload: Partial<IProject> = {
|
const payload: Partial<IProject> = {
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
network: formData.network,
|
network: formData.network,
|
||||||
@ -119,7 +112,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
description: formData.description,
|
description: formData.description,
|
||||||
cover_image: formData.cover_image,
|
cover_image: formData.cover_image,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof formData.emoji_and_icon === "object") {
|
if (typeof formData.emoji_and_icon === "object") {
|
||||||
payload.emoji = null;
|
payload.emoji = null;
|
||||||
payload.icon_prop = formData.emoji_and_icon;
|
payload.icon_prop = formData.emoji_and_icon;
|
||||||
@ -127,7 +119,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
payload.emoji = formData.emoji_and_icon;
|
payload.emoji = formData.emoji_and_icon;
|
||||||
payload.icon_prop = null;
|
payload.icon_prop = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.identifier !== formData.identifier)
|
if (project.identifier !== formData.identifier)
|
||||||
await projectService
|
await projectService
|
||||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||||
@ -136,20 +127,16 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
else await handleUpdateChange(payload);
|
else await handleUpdateChange(payload);
|
||||||
});
|
});
|
||||||
else await handleUpdateChange(payload);
|
else await handleUpdateChange(payload);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network);
|
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network);
|
||||||
const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="relative mt-6 h-44 w-full">
|
<div className="relative mt-6 h-44 w-full">
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
|
||||||
|
|
||||||
<img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" />
|
<img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" />
|
||||||
<div className="z-5 absolute bottom-4 flex w-full items-end justify-between gap-3 px-4">
|
<div className="z-5 absolute bottom-4 flex w-full items-end justify-between gap-3 px-4">
|
||||||
<div className="flex flex-grow gap-3 truncate">
|
<div className="flex flex-grow gap-3 truncate">
|
||||||
@ -180,7 +167,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-shrink-0 justify-center">
|
<div className="flex flex-shrink-0 justify-center">
|
||||||
<div>
|
<div>
|
||||||
<Controller
|
<Controller
|
||||||
@ -225,7 +211,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h4 className="text-sm">Description</h4>
|
<h4 className="text-sm">Description</h4>
|
||||||
<Controller
|
<Controller
|
||||||
@ -245,7 +230,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-full items-center justify-between gap-10">
|
<div className="flex w-full items-center justify-between gap-10">
|
||||||
<div className="flex w-1/2 flex-col gap-1">
|
<div className="flex w-1/2 flex-col gap-1">
|
||||||
<h4 className="text-sm">Identifier</h4>
|
<h4 className="text-sm">Identifier</h4>
|
||||||
@ -280,7 +264,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-1/2 flex-col gap-1">
|
<div className="flex w-1/2 flex-col gap-1">
|
||||||
<h4 className="text-sm">Network</h4>
|
<h4 className="text-sm">Network</h4>
|
||||||
<Controller
|
<Controller
|
||||||
@ -306,7 +289,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<>
|
<>
|
||||||
<Button variant="primary" type="submit" loading={isLoading} disabled={!isAdmin}>
|
<Button variant="primary" type="submit" loading={isLoading} disabled={!isAdmin}>
|
||||||
|
@ -41,13 +41,11 @@ export const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props)
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="inset-y-0 z-20 flex flex-grow-0 h-full w-full gap-2 overflow-x-hidden overflow-y-scroll">
|
<div className="inset-y-0 z-20 flex flex-grow-0 h-full w-full">
|
||||||
<div className="w-80 flex-shrink-0 overflow-y-hidden pt-8 sm:hidden hidden md:block lg:block">
|
<div className="w-80 flex-shrink-0 overflow-y-hidden pt-8 sm:hidden hidden md:block lg:block">
|
||||||
<ProjectSettingsSidebar />
|
<ProjectSettingsSidebar />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0">
|
<div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0 overflow-x-hidden overflow-y-scroll">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
// ui
|
||||||
|
import { Loader } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useUser } from "hooks/store";
|
||||||
// constants
|
// constants
|
||||||
@ -16,6 +18,21 @@ export const ProjectSettingsSidebar = () => {
|
|||||||
|
|
||||||
const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST;
|
const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST;
|
||||||
|
|
||||||
|
if (!currentProjectRole) {
|
||||||
|
return (
|
||||||
|
<div className="flex w-80 flex-col gap-6 px-5">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
||||||
|
<Loader className="flex w-full flex-col gap-2">
|
||||||
|
{[...Array(8)].map(() => (
|
||||||
|
<Loader.Item height="34px" />
|
||||||
|
))}
|
||||||
|
</Loader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-80 flex-col gap-6 px-5">
|
<div className="flex w-80 flex-col gap-6 px-5">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
@ -28,7 +28,7 @@ const GeneralSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
const { currentProjectDetails, fetchProjectDetails } = useProject();
|
const { currentProjectDetails, fetchProjectDetails } = useProject();
|
||||||
// api call to fetch project details
|
// api call to fetch project details
|
||||||
// TODO: removed this API if not necessary
|
// TODO: removed this API if not necessary
|
||||||
useSWR(
|
const { isLoading } = useSWR(
|
||||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
||||||
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
|
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
|
||||||
);
|
);
|
||||||
@ -49,10 +49,11 @@ const GeneralSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
|
<div className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
|
||||||
{currentProjectDetails && workspaceSlug ? (
|
{currentProjectDetails && workspaceSlug && projectId && !isLoading ? (
|
||||||
<ProjectDetailsForm
|
<ProjectDetailsForm
|
||||||
project={currentProjectDetails}
|
project={currentProjectDetails}
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
|
projectId={projectId.toString()}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
Loading…
Reference in New Issue
Block a user