style: project settings theming (#936)
* style: project and workspace members theming * style: project features theming * style: project settings states theming * style: project settings labels theming * style: project settings integrations theming
This commit is contained in:
parent
c80094581e
commit
169a60723b
13 changed files with 211 additions and 294 deletions
|
|
@ -6,7 +6,7 @@ import useSWR, { mutate } from "swr";
|
|||
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
import trackEventServices, { MiscellaneousEventType } from "services/track-event.service";
|
||||
// layouts
|
||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||
// hooks
|
||||
|
|
@ -23,6 +23,52 @@ import type { NextPage } from "next";
|
|||
// fetch-keys
|
||||
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
const featuresList = [
|
||||
{
|
||||
title: "Cycles",
|
||||
description:
|
||||
"Cycles are enabled for all the projects in this workspace. Access them from the sidebar.",
|
||||
icon: <ContrastIcon color="#3f76ff" width={28} height={28} className="flex-shrink-0" />,
|
||||
property: "cycle_view",
|
||||
},
|
||||
{
|
||||
title: "Modules",
|
||||
description:
|
||||
"Modules are enabled for all the projects in this workspace. Access it from the sidebar.",
|
||||
icon: <PeopleGroupIcon color="#ff6b00" width={28} height={28} className="flex-shrink-0" />,
|
||||
property: "module_view",
|
||||
},
|
||||
{
|
||||
title: "Views",
|
||||
description:
|
||||
"Views are enabled for all the projects in this workspace. Access it from the sidebar.",
|
||||
icon: <ViewListIcon color="#05c3ff" width={28} height={28} className="flex-shrink-0" />,
|
||||
property: "issue_views_view",
|
||||
},
|
||||
{
|
||||
title: "Pages",
|
||||
description:
|
||||
"Pages are enabled for all the projects in this workspace. Access it from the sidebar.",
|
||||
icon: <DocumentTextIcon color="#fcbe1d" width={28} height={28} className="flex-shrink-0" />,
|
||||
property: "page_view",
|
||||
},
|
||||
];
|
||||
|
||||
const getEventType = (feature: string, toggle: boolean): MiscellaneousEventType => {
|
||||
switch (feature) {
|
||||
case "Cycles":
|
||||
return toggle ? "TOGGLE_CYCLE_ON" : "TOGGLE_CYCLE_OFF";
|
||||
case "Modules":
|
||||
return toggle ? "TOGGLE_MODULE_ON" : "TOGGLE_MODULE_OFF";
|
||||
case "Views":
|
||||
return toggle ? "TOGGLE_VIEW_ON" : "TOGGLE_VIEW_OFF";
|
||||
case "Pages":
|
||||
return toggle ? "TOGGLE_PAGES_ON" : "TOGGLE_PAGES_OFF";
|
||||
default:
|
||||
return toggle ? "TOGGLE_PAGES_ON" : "TOGGLE_PAGES_OFF";
|
||||
}
|
||||
};
|
||||
|
||||
const FeaturesSettings: NextPage = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
|
@ -91,170 +137,57 @@ const FeaturesSettings: NextPage = () => {
|
|||
<section className="space-y-8">
|
||||
<h3 className="text-2xl font-semibold">Features</h3>
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border border-brand-base bg-brand-surface-1 p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<ContrastIcon color="#3F76FF" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold">Cycles</h4>
|
||||
<p className="text-brand-secondary">
|
||||
Cycles are enabled for all the projects in this workspace. Access them from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.cycle_view ? "bg-green-500" : "bg-brand-surface-2"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.cycle_view}
|
||||
onClick={() => {
|
||||
trackEventServices.trackMiscellaneousEvent(
|
||||
{
|
||||
workspaceId: (projectDetails?.workspace as any)?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectIdentifier: projectDetails?.identifier,
|
||||
projectName: projectDetails?.name,
|
||||
},
|
||||
!projectDetails?.cycle_view ? "TOGGLE_CYCLE_ON" : "TOGGLE_CYCLE_OFF"
|
||||
);
|
||||
handleSubmit({ cycle_view: !projectDetails?.cycle_view });
|
||||
}}
|
||||
{featuresList.map((feature) => (
|
||||
<div
|
||||
key={feature.property}
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border border-brand-base bg-brand-base p-5"
|
||||
>
|
||||
<span className="sr-only">Use cycles</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-brand-surface-1 shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.cycle_view ? "translate-x-5" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border border-brand-base bg-brand-surface-1 p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<PeopleGroupIcon color="#FF6B00" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
<h4 className="-mt-1.5 text-xl font-semibold">Modules</h4>
|
||||
<p className="text-brand-secondary">
|
||||
Modules are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
<div className="flex items-start gap-3">
|
||||
{feature.icon}
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold">{feature.title}</h4>
|
||||
<p className="text-sm text-brand-secondary">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.module_view ? "bg-green-500" : "bg-brand-surface-2"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.module_view}
|
||||
onClick={() => {
|
||||
trackEventServices.trackMiscellaneousEvent(
|
||||
{
|
||||
workspaceId: (projectDetails?.workspace as any)?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectIdentifier: projectDetails?.identifier,
|
||||
projectName: projectDetails?.name,
|
||||
},
|
||||
!projectDetails?.module_view ? "TOGGLE_MODULE_ON" : "TOGGLE_MODULE_OFF"
|
||||
);
|
||||
handleSubmit({ module_view: !projectDetails?.module_view });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Use cycles</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-brand-surface-1 shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.module_view ? "translate-x-5" : "translate-x-0"
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.[feature.property as keyof IProject]
|
||||
? "bg-green-500"
|
||||
: "bg-brand-surface-2"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border border-brand-base bg-brand-surface-1 p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<ViewListIcon color="#05C3FF" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
<h4 className="-mt-1.5 text-xl font-semibold">Views</h4>
|
||||
<p className="text-brand-secondary">
|
||||
Views are enabled for all the projects in this workspace. Access it from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.[feature.property as keyof IProject]}
|
||||
onClick={() => {
|
||||
trackEventServices.trackMiscellaneousEvent(
|
||||
{
|
||||
workspaceId: (projectDetails?.workspace as any)?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectIdentifier: projectDetails?.identifier,
|
||||
projectName: projectDetails?.name,
|
||||
},
|
||||
!projectDetails?.[feature.property as keyof IProject]
|
||||
? getEventType(feature.title, true)
|
||||
: getEventType(feature.title, false)
|
||||
);
|
||||
handleSubmit({
|
||||
[feature.property]: !projectDetails?.[feature.property as keyof IProject],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Use {feature.title}</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-brand-surface-1 shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.[feature.property as keyof IProject]
|
||||
? "translate-x-5"
|
||||
: "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.issue_views_view ? "bg-green-500" : "bg-brand-surface-2"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.issue_views_view}
|
||||
onClick={() => {
|
||||
trackEventServices.trackMiscellaneousEvent(
|
||||
{
|
||||
workspaceId: (projectDetails?.workspace as any)?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectIdentifier: projectDetails?.identifier,
|
||||
projectName: projectDetails?.name,
|
||||
},
|
||||
!projectDetails?.issue_views_view ? "TOGGLE_VIEW_ON" : "TOGGLE_VIEW_OFF"
|
||||
);
|
||||
handleSubmit({ issue_views_view: !projectDetails?.issue_views_view });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Use views</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-brand-surface-1 shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.issue_views_view ? "translate-x-5" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border border-brand-base bg-brand-surface-1 p-5">
|
||||
<div className="flex items-start gap-3">
|
||||
<DocumentTextIcon color="#FCBE1D" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold">Pages</h4>
|
||||
<p className="text-brand-secondary">
|
||||
Pages are enabled for all the projects in this workspace. Access them from the
|
||||
navigation bar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
projectDetails?.page_view ? "bg-green-500" : "bg-brand-surface-2"
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={projectDetails?.page_view}
|
||||
onClick={() => {
|
||||
trackEventServices.trackMiscellaneousEvent(
|
||||
{
|
||||
workspaceId: (projectDetails?.workspace as any)?.id,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectIdentifier: projectDetails?.identifier,
|
||||
projectName: projectDetails?.name,
|
||||
},
|
||||
!projectDetails?.page_view ? "TOGGLE_PAGES_ON" : "TOGGLE_PAGES_OFF"
|
||||
);
|
||||
handleSubmit({ page_view: !projectDetails?.page_view });
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Use cycles</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-brand-surface-1 shadow ring-0 transition duration-200 ease-in-out ${
|
||||
projectDetails?.page_view ? "translate-x-5" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<a href="https://plane.so/" target="_blank" rel="noreferrer">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import projectService from "services/project.service";
|
|||
import workspaceService from "services/workspace.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
// layouts
|
||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||
// components
|
||||
|
|
@ -23,12 +24,7 @@ import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|||
// types
|
||||
import type { NextPage } from "next";
|
||||
// fetch-keys
|
||||
import {
|
||||
PROJECT_DETAILS,
|
||||
PROJECT_INVITATIONS,
|
||||
PROJECT_MEMBERS,
|
||||
WORKSPACE_DETAILS,
|
||||
} from "constants/fetch-keys";
|
||||
import { PROJECT_INVITATIONS, PROJECT_MEMBERS, WORKSPACE_DETAILS } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { ROLE } from "constants/workspace";
|
||||
|
||||
|
|
@ -48,24 +44,13 @@ const MembersSettings: NextPage = () => {
|
|||
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
|
||||
);
|
||||
|
||||
const { data: projectDetails } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.getProject(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { data: projectMembers, mutate: mutateMembers } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||
: null,
|
||||
{
|
||||
onErrorRetry(err, _, __, revalidate, revalidateOpts) {
|
||||
if (err?.status === 403) return;
|
||||
setTimeout(() => revalidate(revalidateOpts), 5000);
|
||||
},
|
||||
}
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: projectInvitations, mutate: mutateInvitations } = useSWR(
|
||||
|
|
@ -176,11 +161,11 @@ const MembersSettings: NextPage = () => {
|
|||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base bg-brand-surface-1 px-6">
|
||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base bg-brand-base px-6">
|
||||
{members.length > 0
|
||||
? members.map((member) => (
|
||||
<div key={member.id} className="flex items-center justify-between py-6">
|
||||
<div className="flex items-center gap-x-8 gap-y-2">
|
||||
<div className="flex items-center gap-x-6 gap-y-2">
|
||||
<div className="relative flex h-10 w-10 items-center justify-center rounded-lg bg-gray-700 p-4 capitalize text-white">
|
||||
{member.avatar && member.avatar !== "" ? (
|
||||
<Image
|
||||
|
|
@ -203,12 +188,12 @@ const MembersSettings: NextPage = () => {
|
|||
<p className="mt-0.5 text-xs text-brand-secondary">{member.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
{!member.member && (
|
||||
<span className="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800">
|
||||
Request Pending
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
{!member.member && (
|
||||
<div className="mr-2 flex items-center justify-center rounded-full bg-yellow-500/20 px-2 py-1 text-center text-xs text-yellow-500">
|
||||
Pending
|
||||
</div>
|
||||
)}
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
value={member.role}
|
||||
|
|
@ -242,6 +227,7 @@ const MembersSettings: NextPage = () => {
|
|||
console.log(err);
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => (
|
||||
<CustomSelect.Option key={key} value={key}>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,10 @@ const StatesSettings: NextPage = () => {
|
|||
handleDeleteState={() => setSelectDeleteState(state.id)}
|
||||
/>
|
||||
) : (
|
||||
<div className="border-b last:border-b-0" key={state.id}>
|
||||
<div
|
||||
className="border-b border-brand-base last:border-b-0"
|
||||
key={state.id}
|
||||
>
|
||||
<CreateUpdateStateInline
|
||||
onClose={() => {
|
||||
setActiveGroup(null);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ const MembersSettings: NextPage = () => {
|
|||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y rounded-[10px] divide-brand-base border border-brand-base bg-brand-surface-1 px-6">
|
||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base bg-brand-base px-6">
|
||||
{members.length > 0
|
||||
? members.map((member) => (
|
||||
<div key={member.id} className="flex items-center justify-between py-6">
|
||||
|
|
@ -184,64 +184,63 @@ const MembersSettings: NextPage = () => {
|
|||
<p className="text-xs text-brand-secondary">{member.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
{!member?.status && (
|
||||
<div className="flex mr-2 px-2 py-1 bg-yellow-200 text-yellow-700 text-center rounded-full text-xs items-center justify-center">
|
||||
<div className="mr-2 flex items-center justify-center rounded-full bg-yellow-500/20 px-2 py-1 text-center text-xs text-yellow-500">
|
||||
<p>Pending</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
value={member.role}
|
||||
onChange={(value: any) => {
|
||||
workspaceService
|
||||
.updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
|
||||
role: value,
|
||||
})
|
||||
.then(() => {
|
||||
mutateMembers(
|
||||
(prevData) =>
|
||||
prevData?.map((m) =>
|
||||
m.id === member.id ? { ...m, role: value } : m
|
||||
),
|
||||
false
|
||||
);
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Member role updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: "An error occurred while updating member role.",
|
||||
});
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
value={member.role}
|
||||
onChange={(value: any) => {
|
||||
workspaceService
|
||||
.updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
|
||||
role: value,
|
||||
})
|
||||
.then(() => {
|
||||
mutateMembers(
|
||||
(prevData) =>
|
||||
prevData?.map((m) =>
|
||||
m.id === member.id ? { ...m, role: value } : m
|
||||
),
|
||||
false
|
||||
);
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Member role updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: "An error occurred while updating member role.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => (
|
||||
<CustomSelect.Option key={key} value={key}>
|
||||
<>{ROLE[parseInt(key) as keyof typeof ROLE]}</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (member.member) {
|
||||
setSelectedRemoveMember(member.id);
|
||||
} else {
|
||||
setSelectedInviteRemoveMember(member.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => (
|
||||
<CustomSelect.Option key={key} value={key}>
|
||||
<>{ROLE[parseInt(key) as keyof typeof ROLE]}</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (member.member) {
|
||||
setSelectedRemoveMember(member.id);
|
||||
} else {
|
||||
setSelectedInviteRemoveMember(member.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove member
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
Remove member
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue