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 { Controller, useForm } from "react-hook-form";
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useWorkspace } from "hooks/store";
|
||||
import { useEventTracker, useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
||||
@ -19,22 +19,19 @@ import { NETWORK_CHOICES } from "constants/project";
|
||||
// services
|
||||
import { ProjectService } from "services/project";
|
||||
import { PROJECT_UPDATED } from "constants/event-tracker";
|
||||
|
||||
export interface IProjectDetailsForm {
|
||||
project: IProject;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const projectService = new ProjectService();
|
||||
|
||||
export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
const { project, workspaceSlug, isAdmin } = props;
|
||||
const { project, workspaceSlug, projectId, isAdmin } = props;
|
||||
// states
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// store hooks
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { updateProject } = useProject();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
@ -47,6 +44,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
setError,
|
||||
reset,
|
||||
formState: { errors, dirtyFields },
|
||||
getValues,
|
||||
} = useForm<IProject>({
|
||||
defaultValues: {
|
||||
...project,
|
||||
@ -56,26 +54,23 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!project) return;
|
||||
reset({
|
||||
...project,
|
||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
||||
workspace: (project.workspace as IWorkspace).id,
|
||||
});
|
||||
}, [project, reset]);
|
||||
|
||||
if (project && projectId !== getValues("id")) {
|
||||
reset({
|
||||
...project,
|
||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
||||
workspace: (project.workspace as IWorkspace).id,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [project, projectId]);
|
||||
const handleIdentifierChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
|
||||
const alphanumericValue = value.replace(/[^a-zA-Z0-9]/g, "");
|
||||
const formattedValue = alphanumericValue.toUpperCase();
|
||||
|
||||
setValue("identifier", formattedValue);
|
||||
};
|
||||
|
||||
const handleUpdateChange = async (payload: Partial<IProject>) => {
|
||||
if (!workspaceSlug || !project) return;
|
||||
|
||||
return updateProject(workspaceSlug.toString(), project.id, payload)
|
||||
.then((res) => {
|
||||
const changed_properties = Object.keys(dirtyFields);
|
||||
@ -107,11 +102,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: IProject) => {
|
||||
if (!workspaceSlug) return;
|
||||
setIsLoading(true);
|
||||
|
||||
const payload: Partial<IProject> = {
|
||||
name: formData.name,
|
||||
network: formData.network,
|
||||
@ -119,7 +112,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
description: formData.description,
|
||||
cover_image: formData.cover_image,
|
||||
};
|
||||
|
||||
if (typeof formData.emoji_and_icon === "object") {
|
||||
payload.emoji = null;
|
||||
payload.icon_prop = formData.emoji_and_icon;
|
||||
@ -127,7 +119,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
payload.emoji = formData.emoji_and_icon;
|
||||
payload.icon_prop = null;
|
||||
}
|
||||
|
||||
if (project.identifier !== formData.identifier)
|
||||
await projectService
|
||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||
@ -136,20 +127,16 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
else await handleUpdateChange(payload);
|
||||
});
|
||||
else await handleUpdateChange(payload);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network);
|
||||
const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="relative mt-6 h-44 w-full">
|
||||
<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" />
|
||||
<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">
|
||||
@ -180,7 +167,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 justify-center">
|
||||
<div>
|
||||
<Controller
|
||||
@ -225,7 +211,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Description</h4>
|
||||
<Controller
|
||||
@ -245,7 +230,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-center justify-between gap-10">
|
||||
<div className="flex w-1/2 flex-col gap-1">
|
||||
<h4 className="text-sm">Identifier</h4>
|
||||
@ -280,7 +264,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex w-1/2 flex-col gap-1">
|
||||
<h4 className="text-sm">Network</h4>
|
||||
<Controller
|
||||
@ -306,7 +289,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<>
|
||||
<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">
|
||||
<ProjectSettingsSidebar />
|
||||
</div>
|
||||
<div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0">
|
||||
{children}
|
||||
</div>
|
||||
<div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0 overflow-x-hidden overflow-y-scroll">{children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// constants
|
||||
@ -16,6 +18,21 @@ export const ProjectSettingsSidebar = () => {
|
||||
|
||||
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 (
|
||||
<div className="flex w-80 flex-col gap-6 px-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
|
@ -28,7 +28,7 @@ const GeneralSettingsPage: NextPageWithLayout = observer(() => {
|
||||
const { currentProjectDetails, fetchProjectDetails } = useProject();
|
||||
// api call to fetch project details
|
||||
// TODO: removed this API if not necessary
|
||||
useSWR(
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : 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"}`}>
|
||||
{currentProjectDetails && workspaceSlug ? (
|
||||
{currentProjectDetails && workspaceSlug && projectId && !isLoading ? (
|
||||
<ProjectDetailsForm
|
||||
project={currentProjectDetails}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user