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:
Anmol Singh Bhatia 2024-02-19 21:09:51 +05:30 committed by GitHub
parent e1bf318317
commit 07a4cb1f7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 35 additions and 37 deletions

View File

@ -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}>

View File

@ -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>
);
});

View File

@ -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">

View File

@ -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}
/>
) : (