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

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

View File

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

View File

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