style: project setting ui revamp (#2177)
* style: project settings navigation sidebar added * chore: emoji and image picker close on outside click added * style: project setting general page revamp * style: project setting member page revamp * style: project setting features page revamp * style: project setting state page revamp * style: project setting integrations page revamp * style: project setting estimates page revamp * style: project setting automation page revamp * style: project setting label page revamp * chore: member select improvement for member setting page * chore: toggle switch component improvement * style: project automation setting ui improvement * style: module icon added * style: toggle switch improvement * style: ui and spacing consistency * style: project label setting revamp * style: project state setting ui improvement * chore: integration setting repo select validation * chore: code refactor * fix: build fix
This commit is contained in:
parent
d0f6ca3bac
commit
87abf3ccb1
31 changed files with 1090 additions and 876 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
|
||||
import useSWR from "swr";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
|
|
@ -13,22 +13,35 @@ import useToast from "hooks/use-toast";
|
|||
import useUser from "hooks/use-user";
|
||||
import useProjectMembers from "hooks/use-project-members";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// layouts
|
||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||
// components
|
||||
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
|
||||
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
|
||||
import { SettingsHeader } from "components/project";
|
||||
import { MemberSelect, SettingsSidebar } from "components/project";
|
||||
// ui
|
||||
import { CustomMenu, CustomSelect, Loader } from "components/ui";
|
||||
import {
|
||||
CustomMenu,
|
||||
CustomSearchSelect,
|
||||
CustomSelect,
|
||||
Icon,
|
||||
Loader,
|
||||
PrimaryButton,
|
||||
SecondaryButton,
|
||||
} from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
import { IProject, IUserLite, IWorkspace } from "types";
|
||||
// fetch-keys
|
||||
import {
|
||||
PROJECTS_LIST,
|
||||
PROJECT_DETAILS,
|
||||
PROJECT_INVITATIONS_WITH_EMAIL,
|
||||
PROJECT_MEMBERS,
|
||||
PROJECT_MEMBERS_WITH_EMAIL,
|
||||
WORKSPACE_DETAILS,
|
||||
} from "constants/fetch-keys";
|
||||
|
|
@ -37,6 +50,11 @@ import { ROLE } from "constants/workspace";
|
|||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
|
||||
const defaultValues: Partial<IProject> = {
|
||||
project_lead: null,
|
||||
default_assignee: null,
|
||||
};
|
||||
|
||||
const MembersSettings: NextPage = () => {
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
const [selectedRemoveMember, setSelectedRemoveMember] = useState<string | null>(null);
|
||||
|
|
@ -55,11 +73,25 @@ const MembersSettings: NextPage = () => {
|
|||
Boolean(workspaceSlug && projectId)
|
||||
);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<IProject>({ defaultValues });
|
||||
|
||||
const { data: activeWorkspace } = useSWR(
|
||||
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null,
|
||||
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||
);
|
||||
|
||||
const { data: people } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: projectMembers, mutate: mutateMembers } = useSWR(
|
||||
workspaceSlug && projectId
|
||||
? PROJECT_MEMBERS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString())
|
||||
|
|
@ -110,6 +142,76 @@ const MembersSettings: NextPage = () => {
|
|||
|
||||
const handleProjectInvitationSuccess = () => {};
|
||||
|
||||
const onSubmit = async (formData: IProject) => {
|
||||
if (!workspaceSlug || !projectId || !projectDetails) return;
|
||||
|
||||
const payload: Partial<IProject> = {
|
||||
default_assignee: formData.default_assignee,
|
||||
project_lead: formData.project_lead === "none" ? null : formData.project_lead,
|
||||
};
|
||||
|
||||
await projectService
|
||||
.updateProject(workspaceSlug as string, projectId as string, payload, user)
|
||||
.then((res) => {
|
||||
mutate(PROJECT_DETAILS(projectId as string));
|
||||
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: "all",
|
||||
})
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Project updated successfully",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (projectDetails)
|
||||
reset({
|
||||
...projectDetails,
|
||||
default_assignee: projectDetails.default_assignee?.id ?? projectDetails.default_assignee,
|
||||
project_lead: (projectDetails.project_lead as IUserLite)?.id ?? projectDetails.project_lead,
|
||||
workspace: (projectDetails.workspace as IWorkspace).id,
|
||||
});
|
||||
}, [projectDetails, reset]);
|
||||
|
||||
const submitChanges = async (formData: Partial<IProject>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload: Partial<IProject> = {
|
||||
default_assignee: formData.default_assignee === "none" ? null : formData.default_assignee,
|
||||
project_lead: formData.project_lead === "none" ? null : formData.project_lead,
|
||||
};
|
||||
|
||||
await projectService
|
||||
.updateProject(workspaceSlug as string, projectId as string, payload, user)
|
||||
.then((res) => {
|
||||
mutate(PROJECT_DETAILS(projectId as string));
|
||||
|
||||
mutate(
|
||||
PROJECTS_LIST(workspaceSlug as string, {
|
||||
is_favorite: "all",
|
||||
})
|
||||
);
|
||||
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Project updated successfully",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ProjectAuthorizationWrapper
|
||||
breadcrumbs={
|
||||
|
|
@ -171,19 +273,69 @@ const MembersSettings: NextPage = () => {
|
|||
user={user}
|
||||
onSuccess={() => mutateMembers()}
|
||||
/>
|
||||
<div className="p-8">
|
||||
<SettingsHeader />
|
||||
<section className="space-y-5">
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">Members</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-custom-primary outline-none"
|
||||
onClick={() => setInviteModal(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Member
|
||||
</button>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="w-80 py-8">
|
||||
<SettingsSidebar />
|
||||
</div>
|
||||
<section className="pr-9 py-8 w-full">
|
||||
<div className="flex items-center py-3.5 border-b border-custom-border-200">
|
||||
<h3 className="text-xl font-medium">Defaults</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 pb-4 w-full">
|
||||
<div className="flex items-center py-8 gap-4 w-full">
|
||||
<div className="flex flex-col gap-2 w-1/2">
|
||||
<h4 className="text-sm">Project Lead</h4>
|
||||
<div className="">
|
||||
{projectDetails ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="project_lead"
|
||||
render={({ field: { value } }) => (
|
||||
<MemberSelect
|
||||
value={value}
|
||||
onChange={(val: string) => {
|
||||
submitChanges({ project_lead: val });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Loader className="h-9 w-full">
|
||||
<Loader.Item width="100%" height="100%" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 w-1/2">
|
||||
<h4 className="text-sm">Default Assignee</h4>
|
||||
<div className="">
|
||||
{projectDetails ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="default_assignee"
|
||||
render={({ field: { value } }) => (
|
||||
<MemberSelect
|
||||
value={value}
|
||||
onChange={(val: string) => {
|
||||
submitChanges({ default_assignee: val });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Loader className="h-9 w-full">
|
||||
<Loader.Item width="100%" height="100%" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-200">
|
||||
<h4 className="text-xl font-medium border-b border-custom-border-100">Members</h4>
|
||||
<PrimaryButton onClick={() => setInviteModal(true)}>Add Member</PrimaryButton>
|
||||
</div>
|
||||
{!projectMembers || !projectInvitations ? (
|
||||
<Loader className="space-y-5">
|
||||
|
|
@ -193,10 +345,13 @@ const MembersSettings: NextPage = () => {
|
|||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y divide-custom-border-200 rounded-[10px] border border-custom-border-200 bg-custom-background-100 px-6">
|
||||
<div className="divide-y divide-custom-border-200">
|
||||
{members.length > 0
|
||||
? members.map((member) => (
|
||||
<div key={member.id} className="flex items-center justify-between py-6">
|
||||
<div
|
||||
key={member.id}
|
||||
className="flex items-center justify-between px-3.5 py-[18px]"
|
||||
>
|
||||
<div className="flex items-center gap-x-6 gap-y-2">
|
||||
{member.avatar && member.avatar !== "" ? (
|
||||
<div className="relative flex h-10 w-10 items-center justify-center rounded-lg p-4 capitalize text-white">
|
||||
|
|
@ -242,7 +397,20 @@ const MembersSettings: NextPage = () => {
|
|||
</div>
|
||||
)}
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
customButton={
|
||||
<button className="flex item-center gap-1">
|
||||
<span
|
||||
className={`flex items-center text-sm font-medium ${
|
||||
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
|
||||
}`}
|
||||
>
|
||||
{ROLE[member.role as keyof typeof ROLE]}
|
||||
</span>
|
||||
{member.memberId !== user?.id && (
|
||||
<Icon iconName="expand_more" className="text-lg font-medium" />
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
value={member.role}
|
||||
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
|
||||
if (!activeWorkspace || !projectDetails) return;
|
||||
|
|
@ -306,7 +474,11 @@ const MembersSettings: NextPage = () => {
|
|||
>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<XMarkIcon className="h-4 w-4" />
|
||||
<span>Remove member</span>
|
||||
|
||||
<span>
|
||||
{" "}
|
||||
{member.memberId !== user?.id ? "Remove member" : "Leave project"}
|
||||
</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue